Add kEnableTrackSubstitutionHook toggle flag
Lets the track-substitution hook be enabled/disabled by flipping one constexpr bool in main.cpp instead of commenting/uncommenting code, so before/after comparisons don't need back-and-forth edits.
This commit is contained in:
+105
-237
@@ -1,5 +1,7 @@
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <cerrno>
|
||||
#include <android/log.h>
|
||||
#include <jni.h>
|
||||
#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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user