[mods] Add ability to patch database.

This commit is contained in:
lzwdgc 2024-02-13 03:28:29 +03:00
parent 3541f7a2ad
commit 9d77576607
6 changed files with 366 additions and 112 deletions

View file

@ -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 )

View file

@ -1,10 +1,12 @@
#pragma once
#include <db2.h>
#include <mmap.h>
#include <primitives/command.h>
#include <primitives/filesystem.h>
#include <format>
#include <fstream>
#include <set>
#include <source_location>
@ -14,9 +16,6 @@ constexpr auto aim_exe = "aim.exe"sv;
using byte_array = std::vector<uint8_t>;
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<path> files_to_pak;
std::set<path> files_to_distribute;
std::set<path> 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<std::string> 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 <typename T>
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<uint8_t>::rw{});
db.quest_.open(get_data_dir() / "quest", primitives::templates2::mmap_file<uint8_t>::rw{});
detect_tools();
prepare_injections();
}
void read_name() {
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<uint8_t> f{find_real_filename(aim_exe), primitives::templates2::mmap_file<uint8_t>::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<uint8_t> f{fn, primitives::templates2::mmap_file<uint8_t>::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 <typename T>
bool patch_raw(path fn, uint32_t offset, T expected, T val) const {
primitives::templates2::mmap_file<uint8_t> f{fn, primitives::templates2::mmap_file<uint8_t>::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) {

View file

@ -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
}

View file

@ -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;

234
src/common/db2.h Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<uint8_t> fdat, find, ftab;
tab *tab_{};
ind *ind_{};
dat *dat_{};
db2() = default;
db2(const path &fn) : fn{fn} {
open(fn, primitives::templates2::mmap_file<uint8_t>::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<std::decay_t<decltype(v)>, int>) {
return sizeof(db2::dat::field_value_base) + sizeof(int);
} else if constexpr (std::same_as<std::decay_t<decltype(v)>, 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<std::decay_t<decltype(v)>, int>) {
return sizeof(db2::dat::field_value_base) + sizeof(int);
} else if constexpr (std::same_as<std::decay_t<decltype(v)>, 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<std::decay_t<decltype(v)>, 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<std::decay_t<decltype(v)>, 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<std::decay_t<decltype(v)>, 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<std::decay_t<decltype(v)>, 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...);
}
}
}
};

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "db.h"
#include "db2.h"
#include <buffer.h>
#include <common.h>
#include <mmap.h>
#include <mmap.h>
#include <primitives/sw/main.h>
#include <primitives/sw/settings.h>
@ -31,78 +29,6 @@
#include <span>
#include <print>
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<uint8_t> 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<path> db_fn(cl::Positional, cl::desc("<db file>"), 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);