diff --git a/examples/mods/aim1_community_fix/my_mod.cpp b/examples/mods/aim1_community_fix/my_mod.cpp index af35986..f6722d0 100644 --- a/examples/mods/aim1_community_fix/my_mod.cpp +++ b/examples/mods/aim1_community_fix/my_mod.cpp @@ -29,17 +29,9 @@ deps: pub.lzwdgc.Polygon4.Tools.aim1_mod_maker-master // patch note: int main(int argc, char *argv[]) { - mod_maker mod; - mod.files_to_distribute.insert(INJECTIONS_FILE_NAME); - mod.files_to_distribute.insert(AIM_TYPES_FILE_NAME); - - // patch note: enable double weap gliders (still have many bugs related) - mod.make_injection(0x004072FA); // can trade for buy purposes - mod.make_injection(0x004D62E4); // setup proper weapon slots for a glider - mod.make_injection(0x00417A6D); // put weapon into the right slot after purchase - mod.make_injection(0x004176BC); // sell correct weapon - mod.make_injection(0x004067C4); // empty light weap - mod.make_injection(0x0040688B); // empty heavy weap + mod_maker mod("community_fix-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: @@ -90,13 +82,30 @@ int main(int argc, char *argv[]) { "IF(_PLAYERHAS(GL_S2_PA_SINYGR)|_PLAYERHAS(GL_S4_S_SINYGR))", "IF(_ISGLIDER(GL_S2_PA_SINYGR)|_ISGLIDER(GL_S4_S_SINYGR))"); - // patch note: _ISGLIDER() function can check exact glider name now, for example _ISGLIDER(GL_M3_A_FIRST1) (lz) + // patch note: * _ISGLIDER() function can check exact glider name now, for example _ISGLIDER(GL_M3_A_FIRST1) (lz) mod.make_injection(0x0043A1F6, 10); - // - // end of scripts section // patch note: + // patch note: Database Changes + // patch note: add name for SINIGR armor, it was unnamed before (lz) + mod.db.quest().add_value("INFORMATION"sv, "EQP_ZERO_ARMOR_S_SIN"sv, "NAME", (const char *)u8"Особая нуль-броня"); + // patch note: + + // patch note: Game Changes + // patch note: enable double weapon gliders (lz) + // patch note: double light weapons: GL_M2_PA_NARGOON and GL_S3_PS_FINDER1 + // patch note: double heavy weapons: GL_M3_PA_EYEDSTONE and GL_S3_PS_FINDER2 + // patch note: (still have many bugs related) + mod.make_injection(0x004072FA); // can trade for buy purposes + mod.make_injection(0x004D62E4); // setup proper weapon slots for a glider + mod.make_injection(0x00417A6D); // put weapon into the right slot after purchase + mod.make_injection(0x004176BC); // sell correct weapon + mod.make_injection(0x004067C4); // empty light weap + mod.make_injection(0x0040688B); // empty heavy weap + // patch note: + + // test scripts mod.replace("Script/bin/B_L1_BASE1.scr", "_ADDBALANCE(300)", R"( _ADDBALANCE(300 ) diff --git a/src/aim1_mod_maker/aim1_mod_maker.h b/src/aim1_mod_maker/aim1_mod_maker.h index 0546ff1..744e165 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 @@ -14,9 +16,6 @@ constexpr auto aim_exe = "aim.exe"sv; using byte_array = std::vector; -struct patcher { -}; - auto operator""_bin(const char *ptr, uint64_t len) { byte_array ret; auto lines = split_lines(ptr); @@ -51,6 +50,13 @@ auto operator""_bin(const char *ptr, uint64_t len) { return ret; } +void log(auto &&format, auto &&arg, auto &&...args) { + std::println("{}", std::vformat(format, std::make_format_args(arg, args...))); +} +void log(auto &&str) { + std::println("{}", str); +} + struct aim_exe_v1_06_constants { enum : uint32_t { trampoline_base_real = 0x00025100, @@ -75,20 +81,43 @@ struct mod_maker { script, sound, }; - enum pak_files { - }; + struct { + mod_maker &m; + db2 db_; + db2 quest_; + + db2 &db() { + add_files("db"); + return db_; + } + db2 &quest() { + add_files("quest"); + return quest_; + } + private: + void add_files(const path &fn) { + auto base = path{"data"} / fn; + m.files_to_distribute.insert(path{base} += ".ind"); + m.files_to_distribute.insert(path{base} += ".dat"); + m.files_to_distribute.insert(path{base} += ".tab"); + } + } db{*this}; std::string name; std::string version; path game_dir; std::set files_to_pak; std::set files_to_distribute; + std::set code_files_to_distribute; std::source_location loc; mod_maker(std::source_location loc = std::source_location::current()) : loc{loc} { init(fs::current_path()); } - mod_maker(const path &dir, std::source_location loc = std::source_location::current()) : loc{loc} { + mod_maker(const std::string &name, std::source_location loc = std::source_location::current()) : name{name}, loc{loc} { + init(fs::current_path()); + } + mod_maker(const std::string &name, const path &dir, std::source_location loc = std::source_location::current()) : name{name}, loc{loc} { init(dir); } @@ -114,6 +143,9 @@ struct mod_maker { } } void apply() { + db.db_.close(); + db.quest_.close(); + std::vector files; for (auto &&p : files_to_pak) { if (p.filename() == aim_exe) { @@ -140,7 +172,10 @@ struct mod_maker { auto pos = line.find(anchor); if (pos != -1) { auto s = line.substr(pos + anchor.size()); - boost::trim(s); + 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; } @@ -151,17 +186,41 @@ struct mod_maker { // we do not check for presence of 7z command here if (has_in_path("7z")) { + auto ar = get_full_mod_name() + ".zip"; + primitives::Command c; c.working_directory = game_dir; c.push_back("7z"); c.push_back("a"); - c.push_back(get_full_mod_name() + ".zip"); // we use zip as more common + c.push_back(ar); // we use zip as more common for (auto &&f : files_to_distribute) { c.push_back(f); } + for (auto &&f : code_files_to_distribute) { + c.push_back(f); + } run_command(c); + + auto rename = [&](auto &&from, auto &&to) { + primitives::Command c; + c.working_directory = game_dir; + c.push_back("7z"); + c.push_back("rn"); + c.push_back(ar); + c.push_back(from); + c.push_back(to); + run_command(c); + }; + for (auto &&f : code_files_to_distribute) { + if (f.filename() == path{loc.file_name()}.filename()) { + rename(f.filename(), path{"data"} / "mods" / get_full_mod_name() / get_full_mod_name() += ".cpp"); + } else { + rename(f.filename(), path{"data"} / "mods" / get_full_mod_name() / f.filename()); + } + } } } + template void patch(path fn, uint32_t offset, T val) { fn = find_real_filename(fn); @@ -190,7 +249,7 @@ struct mod_maker { memcpy(ptr, make_insn_with_address("e8"_bin, aim_exe_v1_06_constants::free_data_base_virtual - (virtual_address + call_command_length))); memcpy(ptr, make_nops(len - call_command_length)); - std::println("making injection on the virtual address 0x{:0X} (real address 0x{:0X}), size {}", virtual_address, ptr - f.p, + log("making injection on the virtual address 0x{:0X} (real address 0x{:0X}), size {}", virtual_address, ptr - f.p, size); } @@ -201,17 +260,25 @@ struct mod_maker { ENABLE_DISABLE_FUNC(win_key, 0x00, 0x10) #undef ENABLE_DISABLE_FUNC + void add_code_file_for_archive(const path &fn) { + code_files_to_distribute.insert(path{loc.file_name()}.parent_path() / fn); + } + private: void init(const path &dir) { read_name(); detect_game_dir(dir); fs::create_directories(get_mod_dir()); - files_to_distribute.insert(loc.file_name()); + code_files_to_distribute.insert(loc.file_name()); + db.db_.open(get_data_dir() / "db", primitives::templates2::mmap_file::rw{}); + db.quest_.open(get_data_dir() / "quest", primitives::templates2::mmap_file::rw{}); detect_tools(); prepare_injections(); } void read_name() { - name = path{loc.file_name()}.stem().string(); + if (name.empty()) { + name = path{loc.file_name()}.stem().string(); + } // use regex? auto p = name.find('-'); if (p != -1) { @@ -261,6 +328,8 @@ private: return arr; } void make_injected_dll() { + log("making injected dll"); + path fn = loc.file_name(); //fs::copy_file(fn, get_mod_dir() / fn.filename(), fs::copy_options::overwrite_existing); std::string contents; @@ -306,7 +375,9 @@ private: enable_free_camera(); #endif create_backup_exe_file(); +#ifdef NDEBUG make_injected_dll(); +#endif files_to_distribute.insert(aim_exe); primitives::templates2::mmap_file f{find_real_filename(aim_exe), primitives::templates2::mmap_file::rw{}}; uint32_t our_data = aim_exe_v1_06_constants::our_code_start_virtual; @@ -439,34 +510,34 @@ FF D7 ; call edi 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); + log("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); + log("patching {} offset 0x{:08X} from {} to {}", fn.string(), offset, expected, val); if (old == expected) { - std::println("success"); + log("success"); old = val; return true; } else if (old == val) { - std::println("success, already patched"); + log("success, already patched"); return true; } else { - std::println("old value {} != expected {}", old, expected); + log("old value {} != expected {}", old, expected); return false; } } path get_mod_dir() const { - return get_data_dir() / "mods" / name; + return get_data_dir() / "mods" / get_full_mod_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); + log("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", ""); @@ -501,7 +572,7 @@ FF D7 ; call edi static void run_command(auto &c) { c.out.inherit = true; c.err.inherit = true; - std::cout << c.print() << "\n"; + log(c.print()); c.execute(); } void detect_game_dir(const path &dir) { diff --git a/src/common/common.cpp b/src/common/common.cpp index f8e7d52..547dd70 100644 --- a/src/common/common.cpp +++ b/src/common/common.cpp @@ -58,3 +58,15 @@ std::wstring str2utf16(const std::string &codepage_str, int cp) SW_UNIMPLEMENTED; #endif } + +std::string str2str(const std::string &codepage_str, int cp_from, int cp_to) { +#ifdef _WIN32 + auto utf16_str = str2utf16(codepage_str, cp_from); + int dest_size = WideCharToMultiByte(cp_to, 0, utf16_str.c_str(), utf16_str.length(), nullptr, 0, nullptr, nullptr); + std::string dest_str(dest_size, '\0'); + WideCharToMultiByte(cp_to, 0, utf16_str.c_str(), utf16_str.length(), &dest_str[0], dest_size, nullptr, nullptr); + return dest_str; +#else + SW_UNIMPLEMENTED; +#endif +} diff --git a/src/common/common.h b/src/common/common.h index bb1e9c3..2b9be6c 100644 --- a/src/common/common.h +++ b/src/common/common.h @@ -24,6 +24,8 @@ std::string str2utf8(const std::string &codepage_str, int cp = 0); std::wstring str2utf16(const std::string &codepage_str, int cp = 0); +std::string str2str(const std::string &codepage_str, int cp_from, int cp_to); + struct progress_bar { const size_t max_elements; const int displaylen; diff --git a/src/common/db2.h b/src/common/db2.h new file mode 100644 index 0000000..4f0dfa8 --- /dev/null +++ b/src/common/db2.h @@ -0,0 +1,234 @@ +/* + * AIM db_extractor + * Copyright (C) 2024 lzwdgc + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "common.h" +#include "mmap.h" + +struct db2 { + using char20 = char[0x20]; + + enum class field_type : uint32_t { + string, + integer, + float_, + }; + + // table structure + struct tab { + struct table { + uint32_t id; + char20 name; + uint32_t unk; + }; + struct field { + uint32_t table_id; + uint32_t id; + char20 name; + field_type type; + }; + + uint32_t n_tables; + uint32_t n_fields; + + auto tables() { + auto base = (table *)(&n_fields + 1); + return std::span{base, base + n_tables}; + } + auto fields() { + auto table_base = (table *)(&n_fields + 1); + auto base = (field *)(table_base + n_tables); + return std::span{base, base + n_fields}; + } + }; + // table values (index) + struct ind { + struct value { + uint32_t table_id; + char20 name; + uint32_t offset; + uint32_t size; + }; + + uint32_t n_values; + + auto values() { + auto base = (value *)(&n_values + 1); + return std::span{base, base + n_values}; + } + }; + // field values + struct dat { + // NOTE: for some reason int fields can be != 4 + // so follow this size field + struct field_value_base { + uint32_t field_id; + uint32_t size; + }; + }; + + path fn; + primitives::templates2::mmap_file fdat, find, ftab; + tab *tab_{}; + ind *ind_{}; + dat *dat_{}; + + db2() = default; + db2(const path &fn) : fn{fn} { + open(fn, primitives::templates2::mmap_file::ro{}); + } + + void open(const path &fn, auto mode) { + close(); + this->fn = fn; + fdat.open(path{fn} += ".dat", mode); + find.open(path{fn} += ".ind", mode); + ftab.open(path{fn} += ".tab", mode); + + dat_ = (dat *)find.p; + ind_ = (ind *)find.p; + tab_ = (tab *)ftab.p; + } + void open(auto mode) { + open(fn, mode); + } + void close() { + fdat.close(); + find.close(); + ftab.close(); + } + + void add_value(std::string_view table, std::string_view value, auto && ... fields1) { + auto tbl = tab_->tables(); + auto fields = tab_->fields(); + auto values = ind_->values(); + + auto calc_fields_size = [](this auto &&f, std::string_view field_name, auto &&n, auto &&v, auto &&...fields) { + if (field_name == n) { + if constexpr (std::same_as, int>) { + return sizeof(db2::dat::field_value_base) + sizeof(int); + } else if constexpr (std::same_as, float>) { + return sizeof(db2::dat::field_value_base) + sizeof(float); + } else { + auto s = str2str(v, CP_UTF8, 1251); + return sizeof(db2::dat::field_value_base) + s.size() + 1; + } + } + if constexpr (sizeof...(fields)) { + return f(field_name, fields...); + } + if constexpr (std::same_as, int>) { + return sizeof(db2::dat::field_value_base) + sizeof(int); + } else if constexpr (std::same_as, float>) { + return sizeof(db2::dat::field_value_base) + sizeof(float); + } else { + return sizeof(db2::dat::field_value_base) + 1; + } + }; + auto write_fields = [](this auto &&f, auto &&p, auto &&field, std::string_view field_name, auto &&n, auto &&v, auto &&...fields) { + if (field_name == n) { + if constexpr (std::same_as, int>) { + if (field.type != db2::field_type::integer) { + throw std::runtime_error{"field type mismatch"}; + } + (*(db2::dat::field_value_base*)p).field_id = field.id; + (*(db2::dat::field_value_base*)p).size = sizeof(int); + p += sizeof(db2::dat::field_value_base); + *(int*)p = v; + p += sizeof(int); + return; + } else if constexpr (std::same_as, float>) { + if (field.type != db2::field_type::float_) { + throw std::runtime_error{"field type mismatch"}; + } + (*(db2::dat::field_value_base *)p).field_id = field.id; + (*(db2::dat::field_value_base *)p).size = sizeof(float); + p += sizeof(db2::dat::field_value_base); + *(float *)p = v; + p += sizeof(float); + return; + } else { + if (field.type != db2::field_type::string) { + throw std::runtime_error{"field type mismatch"}; + } + auto s = str2str(v, CP_UTF8, 1251); + (*(db2::dat::field_value_base *)p).field_id = field.id; + (*(db2::dat::field_value_base *)p).size = s.size() + 1; + p += sizeof(db2::dat::field_value_base); + memcpy(p, s.data(), s.size()); + p[s.size()] = 0; + p += s.size() + 1; + return; + } + } + if constexpr (sizeof...(fields)) { + return f(p, field, field_name, fields...); + } + if constexpr (std::same_as, int>) { + (*(db2::dat::field_value_base *)p).field_id = field.id; + (*(db2::dat::field_value_base *)p).size = 0; + p += sizeof(db2::dat::field_value_base); + return; + } else if constexpr (std::same_as, float>) { + (*(db2::dat::field_value_base *)p).field_id = field.id; + (*(db2::dat::field_value_base *)p).size = 0; + p += sizeof(db2::dat::field_value_base); + return; + } else { + (*(db2::dat::field_value_base *)p).field_id = field.id; + (*(db2::dat::field_value_base *)p).size = 1; + p += sizeof(db2::dat::field_value_base); + p[1] = 0; + p += 1; + return; + } + }; + + auto it = std::ranges::find_if(tbl, [&](auto &v){return v.name == table;}); + if (it == tbl.end()) { + throw std::runtime_error{"no such table"}; + } + auto &t = *it; + auto itv = std::ranges::find_if(values, [&](auto &v){return v.table_id == t.id && value == v.name;}); + if (itv == values.end()) { + db2::ind::value i{}; + i.table_id = t.id; + memcpy(i.name, value.data(), value.size()); + i.offset = fdat.sz; + for (auto &&f : fields) { + if (f.table_id != t.id) { + continue; + } + std::string_view fn = f.name; + i.size += calc_fields_size(fn, fields1...); + } + + ++ind_->n_values; + auto p = find.alloc_raw(find.sz + sizeof(i)); + memcpy(p, &i, sizeof(i)); + + p = fdat.alloc_raw(fdat.sz + i.size); + for (auto &&f : fields) { + if (f.table_id != t.id) { + continue; + } + std::string_view fn = f.name; + write_fields(p, f, fn, fields1...); + } + } + } +}; diff --git a/src/db_extractor2/db_extractor2.cpp b/src/db_extractor2/db_extractor2.cpp index 66addc5..d9bc315 100644 --- a/src/db_extractor2/db_extractor2.cpp +++ b/src/db_extractor2/db_extractor2.cpp @@ -1,6 +1,6 @@ /* * AIM db_extractor - * Copyright (C) 2015 lzwdgc + * Copyright (C) 2024 lzwdgc * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,12 +16,10 @@ * along with this program. If not, see . */ -#include "db.h" +#include "db2.h" #include #include -#include -#include #include #include @@ -31,78 +29,6 @@ #include #include -struct db2 { - using char20 = char[0x20]; - - // table structure - struct tab { - struct table { - uint32_t id; - char20 name; - uint32_t unk; - }; - struct field { - uint32_t table_id; - uint32_t id; - char20 name; - FieldType type; - }; - - uint32_t n_tables; - uint32_t n_fields; - - auto tables() { - auto base = (table *)(&n_fields+1); - return std::span{base, base+n_tables}; - } - auto fields() { - auto table_base = (table *)(&n_fields + 1); - auto base = (field *)(table_base + n_tables); - return std::span{base, base + n_fields}; - } - }; - // table values (index) - struct ind { - struct value { - uint32_t table_id; - char20 name; - uint32_t offset; - uint32_t size; - }; - - uint32_t n_values; - - auto values() { - auto base = (value *)(&n_values + 1); - return std::span{base, base + n_values}; - } - }; - // field values - struct dat { - // NOTE: for some reason int fields can be != 4 - // so follow this size field - struct field_value_base { - uint32_t field_id; - uint32_t size; - }; - }; - - primitives::templates2::mmap_file fdat,find,ftab; - tab *tab_; - ind *ind_; - dat *dat_; - - db2(const path &fn) { - fdat.open(path{fn} += ".dat"); - find.open(path{fn} += ".ind"); - ftab.open(path{fn} += ".tab"); - - dat_ = (dat *)find.p; - ind_ = (ind *)find.p; - tab_ = (tab *)ftab.p; - } -}; - int main(int argc, char *argv[]) { cl::opt db_fn(cl::Positional, cl::desc(""), cl::Required); @@ -130,19 +56,19 @@ int main(int argc, char *argv[]) continue; } switch (f->type) { - case FieldType::Integer: { + case db2::field_type::integer: { auto fv = (int*)p; p += vb->size; std::println("{}{}: {}", spacefield, f->name, *fv); break; } - case FieldType::Float: { + case db2::field_type::float_: { auto fv = (float*)p; p += vb->size; std::println("{}{}: {}", spacefield, f->name, *fv); break; } - case FieldType::String: { + case db2::field_type::string: { auto fv = (const char*)p; p += vb->size; std::println("{}{}: {}", spacefield, f->name, fv);