Clean implementation

This commit is contained in:
√(noham)²
2026-08-20 16:16:55 +02:00
parent 4e040791c1
commit 58dd5db8fd

View File

@@ -6,6 +6,17 @@
#define LOG(fmt, ...) NSLog(@"[HGH] " fmt, ##__VA_ARGS__)
#pragma mark - IL2CPP String Layout
#define IL2CPP_STRING_LENGTH_OFFSET 0x10
#define IL2CPP_STRING_CHARS_OFFSET 0x14
#define IL2CPP_DICT_ENTRIES_OFFSET 0x18
#define IL2CPP_DICT_COUNT_OFFSET 0x20
#define IL2CPP_ARRAY_CAPACITY_OFFSET 0x18
#define IL2CPP_ARRAY_DATA_OFFSET 0x20
#define IL2CPP_DICT_ENTRY_SIZE 24
#define IL2CPP_STRING_MAX_LENGTH (1 << 16)
#pragma mark - Helpers
static uintptr_t getImageBase(void) {
@@ -22,97 +33,86 @@ static uintptr_t getImageBase(void) {
static int requestCount = 0;
static NSString *timestampString(void) {
NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
fmt.dateFormat = @"HH:mm:ss.SSS";
static NSDateFormatter *fmt;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
fmt = [[NSDateFormatter alloc] init];
fmt.dateFormat = @"HH:mm:ss.SSS";
});
return [fmt stringFromDate:[NSDate date]];
}
// Read a System.String via its native layout (NOT ARC / Obj-C casts).
// struct System.String { klass; monitor; int32_t _stringLength(utf16 code units); uint16_t chars[]; }
static NSString *il2cppString(void *str) {
if (!str) return nil;
uint32_t len = *(uint32_t *)((char *)str + 0x10); // UTF-16 code units
if (len == 0 || len > 1 << 16) return @"(empty?)";
NSString *s = [[NSString alloc] initWithBytes:((char *)str + 0x14)
length:len * 2 // byte length
uint32_t len = *(uint32_t *)((char *)str + IL2CPP_STRING_LENGTH_OFFSET);
if (len == 0 || len > IL2CPP_STRING_MAX_LENGTH) return @"(empty?)";
NSString *s = [[NSString alloc] initWithBytes:((char *)str + IL2CPP_STRING_CHARS_OFFSET)
length:len * 2
encoding:NSUTF16LittleEndianStringEncoding];
return s ?: @"(unparseable)";
}
// Render a System.Collections.Generic.Dictionary<string,string> via its native
// layout. Fields: _buckets@0x10, _entries@0x18 (Entry array), _count@0x20.
// Entry = { int32 hashCode; int32 next; void* key; void* value; } (24 bytes).
// Array: obj(16) + bounds(8) + max_length(8) => data at +0x20, capacity at +0x18.
static NSString *il2cppHeaders(void *dict) {
if (!dict) return @"(nil)";
void *entriesArr = *(void **)((char *)dict + 0x18);
uint64_t cap = entriesArr ? *(uint64_t *)((char *)entriesArr + 0x18) : 0;
if (!entriesArr || cap == 0) return @"(empty)";
void *entriesArr = *(void **)((char *)dict + IL2CPP_DICT_ENTRIES_OFFSET);
if (!entriesArr) return @"(empty)";
uint64_t cap = *(uint64_t *)((char *)entriesArr + IL2CPP_ARRAY_CAPACITY_OFFSET);
if (cap == 0) return @"(empty)";
NSMutableString *out = [NSMutableString string];
uint32_t used = 0;
char *data = (char *)entriesArr + 0x20;
char *data = (char *)entriesArr + IL2CPP_ARRAY_DATA_OFFSET;
for (uint64_t i = 0; i < cap; i++) {
char *e = data + i * 24;
char *e = data + i * IL2CPP_DICT_ENTRY_SIZE;
int32_t hashCode = *(int32_t *)e;
void *key = *(void **)(e + 8);
void *value = *(void **)(e + 16);
if (hashCode < 0 || !key) continue; // free slot
if (hashCode < 0 || !key) continue;
[out appendFormat:@"\n %@: %@", il2cppString(key),
value ? il2cppString(value) : @"(null)"];
used++;
}
return used ? out : @"(empty)";
}
#pragma mark - CI.HttpClient.RequestHandler hooks
static void logRequest(NSString *method, void *self, NSString *body) {
requestCount++;
NSString *uri = il2cppString(*(void **)((char *)self + 0x10));
NSString *platform = il2cppString(*(void **)((char *)self + 0x20));
void *hdrs = *(void **)((char *)self + 0x18);
// IDA virtual addresses (before ASLR slide), relative to UnityFramework image:
// PerformGet(ProcessDataDelegateString) = 0x58FE798
// PostJson(string, ProcessDataDelegateBytes) = 0x58FED8C
NSMutableString *msg = [NSMutableString stringWithFormat:
@"[%@] ▶ %@ #%d self=%p\n URI: %@\n Headers:%@\n Platform: %@",
timestampString(), method, requestCount, self, uri, il2cppHeaders(hdrs), platform];
if (body) {
[msg appendFormat:@"\n Body: %@", body];
}
LOG(@"%@", msg);
}
#pragma mark - CI.HttpClient.RequestHandler Hooks
static void (*orig_performGet)(void *, void *);
static void (*orig_postJson)(void *, void *, void *);
// NOTE: self / handler / payload are il2cpp objects — type them as void* so ARC
// never objc_retain/objc_msgSends them. il2cpp methods take NO SEL; the arg
// list here must match the raw signature (self, ...) exactly.
static void hooked_performGet(void *self, void *handler) {
requestCount++;
NSString *uri = il2cppString(*(void **)((char *)self + 0x10));
NSString *platform = il2cppString(*(void **)((char *)self + 0x20));
void *hdrs = *(void **)((char *)self + 0x18);
LOG(@"[%@] ▶ CI.GET #%d self=%p\n URI: %@\n Headers:%@\n Platform: %@",
timestampString(), requestCount, self,
uri, il2cppHeaders(hdrs), platform);
// Pass the ORIGINAL handler through unchanged. We do NOT retain/wrap it:
// il2cpp expects a MulticastDelegate, and ARC must never retain il2cpp objs.
logRequest(@"CI.GET", self, nil);
orig_performGet(self, handler);
}
static void hooked_postJson(void *self, void *payload, void *handler) {
requestCount++;
NSString *uri = il2cppString(*(void **)((char *)self + 0x10));
NSString *platform = il2cppString(*(void **)((char *)self + 0x20));
void *hdrs = *(void **)((char *)self + 0x18);
NSString *body = payload ? il2cppString(payload) : @"(none)";
LOG(@"[%@] ▶ CI.POST #%d self=%p\n URI: %@\n Headers:%@\n Platform: %@\n Body: %@",
timestampString(), requestCount, self,
uri, il2cppHeaders(hdrs), platform, body);
logRequest(@"CI.POST", self, payload ? il2cppString(payload) : nil);
orig_postJson(self, payload, handler);
}
#pragma mark - PlayerInventory currency hooks
// NurtureGamePlatform.Managers.PlayerInventory
// ModifyHC(long value, string information = "", int quantity = 1) IDA RVA: 0x57DC6EC
// ModifySC(long value, string information = "") IDA RVA: 0x57E4978
//
// Negate negative amounts so every spend becomes a gain (cheat).
#pragma mark - PlayerInventory Currency Hooks
static void (*orig_modifyHC)(void *, long, void *, int);
static void (*orig_modifySC)(void *, long, void *);
@@ -133,49 +133,24 @@ static void hooked_modifySC(void *self, long amount, void *info) {
orig_modifySC(self, amount, info);
}
#pragma mark - Constructor
#pragma mark - Hook Installation
#define HOOK(base, rva, hook, orig) \
MSHookFunction((void *)((base) + (rva)), (void *)(hook), (void **)&(orig))
%ctor {
LOG(@"=== tweak loaded ===");
uintptr_t base = getImageBase();
if (!base) {
LOG(@"UnityFramework image not found, skipping CI hooks");
LOG(@"UnityFramework not found, aborting");
return;
}
LOG(@"UnityFramework base = 0x%lx", (unsigned long)base);
// PerformGet(ProcessDataDelegateString) - IDA RVA: 0x58FE798
{
void *addr = (void *)(base + 0x58FE798);
LOG(@"hooking PerformGet(string) at 0x%lx", (unsigned long)addr);
MSHookFunction(addr, (void *)hooked_performGet, (void **)&orig_performGet);
LOG(@"hooked PerformGet(string) — orig=%p", orig_performGet);
}
HOOK(base, 0x58FE798, hooked_performGet, orig_performGet);
HOOK(base, 0x58FED8C, hooked_postJson, orig_postJson);
HOOK(base, 0x57DC6EC, hooked_modifyHC, orig_modifyHC);
HOOK(base, 0x57E4978, hooked_modifySC, orig_modifySC);
// PostJson(string payload, ProcessDataDelegateBytes handler) - IDA RVA: 0x58FED8C
{
void *addr = (void *)(base + 0x58FED8C);
LOG(@"hooking PostJson at 0x%lx", (unsigned long)addr);
MSHookFunction(addr, (void *)hooked_postJson, (void **)&orig_postJson);
LOG(@"hooked PostJson — orig=%p", orig_postJson);
}
// ModifyHC(long, string, int) - IDA RVA: 0x57DC6EC
{
void *addr = (void *)(base + 0x57DC6EC);
LOG(@"hooking ModifyHC at 0x%lx", (unsigned long)addr);
MSHookFunction(addr, (void *)hooked_modifyHC, (void **)&orig_modifyHC);
LOG(@"hooked ModifyHC — orig=%p", orig_modifyHC);
}
// ModifySC(long, string) - IDA RVA: 0x57E4978
{
void *addr = (void *)(base + 0x57E4978);
LOG(@"hooking ModifySC at 0x%lx", (unsigned long)addr);
MSHookFunction(addr, (void *)hooked_modifySC, (void **)&orig_modifySC);
LOG(@"hooked ModifySC — orig=%p", orig_modifySC);
}
LOG(@"=== all hooks installed ===");
LOG(@"All hooks installed");
}