Reverse engineering an IL2CPP NSO binary: Case study of Mojipittan Encore
Tags: reverseengineering, ghidra, il2cpp, unity3d
Published: 2023-01-17 07:49:50

---
title: Reverse engineering an IL2CPP NSO binary: Case study of Mojipittan Encore
published: true
date: 2023-01-16 23:29:35 UTC
tags: ReverseEngineering,Ghidra,IL2CPP,Unity
canonical_url: https://blog.1a23.com/2023/01/16/reverse-engineering-an-il2cpp-nso-binary-case-study-of-mojipittan-encore/
cover_image: https://blog.1a23.com/wp-content/uploads/sites/2/2023/01/nso0-1024x768.png
---

This is yet another random side project I was working on recently, and my first attempt to reverse engineer a real world application compiled into binary. In this article, I want to talk about how I reversed engineered an Unity IL2CPP binary compiled to NSO, in a step-by-step fashion.

## Table of Contents
<nav><ol>
<li><a href="https://blog.1a23.com/2023/01/13/reverse-engineering-an-il2cpp-nso-binary-case-study-of-mojipittan-encore/#forewords">Forewords</a></li>
<li><a href="https://blog.1a23.com/2023/01/13/reverse-engineering-an-il2cpp-nso-binary-case-study-of-mojipittan-encore/#preparation">Preparation</a></li>
<li><a href="https://blog.1a23.com/2023/01/13/reverse-engineering-an-il2cpp-nso-binary-case-study-of-mojipittan-encore/#extract-contents">Extract contents</a></li>
<li><a href="https://blog.1a23.com/2023/01/13/reverse-engineering-an-il2cpp-nso-binary-case-study-of-mojipittan-encore/#unpack-resource-files">Unpack resource files</a></li>
<li><a href="https://blog.1a23.com/2023/01/13/reverse-engineering-an-il2cpp-nso-binary-case-study-of-mojipittan-encore/#preparing-for-decompilation">Preparing for decompilation</a></li>
<li><a href="https://blog.1a23.com/2023/01/13/reverse-engineering-an-il2cpp-nso-binary-case-study-of-mojipittan-encore/#decompile-the-binary">Decompile the binary</a></li>
<li><a href="https://blog.1a23.com/2023/01/13/reverse-engineering-an-il2cpp-nso-binary-case-study-of-mojipittan-encore/#debugging-with-emulator">Debugging with emulator</a></li>
</ol></nav>
## Forewords

_[Kotoba no Puzzle: Mojipittan](https://en.wikipedia.org/wiki/Kotoba_no_Puzzle:_Mojipittan)_ is a word puzzle game series in Japanese, where the player makes words from letter pieces on a board. It sort of like Scrabble, but not exactly the same. I always wanted to give it a try, but it was quite an old game, available only on <abbr title="Game Boy Advanced">GBA</abbr>, <abbr title="PlayStation Portable">PSP</abbr>, <abbr title="Nintendo DS">DS</abbr>, and Wii. That’s until they made an Encore version on Nintendo Switch. This game is also one of the reasons I bought a Switch.

Ever since I bought the game, I was wondering if there is a way to solve the game optimally. The first step to approach this is to first get the dictionary of the game. Despite the game has now released to multiple different platforms, there has no resource online on the word list used in the game. I thus decided to do it myself.

## Preparation

To follow this article, you would need [an exploitable Nintendo Switch](https://damota.me/ssnc/checker/) with a purchased copy of _[Mojipittan Encore](https://store-jp.nintendo.com/list/software/70010000022654.html)_ and a PC running Windows. Although there are [other ways](https://www.reddit.com/r/SwitchPirates/) to do it with only a PC, I do not recommended shem for reasons. You might be able to also use other OSes, but a lot of the resources has precompiled binary for Windows, which involves less effort.

To get the game data from the device, you need to dump it from the console. Yuzu has provided [a detailed guide on how to dump games](https://yuzu-emu.org/help/quickstart/) and corresponding keys. I will use an XCI dump here as an example.

## Extract contents

To extract the dumped XCI file, I used [hactool](https://github.com/SciresM/hactool), specifically this wrapped version [Unpackv2](https://gbatemp.net/threads/how-to-easy-extract-game-files-from-nsp-xci.534724/). 

{% embed https://github.com/SciresM/hactool %}

Once the tool is downloaded and unzipped, follow the next steps:

1. Copy the `prod.keys` extracted form your device to the folder where `Unpack.cmd` is found, and rename it to `keys.txt`.
2. Drop the dumped XCI file onto `Unpack.cmd` to start the script.
3. When prompted…  
`If your patch was inside XCI, press "1" and ENTER
If you don't have a patch, just only press ENTER`  
…press <kbd style="background-color:rgba(0, 0, 0, 0)">Enter</kbd>. This game exported did not come with a patch.
4. Now, in the _Unpackv2_ folder, there will be a new _ExtractedXCI_ folder created with 4 NCA files of various sizes created.  
When prompted…  
`Drop here correct NCA patch file (probably the biggest one) from ExtractedXCI folder in`  
…, drop the 774MB _a0547397496b93fcb08f438bcaad2731.nca_ to the terminal window.
5. Now the terminal print a list of files extracted, ending with the prompt…  
`Press ENTER to delete all temporary files`  
Press <kbd style="background-color:rgba(0, 0, 0, 0)">Enter</kbd> twice to finish the export.

After finishing the export process, there will be a new folder created with the extracted content, with two subfolders: _exefs_ and _romfs_. We will make use of these contents in the next step.

## Unpack resource files

When you open the _romfs/Data_ folder, you will be welcomed with some familiar file names, like _resource.assets_, _sharedassets0.assets_, _level0_, and _level1_. Yes, if you have ever made or opened the directory of a Unity game, you will surely recognize these file names. Unity organizes their asset files in a pretty recognizable pattern, and is well studied by the community with multiple tools created.

At this point, you are free to extract all static assets files found in the game, like text files and textures. The tool I used is [Unity Asset Bundle Extractor](https://github.com/SeriousCache/UABE) (UABE). To extract assets, open the _.assets_ files with the tool, and export the assets using the built-in plugins to easily processable formats.

{% embed https://github.com/SeriousCache/UABE %}

Besides the folder mentioned above, there is a subfolder _StreamingData/Switch/datas_, which contains assets that are loaded after the game has initialized. Here, the files we are interested are the dictionary files under _romfs/​Data/​StreamingData/​Switch/​datas/​dictionary_. Open it with UABE, we can see three text assets: _worddata.aid_, _worddata.cot_, and _worddata.dic_. Extract them with the txt export plugin, we can get three binary files with some sort of patterns.

```sh
$ xxd worddata.aid | head -n 20
00000000: 5744 5000 0002 1ca1 0000 0001 0001 c1e6 WDP.............
00000010: 0000 0002 0001 ffee 0001 5eb2 0001 5eb3 ..........^...^.
00000020: 0000 0003 0001 ffef 0001 fff0 0000 0004 ................
00000030: 0000 0005 0000 0006 0001 5d48 0000 0007 ..........]H....
00000040: 0000 0008 0000 0009 0000 000a 0000 000b ................
00000050: 0001 92b4 0001 92b7 0001 92b3 0001 92b6 ................
00000060: 0001 92b5 0000 000c 0001 5a5f 0001 fff1 ..........Z_....
00000070: 0000 000d 0000 000e 0000 000f 0000 0010 ................
00000080: 0000 0011 0001 9019 0000 0012 0001 fff2 ................
00000090: 0000 0013 0001 fff3 0000 0014 0000 0015 ................
000000a0: 0000 0016 0000 0017 0000 0018 0000 0019 ................
000000b0: 0000 001a 0000 001b 0000 001c 0000 001d ................
000000c0: 0000 001e 0000 001f 0000 0020 0000 0021 ........... ...!
000000d0: 0000 0022 0000 0023 0000 0024 0000 0025 ..."...#...$...%
000000e0: 0000 0026 0000 0027 0001 fff4 0000 0028 ...&...'.......(
000000f0: 0000 0029 0000 002a 0000 002b 0000 002c ...)...*...+...,
00000100: 0000 002d 0000 002e 0002 1191 0002 1838 ...-...........8
00000110: 0001 5f43 0000 0030 0001 fff6 0000 0031 .._C...0.......1
00000120: 0000 0032 0000 0033 0000 0034 0000 0035 ...2...3...4...5
00000130: 0001 6782 0000 0036 0001 6783 0001 6785 ..g....6..g...g.
```

The files start with a `WDP\0` header (which was not found elsewhere on the internet), and a bunch of `0000` bytes spread across the odd columns, but we can’t really interpret the meaning of these data by just staring the files. We definitely need the help of the code logic of the game.

## Preparing for decompilation

As it is commonly known, most logic of Unity games are written in C♯. When compiled to DLL files, C♯ code is rather easy to decompile. However, in environments where .NET runtime is hard to prepare, or where performance is critical, Unity offers an option called _[Intermediate Language to C++](https://docs.unity3d.com/Manual/IL2CPP.html)_ (IL2CPP) that further compiles _Microsoft Intermediate Language_ (MSIL) into C++ and further into native code. This technique is commonly seen on Unity games running on mobile platforms. Nintendo Switch is of no exception.

Nintendo Switch runs on a special binary format called NSO, which is a custom variant of [AArch64](https://en.wikipedia.org/wiki/AArch64) [ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) binary. To save space, a lot of NSO files are by default compressed. We need to first decompress it with hactool.

```sh
hactool --uncompressed=exefs/main_unc exefs/main
```

With the uncompressed binary, we can then use [IL2CPPdumper](https://github.com/Perfare/Il2CppDumper) to extract the offset and signature of each function in the binary.

{% embed https://github.com/Perfare/Il2CppDumper %}

```sh
Il2CppDumper exefs/main romfs/Data/Managed/Metadata/global-metadata.dat il2cppdump
```

In the new _il2cppdump_ folder, you can find the JSON file _script.json_ and a C++ header file _il2cpp.h_ with all the metadata, which we will use later to locate the function code during the actual decompilation.

Browsing the _script.json_ file, we can find some interesting methods that might help us to decode the dictionary file:

- `void CDictionary__GetDictionaryData (CDictionary_o*__this, System_String_o **strReading, System_String_o** strNotation, System_String_o** strMeaning, int32_t nWordId, const MethodInfo* method);`
- `int32_t CDictionary___ConvertKey2String (CDictionary_o*__this, System_String_o** strOut, System_UInt32_array* apKey, uint32_t nLongFlag, const MethodInfo* method);`

Fortunately, not only the class and method names, even the parameter names are kept, which will help us a lot figuring out the code logic.

To actually decompile the file, we will use [Ghidra](https://github.com/NationalSecurityAgency/ghidra), an open-source reverse engineering tool that works on multiple platforms. 

{% embed https://github.com/NationalSecurityAgency/ghidra %}

However, Ghidra does not support NSO and IL2CPP binary out of the box, so we need some install something more to help us, namely:

- [Ghidra Switch Loader](https://github.com/Adubbz/Ghidra-Switch-Loader), which can be installed by going to _File_ -> _Install Extensions…_ in Ghidra and click the _+_ button at the corner.
  {% embed https://github.com/Adubbz/Ghidra-Switch-Loader %}
- [ghidra.py from IL2CPPdumper](https://github.com/Perfare/Il2CppDumper/blob/master/Il2CppDumper/ghidra.py), which can be installed by copying the file to the _%USERPROFILE%/ghidra\_scripts_ folder.

## Decompile the binary

Finally, we can proceed to decompile the binary. To make full use of the metadata we extracted earlier, there are a few steps we need do before starting to read the source code.

When the _main\_unc_ bianry is first loaded into a Ghidra project, it will prompt you to start an automatic analysis. Since the binary contains about 47MB worth of data, it might take a considerable amount of time to conduct the analysis, and we are only interested in a small portion of the code. I thus chose to skip the analysis.

The first step is to import the data types defined in the header file into Ghidra. Since the generated _il2cpp.h_ contains some data types that it does not recognize natively, we need to prepend these lines to it.

```c

typedef unsigned __int8 uint8_t;
typedef unsigned __int16 uint16_t;
typedef unsigned __int32 uint32_t;
typedef unsigned __int64 uint64_t;
typedef __int8 int8_t;
typedef __int16 int16_t;
typedef __int32 int32_t;
typedef __int64 int64_t;
typedef __int64 size_t;
typedef size_t intptr_t;
typedef size_t uintptr_t;

```

With the modified header file, we can then return to Ghidra, open _File_ -> _Parse C Source…_ to import it. When the _Parse C Source_ dialog is opened, clear everything in the _Source files to parse_ and _Parse options_ section, then add the header file we prepared. Finally, click _Parse to Program_ to start.

![A screenshot of the Parse C Source dialog of Ghidra, configure as instructed.](https://blog.1a23.com/wp-content/uploads/sites/2/2023/01/image-802x1024.png)
_Clear everything and add only the il2cpp.h file to the list._

Next, we need to label the functions at their respective offsets. Open the Script Manager from _Windows_ -> _Script Manager_, search for _ghidra.py_, then click the green play <kbd style="background-color:rgba(0, 0, 0, 0)"> ⃝▶</kbd> button to run the script. When prompted for files, select the _script.json_ file exported from IL2CPPdumper.

Once the script is finished, we will see there will be all the functions imported in the _Symbol Tree_ panel in the sidebar. In the _Filter_ box of at the bottom of the section, we can enter _CDictionary_ to find all the dictionary related method.

![Symbol Tree panel with the imported functions](https://blog.1a23.com/wp-content/uploads/sites/2/2023/01/Screenshot-2023-01-11-193330.png)
_Symbol Tree panel with the imported functions_

Then we can select the `CDictionary$$GetDictionaryData` in the _Symbol Tree_, and Ghidra will direct us to the correct byte offset in the _Listing_ window. If you see a bunch of `??` in the listing, press <kbd style="background-color:rgba(0, 0, 0, 0)"><span style="background-color:#38d1c4">D</span></kbd> on the keyboard to disassemble the function. As it disassembles, some C-like source code will also show up on the right side in the _Decompile_ window.

Inside the window, you may see a lot of types are set as `long`, `unknown8`, and strange type that might not make sense. A lot of these can be fixed by correcting the function signature and let Ghidra to re-infer the types. To do so, right click the function name and click _Edit Function Signature_. In the dialog opened, replace the arguments and return type in the large text box with what you can find in the corresponding `"signature"` field in the _script.json_ file. Take note that you need to drop the `MethodInfo * method` argument (which is always the last argument), as it is not decompiled by Ghidra.

To help improving the readability of the decompiled source, here are some helpful shortcut keys:

- <kbd style="background-color:rgba(0, 0, 0, 0)"><span style="background-color:#38d1c4">;</span></kbd> to add comments
- <kbd style="background-color:rgba(0, 0, 0, 0)"><span style="background-color:#38d1c4">L</span></kbd> to rename variable
- <kbd style="background-color:rgba(0, 0, 0, 0)"><span style="background-color:#38d1c4">Ctrl</span></kbd> + <kbd style="background-color:rgba(0, 0, 0, 0)"><span style="background-color:#38d1c4">L</span></kbd> to reassign type of a variable
- <kbd style="background-color:rgba(0, 0, 0, 0)"><span style="background-color:#38d1c4">Mouse middle click</span></kbd> to highlight all reference of the variable

With correct types, Ghidra is able to better infer some pointer offsets as struct properties or array indexes, which makes. the decompiled source easier to read.

![Disassembled listing and annotated decompiled source code side by side](https://blog.1a23.com/wp-content/uploads/sites/2/2023/01/Screenshot-2023-01-11-193842.png)
_Disassembled listing and annotated decompiled source code side by side_

## Debugging with emulator

With all the source disassembled and decompiled, it is sufficient to conduct a static analysis and recover most of the logic to parse the dictionary. However, there are still some portion of the code where the decompiled code does not make sense potentially due to some misassigned types.

```c
int CDictionary$$_ConvertKey2String
              (CDictionary_o *__this,System_String_o **strOut,System_UInt32_array *apKey,
              uint32_t nLongFlag)

{
  undefined8 ex;
  CMojiBlock_o *mojiBlock;
  System_String_o *result;
  int i;
  ulong apKey10;
  ulong uVar1;
  long lVar2;
  float *matrix03;
  uint apKey0;
  uint apKeySize;
  
  if ((DAT_7102d7e960 & 1) == 0) {
    __this = (CDictionary_o *)InitializeMethodMetadata(0xc24);
    DAT_7102d7e960 = 1;
    apKeySize = *(uint *)&apKey->max_length;
  }
  else {
    apKeySize = *(uint *)&apKey->max_length;
  }
  if (apKeySize == 0) {
    ex = IndexOutOfRange_FUN_7100bd84b0(__this);
    __this = (CDictionary_o *)throw_FUN_7100bd72c0(ex,0,0);
    apKeySize = *(uint *)&apKey->max_length;
    apKey0 = apKey->m_Items[0];
  }
  else {
    apKey0 = apKey->m_Items[0];
  }
  if (apKeySize < 2) {
    ex = IndexOutOfRange_FUN_7100bd84b0(__this);
    throw_FUN_7100bd72c0(ex,0,0);
  }
  result = EmptyString;
  apKey10 = (ulong)apKey0 & 0x7fffffff;
  if (nLongFlag != 0) {
    apKey10 = CONCAT44(apKey->m_Items[1],apKey0);
  }
  i = 0;
  uVar1 = (ulong)((uint)apKey10 & 0x7f);
  *strOut = EmptyString;
  if ((apKey10 & 0x7f) != 0) {
    do {
      if (((*(byte *)(SingletonMonoBehaviour<GlobalFunc>_TypeInfo + 0x127) >> 1 & 1) != 0) &&
         (*(int *)(SingletonMonoBehaviour<GlobalFunc>_TypeInfo + 0xd8) == 0) ) {
        ExclusiveMonitor_FUN_7100bb3c20();
      }
      mojiBlock = (CMojiBlock_o *)
                  SingletonMonoBehaviour<_CMojiBlock>$$get_Instance
                            (Method$SingletonMonoBehaviour<GlobalFunc>.get_Instance() );
      matrix03 = *(float **)&(mojiBlock->fields).m_MojiMtx.fields.m03;
      lVar2 = (long)(int)uVar1 + -1;
      if ((uint)matrix03[6] <= (uint)(float)lVar2) {
        ex = IndexOutOfRange_FUN_7100bd84b0();
        throw_FUN_7100bd72c0(ex,0,0);
      }
      /* What is this mess trying to get a string from a Unity matrix offset? */
      result = System.String$$Concat(result,*(System_String_o **)(matrix03 + lVar2 * 2 + 8) );
      uVar1 = apKey10 >> 7 & 0x7f;
      apKey10 = apKey10 >> 7;
      i = i + 1;
      *strOut = result;
    } while ((int)uVar1 != 0);
  }
  return i;
}

```

Without much clue to untangle this mess, I thought it would be easier to get the game running and attach a debugger to it to actually see how it works. Luckily there’s [Yuzu](https://github.com/yuzu-emu/yuzu/), a Nintendo Switch emulator that comes with GDB stub that can allow us to attach a GDB session to it.

{% embed https://github.com/yuzu-emu/yuzu/ %}

To enable GDB on Yuzu, go to _Emulation_ -> _Configure…_. In the popup, go to _General_ -> _Debug_ -> _Debug_, and check _Enable GDB Stub_. To the right end of the check box is the port number where Yuzu is listening for GDB connections.

![GDB Stub settings in Yuzu](https://blog.1a23.com/wp-content/uploads/sites/2/2023/01/Screenshot-2023-01-12-193433.png)
_GDB Stub settings in Yuzu_

Once GDB Stub is enabled, the game will only initialize the essential parts, and pause to wait for us to inspect it, and set up breakpoints before we ask it to `continue`.

While Yuzu conveniently provides a way for us to plug GDB into the emulator, not all GDB would work with it. As NSO binaries are essentially AArch64 binaries, we need a GDB that’s compiled to support this architecture to work with it. Fortunately, [devKitPro](https://devkitpro.org/wiki/Getting_Started) has offered a GDB that’s compatible with AArch64. Once devKitPro is installed, run the following command to install GDB for AArch64:

```sh
dkp-pacman -Syu devkitA64-gdb
```

With this, we are ready start debugging with GDB and Ghidra on Yuzu. In the Ghidra project window, click the 🪲 icon in the toolbar to open the Ghidra debugger.

In the debugger window, look for the _Debugger Targets_ panel to the left, and click the _Create a new connection to an (sic.) debugging agent_. In the dialog, select _IN-VM GNU gdb local debugger_, and enter the full absolute path of the previously installed `gdb` for AArch64 in the _GDB launch command_ field.

![Screenshot of the Ghidra Connect dialog to specify the GDB command](https://blog.1a23.com/wp-content/uploads/sites/2/2023/01/image-1.png)
_Screenshot of the Ghidra Connect dialog to specify the GDB command_

Once finished, there will be a new _Interpreter_ panel shown up to the right, with a `(gdb)` prompt at the bottom of the panel. To connect the GDB session to Yuzu, use the following command:

```gdb
(gdb) target extended-remote 127.0.0.1:5678
```

…where `5678` is the port number previously set in Yuzu settings.

If the connection is successful, you can try to inspect the offset of the running game by running `monitor get info`. You should get an output similar to this:

```gdb
(gdb) monitor get info
Process: 0x51 (main)
Program Id: 0x01006b900f436000
Layout:
  Alias: 0x108c600000 - 0x208c600000
  Heap: 0x208c600000 - 0x220c600000
  Aslr: 0x0008000000 - 0x8000000000
  Stack: 0x100c600000 - 0x108c600000
Modules:
  0x0008000000 - 0x0008003fff rtld
  0x0008004000 - 0x000b091fff main
  0x000b092000 - 0x000b7a5fff subsdk0
  0x000b7a6000 - 0x000c4a2fff sdk
```

In the output, we can see a line that says `0x0008004000 - 0x000b091fff main`. This tells you the address range of our binary is mapped in the RAM. In this case, the starting address in the binary `0x710000000` is corresponding to `0x0008004000` in the RAM from the debugger.

To let Ghidra to match the RAM data against our disassembled binary, we can first drag the `main_unc` item from the Ghidra project window to the debugger, which allows us to see the RAM view and the decompiled listing side by side.

Then, in the _Module_ tab of the right sidebar of the debugger, click the 📄 icon to open the _Static Mapping_ dialog. In the dialog, press the <kbd style="background-color:rgba(0, 0, 0, 0)"></kbd> button to create a mapping. In the _Add Static Mapping_ dialog, fill the following fields accordingly:

- **Static range** : [`ram`: _starting address in listing_, _<ending address in listing>_]
- **Dynamic range** : [`ram`: _starting address of the `main` module_,_ending address of the `main` module_]
- **Length** : (auto filled)
- **Lifespan** : (-∞ .. +∞)

![Add Static Mappings dialog set with the instruction above](https://blog.1a23.com/wp-content/uploads/sites/2/2023/01/Screenshot-2023-01-13-084954.png)
_Add Static Mappings dialog set with the instruction above_

Click _Apply_ to save.

Now, we have the disassembled and annotated binary mapped against the memory space. We can easily navigate like we did in the _CodeBrowser_. Functions are listed in the _Symbol browser_ at the same place, and we can navigate back to `CDictionary$$GetDictionaryData` from there.

To set a breakpoint, select the instruction from the _Dynamic_, _Listing_, or _Decompile_ panel, and press <kbd style="background-color:rgba(0, 0, 0, 0)"><span style="background-color:#38d1c4">K</span></kbd>.

Once all necessary breakpoints are set, we can move back to the _Interpreter_ window, and type `continue` to resume the game.

While debugging, some important tools and informations can be found in different panels of the debugger window:

- **Step into, Step over, Continue** are available in the _Object_ panel to the left.
- **Call stack** is available at the _Stack_ panel at bottom left.
- **Register values** are available at the _Registers_ panel to the right. Right click a register can also jump to the _Dynamic_ view to the memory address.

All GDB commands are still available from the _Interpreter_ panel_._

To trigger the game to load up from the dictionary with predictable result, the easiest way is to search for a word in the game’s word lookup feature. Once we hit submit, the game is paused for us at the beginning of the `GetDictionaryData` method as the first breakpoint hit.

![Screenshot of the word lookup feature in the game](https://blog.1a23.com/wp-content/uploads/sites/2/2023/01/2023011308312800-90E1EBC1CB6A8579EB92000ACF674175-1024x576.jpg)
_Screenshot of the word lookup feature in the game_

* * *

With help of these tools and the memory value at each step, it is much easier to understand the logic of the code, and to untangle those sections that the decompiler did not handle properly.

By inspecting the memory content around the matrix offset, It turns out that that part of the code is actually loading from a list of strings that converts the _key_ into a Hiragana, which happened to follow the Unicode order with a few exceptions.

From there, I was able to fully decode the dictionary files and extract all words with their definitions, which wraps up the project with a success.

I have released the parsing script, and the parsed dictionary data as JSON file to a [GitHub repository](https://github.com/blueset/MojipittanDictionary). There are a lot of binary processing due to the nature of the format, but is in general much more readable. Note that thre are still other data not used in the dictionary files, but since we are only interested in the words and definitions, I think I have achieved the goal.

{% embed https://github.com/blueset/MojipittanDictionary %}

> **Trivia**
> As it is obvious from the game’s word lookup feature, the longest word in the dictionary has 9 _kana_, which means リバース・エンジニアリング (_reverse engineering_) in the cover picture doesn’t actually exist in the dictionary, although リバース and エンジニアリング do. Also, ユニティー (_Unity_) isn’t in the dictionary either despite being short enough.

> **Fun fact**
> In order to save space, _Mojipittan Encore_ uses [Shift JIS](https://en.wikipedia.org/wiki/Shift_JIS) instead of [UTF-8](https://en.wikipedia.org/wiki/UTF-8) to store the phrases and definitions, as most characters used take 2 bytes in Shift JIS against 3 in UTF-8. However, this has a problem where some accented letters, like the é in café and cliché, are not encoded in Shift JIS. How the game solves it is to encode these characters as question marks in the dictionary, and hardcoded all the affected words in the game logic when converting them back to UTF-8.

The post [Reverse engineering an IL2CPP NSO binary: Case study of Mojipittan Encore](https://blog.1a23.com/2023/01/16/reverse-engineering-an-il2cpp-nso-binary-case-study-of-mojipittan-encore/) appeared first on [1A23 Blog](https://blog.1a23.com).