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:
+95
-227
@@ -1,5 +1,7 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cerrno>
|
||||||
#include <android/log.h>
|
#include <android/log.h>
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
@@ -73,249 +75,115 @@ bool is_memory_writable(void* addr, size_t size) {
|
|||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
// Базовый класс - Животное
|
// ---- RaceLoaderTask_BuildTrackScenePath hook (see ANALYSIS.md §6j) ----
|
||||||
class Animal {
|
// Target compiled in ARM mode (PUSH {R4-R11,LR}; ADD R11,SP,#0x1C - both
|
||||||
protected:
|
// position-independent, safe to relocate into the trampoline as-is).
|
||||||
string name;
|
#define BUILDTRACKSCENEPATH_OFFSET 0x2a8424
|
||||||
int age;
|
|
||||||
|
|
||||||
public:
|
typedef int (*BuildTrackScenePathFn)(void* raceLoaderTask);
|
||||||
// Конструктор
|
static BuildTrackScenePathFn orig_BuildTrackScenePath = nullptr;
|
||||||
Animal(const string& name, int age) : name(name), age(age) {
|
|
||||||
cout << "Animal constructor: " << name << endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Виртуальный деструктор
|
// Deliberately different from any real event's track, so a successful
|
||||||
virtual ~Animal() {
|
// override is visually unmistakable. region3/colorado was tried first and
|
||||||
cout << "Animal destructor: " << name << endl;
|
// 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) {
|
||||||
virtual void makeSound() const {
|
// a1[8] (word offset 8 = byte 32): pointer to the RaceDefinition-like
|
||||||
cout << name << " makes a generic animal sound" << endl;
|
// 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);
|
||||||
void sleep() const {
|
*(const char**)((uint8_t*)raceDef + 100) = kOverrideEnvName;
|
||||||
cout << name << " is sleeping" << endl;
|
*(const char**)((uint8_t*)raceDef + 104) = kOverrideEnvName + envLen;
|
||||||
}
|
|
||||||
|
|
||||||
string getName() const { return name; }
|
Log("BuildTrackScenePath hook fired: track -> %s, env -> %s", kOverrideTrackName, kOverrideEnvName);
|
||||||
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 {
|
} else {
|
||||||
cout << name << " squawks" << endl;
|
Log("BuildTrackScenePath hook fired but raceDef (a1[8]) is NULL, skipping override");
|
||||||
}
|
}
|
||||||
|
return orig_BuildTrackScenePath(a1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Новый метод
|
static bool InstallBuildTrackScenePathHook() {
|
||||||
void repeat(const string& phrase) const {
|
uintptr_t target = (uintptr_t)libapp_base + BUILDTRACKSCENEPATH_OFFSET;
|
||||||
if (canTalk) {
|
uint32_t* target32 = (uint32_t*)target;
|
||||||
cout << name << " repeats: " << phrase << endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Пример использования
|
void* tramp = mmap(nullptr, (size_t)getpagesize(), PROT_READ | PROT_WRITE | PROT_EXEC,
|
||||||
int main() {
|
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||||
cout << "=== Creating objects ===" << endl;
|
if (tramp == MAP_FAILED) {
|
||||||
|
Log("BuildTrackScenePath hook: mmap trampoline failed");
|
||||||
Dog dog("Rex", 3, true, "German Shepherd");
|
return false;
|
||||||
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;
|
uint32_t* tramp32 = (uint32_t*)tramp;
|
||||||
// Деструкторы будут вызваны автоматически
|
// Relocate the 2 displaced original ARM instructions verbatim (both
|
||||||
return 0;
|
// 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) {
|
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
|
||||||
|
|
||||||
get_libapp_base();
|
if (get_libapp_base()) {
|
||||||
|
if (kEnableTrackSubstitutionHook) {
|
||||||
|
InstallBuildTrackScenePathHook();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return JNI_VERSION_1_6;
|
return JNI_VERSION_1_6;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user