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):

bully1

bully1

bully1

My notes at the time were brief, so the whole diff boils down to these steps:

  1. Replace xml
  2. Replace font
  3. Replace english (menu crashed)
  4. 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:

Cat1

Cat2

Dictionary file encryption/decryption:

encryption key aCDEfGHijKlMnopQRsUvWXz012345689

Encrypt: 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.enc

Decrypt: 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