mirror of
https://github.com/aimrebirth/tools.git
synced 2026-04-14 17:33:25 +00:00
[mod_maker] Allow to add map goods.
This commit is contained in:
parent
8c68330365
commit
23ddad574e
3 changed files with 197 additions and 86 deletions
|
|
@ -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<uint8_t>("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
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <db2.h>
|
||||
#include <mmap.h>
|
||||
#include <mmo2.h>
|
||||
|
||||
#include <boost/container_hash/hash.hpp>
|
||||
#include <primitives/command.h>
|
||||
#include <primitives/filesystem.h>
|
||||
#include <primitives/sw/main.h>
|
||||
|
||||
#include <format>
|
||||
#include <fstream>
|
||||
|
|
@ -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<uint8_t> f{rfn, primitives::templates2::mmap_file<uint8_t>::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<uint8_t> f{fn, primitives::templates2::mmap_file<uint8_t>::rw{}};
|
||||
if (::memcmp(f.p + insertion_offset, data.data(), data.size()) == 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
insert(mmo_fn, insertion_offset, data);
|
||||
|
||||
primitives::templates2::mmap_file<uint8_t> f{fn, primitives::templates2::mmap_file<uint8_t>::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<uint8_t>::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
|
||||
|
|
|
|||
3
sw.cpp
3
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");
|
||||
|
|
|
|||
Loading…
Reference in a new issue