iOS Game Localization: Reusing Translations and Hook-Based Text Replacement
About the author. I’m Colin Xiao — an independent reverse engineer, mostly working solo on contract jobs. I dig into compiled code of every kind, including but not limited to: Android and iOS apps, x86/x64/ARM native binaries, packers and obfuscators, DRM and license checks, network protocols and APIs, crypto and signature algorithms, malware, game binaries, and embedded/IoT firmware. I also handle large-scale data collection and vulnerability research. If you have any project in mind, I’d be glad to take it on — whatever it is, as long as it’s within my wheelhouse. Just reach out. You can reach me on X at @colinxiao_lab or on Telegram at https://t.me/ColinXiao666.
This research is published for educational and defensive purposes. The target sample is anonymized throughout.
iOS Game Localization: Reusing Translations and Hook-Based Text Replacement
IPA localization via Caption Hook
Game and software localization is mature on Windows, with plenty of tutorials and forum threads. On iOS, though, resources are scarce. Here I want to cover two less-common approaches. The projects are old and my memory is fuzzy, so some details may be off.
1. Localizing a cross-platform game
Most cross-platform games compile the executable resources from the same codebase. For those, you can “borrow” an existing translation — though it isn’t universally applicable; it depends on the game. Take Bully, for example: it’s cross-platform, and by the time I wanted to localize it, the Windows version already had a fan translation. To cut down the workload, I reused that translated resource set and swapped it into the iOS build.
The result (iPad screenshots):



My notes at the time were brief, so the whole diff boils down to these steps:
- Replace xml
- Replace font
- Replace english (menu crashed)
- Replace menu (extract the menu files from the extracted menu folder) zip -0 -qr /Users/me/Desktop/Bully/Bully_-1.03.5/Payload/bully.app/Bully2.zip . zip -0 -r Bully3.zip Bully
2. Hook-based localization
Whether you’re localizing or cracking a game, translated resources are hard to protect. If you want to shield the work from being casually ripped off, inject a dylib that decrypts the resources on the fly. A couple of years ago the popular games were Travel Frog and its sibling Neko Atsume — those can be localized by directly editing resources, the same way as Travel Frog.
But is there a simpler path than editing the resource files? Looking at the game’s rendering, all text is ultimately drawn through the -[HpLib_Graphics makeTextImage:] function. If I hook that function and dynamically match the text being rendered against a dictionary, the localization problem is solved — and my dylib stays necessary: if someone strips it out, the localization stops working. That keeps the dylib alive, to a degree. It doesn’t guarantee survival — someone who reverse-engineers the dylib can just write their own replacement — so the next step is encrypting the translation dictionary to raise the cost of replacing it.
The rendering function prototype:
id __cdecl -[HpLib_Graphics makeTextImage:](HpLib_Graphics *self, SEL a2, id a3)
The hook:
@class HpLib_Graphics;
CHDeclareClass(HpLib_Graphics);
CHOptimizedMethod(1, self ,id, HpLib_Graphics, makeTextImage,NSString *, arg1)
{
NSLog(@"BABY_HOOK: makeTextImage original:%@",arg1);
NSString * ch_string = @"meow";
if ([ch_dic objectForKey:arg1]) {
ch_string =[ch_dic objectForKey:arg1];
if ([ch_string length]== 0) {
ch_string = @"meow";
}
NSLog(@"BABY_HOOK: makeTextImage translated:%@",ch_strin
return CHSuper(1, HpLib_Graphics,makeTextImage, ch_string);
}else{
return CHSuper(1, HpLib_Graphics,makeTextImage, arg1);
}
}
// Neko Atsume
CHLoadLateClass(HpLib_GSystem);
CHHook(1, HpLib_GSystem, strNSString);
CHLoadLateClass(NSPlaceholderString);
CHHook(3, NSPlaceholderString ,initWithBytes,length,encoding);
CHHook(2, NSPlaceholderString ,initWithCString,encoding);
CHHook(1, NSPlaceholderString ,initWithUTF8String);
CHLoadLateClass(HpLib_Graphics);
CHHook(1, HpLib_Graphics, makeTextImage);
The next step is encrypting the translation dictionary, done with BABYStringEncoding — a Google open-source library with the class and method names modified. Decryption looks like this:
NSString *cn_file = [[NSBundle mainBundle] pathForResource:@"cn_strings_jp" ofType:@"hppy"];
BABYStringEncoding * gtc2 = [[BABYStringEncoding alloc] initWithString:@"aCDEfGHijKlMnopQRsUvWXz012345689"]; //WxEHUf2GfEnCosHjE3j5zUs //aCDEfGHijKlMnopQRsUvWXz012345689
NSError *err;
NSString *encodeString = [NSString stringWithContentsOfFile:cn_file encoding:NSUTF8StringEncoding error:nil];
NSData *decodeData = [gtc2 decode:encodeString error:&err];
//NSString *decodeString = [[NSString alloc] initWithData:decodeData encoding:NSUTF8StringEncoding];
//NSDictionary *decodeDic = [NSJSONSerialization JSONObjectWithData:decodeData options:kNilOptions error:&err];
NSString *error;
NSPropertyListFormat format;
NSDictionary* plist = [NSPropertyListSerialization propertyListFromData:decodeData mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&error];
NSLog( @"BABY_HOOK: plist is %@", plist );
if(!plist){
NSLog(@"BABY_HOOK: Error: %@",error);
}
Besides the rendering function there are other methods to handle — not all text goes through it. See the project code for the rest.
The result:


Dictionary file encryption/decryption:
encryption key
aCDEfGHijKlMnopQRsUvWXz012345689Encrypt:
GTM_Encoder -encodeFile <key> <input> <output>GTM_Encoder -encodeFile aCDEfGHijKlMnopQRsUvWXz012345689 /Users/me/Desktop/projects/localization/cn_strings_jp.plist /Users/me/Desktop/projects/localization/cn_strings_jp.encDecrypt:
GTM_Encoder -decodeFile <key> <input> <output>GTM_Encoder -decodeFile aCDEfGHijKlMnopQRsUvWXz012345689 /Users/me/Desktop/projects/localization/cn_strings_jp.enc /Users/me/Desktop/projects/localization/cn_strings_jp.plist.dec
Project: https://github.com/obaby/ipa_chinesizing_via_hook
Colin Xiao — I take on contract reverse-engineering work, and writeups like this are how I show what I can do. If you need help with anything in this space — including but not limited to the areas I cover above — I’m available. No project is too big or too small — if it’s reverse engineering and I can do it, I’ll take it. DM me on X (@colinxiao_lab) or Telegram (@ColinXiao666).
Get in touch