Gues what! Your faourite blog with a glacial update schedule is back!

Today we have another reverse engineering project on our hands, which is quite typical for this blog I must admit. I did originally intend this to be a bit more diverse, but to be honest I do that much outside of this.

Also let it be known that I do have a follow up post to this one in the works, as well as yet another different reverse engineering project cooking up. That may take a while though.

I really need other topics for this blog, wow.

Anyway, moving on..

All the code I wrote for this project is available here:

codeberg.org/rainey/tsumimi-mod

(yes, I am probably going to move over to Codeberg soon :3)

Contents

The game

Red Chat Ritual - TSUMIMI TIME is a game I picked up “recently” (nearly two months ago, wow) when I saw it in Steam Next Fest.

The demo was really cool, and I definitely want to play the full thing when it releases.

However, as I always find myself drawn to, I had to poke around at its internals. Turns out, it’s a Unity game. Meh. Unity is kind of boring, there are already so many tools to decompile and unpack it.

But wait!

What is ark0.dat, that’s not standard! And script.dat too! This might actually be more interesting than I first thought…

The engine

As I said before, this is a Unity game. No weird engine hacks or anything. It’s also IL2CPP, which would normally be a dealbreaker for me (or at least a huge demotivator), but in this case it’s actually not, for reasons I’ll mention soon.

Opening the two .dat files in a hex editor (ImHex my beloved <3), there’s a (likely) magic number at the start, ACV1, followed by a bunch of data. It doesn’t look completely encrypted or compressed, as I can spot some text as well as PNG file headers, as well as long repeating patterns that encryption or compression would have scattered.

Finally, looking into the Unity native plugins folder, we can see a few DLLs.

AkUnitySoundEngine.dll and WwiseProjectDatabase.dll are both from Wwise, which is a common third party sound engine. It’s pretty neat, adaptive audio is fun to play with.

Next, we have steam_api64.dll. This is really self explanatory.

lib_burst_generated.dll is a standard Unity thing, so we don’t have to worry about that.

Finally, there’s libonigwrap.dll and Engine.dll, which are non-standard.

Just by searching up the former, we find this GitHub repo by the same name, which seems to be a wrapper around an unmanaged RegEx library. Since it’s open source, we can leave it alone for now.

Finally, Engine.dll. The first thing I did was pull it into PE-bear, which is great for things like looking at library imports/exports, strings, etc.

From the exports we can guess that it does the following:

The last thing we can look at before pulling out any decompilers is the README file that comes with the game.

Helpfully, this tells us some of the third party unmanaged libraries used in the game. These are:

The decompilers

Now we can break out the big guns. (Ghidra and ILspy, if you couldn’t guess)

The first thing to tackle is the managed game code, since that will help us identify the signatures of the unmanaged Engine.dll functions later.

“Wait, managed code? Didn’t you say this was an IL2CPP game?”

you may be thinking.

While yes, this is IL2CPP, the developers made a rather silly mistake, which was to ship with their game the folder called ”…_BackUpThisFolder_ButDontShipItWithYourGame”.

Laughing fit aside, this folder is really useful for us reverse engineers because it contains not only the intermediate C++ code produced by the IL2CPP compiler before it gets built into a DLL, but also the original managed assemblies!

This means that with a bit of digging, we can find the Unity version the game used (6000.3.14f1), download the blank Mono player off their website, and transplant the game assets into it, along with the managed DLLs we grabbed from the “don’t ship this folder” folder that got shipped.

I’ll stop now, I swear :p

This gives us a wonderful debuggable and decompilable version of the game as if IL2CPP never touched it!

The main file that sticks out to me is CEngine.dll, since it looks like it could be the managed side of that unmanaged DLL from earlier. Dumping the code, we can see that yes, in fact it is.

From now on, I’m going to refer to this semi-engine as “Novus”, since that’s the actual C# namespace inside CEngine.dll. It’s probably their internal name for it.

My first thought, in the nicest way possible, was:

Are you kidding me???

The code in the Novus DLL is really messy. While yes, a good chunk of it can be chalked up to decompiler noise, the design decisions are Really Weird™. Everything seems to be stored in static variables, when there are surely much better ways of implementing all of this. But whatever, we can work through it.

The Novus.Parser class is the most interesting for us right now, as it’s the one responsible for the archive (.dat) files the game uses.

Editor rainey’s note: I’m going to skip the detailed like, step by step walkthrough from now on, or else this post will take ten more years to come out.

The archives

Right out of the gate, this is one of the most annoying archive formats I’ve dealt with so far. It has so many layers of encryption and obfuscation, it was a nightmare to unpick.

Here we go…

The header

The header can start with a magic number: “ACV1” as ASCII bytes, as we guessed earlier.

Whether or not there is a magic number, the number of entries in the archive follows (as a 32 bit number). If there is a magic number, the entry count is XOR-ed with the key 0x8B6A4E5F. This key then also becomes the “main key”, which I’ll explain in a second. If there isn’t a magic number, the entry count is XOR-ed with the key 0x26ACA46E. The main key is set to zero.

Then, we read the specified number of entries. Each entry has the following structure:

The flags specify what compression/encryption mode was used. The only values I’ve seen are:

I’m not going to write out the exact algorithms here, but you can find it in the git repository for this project linked at the top of the article.

There! In theory, that’s all you need to extract the archives. But…

You may have noticed how the only way to pick out specific files is by the hashes of their file names. That’s right, the filenames aren’t stored anywhere!

The only way to find them is by pulling them out of the game code and hashing them yourself. I would be slow clapping at this if you could hear me.

I had a plan though.

The patcher

The easiest way to dump a large amount of filename hashes is simply to play the game through with a mod installed to intercept and dump any reads from the archive. So, I used the patcher I made for Transistor as a base.

I installed a hook on the function Novus.Parser.openFile, and made it print out all filenames to a text file. This worked great! I had a lovely list of filenames that I could run through a script to hash and save into a big JSON file for future use. However, the one thing I couldn’t find was the initial file, the one that the game would load first. But no matter where I looked, I couldn’t find any filenames.

Finally, I saw this.

The scripts

In the parser class, there’s an array of 64-bit numbers called txtHashList.

public static ulong[] txtHashList = new ulong[4]
{
    1437814575074953728UL,
    17146440702141048396UL,
    12467166482808579083UL,
    0UL
};

Hashes, you say?

The first three of these all get passed sequentially into Novus.Parser.readScript, and are looked up in the second archive file of the game, “script.dat”.

However, it’s not really that easy. Scripts are handled differently, because of course, and go through yet more XOR encryption. Who could have guessed??? This time, the key is the hash of the filename XOR-ed with the (same type of) hash of the game name, which in this case is “Tsumimijikan”. Note that the resulting key is cast into a 32-bit uint after XOR-ing the two together.

We can then use this key to decrypt the script data 4 bytes at a time, then finally run it through Zlib decompression to get our code as a UTF-16 string. Note that the Zlib implementation isn’t a .NET one, it’s actually that mysterious EG_uncompress function from the unmanaged library earlier! The game does do a bit of cleanup on the string, but I left this out of both my program and this post for simplicity’s sake. It’s not that important for understanding the scripts anyway.

Cool! We have our entry point(s) now!

I’m not going to put the full scripts here, because the startup ones are hundreds to thousands of lines long, however, the gist of it is that they seem to be written using some custom syntax. Going through the Novus library some more will probably reveal all.

You can also see copies of the scripts in this project’s repo, or use the tools to dump them yourself if you’re so inclined.

Where next?

To tell the truth I got a bit stuck at this point. From my mod, I can see that the game loads a file called “embed/main.nut” (a Squirrel script), but no matter how much I search I can’t find any reference to that. My guess is that it’s probably referenced from some Unity asset somewhere, not directly in the code.

But no matter!

Just because we don’t know where it came from, we can still use it.

main.nut loads 5 other script files from the archive, as follows:

config.nut also checks to see if config2.nut exists in the same directory and loads it if it does. It just has some more basic stuff in it, nothing too noteworthy.

Other files

As well as these two types of scripts, there are also some other interesting files.

First, in the first startup script, there are references to sys/mode/sdlist.csv and sys/mode/cglist.csv. The only issue is, these don’t seem to turn up in the actual archive file. I’m either doing something wrong, or these files were removed at some point in development. At this point, who knows? I need to rip all the files out and have a closer look.

Second, there are some .txt files which look to contain the dialogue/text in the game. For example, 1txt__V1_0001_0.txt contains:

0000	主人公@Main Character	Official site... I might learn something about Mashiro.

I have only seen two of these with my dumping mod so far, but I need to go through more of the game again with it.

Third, the font the game uses is DotGothic16! This isn’t really that interesting, but I think it’s a fun fact :3

The rest

(I really don’t know why I started every heading with “The …”, I swear it was accidental at first)

I’m going to end this post here, since I am rather tired. I’m planning on writing a followup at some point though, so stay tuned! (or add this as an RSS feed :p)

My next jobs are probably to:

Now that I think of it I could probably automatically classify the files by MIME type or something. Take notes, future me!

Once again, the code for this stuff is all published here, and I’m open to contact anywhere listed on my website.

As always, thank you for reading and goodnight!