From 1d2e981bddcec58fb82f759c8fa7673525ec06f8 Mon Sep 17 00:00:00 2001 From: lzwdgc Date: Sat, 3 Feb 2024 20:34:28 +0300 Subject: [PATCH] Make mod maker a library. Add mod example. --- .../aim1_mod_maker.cpp => examples/my_mod.cpp | 35 ++- src/aim1_mod_maker/aim1_mod_maker.h | 287 ++++++++++++++++++ sw.cpp | 15 +- 3 files changed, 322 insertions(+), 15 deletions(-) rename src/aim1_mod_maker/aim1_mod_maker.cpp => examples/my_mod.cpp (88%) create mode 100644 src/aim1_mod_maker/aim1_mod_maker.h diff --git a/src/aim1_mod_maker/aim1_mod_maker.cpp b/examples/my_mod.cpp similarity index 88% rename from src/aim1_mod_maker/aim1_mod_maker.cpp rename to examples/my_mod.cpp index 883e5e1..9a5b9bf 100644 --- a/src/aim1_mod_maker/aim1_mod_maker.cpp +++ b/examples/my_mod.cpp @@ -1,11 +1,22 @@ +/* +c++: 23 +deps: pub.lzwdgc.Polygon4.Tools.aim1_mod_maker-master +*/ + +/* + * Put this file near aim.exe. + * Invoke mod creation using the following command: + * sw run my_mod.cpp + * You can run the modded game now. + **/ + #include "aim1_mod_maker.h" -#include -#include -#include - int main(int argc, char *argv[]) { - mod_maker mod{"my_mod"}; + mod_maker mod{ "my_mod" }; + + mod.enable_free_camera(); + mod.enable_win_key(); mod.patch("location4.mmo", 0x7F599, 0x31, 0x33); // rename second FINSWIL-1 to FINSWIL-3 to make it appear mod.patch("location4.mmo", 0x7FA34, 1, 0); // make SWIRE appear @@ -15,8 +26,14 @@ int main(int argc, char *argv[]) { mod.patch("location5.mmo", 0xBAFF7, 0x18, 0x17); // reposition SACREFI-2 mod.patch("location6.mmo", 0x575DD, 'R', 'F'); // set correct model for a plant + mod.replace("location5.scr", "TOV_POLYMER_PLATES", "TOV_POLYMER_PLATE"); + mod.replace("location6.scr", "TOV_POLYMER_PLATES", "TOV_POLYMER_PLATE"); + + mod.replace("ORG_FIRST.scr", "IF(_PLAYERHAS(GL_M3_A_FIRST1)||_PLAYERHAS(GL_M3_A_FIRST1))", "IF(_PLAYERHAS(GL_M3_A_FIRST1)||_PLAYERHAS(GL_M3_A_FIRST2))"); + mod.replace("ORG_FIRST.scr", "IF(_PLAYERHAS(GL_M4_A_FIRST1)||_PLAYERHAS(GL_M4_A_FIRST1))", "IF(_PLAYERHAS(GL_M4_S_FIRST1)||_PLAYERHAS(GL_M4_S_FIRST2))"); + mod.replace("Script/bin/B_L1_BASE1.scr", "_ADDBALANCE(300)", R"( -_ADDBALANCE(300 ) + _ADDBALANCE(300 ) //_ADDOBJECT(GL_M4_S_FIRST2) //_ADDOBJECT(EQP_VACUUM_DRIVE_S4) @@ -48,12 +65,6 @@ _ADDBALANCE(300 ) _SETEVENT(SECTOR8.ACCESS) )"); - mod.replace("ORG_FIRST.scr", "IF(_PLAYERHAS(GL_M3_A_FIRST1)||_PLAYERHAS(GL_M3_A_FIRST1))", "IF(_PLAYERHAS(GL_M3_A_FIRST1)||_PLAYERHAS(GL_M3_A_FIRST2))"); - mod.replace("ORG_FIRST.scr", "IF(_PLAYERHAS(GL_M4_A_FIRST1)||_PLAYERHAS(GL_M4_A_FIRST1))", "IF(_PLAYERHAS(GL_M4_S_FIRST1)||_PLAYERHAS(GL_M4_S_FIRST2))"); - mod.replace("location5.scr", "TOV_POLYMER_PLATES", "TOV_POLYMER_PLATE"); - mod.replace("location6.scr", "TOV_POLYMER_PLATES", "TOV_POLYMER_PLATE"); - mod.enable_free_camera(); - mod.enable_win_key(); mod.apply(); return 0; diff --git a/src/aim1_mod_maker/aim1_mod_maker.h b/src/aim1_mod_maker/aim1_mod_maker.h new file mode 100644 index 0000000..eeed217 --- /dev/null +++ b/src/aim1_mod_maker/aim1_mod_maker.h @@ -0,0 +1,287 @@ +#pragma once + +#include + +#include +#include + +#include +#include + +struct mod_maker { + enum class file_type { + unknown, + + mmp, + mmo, + mmm, + model, + tm, + script, + sound, + }; + enum pak_files { + }; + + std::string name; + path game_dir; + std::set files_to_mod; + + mod_maker(const std::string &name) : name{name} { + detect_game_dir(fs::current_path()); + detect_tools(); + } + mod_maker(const std::string &name, const path &dir) : name{name} { + detect_game_dir(dir); + detect_tools(); + } + + void replace(const path &fn, const std::string &from, const std::string &to) { + auto ft = check_file_type(fn); + switch (ft) { + case file_type::script: { + auto p = find_real_filename(fn); + auto txt = make_script_txt_fn(p); + if (!fs::exists(txt)) { + run_p4_tool("script2txt", p); + } + replace_in_file_raw(txt, from, to); + run_p4_tool("txt2script", txt); + files_to_mod.insert(get_mod_dir() / txt.stem()); + break; + } + default: + SW_UNIMPLEMENTED; + } + } + void apply() { + std::vector files; + for (auto &&p : files_to_mod) { + if (p.filename() == "aim.exe") { + continue; + } + files.push_back(p.string()); + } + auto fn = get_mod_dir() / name += ".pak"s; + run_p4_tool("paker", fn, files); + fs::copy_file(fn, get_data_dir() / fn.filename(), fs::copy_options::overwrite_existing); + } + template + void patch(path fn, uint32_t offset, T val) { + fn = find_real_filename(fn); + files_to_mod.insert(fn); + patch_raw(fn, offset, val); + } + // this one checks for old value as well, so incorrect positions (files) won't be patched + template + bool patch(path fn, uint32_t offset, T oldval, T val) { + fn = find_real_filename(fn); + files_to_mod.insert(fn); + return patch_raw(fn, offset, oldval, val); + } + +#define ENABLE_DISABLE_FUNC(name, enable, disable) \ + void enable_##name() { name(enable); } \ + void disable_##name() { name(disable); } + ENABLE_DISABLE_FUNC(free_camera, 1, 0) + ENABLE_DISABLE_FUNC(win_key, 0x00, 0x10) +#undef ENABLE_DISABLE_FUNC + +private: + path find_real_filename(path fn) const { + auto s = fn.wstring(); + boost::to_lower(s); + fn = s; + if (fs::exists(fn)) { + return fn; + } + if (fn == "aim.exe") { + return game_dir / fn; + } + + auto ft = check_file_type(fn); + switch (ft) { + case file_type::script: { + auto p = get_data_dir() / "scripts.pak"; + unpak(p); + p = make_unpak_dir(p); + if (!fs::exists(p / fn)) { + p = p / "Script" / "bin" / fn.filename(); + } else { + p /= fn; + } + if (!fs::exists(p)) { + throw SW_RUNTIME_ERROR("Cannot find file in archives: "s + fn.string()); + } + return p; + } + case file_type::mmo: { + auto p = get_data_dir() / "maps2.pak"; + if (fs::exists(p)) { + unpak(p); + p = make_unpak_dir(p); + if (!fs::exists(p / fn)) { + p = p / fn.filename(); + } else { + p /= fn; + } + if (fs::exists(p)) { + return p; + } + } + p = get_data_dir() / "maps.pak"; + unpak(p); + p = make_unpak_dir(p); + if (!fs::exists(p / fn)) { + p = p / fn.filename(); + } else { + p /= fn; + } + if (!fs::exists(p)) { + throw SW_RUNTIME_ERROR("Cannot find file in archives: "s + fn.string()); + } + return p; + } + default: + SW_UNIMPLEMENTED; + } + } + // from https://github.com/Solant/aim-patches + void free_camera(uint8_t val) { + patch("aim.exe", 0x1F805, val); + } + void win_key(uint8_t val) { + patch("aim.exe", 0x4A40D, val); + } + template + void patch_raw(path fn, uint32_t offset, T val) const { + primitives::templates2::mmap_file f{fn, primitives::templates2::mmap_file::rw{}}; + auto &old = *(T *)(f.p + offset); + std::println("patching {} offset 0x{:08X} to {} (old value: {})", fn.string(), offset, val, old); + old = val; + } + template + bool patch_raw(path fn, uint32_t offset, T expected, T val) const { + primitives::templates2::mmap_file f{fn, primitives::templates2::mmap_file::rw{}}; + auto &old = *(T *)(f.p + offset); + std::println("patching {} offset 0x{:08X} from {} to {}", fn.string(), offset, expected, val); + if (old == expected) { + std::println("success"); + old = val; + return true; + } else if (old == val) { + std::println("success, already patched"); + return true; + } else { + std::println("old value {} != expected {}", old, expected); + return false; + } + } + path get_mod_dir() const { + return get_data_dir() / "mods" / name; + } + path get_data_dir() const { + return game_dir / "data"; + } + static void replace_in_file_raw(const path &fn, const std::string &from, const std::string &to) { + std::println("replacing in file {} from '{}' to '{}'", fn.string(), from, to); + auto f = read_file(fn); + boost::replace_all(f, from, to); + boost::replace_all(f, "\r", ""); + write_file_if_different(fn, f); + } + static path make_unpak_dir(path p) { + p += ".dir"; + return p; + } + static path make_script_txt_fn(path p) { + p += ".txt"; + return p; + } + void unpak(const path &p) const { + if (fs::exists(make_unpak_dir(p.filename()))) { + return; + } + run_p4_tool("unpaker", p); + } + void run_p4_tool(const std::string &tool, auto && ... args) const { + run_sw("pub.lzwdgc.Polygon4.Tools."s + tool + "-master", args...); + } + void run_sw(auto &&...args) const { + primitives::Command c; + c.working_directory = get_mod_dir(); + fs::create_directories(c.working_directory); + c.push_back("sw"); + c.push_back("run"); + (c.push_back(args),...); + run_command(c); + } + static void run_command(auto &c) { + c.out.inherit = true; + c.err.inherit = true; + std::cout << c.print() << "\n"; + c.execute(); + } + void detect_game_dir(const path &dir) { + const auto aim1_exe = "aim.exe"sv; + if (fs::exists(dir / aim1_exe)) { + game_dir = dir; + } else if (fs::exists(dir.parent_path() / aim1_exe)) { + game_dir = dir.parent_path(); + } else { + throw SW_RUNTIME_ERROR("Cannot detect aim1 game dir."); + } + game_dir = fs::absolute(game_dir).lexically_normal(); + } + void detect_tools() { + check_in_path("git"); + check_in_path("sw"); + } + void check_in_path(const path &program) const { + if (!has_in_path(program)) { + throw SW_RUNTIME_ERROR("Cannot find "s + program.string() + " in PATH."); + } + } + static bool has_in_path(const path &program) { + return !primitives::resolve_executable(program).empty(); + } + static file_type detect_file_type(const path &fn) { + auto ext = fn.extension().string(); + boost::to_lower(ext); + if (ext.empty()) { + return file_type::model; + } else if (ext == ".mmp") { + return file_type::mmp; + } else if (ext == ".mmo") { + return file_type::mmo; + } else if (ext == ".mmm") { + return file_type::mmm; + } else if (ext == ".scr" || ext == ".qst") { + return file_type::script; + } else if (ext == ".tm") { + return file_type::tm; + } else if (ext == ".ogg") { + return file_type::sound; + } + return file_type::unknown; + } + file_type check_file_type(const path &fn) const { + auto t = detect_file_type(fn); + if (t == file_type::unknown) { + throw SW_RUNTIME_ERROR("Unknown file type: "s + fn.string()); + } + return t; + } +}; + +int main1(int argc, char *argv[]); +int main(int argc, char *argv[]) { + try { + return main1(argc, argv); + } catch (std::exception &e) { + std::cerr << e.what() << "\n"; + } catch (...) { + std::cerr << "unknown exception" << "\n"; + } +} +#define main main1 diff --git a/sw.cpp b/sw.cpp index 67b3a80..33d25ed 100644 --- a/sw.cpp +++ b/sw.cpp @@ -54,9 +54,6 @@ void build(Solution &s) add_exe_with_common("tm_converter"); add_exe("name_generator"); add_exe_with_common("save_loader"); - add_exe_with_common("aim1_mod_maker") += - "pub.egorpugin.primitives.command"_dep - ; add_exe_with_common("unpaker") += "org.sw.demo.oberhumer.lzo.lzo"_dep, "org.sw.demo.xz_utils.lzma"_dep @@ -75,6 +72,18 @@ void build(Solution &s) ; } + auto &aim1_mod_maker = tools.addStaticLibrary("aim1_mod_maker"); + { + auto &t = aim1_mod_maker; + auto name = "aim1_mod_maker"; + t.PackageDefinitions = true; + t += cppstd; + t.setRootDirectory("src/"s + name); + //t.Public += "pub.egorpugin.primitives.sw.main"_dep; + t.Public += "pub.egorpugin.primitives.command"_dep; + t.Public += common; + } + add_exe("mod_reader") += model; path sdk = "d:/arh/apps/Autodesk/FBX/FBX SDK/2019.0";