From 23ddad574ecff9d227e3328fb9f448102cb6a322 Mon Sep 17 00:00:00 2001 From: lzwdgc Date: Tue, 13 Feb 2024 21:49:18 +0300 Subject: [PATCH] [mod_maker] Allow to add map goods. --- examples/mods/aim1_community_fix/my_mod.cpp | 51 +++-- src/aim1_mod_maker/aim1_mod_maker.h | 229 ++++++++++++++------ sw.cpp | 3 +- 3 files changed, 197 insertions(+), 86 deletions(-) diff --git a/examples/mods/aim1_community_fix/my_mod.cpp b/examples/mods/aim1_community_fix/my_mod.cpp index f6722d0..dda1102 100644 --- a/examples/mods/aim1_community_fix/my_mod.cpp +++ b/examples/mods/aim1_community_fix/my_mod.cpp @@ -1,6 +1,7 @@ /* name: aim_mod_maker c++: 23 +package_definitions: true deps: pub.lzwdgc.Polygon4.Tools.aim1_mod_maker-master */ @@ -29,15 +30,20 @@ deps: pub.lzwdgc.Polygon4.Tools.aim1_mod_maker-master // patch note: int main(int argc, char *argv[]) { - mod_maker mod("community_fix-0.0.2"s); + mod_maker mod( +#ifdef NDEBUG + "community_fix"s +#else + "test_mod"s +#endif + + "-0.0.2"s + ); mod.add_code_file_for_archive(INJECTIONS_FILE_NAME); mod.add_code_file_for_archive(AIM_TYPES_FILE_NAME); // patch note: CHANGES // patch note: // patch note: General Notes - // patch note: enabled free camera (use F3 key) (Solant) - mod.enable_free_camera(); // patch note: enabled WIN key during the game (Solant) mod.enable_win_key(); // patch note: @@ -63,6 +69,18 @@ int main(int argc, char *argv[]) { // patch note: // patch note: Hills Sector + // patch note: allow to buy double heavy weapon Finder-2 glider on Finders base after the second quest. You must start the new game to make it appear (lz) + mod.add_map_good("location6.mmo", "B_L6_IK_FINDER", "GL_S3_PS_FINDER1", R"( +47 4c 5f 53 33 5f 50 53 5f 46 49 4e 44 45 52 32 +00 d2 e2 77 42 04 06 00 35 01 00 00 76 0c 01 30 +54 5f 4c 36 5f 49 4b 5f 46 32 2e 43 4f 4d 50 4c +45 54 45 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 +)"_bin); // patch note: set correct model for a plant (Streef) mod.patch("location6.mmo", 0x575DD, 'R', 'F'); // patch note: fix 'TOV_POLYMER_PLATE' spawn (Streef) @@ -106,22 +124,27 @@ int main(int argc, char *argv[]) { // patch note: // test scripts +#ifndef NDEBUG + // patch note dev: Developer Mode!!! + // patch note dev: enabled developer mode (free camera - F3 key, time shift - N key) (lz, Solant) + mod.enable_free_camera(); + // patch note dev: start money, rating, glider and sector access mod.replace("Script/bin/B_L1_BASE1.scr", "_ADDBALANCE(300)", R"( _ADDBALANCE(300 ) - _ADDOBJECT(GL_S3_PS_FINDER1) - _ADDOBJECT(EQP_VACUUM_DRIVE_S3) - _ADDOBJECT(EQP_ZERO_ARMOR_S3) - _ADDOBJECT(EQP_SHIELD_GENERATOR4_S3) + //_ADDOBJECT(GL_S3_PS_FINDER1) + //_ADDOBJECT(EQP_VACUUM_DRIVE_S3) + //_ADDOBJECT(EQP_ZERO_ARMOR_S3) + //_ADDOBJECT(EQP_SHIELD_GENERATOR4_S3) - //_ADDOBJECT(GL_M4_S_FIRST2) - //_ADDOBJECT(EQP_VACUUM_DRIVE_S4) + _ADDOBJECT(GL_M4_S_FIRST2) + _ADDOBJECT(EQP_VACUUM_DRIVE_S4) //_ADDOBJECT(EQP_MEZON_REACTOR_S4) //_ADDOBJECT(EQP_GLUON_REACTOR_S1) - //_ADDOBJECT(EQP_ZERO_ARMOR_S4) - //_ADDOBJECT(EQP_SHIELD_GENERATOR4_S4) - //_ADDOBJECT(GUN_MICROWAVE_OSCILLATOR) - //_ADDOBJECT(GUN_RAILGUN) + _ADDOBJECT(EQP_ZERO_ARMOR_S4) + _ADDOBJECT(EQP_SHIELD_GENERATOR4_S4) + _ADDOBJECT(GUN_MICROWAVE_OSCILLATOR) + _ADDOBJECT(GUN_RAILGUN) _ADDRATING(300000000) _ADDBALANCE(30000000) @@ -143,6 +166,8 @@ int main(int argc, char *argv[]) { //_SETEVENT(SECTOR8.VISIT) _SETEVENT(SECTOR8.ACCESS) )"); + // patch note dev: +#endif // patch note: Release Manager // patch note: lz diff --git a/src/aim1_mod_maker/aim1_mod_maker.h b/src/aim1_mod_maker/aim1_mod_maker.h index 744e165..6d95859 100644 --- a/src/aim1_mod_maker/aim1_mod_maker.h +++ b/src/aim1_mod_maker/aim1_mod_maker.h @@ -1,10 +1,12 @@ #pragma once #include -#include +#include +#include #include #include +#include #include #include @@ -61,7 +63,7 @@ struct aim_exe_v1_06_constants { enum : uint32_t { trampoline_base_real = 0x00025100, trampoline_target_real = 0x001207f0, - //code_base = 0x00401000, + code_base = 0x00401000, //data_base = 0x00540000, //free_data_base_virtual = 0x006929C0, free_data_base_virtual = 0x00692FF0, @@ -161,6 +163,9 @@ struct mod_maker { auto patchnotes_fn = path{game_dir / get_full_mod_name()} += ".README.txt"; files_to_distribute.insert(patchnotes_fn.filename()); std::ofstream ofile{patchnotes_fn}; +#ifndef NDEBUG + ofile << "Developer Mode!!!\nOnly for testing purposes!\nDO NOT USE FOR ACTUAL PLAYING !!!\n\n"; +#endif ofile << name; if (!version.empty()) { ofile << " (version: " << version << ")"; @@ -168,19 +173,26 @@ struct mod_maker { ofile << "\n\n"; ofile << std::format("Release Date\n{:%d.%m.%Y %X}\n\n", std::chrono::system_clock::now()); for (auto &&line : read_lines(loc.file_name())) { + auto f = [&](auto &&a) { + auto pos = line.find(a); + if (pos != -1) { + auto s = line.substr(pos + a.size()); + if (!s.empty() && s[0] == ' ') { + s = s.substr(1); + } + boost::trim_right(s); + if (!s.empty() && (s[0] >= 'a' && s[0] <= 'z' || s[0] >= '0' && s[0] <= '9')) { + s = "* " + s; + } + ofile << s << "\n"; + } + }; auto anchor = "patch note:"sv; - auto pos = line.find(anchor); - if (pos != -1) { - auto s = line.substr(pos + anchor.size()); - if (!s.empty() && s[0] == ' ') { - s = s.substr(1); - } - boost::trim_right(s); - if (!s.empty() && (s[0] >= 'a' && s[0] <= 'z' || s[0] >= '0' && s[0] <= '9')) { - s = "* " + s; - } - ofile << s << "\n"; - } + auto anchor_dev = "patch note dev:"sv; + f(anchor); +#ifndef NDEBUG + f(anchor_dev); +#endif } ofile.close(); @@ -234,6 +246,62 @@ struct mod_maker { files_to_pak.insert(fn); return patch_raw(fn, offset, oldval, val); } + void insert(path fn, uint32_t offset, const byte_array &data) { + files_to_pak.insert(find_real_filename(fn)); + if (is_already_inserted(fn, data)) { + return; + } + + log("inserting into {} offset 0x{:08X} {} bytes", fn.string(), offset, data.size()); + + auto rfn = find_real_filename(fn); + fs::resize_file(rfn, fs::file_size(rfn) + data.size()); + primitives::templates2::mmap_file f{rfn, primitives::templates2::mmap_file::rw{}}; + memmove(f.p + offset + data.size(), f.p + offset, f.sz - offset); + ::memcpy(f.p + offset, data.data(), data.size()); + f.close(); + + write_file(get_hash_fn(fn, data), ""s); + } + void add_map_good(path mmo_fn, const std::string &building_name, const std::string &after_good_name, const byte_array &data) { + files_to_pak.insert(find_real_filename(mmo_fn)); + if (is_already_inserted(mmo_fn, data)) { + return; + } + + auto fn = find_real_filename(mmo_fn); + mmo_storage2 m; + m.load(fn); + + auto it = m.map_building_goods.find(building_name); + if (it == m.map_building_goods.end()) { + throw std::runtime_error{"no such building: "s + building_name}; + } + + uint32_t insertion_offset = it->second.offset + sizeof(uint32_t); + if (!after_good_name.empty()) { + auto it2 = it->second.building_goods.find(after_good_name); + if (it2 == it->second.building_goods.end()) { + throw std::runtime_error{"no such building good: "s + after_good_name}; + } + insertion_offset = it2->second; + } + + { + primitives::templates2::mmap_file f{fn, primitives::templates2::mmap_file::rw{}}; + if (::memcmp(f.p + insertion_offset, data.data(), data.size()) == 0) { + return; + } + } + + insert(mmo_fn, insertion_offset, data); + + primitives::templates2::mmap_file f{fn, primitives::templates2::mmap_file::rw{}}; + // increase section size + *(uint32_t *)(f.p + m.sections.map_goods.offset) += data.size(); + // increase number of goods + ++*(uint32_t *)(f.p + it->second.offset); + } // all you need is to provide injection address (virtual) with size // handle the call instruction in 'dispatcher' symbol (naked) of your dll @@ -265,6 +333,22 @@ struct mod_maker { } private: + path get_hash_fn(path fn, const byte_array &data) const { + return get_mod_dir() / std::format("{:0X}.hash", get_insert_hash(fn, data)); + } + size_t get_insert_hash(path fn, const byte_array &data) const { + auto s = fn.wstring(); + boost::to_lower(s); + fn = s; + + size_t hash{}; + boost::hash_combine(hash, fn); + boost::hash_combine(hash, data); + return hash; + } + bool is_already_inserted(path fn, const byte_array &data) const { + return fs::exists(get_hash_fn(fn,data)); + } void init(const path &dir) { read_name(); detect_game_dir(dir); @@ -274,6 +358,9 @@ private: db.quest_.open(get_data_dir() / "quest", primitives::templates2::mmap_file::rw{}); detect_tools(); prepare_injections(); +#ifndef NDEBUG + enable_win_key(); +#endif } void read_name() { if (name.empty()) { @@ -370,10 +457,19 @@ private: decltype(name) get_dll_name() const { return get_full_mod_name() + ".dll"; } + uint32_t virtual_to_real(uint32_t v) { + return v - aim_exe_v1_06_constants::code_base + 0x1000; + } + void patch(uint8_t *p, uint32_t off, const byte_array &from, const byte_array &to) { + if (from.size() != to.size()) { + throw std::runtime_error{"size mismatch"}; + } + if (memcmp(p + off, to.data(), to.size()) == 0) { + return; // ok, already patched + } + ::memcpy(p + off, to.data(), to.size()); + } void prepare_injections() { -#ifndef NDEBUG - enable_free_camera(); -#endif create_backup_exe_file(); #ifdef NDEBUG make_injected_dll(); @@ -383,51 +479,30 @@ private: uint32_t our_data = aim_exe_v1_06_constants::our_code_start_virtual; auto ptr = f.p + aim_exe_v1_06_constants::trampoline_target_real; -#ifdef NDEBUG - auto dllnamelen = get_sw_dll_name().size() + 1; - strcpy((char *)ptr, get_sw_dll_name().c_str()); -#else - auto dllname = "h:\\Games\\AIM\\1\\.sw\\out\\d\\aim_fixes-0.0.1.dll"s; - auto dllnamelen = dllname.size() + 1; - strcpy((char *)ptr, dllname.c_str()); -#endif - ptr += dllnamelen; + + auto strcpy = [&](const std::string &s) { + ::strcpy((char *)ptr, s.c_str()); + ptr += s.size() + 1; + our_data += s.size() + 1; + }; + auto push_dll_name = make_insn_with_address("68"_bin, our_data); // push - our_data += 0x20; - strcpy((char *)ptr, "dispatcher"); - auto dispatcher_func_name = make_insn_with_address("68"_bin, our_data); // push - ptr += 0x20; - our_data += 0x20; +#ifdef NDEBUG + strcpy(get_sw_dll_name()); +#else + strcpy("h:\\Games\\AIM\\1\\.sw\\out\\d\\aim_fixes-0.0.1.dll"s); +#endif const auto jumppad = "68 30 B8 51 00"_bin; // push offset SEH_425100 uint32_t jump_offset = ptr - f.p - aim_exe_v1_06_constants::trampoline_base_real - jumppad.size() * 2; - memreplace(f.p, f.sz, jumppad, make_insn_with_address("e9"_bin, jump_offset)); + patch(f.p, virtual_to_real(0x00425105), jumppad, make_insn_with_address("e9"_bin, jump_offset)); memcpy(ptr, jumppad); // put our removed insn memcpy(ptr, R"( 60 ; pusha )"_bin); memcpy(ptr, push_dll_name); memcpy(ptr, R"( -8B 3D D8 10 52 00 ; mov edi, ds:LoadLibraryA - not working ; but do not remove, it does not work without it -;bf 30 0f 91 75 ; mov edi, 0x75910f30 - load direct adress -; edi has wrong address after prev. insn, so we fix it manually -;81 EF 00 BD 00 00 ; sub edi, 0BD00h -)"_bin); - memcpy(ptr, R"( +8B 3D D8 10 52 00 ; mov edi, ds:LoadLibraryA FF D7 ; call edi -)"_bin); - memcpy(ptr, dispatcher_func_name); - // get proc addr - memcpy(ptr, R"( -8B 3D D4 10 52 00 ; mov edi, ds:GetProcAddr - not working ; but do not remove, it does not work without it -;bf 2C 0f 91 75 ; mov edi, 0x75910f30 - load direct adress -; edi has wrong address after prev. insn, so we fix it manually -;81 EF FC BC 00 00 ; sub edi, 0BC00h -50 ; push eax -)"_bin); - memcpy(ptr, R"( -FF D7 ; call edi -)"_bin); - memcpy(ptr, R"( 61 ; popa )"_bin); memcpy(ptr, make_insn_with_address("e9"_bin, -(ptr - f.p - aim_exe_v1_06_constants::trampoline_base_real - jumppad.size()))); @@ -461,8 +536,13 @@ FF D7 ; call edi } case file_type::mmo: { auto p = get_data_dir() / "maps2.pak"; - if (fs::exists(p)) { - unpak(p); + if (fs::exists(p) && (false + // it contains only these + || fn == "location3.mmo" + || fn == "location4.mmo" + || fn == "location5.mmo" + )) { + unpak(p, fn); p = make_unpak_dir(p); if (!fs::exists(p / fn)) { p = p / fn.filename(); @@ -470,11 +550,19 @@ FF D7 ; call edi p /= fn; } if (fs::exists(p)) { - return p; + auto dst = get_mod_dir() / p.filename(); + if (!fs::exists(dst)) { + fs::copy_file(p, dst); + } + return dst; } } p = get_data_dir() / "maps.pak"; - unpak(p); + unpak(p + // takes too long to extract specific files + // maybe speedup the unpaker + //, fn + ); p = make_unpak_dir(p); if (!fs::exists(p / fn)) { p = p / fn.filename(); @@ -484,7 +572,11 @@ FF D7 ; call edi if (!fs::exists(p)) { throw SW_RUNTIME_ERROR("Cannot find file in archives: "s + fn.string()); } - return p; + auto dst = get_mod_dir() / p.filename(); + if (!fs::exists(dst)) { + fs::copy_file(p, dst); + } + return dst; } default: SW_UNIMPLEMENTED; @@ -551,11 +643,16 @@ FF D7 ; call edi p += ".txt"; return p; } - void unpak(const path &p) const { - if (fs::exists(make_unpak_dir(p))) { + void unpak(const path &p, const path &fn = {}) const { + auto udir = make_unpak_dir(p); + if (fs::exists(udir) && (fn.empty() || fs::exists(udir / fn))) { return; } - run_p4_tool("unpaker", p); + if (fn.empty()) { + run_p4_tool("unpaker", p); + } else { + run_p4_tool("unpaker", p, fn); + } } void run_p4_tool(const std::string &tool, auto && ... args) const { run_sw("pub.lzwdgc.Polygon4.Tools."s + tool + "-master", args...); @@ -627,15 +724,3 @@ FF D7 ; call edi 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 d715f79..9fba505 100644 --- a/sw.cpp +++ b/sw.cpp @@ -27,7 +27,7 @@ void build(Solution &s) auto add_exe = [&](const String &name) -> decltype(auto) { auto &t = add_exe_base(name); - t += "pub.egorpugin.primitives.sw.main"_dep; + t.Public += "pub.egorpugin.primitives.sw.main"_dep; return t; }; @@ -50,6 +50,7 @@ void build(Solution &s) add_exe_with_data_manager("db_extractor2"); add_exe_with_data_manager("mmm_extractor"); add_exe_with_data_manager("mmo_extractor"); + add_exe_with_common("mmo_extractor2"); add_exe_with_common("mmp_extractor") += "org.sw.demo.intel.opencv.highgui"_dep; add_exe_with_common("mpj_loader"); add_exe_with_common("paker");