mirror of
https://github.com/aimrebirth/tools.git
synced 2026-04-14 17:33:25 +00:00
Make mod maker a library. Add mod example.
This commit is contained in:
parent
f68c349edc
commit
1d2e981bdd
3 changed files with 322 additions and 15 deletions
|
|
@ -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 <primitives/sw/cl.h>
|
||||
#include <primitives/sw/main.h>
|
||||
#include <primitives/sw/settings.h>
|
||||
|
||||
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<uint8_t>("location4.mmo", 0x7F599, 0x31, 0x33); // rename second FINSWIL-1 to FINSWIL-3 to make it appear
|
||||
mod.patch<uint8_t>("location4.mmo", 0x7FA34, 1, 0); // make SWIRE appear
|
||||
|
|
@ -15,8 +26,14 @@ int main(int argc, char *argv[]) {
|
|||
mod.patch<uint8_t>("location5.mmo", 0xBAFF7, 0x18, 0x17); // reposition SACREFI-2
|
||||
mod.patch<uint8_t>("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;
|
||||
287
src/aim1_mod_maker/aim1_mod_maker.h
Normal file
287
src/aim1_mod_maker/aim1_mod_maker.h
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
#pragma once
|
||||
|
||||
#include <mmap.h>
|
||||
|
||||
#include <primitives/command.h>
|
||||
#include <primitives/filesystem.h>
|
||||
|
||||
#include <set>
|
||||
#include <print>
|
||||
|
||||
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<path> 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<std::string> 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 <typename T>
|
||||
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 <typename T>
|
||||
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 <typename T>
|
||||
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);
|
||||
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);
|
||||
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
|
||||
15
sw.cpp
15
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";
|
||||
|
|
|
|||
Loading…
Reference in a new issue