diff --git a/mpcore/src/main/cpp/main.cpp b/mpcore/src/main/cpp/main.cpp index 7ecddbf..0e8b430 100644 --- a/mpcore/src/main/cpp/main.cpp +++ b/mpcore/src/main/cpp/main.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include #include "main.h" @@ -73,249 +75,115 @@ bool is_memory_writable(void* addr, size_t size) { using namespace std; -// Базовый класс - Животное -class Animal { -protected: - string name; - int age; +// ---- RaceLoaderTask_BuildTrackScenePath hook (see ANALYSIS.md §6j) ---- +// Target compiled in ARM mode (PUSH {R4-R11,LR}; ADD R11,SP,#0x1C - both +// position-independent, safe to relocate into the trampoline as-is). +#define BUILDTRACKSCENEPATH_OFFSET 0x2a8424 -public: - // Конструктор - Animal(const string& name, int age) : name(name), age(age) { - cout << "Animal constructor: " << name << endl; +typedef int (*BuildTrackScenePathFn)(void* raceLoaderTask); +static BuildTrackScenePathFn orig_BuildTrackScenePath = nullptr; + +// Deliberately different from any real event's track, so a successful +// override is visually unmistakable. region3/colorado was tried first and +// abandoned: it's cut/incomplete content in this build - models/environments/ +// has no colorado/ folder at all (only chicago, desert, foothills, garage, +// newyork), even though region3_colorado_track2.scene.sb itself contains +// full embedded geometry. The scene's internal m3g loader still tries to +// open "/published/models/environments/colorado/region3_colorado_track2.m3g" +// as a loose file, which was never shipped -> geometry never loads -> every +// downstream consumer (spatial index, checkpoints, ...) sees empty/zero data +// and null-derefs, which is what the whole sub_53A5FC/sub_52A9B8/sub_52A620/ +// sub_58E5E8 crash chain actually was (see ANALYSIS.md §6n/§6o). Switched to +// region4_chicago_track4/chicago instead: confirmed shipped and playable +// (event_01_race.prefabs.sb's real TrackName, verified start/finish actors +// in the scene, and a full models/environments/chicago/ folder present). +static const char* kOverrideTrackName = "region4_chicago_track4"; +// Environment prefabs are per-track variants ("chicago1.prefabs.sb" .. +// "chicago6.prefabs.sb", matching "region4_chicago_track1".."track6"), not a +// single generic "chicago.prefabs.sb" - confirmed live: env="chicago" alone +// hit "Could not open database at published/prefabs/environments/chicago. +// prefabs.sb" followed by an immediate SIGSEGV. Only colorado shipped as one +// un-numbered colorado.prefabs.sb instead of colorado1..6 - further evidence +// region3/colorado is unfinished/cut content (see kOverrideTrackName above). +static const char* kOverrideEnvName = "chicago4"; + +int Hook_BuildTrackScenePath(void* a1) { + // a1[8] (word offset 8 = byte 32): pointer to the RaceDefinition-like + // struct. Track name is a {begin,end} pair at byte offsets +72/+76, + // environment name likewise at +100/+104. BuildTrackScenePath only + // READS these fields (never frees them), so we just repoint begin/end + // at our own static buffers instead of freeing/reallocating the + // originals - avoids guessing an unconfirmed capacity-field offset. + // The original buffers are deliberately leaked (two small allocations + // per race load - negligible). + void* raceDef = *(void**)((uint8_t*)a1 + 32); + if (raceDef) { + size_t trackLen = strlen(kOverrideTrackName); + *(const char**)((uint8_t*)raceDef + 72) = kOverrideTrackName; + *(const char**)((uint8_t*)raceDef + 76) = kOverrideTrackName + trackLen; + + size_t envLen = strlen(kOverrideEnvName); + *(const char**)((uint8_t*)raceDef + 100) = kOverrideEnvName; + *(const char**)((uint8_t*)raceDef + 104) = kOverrideEnvName + envLen; + + Log("BuildTrackScenePath hook fired: track -> %s, env -> %s", kOverrideTrackName, kOverrideEnvName); + } else { + Log("BuildTrackScenePath hook fired but raceDef (a1[8]) is NULL, skipping override"); } - - // Виртуальный деструктор - virtual ~Animal() { - cout << "Animal destructor: " << name << endl; - } - - // Виртуальный метод (будет переопределяться) - virtual void makeSound() const { - cout << name << " makes a generic animal sound" << endl; - } - - // Не виртуальный метод (не будет переопределяться) - void sleep() const { - cout << name << " is sleeping" << endl; - } - - string getName() const { return name; } - int getAge() const { return age; } -}; - -// Производный класс - Млекопитающее -class Mammal : public Animal { -protected: - bool hasFur; - -public: - // Конструктор - Mammal(const string& name, int age, bool hasFur) - : Animal(name, age), hasFur(hasFur) { - cout << "Mammal constructor: " << name << endl; - } - - // Деструктор - ~Mammal() override { - cout << "Mammal destructor: " << name << endl; - } - - // Переопределение метода - void makeSound() const override { - cout << name << " makes a mammal sound" << endl; - } - - // Новый метод - void feedMilk() const { - cout << name << " is feeding milk" << endl; - } -}; - -// Производный класс - Птица -class Bird : public Animal { -protected: - double wingspan; - -public: - // Конструктор - Bird(const string& name, int age, double wingspan) - : Animal(name, age), wingspan(wingspan) { - cout << "Bird constructor: " << name << endl; - } - - // Деструктор - ~Bird() override { - cout << "Bird destructor: " << name << endl; - } - - // Переопределение метода - void makeSound() const override { - cout << name << " chirps" << endl; - } - - // Новый метод - void fly() const { - cout << name << " is flying with wingspan " << wingspan << "m" << endl; - } -}; - -// Производный класс от Млекопитающего - Собака -class Dog : public Mammal { -private: - string breed; - -public: - // Конструктор - Dog(const string& name, int age, bool hasFur, const string& breed) - : Mammal(name, age, hasFur), breed(breed) { - cout << "Dog constructor: " << name << endl; - } - - // Деструктор - ~Dog() override { - cout << "Dog destructor: " << name << endl; - } - - // Переопределение метода - void makeSound() const override { - cout << name << " barks: Woof! Woof!" << endl; - } - - // Новый метод - void fetch() const { - cout << name << " is fetching the ball" << endl; - } -}; - -// Производный класс от Млекопитающего - Кошка -class Cat : public Mammal { -private: - int lives; - -public: - // Конструктор - Cat(const string& name, int age, bool hasFur, int lives = 9) - : Mammal(name, age, hasFur), lives(lives) { - cout << "Cat constructor: " << name << endl; - } - - // Деструктор - ~Cat() override { - cout << "Cat destructor: " << name << endl; - } - - // Переопределение метода - void makeSound() const override { - cout << name << " meows: Meow! Meow!" << endl; - } - - // Новый метод - void purr() const { - cout << name << " is purring" << endl; - } -}; - -// Производный класс от Птицы - Орел -class Eagle : public Bird { -private: - double visionRange; - -public: - // Конструктор - Eagle(const string& name, int age, double wingspan, double visionRange) - : Bird(name, age, wingspan), visionRange(visionRange) { - cout << "Eagle constructor: " << name << endl; - } - - // Деструктор - ~Eagle() override { - cout << "Eagle destructor: " << name << endl; - } - - // Метод НЕ переопределяется (используется версия из Bird) - // void makeSound() const override {...} - - // Новый метод - void hunt() const { - cout << name << " is hunting with vision range " << visionRange << "km" << endl; - } -}; - -// Производный класс от Птицы - Попугай -class Parrot : public Bird { -private: - bool canTalk; - -public: - // Конструктор - Parrot(const string& name, int age, double wingspan, bool canTalk) - : Bird(name, age, wingspan), canTalk(canTalk) { - cout << "Parrot constructor: " << name << endl; - } - - // Деструктор - ~Parrot() override { - cout << "Parrot destructor: " << name << endl; - } - - // Переопределение метода - void makeSound() const override { - if (canTalk) { - cout << name << " says: Hello! Polly wants a cracker!" << endl; - } else { - cout << name << " squawks" << endl; - } - } - - // Новый метод - void repeat(const string& phrase) const { - if (canTalk) { - cout << name << " repeats: " << phrase << endl; - } - } -}; - -// Пример использования -int main() { - cout << "=== Creating objects ===" << endl; - - Dog dog("Rex", 3, true, "German Shepherd"); - Cat cat("Whiskers", 2, true); - Eagle eagle("Thor", 5, 2.1, 3.5); - Parrot parrot("Polly", 1, 0.3, true); - - cout << "\n=== Testing methods ===" << endl; - - dog.makeSound(); // Переопределен в Dog - dog.fetch(); // Уникальный метод Dog - - cat.makeSound(); // Переопределен в Cat - cat.purr(); // Уникальный метод Cat - - eagle.makeSound(); // НЕ переопределен - использует версию Bird - eagle.hunt(); // Уникальный метод Eagle - - parrot.makeSound(); // Переопределен в Parrot - parrot.repeat("I love C++"); // Уникальный метод Parrot - - cout << "\n=== Testing polymorphism ===" << endl; - Animal* animals[] = {&dog, &cat, &eagle, &parrot}; - - for (Animal* animal : animals) { - animal->makeSound(); // Полиморфный вызов - animal->sleep(); // Не виртуальный метод - всегда Animal::sleep() - } - - cout << "\n=== Objects going out of scope ===" << endl; - // Деструкторы будут вызваны автоматически - return 0; + return orig_BuildTrackScenePath(a1); } +static bool InstallBuildTrackScenePathHook() { + uintptr_t target = (uintptr_t)libapp_base + BUILDTRACKSCENEPATH_OFFSET; + uint32_t* target32 = (uint32_t*)target; + + void* tramp = mmap(nullptr, (size_t)getpagesize(), PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (tramp == MAP_FAILED) { + Log("BuildTrackScenePath hook: mmap trampoline failed"); + return false; + } + + uint32_t* tramp32 = (uint32_t*)tramp; + // Relocate the 2 displaced original ARM instructions verbatim (both + // confirmed position-independent: plain PUSH and ADD, no PC-relative + // addressing), then jump back into the function body past them. + tramp32[0] = target32[0]; + tramp32[1] = target32[1]; + tramp32[2] = 0xE51FF004; // LDR PC, [PC, #-4] + tramp32[3] = (uint32_t)(target + 8); + orig_BuildTrackScenePath = (BuildTrackScenePathFn)tramp; + + uintptr_t page = target & ~((uintptr_t)getpagesize() - 1); + if (mprotect((void*)page, (size_t)getpagesize(), PROT_READ | PROT_WRITE | PROT_EXEC) != 0) { + Log("BuildTrackScenePath hook: mprotect target failed: %s", strerror(errno)); + return false; + } + + target32[0] = 0xE51FF004; // LDR PC, [PC, #-4] + // &Hook_BuildTrackScenePath has bit0 set (Thumb-compiled mpcore code), + // triggering the ARM->Thumb interworking switch on load into PC. + target32[1] = (uint32_t)(uintptr_t)&Hook_BuildTrackScenePath; + + __builtin___clear_cache((char*)target, (char*)(target + 8)); + __builtin___clear_cache((char*)tramp, (char*)tramp + 16); + + Log("Installed RaceLoaderTask_BuildTrackScenePath hook at %p, trampoline=%p", (void*)target, tramp); + return true; +} + +// Flip to false to run the game completely unmodified (e.g. to capture a +// baseline/"before" comparison) - true installs the track-substitution hook. +// Just edit this and rebuild, no need to touch anything else. +static constexpr bool kEnableTrackSubstitutionHook = true; + JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { - get_libapp_base(); - + if (get_libapp_base()) { + if (kEnableTrackSubstitutionHook) { + InstallBuildTrackScenePathHook(); + } + } return JNI_VERSION_1_6; }