From e1e78f6be5fb104e5ff8ac0bcd744b9cce411276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Porto?= Date: Wed, 28 Jul 2021 18:00:37 +0300 Subject: [PATCH] Tool: agfexport - export miscellaneous data from the game source The agfexport parses the game project file (*.agf), and exports various stuff depending on command. built on top of agfexport 0.1.0 by @ivan-mogilko --- Solutions/Tools.App/agfexport.vcxproj | 159 ++++++++++ Solutions/Tools.App/agfexport.vcxproj.filters | 79 +++++ Solutions/Tools.sln | 10 + Tools/CMakeLists.txt | 10 +- Tools/agfexport/Makefile | 97 ++++++ Tools/agfexport/main.cpp | 293 ++++++++++++++++++ Tools/data/agfreader.cpp | 15 + Tools/data/agfreader.h | 2 + 8 files changed, 664 insertions(+), 1 deletion(-) create mode 100644 Solutions/Tools.App/agfexport.vcxproj create mode 100644 Solutions/Tools.App/agfexport.vcxproj.filters create mode 100644 Tools/agfexport/Makefile create mode 100644 Tools/agfexport/main.cpp diff --git a/Solutions/Tools.App/agfexport.vcxproj b/Solutions/Tools.App/agfexport.vcxproj new file mode 100644 index 0000000000..a4c2f92626 --- /dev/null +++ b/Solutions/Tools.App/agfexport.vcxproj @@ -0,0 +1,159 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + + + + + + + {776E38BA-D6A4-431E-8053-299310D85301} + MakeRoomHeader + 8.1 + agfexport + + + + Application + true + v140 + MultiByte + + + Application + false + v140 + true + MultiByte + + + Application + true + v140 + MultiByte + + + Application + false + v140 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)\.build\$(Configuration)\ + $(Configuration)\$(ProjectName)\ + + + $(SolutionDir)\.build\$(Configuration)\ + $(Configuration)\$(ProjectName)\ + + + + Level3 + Disabled + true + _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + ..\..\Tools;..\..\Common;..\..\libsrc\tinyxml2;%(AdditionalIncludeDirectories) + 4996 + MultiThreadedDebug + + + shlwapi.lib;%(AdditionalDependencies) + Console + + + + + Level3 + Disabled + true + + + + + Level3 + MaxSpeed + true + true + true + ..\..\Tools;..\..\Common;..\..\libsrc\tinyxml2;%(AdditionalIncludeDirectories) + _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + 4996 + MultiThreaded + + + true + true + shlwapi.lib;%(AdditionalDependencies) + Console + + + + + Level3 + MaxSpeed + true + true + true + + + true + true + + + + + + diff --git a/Solutions/Tools.App/agfexport.vcxproj.filters b/Solutions/Tools.App/agfexport.vcxproj.filters new file mode 100644 index 0000000000..d7643c6c59 --- /dev/null +++ b/Solutions/Tools.App/agfexport.vcxproj.filters @@ -0,0 +1,79 @@ + + + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {eadadb9c-903a-4a50-8db9-a82dbf36b434} + + + {46208ff5-d822-4f9b-8208-d51993dbec22} + + + {bdb62516-d050-4c05-81b5-4bedb3b8da0b} + + + {731e5dde-24bd-46ed-ae96-82f7baf41d3d} + + + + + Common + + + Common + + + Common + + + Common + + + Common + + + Common + + + Common + + + Common + + + Common + + + Common + + + tinyxml2 + + + data + + + data + + + agfexport + + + Common + + + Common + + + + + tinyxml2 + + + data + + + diff --git a/Solutions/Tools.sln b/Solutions/Tools.sln index 57f4948473..41399e0e69 100644 --- a/Solutions/Tools.sln +++ b/Solutions/Tools.sln @@ -19,6 +19,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "agsunpak", "Tools.App\agsun EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "crmpak", "Tools.App\crmpak.vcxproj", "{20F0B521-1E8C-4F96-9C35-8CF244CA6947}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "agfexport", "Tools.App\agfexport.vcxproj", "{776E38BA-D6A4-431E-8053-299310D85301}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -91,6 +93,14 @@ Global {20F0B521-1E8C-4F96-9C35-8CF244CA6947}.Release|x64.Build.0 = Release|x64 {20F0B521-1E8C-4F96-9C35-8CF244CA6947}.Release|x86.ActiveCfg = Release|Win32 {20F0B521-1E8C-4F96-9C35-8CF244CA6947}.Release|x86.Build.0 = Release|Win32 + {776E38BA-D6A4-431E-8053-299310D85301}.Debug|x64.ActiveCfg = Debug|x64 + {776E38BA-D6A4-431E-8053-299310D85301}.Debug|x64.Build.0 = Debug|x64 + {776E38BA-D6A4-431E-8053-299310D85301}.Debug|x86.ActiveCfg = Debug|Win32 + {776E38BA-D6A4-431E-8053-299310D85301}.Debug|x86.Build.0 = Debug|Win32 + {776E38BA-D6A4-431E-8053-299310D85301}.Release|x64.ActiveCfg = Release|x64 + {776E38BA-D6A4-431E-8053-299310D85301}.Release|x64.Build.0 = Release|x64 + {776E38BA-D6A4-431E-8053-299310D85301}.Release|x86.ActiveCfg = Release|Win32 + {776E38BA-D6A4-431E-8053-299310D85301}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Tools/CMakeLists.txt b/Tools/CMakeLists.txt index bceada29b3..5775d0d043 100644 --- a/Tools/CMakeLists.txt +++ b/Tools/CMakeLists.txt @@ -84,6 +84,14 @@ set_target_properties(agf2glvar PROPERTIES ) target_link_libraries(agf2glvar PUBLIC libtools) +#----- agfexport ----------------------------------------------- +add_executable(agfexport agfexport/main.cpp) +set_target_properties(agfexport PROPERTIES + CXX_STANDARD 11 + CXX_EXTENSIONS NO + ) +target_link_libraries(agfexport PUBLIC libtools) + #----- agspak ------------------------------------------------- add_executable(agspak agspak/main.cpp) set_target_properties(agspak PROPERTIES @@ -124,7 +132,7 @@ set_target_properties(trac PROPERTIES ) target_link_libraries(trac PUBLIC libtools) -list(APPEND TOOLS_TARGETS agf2autoash agf2dlgasc agf2glvar agspak agsunpak crm2ash crmpak trac) +list(APPEND TOOLS_TARGETS agf2autoash agf2dlgasc agf2glvar agfexport agspak agsunpak crm2ash crmpak trac) # Bundle-like target to build all tools add_custom_target(Tools) diff --git a/Tools/agfexport/Makefile b/Tools/agfexport/Makefile new file mode 100644 index 0000000000..b9490cbd90 --- /dev/null +++ b/Tools/agfexport/Makefile @@ -0,0 +1,97 @@ +INCDIR = ../../Common ../../Tools ../../libsrc/tinyxml2 +LIBDIR = + +CFLAGS := -O2 -g \ + -fsigned-char -fno-strict-aliasing -fwrapv \ + -Wunused-result \ + -Wno-unused-value \ + -Werror=write-strings -Werror=format -Werror=format-security \ + -DNDEBUG \ + -D_FILE_OFFSET_BITS=64 -DRTLD_NEXT \ + $(CFLAGS) + +CXXFLAGS := -std=c++11 -Werror=delete-non-virtual-dtor $(CXXFLAGS) + +PREFIX ?= /usr/local +CC ?= gcc +CXX ?= g++ +AR ?= ar +CFLAGS += $(addprefix -I,$(INCDIR)) +CXXFLAGS += $(CFLAGS) +ASFLAGS += $(CFLAGS) +LDFLAGS += -rdynamic -Wl,--as-needed $(addprefix -L,$(LIBDIR)) +CFLAGS += -Werror=implicit-function-declaration + +COMMON_OBJS = \ + ../../Common/debug/debugmanager.cpp \ + ../../Common/util/bufferedstream.cpp \ + ../../Common/util/cmdlineopts.cpp \ + ../../Common/util/file.cpp \ + ../../Common/util/filestream.cpp \ + ../../Common/util/path.cpp \ + ../../Common/util/stdio_compat.c \ + ../../Common/util/stream.cpp \ + ../../Common/util/string.cpp \ + ../../Common/util/string_compat.c \ + ../../Common/util/string_utils.cpp \ + ../../Common/util/textstreamreader.cpp + +TOOL_OBJS = \ + ../../Tools/data/agfreader.cpp \ + ../../Tools/data/scriptgen.cpp + +TINYXML2 = \ + ../../libsrc/tinyxml2/tinyxml2.cpp + +OBJS := main.cpp \ + $(COMMON_OBJS) \ + $(TOOL_OBJS) \ + $(TINYXML2) +OBJS := $(OBJS:.cpp=.o) +OBJS := $(OBJS:.c=.o) + +DEPFILES = $(OBJS:.o=.d) + +-include config.mak + +.PHONY: printflags clean install uninstall rebuild + +all: printflags agfexport + +agfexport: $(OBJS) + @echo "Linking..." + $(CXX) -o $@ $^ $(CXXFLAGS) $(LDFLAGS) $(LIBS) + +debug: CXXFLAGS += -UNDEBUG -D_DEBUG -Og -g -pg +debug: CFLAGS += -UNDEBUG -D_DEBUG -Og -g -pg +debug: LDFLAGS += -pg +debug: printflags agfexport + +-include $(DEPFILES) + +%.o: %.c + @echo $@ + $(CMD_PREFIX) $(CC) $(CFLAGS) -MD -c -o $@ $< + +%.o: %.cpp + @echo $@ + $(CMD_PREFIX) $(CXX) $(CXXFLAGS) -MD -c -o $@ $< + +printflags: + @echo "CFLAGS =" $(CFLAGS) "\n" + @echo "CXXFLAGS =" $(CXXFLAGS) "\n" + @echo "LDFLAGS =" $(LDFLAGS) "\n" + @echo "LIBS =" $(LIBS) "\n" + +rebuild: clean all + +clean: + @echo "Cleaning..." + $(CMD_PREFIX) rm -f agfexport $(OBJS) $(DEPFILES) + +install: agfexport + mkdir -p $(PREFIX)/bin + cp -t $(PREFIX)/bin agfexport + +uninstall: + rm -f $(PREFIX)/bin/agfexport diff --git a/Tools/agfexport/main.cpp b/Tools/agfexport/main.cpp new file mode 100644 index 0000000000..e965b64970 --- /dev/null +++ b/Tools/agfexport/main.cpp @@ -0,0 +1,293 @@ +#include +#include +#include +#include +#include +#include +#include +#include "data/agfreader.h" +#include "data/scriptgen.h" +#include "util/file.h" +#include "util/stream.h" + +using namespace AGS::Common; +using namespace AGS::DataUtil; +namespace AGF = AGS::AGF; + +const char *HELP_STRING = "" + "agfexport v0.2.0 - AGS game project miscellaneous export tool\n" + "Copyright (c) 2024 AGS Team and contributors\n" + "Usage: agfexport [] \n" + "Commands:\n" + " autoash Generate auto script header\n" + " glvar Generate global variables scripts\n" + " header-list Exports ordered list of headers from script modules\n" + " script-list Exports ordered list of scripts from script modules\n" + " room-list Exports list of active rooms\n" + " -h, --help Show help message for command\n"; + +const char *HELP_AUTOASH = "" + "Usage: agfexport autoash \n" + "Writes , the auto-generated script header from \n" + "This header has global elements from the game necessary for building scripts.\n" + "Commands:\n" + " -h, --help Show this help message\n"; + +const char *HELP_GLVAR = "" + "Usage: agfexport glvar \n" + "Writes both (e.g. globalvars.ash) and (e.g. globalvars.asc).\n" + "These are retrieved from the game project in .\n" + "Commands:\n" + " -h, --help Show this help message\n"; + +const char *HELP_HEADER_LIST = "" + "Usage: agfexport header-list \n" + "Writes , a file with a list of headers from script modules." + "Commands:\n" + " -h, --help Show this help message\n" + " -t, --to-stdout Instead write the list of scripts to stdout"; + +const char *HELP_SCRIPT_LIST = "" + "Usage: agfexport script-list \n" + "Writes , a file with an ordered list of scripts from script modules." + "Commands:\n" + " -h, --help Show this help message\n" + " -t, --to-stdout Instead write the list of scripts to stdout"; + +const char *HELP_ROOM_LIST = "" + "Usage: agfexport room-list \n" + "Writes , a file with a list of rooms." + "Commands:\n" + " -h, --help Show this help message\n" + " -t, --to-stdout Instead write the list of rooms to stdout"; + +enum CommandType +{ + kCmdAutoAsh = 0, + kCmdGlVar, + kCmdHeaderList, + kCmdScriptList, + kCmdRoomList, + kCmdMAX, + kCmdNone = kCmdMAX +}; + +struct Command +{ + const char *Opt; + const CommandType Cmd; + const size_t NumArgs; + const char *Help; +} Command[] = { + {"autoash", kCmdAutoAsh, 2, HELP_AUTOASH}, + {"glvar", kCmdGlVar, 3, HELP_GLVAR}, + {"header-list", kCmdHeaderList, 2, HELP_HEADER_LIST}, + {"script-list", kCmdScriptList, 2, HELP_SCRIPT_LIST}, + {"room-list", kCmdRoomList, 2, HELP_ROOM_LIST}, + {nullptr, kCmdNone, 0, nullptr} +}; + +HError write_to_file(const String &content, const String &file) +{ + std::unique_ptr out(File::CreateFile(file)); + if (!out) + { + + return new Error(String::FromFormat("Failed to open output file '%s' for writing.", file.GetCStr())); + } + out->Write(content.GetCStr(), content.GetLength()); + return HError::None(); +} + +HError list_command(const AGF::AGFReader &reader, CommandType cmd, const String &file, bool to_stdout) +{ + String exp_data; + + if (cmd == kCmdScriptList) + { + std::vector scripts; + AGF::ReadScriptList(scripts, reader.GetGameRoot()); + for (const auto &s: scripts) + exp_data.AppendFmt("%s\n", s.GetCStr()); + } + + if (cmd == kCmdHeaderList) + { + std::vector scripts; + AGF::ReadScriptHeaderList(scripts, reader.GetGameRoot()); + for (const auto &s: scripts) + exp_data.AppendFmt("%s\n", s.GetCStr()); + } + + if (cmd == kCmdRoomList) + { + std::vector rooms; + std::vector> rooms_dsc; + AGF::ReadRoomList(rooms_dsc, reader.GetGameRoot()); + rooms.reserve(rooms_dsc.size()); + for (const auto &rd: rooms_dsc) + rooms.push_back(rd.first); + + std::sort(rooms.begin(), rooms.end()); + for (const auto &r: rooms) + exp_data.AppendFmt("room%d.crm\n", r); + } + + if (to_stdout) + { + printf("%s", exp_data.GetCStr()); + return HError::None(); + } + + return write_to_file(exp_data, file); +} + +HError autoash_command(AGF::AGFReader &reader, const String &dst) +{ + const char *dst_autoash = dst.GetCStr(); + printf("Output script header: %s\n", dst_autoash); + + GameRef game_ref; + AGF::ReadGameRef(game_ref, reader); + String header = MakeGameAutoScriptHeader(game_ref); + return write_to_file(header, dst_autoash); +} + +HError glvar_command(AGF::AGFReader &reader, const String &header_file, const String &body_file) +{ + printf("Output script header: %s\n", header_file.GetCStr()); + printf("Output script body: %s\n", body_file.GetCStr()); + + std::vector vars; + AGF::ReadGlobalVariables(vars, reader.GetGameRoot()); + String header = MakeVariablesScriptHeader(vars); + String body = MakeVariablesScriptBody(vars); + + auto err = write_to_file(header, header_file); + if (!err) + return err; + + printf("Script header written successfully.\n"); + + err = write_to_file(body, body_file); + if (!err) + return err; + + printf("Script body written successfully.\n"); + + return HError::None(); +} + + +int main(int argc, char *argv[]) +{ + std::set options_with_values; + + auto result = CmdLineOpts::Parse(argc, argv, options_with_values); + + if (result.PosArgs.empty()) + { + printf("%s\n", HELP_STRING); + return result.HelpRequested ? 0 : -1; + } + + const bool stdout_list_print = result.Opt.count("-t") || result.Opt.count("--to-stdout"); + // for (auto &owv: result.OptWithValue) + // { + // const String &opt = owv.first; + // const String &value = owv.second; + // } + + //-----------------------------------------------------------------------// + // Parse command specific arguments + //-----------------------------------------------------------------------// + + const String &asked_command = result.PosArgs[0]; + const size_t asked_command_argc = result.PosArgs.size() - 1; + CommandType command = kCmdNone; + String out_file = nullptr; + String game_agf = nullptr; + + for (int cmd = 0; cmd < kCmdMAX; cmd++) + { + if (asked_command.Equals(Command[cmd].Opt)) + { + command = static_cast(cmd); + const size_t required_cmd_argc = Command[cmd].NumArgs - (stdout_list_print ? 1 : 0); + const char *cmd_help = Command[cmd].Help; + if (result.HelpRequested) + { + printf("%s\n", cmd_help); + return 0; + } + if (asked_command_argc != required_cmd_argc) + { + printf("Error: required positional arguments don't match\n"); + printf("Requires %zu arguments, passed %zu\n", required_cmd_argc, asked_command_argc); + printf("%s\n", cmd_help); + return -1; + } + + game_agf = result.PosArgs[1]; + if(!stdout_list_print) + { + out_file = result.PosArgs[2]; + } + } + } + + if (command == kCmdNone) + { + printf("Error: unknown command '%s'\n", asked_command.GetCStr()); + printf("%s\n", HELP_STRING); + return -1; + } + + + //-----------------------------------------------------------------------// + // Read Game.agf + //-----------------------------------------------------------------------// + + AGF::AGFReader reader; + HError err = reader.Open(game_agf.GetCStr()); + if (!err) + { + printf("Error: failed to open source AGF '%s':\n", game_agf.GetCStr()); + printf("%s\n", err->FullMessage().GetCStr()); + return -1; + } + + //-----------------------------------------------------------------------// + // Execute command + //-----------------------------------------------------------------------// + String exp_data; + switch (command) + { + case kCmdHeaderList: + case kCmdScriptList: + case kCmdRoomList: + err = list_command(reader, command, out_file, stdout_list_print); + break; + case kCmdAutoAsh: + err = autoash_command(reader, out_file); + break; + case kCmdGlVar: + err = glvar_command(reader, out_file, result.PosArgs[3]); + break; + case kCmdMAX: + default: + // should never happen but handle just in case + err = new Error("Internal error caused invalid command"); + break; + } + + if (!err) + { + printf("Error: failed to execute command\n"); + printf("%s\n", err->FullMessage().GetCStr()); + return -1; + } else if(!stdout_list_print) { + printf("Data exported successfully.\n"); + } + return 0; +} diff --git a/Tools/data/agfreader.cpp b/Tools/data/agfreader.cpp index 07bd62bdd5..304fc5951d 100644 --- a/Tools/data/agfreader.cpp +++ b/Tools/data/agfreader.cpp @@ -406,6 +406,21 @@ void ReadScriptList(std::vector &script_list, DocElem root) } } +void ReadScriptHeaderList(std::vector &headers_list, DocElem root) +{ + AGF::ScriptModules scmodules; + AGF::ScriptWithHeader scmodule; + AGF::ScriptElem scelem; + std::vector modules; + scmodules.GetAll(root, modules); + for (const auto &m : modules) + { + DocElem header = scmodule.GetHeader(m); + if (!header) continue; + headers_list.push_back(scelem.ReadFilename(header)); + } +} + void ReadRoomList(std::vector> &room_list, DocElem root) { AGF::Rooms rooms; diff --git a/Tools/data/agfreader.h b/Tools/data/agfreader.h index 45db432c46..3750e7b020 100644 --- a/Tools/data/agfreader.h +++ b/Tools/data/agfreader.h @@ -428,6 +428,8 @@ void ReadGameSettings(DataUtil::GameSettings &opt, DocElem root); void ReadGameRef(DataUtil::GameRef &game, AGFReader &reader); // Reads an ordered list of script module names (their order determines dependency). void ReadScriptList(std::vector &script_list, DocElem root); +// Reads an ordered list of script header module names (their order determines dependency). +void ReadScriptHeaderList(std::vector &script_list, DocElem root); // Reads a list of room ID and descriptions found in the game document. void ReadRoomList(std::vector> &room_list, DocElem root);