diff --git a/HatchDragons/Tweak.x b/HatchDragons/Tweak.x index ab438af..fde0955 100644 --- a/HatchDragons/Tweak.x +++ b/HatchDragons/Tweak.x @@ -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 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"); } \ No newline at end of file