[Tutorials] [Case Study][KirikiriZ] Tsumugi no Hanayome

Entai2965

Moderator
Moderator
Elite Member
Dec 28, 2023
103
63
splash.jpg


Very important, this case study covers translating krikiriZ txt.scn (e-mote) files and bytecoded tjs2 files as part 2 of working with the kirikiriZ game engine. Part 1 is the Koakuma-chan no Yuuwaku case study.

Part 1, Koakuma-chan no Yuuwaku, establishes a core understanding the kirikiriZ game engine which this post builds on. This part 2 assumes all content in part 1 has been read and fully understood, including the parts about cli usage and automation.

Yet another disclaimer, The intent of these guides is to show people the mindset and real-world approaches I use when tackling a new proof of concept for a translation project. Ideally, these guides should empower you to figure things out the various issues related to translation yourself by solving the most common ones in a step-by-step way for a particular title. These guides do not necessarily outline an optimized workflow for translating a particular title or titles from a particular game engine because they are meant to show the process, not just the final answer.

Part A) Learning about the game, the repackage version, and determining the game engine
Part B) Translating the first line of dialogue 1
Part C) Translating the first line of dialogue 2
Part D) Translating images
Part E) Translating the title and game engine strings
Part F) Adding subfolders to unencrypted.xp3
Part G) Automating dialogue extraction, translation, and insertion

This case study covers kirikiriZ title 紬の花嫁, Tsumugi no Hanayome, which could be translated as The Bride of Tsumugi. This is the story of how the english MTL patch was created.

Information
- https://vndb.org/v51186
- https://www.hendingerg.com/products/tsumuginohanayome/index.html

It looks like the game's story involves yuri + futanari + romance, and some supernatural elements. Right...

It was released in late 2024, so it is a relatively recently released game and an english mtl patch for it was published two weeks after the game's release. That is a much better turn around time than the years of waiting it used to take for translations to happen prior to having well developed translation tools a decade ago. With AI MTL, the MTL is half readable as well and any human translator or editor that wants to pick this project up can do so knowing they have a fairly good template to work from.

Download
- Getchu
- Suruga-ya
- Softmap
- girlcelly's thread
- English patch thread

To keep everything organized, I am using a base folder on the desktop named "Desktop\Tsumugi no Hanayome" and putting everything related to this project underneath it in more subfolders: bin\, extracts\, tools\, MTL\. Most paths in this case study are relative to that one since it is serving as the main project directory.

Code:
Tsumugi no Hanayome/
Tsumugi no Hanayome/bin/
Tsumugi no Hanayome/extracts/
Tsumugi no Hanayome/tools/
Tsumugi no Hanayome/MTL/
Tsumugi no Hanayome/other/

For me, the main executable for this game is under "Desktop\Tsumugi no Hanayome\bin\Tsumugi no Hanayome\Tsumugi.exe". The folder can moved to other places, and the game should still launch, at least for the repackage linked above.

Here are the raw installed files under 紬の花嫁/ for the installed version.

Code:
plugin/
bgimage.xp3
bgimage.xp3.sig
BootStrap.exe
data.xp3
data.xp3.sig
evimage.xp3
evimage.xp3.sig
fgimage.xp3
fgimage.xp3.sig
patch_append1.xp3
patch_append1.xp3.sig
readme.txt
Tsumugi.cf
Tsumugi.exe
Tsumugi.exe.sig
Uninstall.exe
video.xp3
video.xp3.sig
voice.xp3
voice.xp3.sig
ファイル破損チェックツール.exe
ファイル破損チェックツール.ini

Here are the files for the repackage installed under Tsumugi no Hanayome/.

Code:
Extras/
plugin/
bgimage.xp3
data.xp3
evimage.xp3
fgimage.xp3
patch_append1.xp3
readme.JPN.txt
Tsumugi.config.cmd
Tsumugi.exe
video.xp3
voice.xp3

The .sig files are just text files that can optionally be used to check the file integrity of certain files using RSA public-private key crypography. The contents look similar to this.

Code:
-- SIGNATURE - SHA256/PSS/RSA --
x9//PerHIQVUmaYtfET466Niq4sMBvRVXz7dPhMQoMd0LSuzz44/VhegIZsXw7qT
G+6Ga6roUV1pxgqVFUkJ2fU9WZ3NCFInMGP12uKPKvTOrF4BhwCxUUIKu+7V7Puf

Interestingly, there is an external file corruption tool, ファイル破損チェックツール.exe, "File Corruption Checking Tool", that can read and verify the hashes of the files using signature data in .sig. Here is what running the tool looks like after replacing Tsumugi.exe with the no-DVD patch.

file_corruption_checking_tool.png


- The .sig files in the main directory are not needed for the game to launch, so they were moved to Extras/backup/. However, the plugins/*.sig files are required or the game will not load the associated .dll files. Then, after refusing to load them, it will complain the classes that were supposed to be loaded from the .dll files do not exist because it never loaded the .dll files they were in.
- Tsumugi.cf is an optional configuration file that tells the game where to keep savedata. If this file is not present in the root directory of the game, then the game will use a folder called "savedata" at the root of the game's directory instead of somewhere else under the User/ folder.
- BootStrap.exe launches an small menu before the game launches with some options. It is not necessary and having multiple .exe files in the game's directory can be confusing, so it was moved to Extras/backup/. Move it back to the main directory to restore its functionality. It was not translated in the published patch.

Here is also what the repackage should look like with the translation patch applied.

Code:
Tsumugi no Hanayome
|   bgimage.xp3
|   data.xp3
|   evimage.xp3
|   fgimage.xp3
|   output.txt
|   patch_append1.xp3
|   readme.JPN.txt
|   Tsumugi.config.cmd
|   Tsumugi.exe
|   unencrypted.xp3
|   version.dll
|   video.xp3
|   voice.xp3
|   
+---Extras
|   |   Tsumugi_no_Hanayome.complete_savedata.7z
|   |   
|   +---backup
|   |       BootStrap.exe
|   |       Tsumugi.cf
|   |       Tsumugi.exe
|   |       Uninstall.exe
|   |       
|   +---sig
|   |       bgimage.xp3.sig
|   |       data.xp3.sig
|   |       evimage.xp3.sig
|   |       fgimage.xp3.sig
|   |       patch_append1.xp3.sig
|   |       Tsumugi.exe.sig
|   |       video.xp3.sig
|   |       voice.xp3.sig
|   |       ファイル破損チェックツール.exe
|   |       ファイル破損チェックツール.ini
|   |       
|   \---wallpapers
|           1080_1920.jpg
|           1080_2160.jpg
|           1125_2436.jpg
|           1366_768.jpg
|           1536_864.jpg
|           1920_1080.jpg
|           1920_1200.jpg
|           750_1334.jpg
|           
\---plugin
        AlphaMovie.dll
        AlphaMovie.dll.sig
        dmmcloud.dll
        dmmcloud.dll.sig
        extNagano.dll
        extNagano.dll.sig
        extrans.dll
        extrans.dll.sig
        getSample.dll
        getSample.dll.sig
        json.dll
        json.dll.sig
        k2compat.dll
        k2compat.dll.sig
        kagexopt.dll
        kagexopt.dll.sig
        KAGParserEx.dll
        KAGParserEx.dll.sig
        krmovie.dll
        krmovie.dll.sig
        layerExDraw.dll
        layerExDraw.dll.sig
        lzfs.dll
        lzfs.dll.sig
        menu.dll
        menu.dll.sig
        PackinOne.dll
        PackinOne.dll.sig
        psbfile.dll
        psbfile.dll.sig
        psd.dll
        psd.dll.sig
        textrender.dll
        textrender.dll.sig
        win32dialog.dll
        win32dialog.dll.sig
        win32ole.dll
        win32ole.dll.sig
        windowEx.dll
        windowEx.dll.sig
        wuopus.dll
        wuopus.dll.sig
        wuvorbis.dll
        wuvorbis.dll.sig

First, launch Tsumugi.exe using a Japanese, Japan locale emulator to make sure the game actually works. Then take a screenshot or otherwise record the first dialogue line of the game. For the initial proof of concept, we need to translate that one line.

first_line.png

Next, we need to verify the game engine. This is a kirikiriZ title, but how do we know that? In addition to the heuristic of .xp3 files in the root of the directory, here is the Garbro ouput for the tested working noDVD executable Tsumugi no Hanayome\Tsumugi.exe\RT VERSION\00001. The original executable can't be opened as a resource archive in Garbro.

Code:
BLOCK "041104b0"
{
    VALUE "FileDescription", "紬の花嫁"
    VALUE "FileVersion", "1.2.0.3"
    VALUE "InternalName", "tvp2/win32"
    VALUE "LegalCopyright", "(KIRIKIRI core) (C) W.Dee and contributors All Rights Reserved. This software is based in part on the work of Independent JPEG Group. For details: Run this program with '-about' option."
    VALUE "OriginalFilename", "tvpwin32.exe"
    VALUE "ProductName", "TVP(KIRIKIRI) Z core / Scripting Platform for Win32"
    VALUE "ProductVersion", "1.2.0.3"
    VALUE "Comments", "Hending"
}

garbro.kirikiriarchive.png


The Garbro output at the bottom left indicates the archive type when opening the .xp3. That information means the file is actually a valid kirikiri archive (.xp3) instead of some other archive type re-named to have a .xp3 extension.

All together, that makes it very likely this is a kirikiri game, specifically kirikiriZ.
 
Last edited:
Part B) Translating the first line of dialogue 1

In order to translate the first line of dialogue, we first need to extract the file that line is located in.

Unfortunately, the file names and contents in the .xp3 are obfuscated, likely with some form of encryption instead of just earlier generation obfuscation. The encrypted contents mean we need to either specify a scheme in Garbro to extract the assets or find a different way to extract the contents.

The file names are part of the metadata, information describing the contents, rather than the contents of the files themselves. Encrypted metadata, not knowing the original structure of the archives and file names, is annoying since it interferes with creating that archive structure again for the patch to keep everything organized, but ultimately, it does not affect much more than that.

We could search through Garbro's list for a working algorithm, but if the developer is using RSA PKI on a modern kirikiriZ title, then the odds of that working is pretty low. Nothing worked in that list worked for Koamuma-chan, remember? Instead, let's just use crskycode's KrkrDump again. This tool for extracting assets is very likely to work for Tsumugi since it is known to work with a different game that operates on the same kirikiriZ game engine, Koakuma-chan no Yuuwaku.

Here is the KrkrDump.json for Tsumugi. For the path separator in the "outputDirectory" entry, either use double \\ notation as in the original example or a single /. Both work, but a single \ will likely not.

Code:
{
    "loglevel": 1,
    "enableExtract": true,
    "outputDirectory": "C:/Users/User/Desktop/Tsumugi no Hanayome/extracts/Tsumugi_extracts",
    "rules": [
        "file://\\./.+?\\.xp3>(.+?\\..+$)",
        "archive://./(.+)",
        "arc://./(.+)",
        "bres://./(.+)"
    ],
    "includeExtensions": [],
    "excludeExtensions": [
        ".ogg"
    ],
    "decryptSimpleCrypt": true
}

Let's play the game while using KrkrDumpLoader.exe and extract all of the files. Multiple playthroughs might be required to select every choice combination.

There is usually an option in the Config screen to skip unread lines which can be used to make this take less time. This is usually a toggle, so keep toggling options on and off until it is found. Tsumugi no Hanayome has another option somewhere in the Config menus to make sure the game keeps going when in the background instead of pausing. Let's enable that too.

With the files fully extracted, now we need to organize them in order to begin to make sense of them and eventually find the dialogue scripts. Thankfully, the original file names including the extensions have been restored, but we can't use the original structure for guidance since the developer encrypted the file and folder name metadata in the .xp3 files. The alternative way of doing this is by file type (extensions) and the fastest way to do that is to use command prompt wildcards.

- Open "Tsumugi no Hanayome/extracts/Tsumugi_extracts" in Windows Explorer.
- Enter "cmd" in the address bar.
- Press enter.

With a comand prompt, organize the files into subfolders. Here is the structure I am using for Tsumugi.

Code:
mkdir tjs
move *.tjs tjs
mkdir movies
move *.webm movies
move mv*.csv movies
mkdir images
move *.png images
move *.tlg images
mkdir fonts
move *.ttf fonts
mkdir pbd
move *.pbd pbd
mkdir scripts
move *.txt.scn scripts
mkdir ks
move *.ks ks

The images folder is an absolute mess. CGs, backgrounds, character sprites, and gui overlays are all mixed together. It needs a couple more subfolders before some semblance of organization takes root. We can deal with that later though. For now, we need to focus on translating the first line of the game.

Browsing the extracted .ks files, those are all very small system scripts that define macros and such. Character dialogue lines are not there and neither is the game's core logic.

After some searching and browsing headers with notepad++, the game's scripts are likely in the .txt.scn files since they are valid e-mote files and start with the e-mote header "PSB".

Here is kirikiri's documentation. https://krkrz.github.io/
https://krkrz.github.io/docs/specification/fileformat.html
Which leads us to the e-mote homepage. https://emote.mtwo.co.jp
And their about page. https://emote.mtwo.co.jp/about/

描いたら、動かそう。 キャラクターアニメーションツールE-mote
Once you've drawn it, move it. Character anime tool E-mote

「E-mote」はキャラクターアニメーションを制作する際の『パーツ配置』『ボーン設定』『ボーン割り当て』などの煩雑なセットアップ工程を完全カットし、低い工数でイラストを動かすことを実現した技術です。
また制作されたデータは、様々なプラットフォームでリアルタイムで動かすことができ、動画データよりも圧倒的に少ないデータ量で、フルアニメーションを再生することが可能です。
"E-mote" is a technology that completely cuts out the complicated setup process such as "parts placement", "bone setting", and "bone assignment" when creating character anime, and realizes moving illustrations with low man-hours.
In addition, the produced data can be moved in real time on various platforms, and it is possible to play back full anime with an overwhelmingly smaller amount of data than video data.

ハイクオリティアニメーション
High quality anime

「E-mote」では『工数の削減』と『繊細な完成度の高い動き』を両立しました。
演奏にあわせてコードを押さえる指の動きや、ラーメンを箸でつまんで口に運ぶ動きなど、これまでに様々なハイクオリティアニメーションが「E-mote」を用いて作製されています。
"E-mote" achieves both "reduction of man-hours" and "delicate and highly complete movements". Various high-quality anime productions have been created using "E-mote", such as the movement of fingers holding down chords according to the performance, and the movement of picking up ramen with chopsticks and bringing it to the mouth.

「E-mote」のコスト
The cost of "E-mote"

"E-mote" provides "SDK for 1 platform + 1 editor" for medium and large companies for the amount of "300,000 yen for 1 year" or "300,000 yen for 2 years for 1 title only", and supports support during the license period "free of charge". If you are using it for personal use, you can use "Emofuri", which has almost the same function as "E-mote", for free. The new "Emofuri" has been removed from the limit on the number of frames in the video, and can be freely used in apps and games in combination with the Unity SDK and Tyranno Script.

In other words, the emphasis of the E-mote format is animation, not dialogue, and it has some cross platform features. KirikiriZ's fileformat documentation also curiously omits mentioning it.

The E-mote SDK does cost about $3000, but that depends on the current exchange ¥ vs $ exchange rate, which means low budget games probably would be better off using kirikiriZ's k2compat system and .ks files for the dialogue. That probably explains why Koakuma-chan used .ks files for dialogue even though those files are traditionally limited to shift-jis and utf-16-le-bom encoding instead of supporting utf-8 natively like the e-mote format. As someone trying to translate stuff, utf-8 support is important and better than utf-16-le-bom, but is it important to the tune of $3000? IDK honestly. The game developer has to decide that. There are also some modern KirikiriZ games that support utf-8 encoded .ks files, so it is possible at least theoretically.

That cost of the E-mote SDK also makes me grateful that UlyssesWu's FreeMote project exists which allows us to work with E-mote files in a comprehensive way without the E-mote SDK. The documentation being in easy to understand english is a big plus.

There are a lot of alternatives to UlyssesWu's FreeMote for working with E-mote/PSB files, however, at least for translating kirikiriZ .txt.scn files, no FreeMote alternatives are worth using because FreeMote provides comprehensive unpacking and packing support.

The alternative to FreeMote are tools that use regex to blindly extract strings which makes using them likey to cause crashes since those other tools do not try to figure out what is translatable and what is not. If FreeMote did not exist, those other approaches could be useful, but FreeMote exists, so please avoid using anything else when translating .txt.scn files, even if it means taking the time to learn cli's and how to parse json with python. There are some FreeMote json parsing examples in my codeberg repository under translation projects.

If FreeMote is too difficult to use, then spend some time learning how to use a cli, read the FreeMote documentation, and learn python. It is simpler than it sounds. Instead of using other tools, that will help more in the long run, especially when it comes to automation which can dramatically reduce the amount of work it takes to translate a game down to managable levels so even a single person can fully publish a translation of a game within two weeks of its release, excluding the actual translation step of course.

First, let's download the FreeMote Toolkit from the releases page, and then read up on the documentation linked above. In my case, I extracted FreeMote Toolkit 3.x to Tsumugi no Hanayome/tools/FreeMoteToolkit/, but 4.x probably also works. Here is a snippet of the FreeMote Toolkit wiki.

Guide for rookies
- Make sure you have .NET Framework 4.8 installed.
- Drag and drop a xxx.psb/xxx.scn/xxx.pimg file to PsbDecompile.exe to get json files and images.
- Drag and drop a xxx.json file (you got from PsbDecompile.exe) to PsBuild.exe to get a PSB file.
- Drag and drop a dx_xxx.psb file (you got from some galgames) to FreeMoteViewer.exe to view it.
- For other operations, you must learn how to use CMD or Windows Terminal first. Then read commandline usages.
- Keep in mind that there is no "double-click" or keyboard input involved in a "drag and drop" operation.

Type <tool_name.exe> -h command in CMD or Terminal for tools' instructions. For example, if you don't know how to use PsBuild, just type PsBuild -h and press Enter; If you don't understand PsBuild "info-psb" command (what's that or how to use that), type PsBuild info-psb -h and press Enter.

Here is some information on the Microsoft .Net framework. It is already included in most newer versions of Windows 10 and newer, including Windows 11, but Windows 10 <=1809 will need to install it.

Code:
C:\Users\User>"C:\Users\User\Desktop\Tsumugi no Hanayome\tools\FreeMoteToolkit\PsbDecompile.exe" --help
FreeMote PSB Decompiler
by Ulysses, [email protected]
18 Plugins Loaded.
 
Usage:  [command] [options] <Files>
 
Arguments:
  Files                          File paths
 
Options:
  -?|-h|--help                   Show help information
  -s|--seed <SEED>               Set MT19937 MDF seed (Key+FileName)
  -l|--length <LEN>              Set MT19937 MDF key length. Default=131
  -k|--key                       Set PSB key (uint, dec)
  -raw|--raw                     Output raw resources
  -oom|--memory-limit            Disable In-Memory Loading
  -1by1|--enumerate              Disable parallel processing (can be very slow)
  -hex|--json-hex                (Json) Use hex numbers
  -indent|--json-array-indent    (Json) Indent arrays
  -dfa|--disable-flatten-array   Disable represent extra resource as flatten
                                 arrays
  -e|--encoding <ENCODING>       Set encoding (e.g. SHIFT-JIS). Default=UTF-8
  -t|--type <TYPE>               Set PSB type manually
                                 Allowed values are: PSB, Pimg, Scn, Mmo,
                                 Tachie, ArchiveInfo, BmpFont, Motion,
                                 SoundArchive, Map
  -dci|--disable-combined-image  Output chunk images (pieces) for image (Tachie)
 
                                 PSB (try this if you have problem on image type
 
                                 PSB)
 
Commands:
  image                          Extract (combined) images from image (Tachie)
                                 type PSBs (with "imageList")
  info-psb                       Extract files from info.psb.m & body.bin
                                 (FreeMote.Plugins required)
  unlink                         Unlink textures from PSBs
 
Run ' [command] -?|-h|--help' for more information about a command.
 
Plugins:
  FreeMote.Lz4 by Ulysses : LZ4 support.
  FreeMote.Mdf by Ulysses : MDF (ZLIB) support.
  FreeMote.Mxb by Ulysses : MXB (XMemCompress) support.
  FreeMote.Mzs by Ulysses : MZS (ZStandard) support.
  FreeMote.Psd by Ulysses : PSD export.
  FreeMote.Psp by Ulysses & morkt : PSP (LZSS) unpack support.
  FreeMote.Psz by Ulysses : PSZ (ZLIB) support.
  FreeMote.Mfl by Ulysses : MFL (FastLZ) support.
  FreeMote.Astc by Ulysses : ASTC support.
  FreeMote.Bc7 by Ulysses : BC7 support via BCnEncoder.NET.
  FreeMote.Tlg by Ulysses : TLG support via TlgLib.
  FreeMote.Wav by Ulysses : Wav/P16 support.
  FreeMote.Audio.File by Ulysses : Export/Import audio file.
  FreeMote.At9 by Ulysses : At9 support via VGAudio.
  FreeMote.NxAdpcm by Ulysses : NX ADPCM support via VGAudio.
  FreeMote.NxOpus by Ulysses : NX Opus support via VGAudio.
  FreeMote.Vag by Ulysses : VAG support.
  FreeMote.Xwma by Ulysses : XWMA support.
 
Examples:
  PsbDecompile -k 123456789 sample.psb
 
Done.

So, the core syntax to make something happen is just this.

Code:
PsbDecompile.exe script.psb

The .psb extension in the above example and extensions in general are inconsequential. While operating systems can use them to associate which file should be opened with which program, the file contents are what matters to actually render them properly.

About PSB
FreeMote is a set of tool/libs for M2 Packaged Struct Binary file format. The file header usually starts with PSB/PSZ/mdf, and the file extensions usually are .psb|.psz|.mdf|.pimg|.scn|.mmo|.emtbytes|.mtn|.dpak|.psb.m

E-mote files, at least in Tsumugi, begin with a "PSB" header, and are renamed to .scn or .pimg but can be various other extensions as well for different games. In our case, the extension used by Tsumugi is .scn. There are also a secondary .txt extensions before the final .scn extension to improve the clarity on the type of E-mote file when looking at the file name, meaning that they are intended to hold text.

E-mote files can also have an optional pin number that weakly encrypts their contents. Since using a pin number is always a terrible idea for security, if present, these pins can be determined using brute force. There is this old tool, EMotePrivateKey, by xmoezzz that can probably do it, but this is not necessary for Tsumugi since the .txt.scn are in plaintext E-mote format already.

I have not personally encountered any KirikiriZ games that use encrypted e-mote, .txt.scn, dialogue files. If you have, please leave a comment letting me know of a title that does so I can work out how to brute force them into a series of more tangible steps. Or if you already know the steps to do brute force them, please post or link to them.

Let's make sure PsbDecompile.exe and repacking any changes actually works since that is enough to finish the proof of concept. Then we can automate all of it.

The files in Tsumugi no Hanayome\extracts\Tsumugi_extracts\scripts are actually numbered sequentually, so I am assuming "紬の花嫁01 プロローグ.txt.scn" is the first one.

What if we did not want to assume that file is the first one?

If we go back to the KrkrDump.log, it lists files in the order the game reads them, so it is simple browse the log for the very first .txt.scn file that appears using ctrl+f. That will likely display the .txt.scn file that was extracted first when the first line of dialogue appeared in the game.

notepadplusplus.search.txt.scn.png


Code:
C:\Users\User>"C:\Users\User\Desktop\Tsumugi no Hanayome\tools\FreeMoteToolkit\P
sbDecompile.exe" "C:\Users\User\Desktop\Tsumugi no Hanayome\extracts\Tsumugi_ext
racts\scripts\紬の花嫁01 プロローグ.txt.scn"
FreeMote PSB Decompiler
by Ulysses, [email protected]
18 Plugins Loaded.
 
Decompiling: 紬の花嫁01 プロローグ.txt
Done.
 
C:\Users\User>

It says it decompiled something, but it did not say where it wrote the output. There is nothing in the C:\Users\User folder. Over at Tsumugi no Hanayome\extracts\Tsumugi_extracts\scripts there are now two new files called "紬の花嫁01 プロローグ.txt.json" and "紬の花嫁01 プロローグ.txt.resx.json". In other words, the PsbDecompile.exe tool creates its output relative to the target instead of the current directory of the command prompt.

The non-resx .json is several megabytes in size, but the .resx.json file is just 156 bytes. Here are the contents of the .resx.json file.

Code:
{
  "PsbVersion": 3,
  "PsbType": "Scn",
  "Platform": "none",
  "CryptKey": null,
  "ExternalTextures": false,
  "Context": {},
  "Resources": {}
}

It looks like the .resx.json contains metadata about the main .json file. The Psb file it decompiled was a "Scn" PsbType which maybe means "scenario" or "scene", without any platform, no encryption, and was version 3. That will likely be useful to know when packing it again.

Opening up "紬の花嫁01 プロローグ.txt.json", the chaos begins. PsbDecompile.exe converts the compressed .scn.txt to uncompressed .json. JavaScript Object Notation (.json) is a normal way to represent complex data when it needs to be done in a language and program independent way. There are other ways to do this, but PsbDecompile prefers .json evidently.

Does this script.json have the line we are looking for? Instead of finding it manually, it would be nice to be able to search for it with ctrl+f just like we searched through the KrkrDump.log file. Let's try running ocr on the first line of the game to get the line we are searching for.

- Go back to first_line.png, which is the screenshot of the first line taken earlier.
- Cut the first line into first_line_snip.png

first_line_snip.png


- Run ocr software on that image.

Ocr options,
- Tesseract using Capture2Text
- Yandex's Ocr
- ocr.py/ocr_text_recognition.py on my codeberg's ocr_tools repository implements manga-ocr which uses PyTorch and the kha-white--manage-ocr-base model.
- Google lens
- Microsoft's Text Extractor Powertoy

In my case, I did ocr.py since that should already be set up from the Koakuma-chan case study.

Code:
cd C:\Users\User\Desktop\Tsumugi no Hanayome\tools\translation_tools
python ocr.py "C:\Users\User\Desktop\Tsumugi no Hanayome\MTL\ocr\first_line_snip.png" -o

or if using ocr_text_recognition.py part of codeberg/ocr_tools, that should be

Code:
cd C:\Users\User\Desktop\Tsumugi no Hanayome\tools\ocr_tools
python ocr_text_recognition.py "C:\Users\User\Desktop\Tsumugi no Hanayome\MTL\ocr\first_line_snip.png" -o

That created "Tsumugi no Hanayome\MTL\ocr\first_line_snippet.png.ocr.txt" with the following text 人は、罪を犯しながら生きている。 Searching for it in the extracted.json leads to a single hit here.

Code:
    "phonechat_showing": 0
  },null,1,3558]],
"nexts": [{
  "storage": "紬の花嫁02 吐瀉丸(ev213放尿あり).txt",
  "target": "*start",
  "type": 0
}],
"spCount": 671,
"texts": [[null,[[null,"人は、罪を犯しながら生きている。",16]],null,208,{
    "data": [["bgm","bgm",{
          "name": "bgm",
          "replay": {
            "filename": "bgm14",
            "loop": 1,
            "start": null,
            "state": 1,
            "volume": 100.0
          },
          "update": {
            "state": 1
          }
        }],["se","se",{
          "name": "se"

We will have to deal with that mess eventually, but for now, let's just ignore absolutely everything else and only translate that one string. Once changed, here is what the line becomes.

Code:
"texts": [[null,[[null,"People live their lives while committing sins.",16]],null,208,{

Let's save the modified .json to "Tsumugi no Hanayome/MTL/dialogue_scn_json_translated/紬の花嫁01 プロローグ.txt.json". Never overwrite the original. Now, how do we pack it up so the game can read that string? We've done

data.xp3 -> file.txt.scn -> file.txt.json

To pack it up, we need to do

file.txt.json -> file.txt.scn -> patch.xp3

For the file.txt.json -> file.txt.scn step, we can use FreeMote again. Tsumugi no Hanayome\tools\FreeMoteToolkit has a couple different executables. EmtConvert.exe, EmtMake.exe, FreeMoteViewer.exe, PsbDecompile.exe, PsBuild.exe. EmtConvert.exe and PsBuild.exe seem promising but let's read the wiki instead.

- https://github.com/UlyssesWu/FreeMote/wiki/CommandLine-Usage-for-Tools
- https://github.com/UlyssesWu/FreeMote/wiki/PsBuild
- https://github.com/UlyssesWu/FreeMote/wiki/EmtMake

While organizing the extracted files earlier, there was this interesting obviously an image file called hending_brandlogo.pimg that was not properly categorized when moving all the .tlg and .png files into the Tsumugi no Hanayome/extracts/Tsumugi_extracts/images/ subfolder. However, it does not open up in any image viewing program. Why not?

Curious, I opened it in notepad++, and it does have a PSB header. So, it is some sort of renamed .psb file? Well, no image viewing program would work then since it needs to be rendered in a special way as a .psb file. Then, while looking through the CommandLine-Usage-for-Tools wiki entry just now, it said this.

PsbDecompile

Convert PSB to description jsons and resources.

PsbDecompile sample.psb
image

Get combined image from pimg files.

PsbDecompile image sample.psb

How interesting. Up until now, there were only dialogue "Scn" type psb files to play around with, but maybe the above syntax would work?

Code:
C:\Users\User>"C:\Users\User\Desktop\Tsumugi no Hanayome\tools\FreeMoteToolkit\P
sbDecompile.exe" image "C:\Users\User\Desktop\Tsumugi no Hanayome\extracts\Tsumu
gi_extracts\hending_brandlogo.pimg"
FreeMote PSB Decompiler
by Ulysses, [email protected]
18 Plugins Loaded.
 
Done.

That created a file called extracts/Tsumugi_extracts/hending_brandlogo.resx.json, meaning the tool has the same behavior as when it works with the dialogue "Scn" type psb files.

Code:
{
  "PsbVersion": 3,
  "PsbType": "Pimg",
  "Platform": "none",
  "CryptKey": null,
  "ExternalTextures": false,
  "Context": {},
  "Resources": {
    "154": "hending_brandlogo/154.png",
    "155": "hending_brandlogo/155.png",
    "157": "hending_brandlogo/157.png",
    "158": "hending_brandlogo/158.png"
  }
}

So the PsbType is "Pimg". How interesting. That explains the otherwise seemingly odd choice of extension, .pimg, for the psb file. It also created a folder called "hending_brandlogo". Inside that folder it had a few recognizable images.

When Tsumugi no Hanayome/Tsumugi.exe launches, the first thing that gets displayed is Hending's logo which has a cute animation. These files are the likely the ones that get displayed and animated by logic found elsewhere. Since this is a logo, it is better not to mess with it, but now we know how to mess with .pimg files in general which may or may not be helpful in the future.

So EmtConvert is for encryption and decryption. That is not what we want. PsBuild says it "Convert description jsons and resources to PSB." Does it work for .json files that are not descriptions and do not have external resources? Let's find out!

Code:
C:\Users\User>"C:\Users\User\Desktop\Tsumugi no Hanayome\tools\FreeMoteToolkit\P
sBuild.exe" --help
FreeMote PSB Compiler
by Ulysses, [email protected]
18 Plugins Loaded.
 
Usage:  [command] [options] <Files>
 
Arguments:
  Files                     File paths
 
Options:
  -?|-h|--help              Show help information
  -v|--ver <VER>            Set PSB version [2,4]. Default=3
  -k|--key <KEY>            Set PSB key (uint, dec)
  -p|--spec <SPEC>          Set PSB platform (krkr/common/win/ems)
                            Allowed values are: none, common, krkr, win, ems,
                            psp, vita, ps3, ps4, nx, citra, and, x360, other
  -nr|--no-rename           Prevent output file renaming, may overwrite your
                            original PSB files
  -ns|--no-shell            Prevent shell packing (compression)
  -double|--json-double     (Json) Use double numbers only (no float)
  -e|--encoding <ENCODING>  Set encoding (e.g. SHIFT-JIS). Default=UTF-8
 
Commands:
  info-psb                  Pack files to info.psb.m & body.bin
                            (FreeMote.Plugins required).
  link                      Link textures into an external texture PSB
  port                      Re-compile a PSB to another platform
  replace                   In-place Replace the images in PSB
 
Run ' [command] -?|-h|--help' for more information about a command.
 
Plugins:
  FreeMote.Lz4 by Ulysses : LZ4 support.
  FreeMote.Mdf by Ulysses : MDF (ZLIB) support.
  FreeMote.Mxb by Ulysses : MXB (XMemCompress) support.
  FreeMote.Mzs by Ulysses : MZS (ZStandard) support.
  FreeMote.Psd by Ulysses : PSD export.
  FreeMote.Psp by Ulysses & morkt : PSP (LZSS) unpack support.
  FreeMote.Psz by Ulysses : PSZ (ZLIB) support.
  FreeMote.Mfl by Ulysses : MFL (FastLZ) support.
  FreeMote.Astc by Ulysses : ASTC support.
  FreeMote.Bc7 by Ulysses : BC7 support via BCnEncoder.NET.
  FreeMote.Tlg by Ulysses : TLG support via TlgLib.
  FreeMote.Wav by Ulysses : Wav/P16 support.
  FreeMote.Audio.File by Ulysses : Export/Import audio file.
  FreeMote.At9 by Ulysses : At9 support via VGAudio.
  FreeMote.NxAdpcm by Ulysses : NX ADPCM support via VGAudio.
  FreeMote.NxOpus by Ulysses : NX Opus support via VGAudio.
  FreeMote.Vag by Ulysses : VAG support.
  FreeMote.Xwma by Ulysses : XWMA support.
 
Examples:
  PsBuild -v 4 -k 123456789 -p krkr sample.psb.json
 
Done.

Which means the syntax is essentially this.

Code:
PsBuild.exe --ver 3 --encoding utf-8 file.psb

Version 3 and utf-8 are default values, but it is always better to be explicit about it since that makes it clearer what is happening, especially in case there is a bug later or a software update changes the defaults or interface.

Code:
C:\Users\User>"C:\Users\User\Desktop\Tsumugi no Hanayome\tools\FreeMoteToolkit\P
sBuild.exe" --ver 3 --encoding utf-8 "C:\Users\User\Desktop\Tsumugi no Hanayome\
MTL\dialogue_scn_json_translated\紬の花嫁01 プロローグ.txt.json"
FreeMote PSB Compiler
by Ulysses, [email protected]
18 Plugins Loaded.
 
Compiling 紬の花嫁01 プロローグ.txt ...
Compile 紬の花嫁01 プロローグ.txt done.
Done.

It says it compiled something, but there is nothing at Tsumugi no Hanayome/MTL/dialogue_scn_json_translated/ besides the source .json file. However, C:\Users\User has a new file now called "紬の花嫁01 プロローグ.txt.psb". PsbDecompile.exe outputs relative to the target directory, but PsBuild.exe outputs to the current location of the command prompt.

Despite being part of the same toolkit, these tools use inconsistent logic. Consistency would be nice, but the only thing that really matters is whether it works or not. As long as it does, we can workaround the inconsistencies as long as we are aware of them.
 
Last edited:
Part C) Translating the first line of dialogue 2

The next step is to make the game read this file. Since the game is expecting a file named "紬の花嫁01 プロローグ.txt.scn", let's rename the file by swapping the .psb extension to .scn.

Older kirikiri games, usually kirikiri2, do not necessarily need any special handling for the created .xp3 archives. However, newer kirikiriZ games tend to require assistance to read from unencrypted .xp3 archives, although this of course depends on the developer.

First, let's try using Garbro without any special handling, then arcusmaximus's KiriKiriTools since that worked before, then other programs.

garbro.create_xp3.png


What should the archive.xp3 created with Garbro be named? patch.xp3, patch2.xp3 and so forth are the most common names for patch archives in kirikiri since that is the default naming scheme the engine uses, but developers are able to customize it to whatever they want.

Having patch.xp3 in the game's directory does not seem to change anything. The first line is not translated, and the game still runs normally. If the game's displayed content was altered in some way or if it gave an error, then we would have a sign that it was attempting to read the archive's contents, but we are not running into that sort of issue. Since the game's behavior does not change at all, it seems the file is being completely ignored.

Using KiriKiriTools v1.7, Xp3Pack.exe to create the .xp3 from a folder and version.dll placed in the game's main folder, results in the same behavior. The archive is simply ignored, so the translated "紬の花嫁01 プロローグ.txt.scn" file never loads. Why not?

If patch.xp3 does not work, even with KiriKiriTools, then what names for archives is Tsumugi expecting? If "patch.xp3" does not work, is the game expecting the patch to be called something else? That information is found in the Initialize.tjs file extracted earlier.

Let's open extracts/Tsugumi_extracts/system/initialize.tjs in notepad++.

notepadplusplus.initialize.png


wxhexeditor.initialize.tjs.png


That is not normal tjs2 script. It seems to have been altered in some way. Let's refer back to the kirikiri engine documentation.

バイトコード化
TJS2スクリプトがそのままの状態で格納されていると解析が容易なので、スクリプトをバイトコード化する。
バイトコード化はScripts.compileStorageによって可能。
アーカイブが暗号化されていても、そこを突破されることはあるので、スクリプトの解析を困難にしておく意味はある。
また、バイトコードの方がスクリプトよりも高速に読み込める(スクリプトのコンパイルが事前に行われているので)。

Byte-code
Since it is easy to analyze the TJS2 script when it is stored as it is, the script is bytecoded.
Bytecoding is possible with Scripts.compileStorage.
Even if the archive is encrypted, it can still be breached, so it makes sense to make it difficult to parse the script.
Also, bytecode can be loaded faster than scripts (since the scripts are pre-compiled).

Tjs2 scripts are scripts meaning they need to be fed into an interpreter before being compiled into native machine code. Once in machine code, the code can be executed by a processor just like other compiled code.

Here is some more documentation on TJS2.

TJS2 Documentation: https://a-rabin.github.io/krkr2doc-en/tjs2doc/contents/index.html
Documentation for bytecode/disassembled TJS2 VM: https://gist.github.com/uyjulian/1433ac019acaa6f3582a44efaadfb5bf

The Initialize.tjs script for Tsumugi is in .tjs2 bytecode. Some implementations of languages use bytecode as an intermediary between the "human readable" source code and machine code before eventually turning that bytecode into machine code at runtime. Think of it like having to be compiled twice.

step 1. script.tjs + Scripts.compileStorage => tjs2 bytecode
step 2. tjs2 bytecode + .tjs interpreter at runtime => machine code

CPython also does this transparently (with .pyc files) to speed up interpretation during runtime for subsequent executions of the same code. This means that creating bytecode is a normal part of running interpreted scripts for optimized implementations of certain programming languages.

For translation, the important part is that tjs2 in bytecode format cannot get rid of the original strings that we need to translate because it still needs the original strings in order to display them eventually at runtime, like the game's title and other game engine strings. All of that together makes it very unlikely there is any encryption going on with the tjs2 scripts files.

Therefore, it should still be possible to extract and translate them by deciphering the tjs2 bytecode intermediary language. We just need to create or find a program that can work with tjs2 bytecode.

Since we would like to understand what archive name initialize.tjs is expecting, the most direct way to find that out is with a tjs2 disassembler or decompiler that can convert the tjs2 bytecode into something more readable, or at least searchable with notepad++. The only one I am aware of is Furikiri described as "Managed (& the world's first) TJS2 Decompiler 'FreeKiri' (In Dev)".

A disassembler takes the assembled code or bytecode and adds semantics to help humans understand what the code literally does. Disassembled code is theoretically alterable and convertable back into assembler or bytecode using a special program that understands the semantics added by the disassembler.

A decompiler takes the assembled code or bytecode and tries to recreate the original source code. In the case of tjs2 bytecode, that means recreating the .tjs scripts before they were turned into bytecode. Decompiled code is theoretically alterable and should be able to be compiled again using a normal compiler for that language without any knowledge of special semantics.

In the case of UlyssesWu's Furikiri, in addition to the readme.md, here is this comment made by the developer to clarify its use.

> I've found out how to decompile and compile.
> First, Furikiri seems to be the only decompiler that exists for tjs, everything else is a dissembler.[...]
>The main thing to know is that you can't use a compiler successfully on a dissembled tjs, only a decompiled one.
Basically you're correct, there is no "TJS2 decompiler"(*) other than my Furikiri. But Furikiri is not finished (the TJS2 language is a bloated, badly-implemented language after all), and currently the decompilation result can only be used as a reference of TJS2 bytecode's behaviour - that's already useful in my cases. You cannot expect the decompilation result can be directly compiled again, it's impossible for now.

However, it might be more practical to implement a tool to compile the disassmbled assembly. Furikiri has a plan (issue) to do that, but I currently just focused on decompilation.

Furikiri also provides the fastest TJS2 disassemble feature - definitely faster and more complete than the official one. That's the main feature of girigiri for now.

(*): Xmoe said on weibo (at 2019-5-25) that he will release a tjs decompiler after a while. Maybe he can make a better tool than my shit

Here is the referenced xmoe's tjs2Decompiler. Yeah... that never happened. xmoe did release a tjs2Compiler, but that is not really relevant for our purposes.

The main problem with using furikiri is that it is not compiled.

.sln files are Microsoft Visual Studio project files. Compiling them requires Microsoft Visual Studio, the Windows development SDK, the required build tools which is 5-20 GB minimum + a supported version of Windows for that Visual Studio version + the source code + the dependencies, and all of that must be cross compatibile with exactly the correct versions of one another before anything can work. It is entirely unreasonable for anyone except the original developer to know how to compile their software since only they know what versions of what and on what platforms and architectures they used to make their software work.

Compiling software is hell on earth, and I will not be covering how to do it here. Attached to this post is a compiled version of furikiri obtained via black magic and vs 2022. It is also available on Mediafire. Source code.

Code:
Name: furikiri.cli_girigiri.cli_net8.0.7z
Size: 211793 bytes (206 KiB)
CRC32: 871CB2A6
CRC64: 7E891DCB3EA1FB5B
SHA256: 5731ca13d3bcdc53b41ab6d1de53503e39ef9b43e0ada1b9841525cd68b1d4f9
SHA1: 674d05c4c3462d40c35246163946acd68c5532af
BLAKE2sp: b80015b286adb77f28f9280d5b218b44be0317baf03e999a63be5bdc73474629
XXH64: 2085D36F55282F16

Name: furikiri.sourcecode.2024-10-05.7z
Size: 3913279 bytes (3821 KiB)
CRC32: EE26ACC1
CRC64: 8B19665C63B56BF5
SHA256: d671507377d377c59f91488d0893f65a375fefb33cf84f391c0b86753b1fe8ff
SHA1: 0a5d89e6f49b41f6ffe45f09641689272650ec39
BLAKE2sp: 87a4c2a0cc6bd42f89b329c285b667837210e95ab065dac03989fec43e3d1d52
XXH64: C9A26EFC24CBA167

It requires the .net cli runtime 8.0. If it does not work on your machine or you encounter issues, bother the original developer about how to compile their software by creating a new issue in their repository.

So anyway, let's install the furikiri dependencies, whatever they may be since they are not actually officially documented anywhere as documentation, and extract the tool to Tsumugi no Hanayome/tools/furikiri.cli_girigiri.cli_net8.0/.

Code:
C:\Users\User>"C:\Users\User\Desktop\Tsumugi no Hanayome\tools\furikiri.cli_giri
giri.cli_net8.0\girigiri.exe" --help
Furikiri TJS2 Disassembler/Decompiler
by Ulysses, [email protected]

Usage:  [options] <Files>

Arguments:
  Files              File paths

Options:
  -?|-h|--help       Show help information
  -da|--disassemble  Disassemble byte code
  -d|--dec           Decompile byte code
  -p|--print         Print result
Examples:
  Girigiri init.tjs

All done!

The following syntaxes should work. The first one is for disassembly, and the second one is for decompilation.

Code:
girigiri.exe --disassemble script.tjs
girigiri.exe --dec script.tjs

Since we do not know how these tools will behave (besides poorly due to being written in c#) and do not want to risk altering anything in the Tsumugi no Hanayome/extracts/ folder, lets create a new "tjs" subfolder as tools/furikiri/tjs and copy all .tjs files there.

Code:
cd Desktop\Tsumugi no Hanayome\tools\furikiri.cli_girigiri.cli_net8.0
girigiri.exe tjs\initialize.tjs
girigiri.exe --dec tjs\initialize.tjs

Every invocation of the tool seems to need user input as in Enter or ctrl+c meaning that this tool is not at all scripting friendly which contradicts one of the main advantages of having a cli. After some further testing, the "All done!" at the end does not take into account whether or not the tool crashed during execution. I stand by my poorly written comment earlier. Moving on.

The first command without any options created tjs/initialize.tjsasm which is a disassembled version of the tjs2 bytecode. This tool assumes --disassemble if no option was specified. Running the same command with --dec produced tjs/initialize.dec.tjs which is a more readable version of the disassembled version.

Opening the files in notepad++ results in some interesting code to look at, but first, while we are here, let's dissassemble and decompile every single .tjs script so we can use them as reference later.

Interestingly, not every .tjs script is in bytecode. A tjs2 script in bytecode will start with the following characters.

Code:
TJS2100

So, let's open all the .tjs files and look for that header. If the .tjs script does not start with that header, then the .tjs script is in plaintext. Let's move all the plaintext .tjs files into a folder called tools\furikiri.cli_girigiri.cli_net8.0\tjs\plaintext. As a notepad++ tip, after opening them all at once, press ctrl+w to quickly close a file and move on to the next one.

For Tsumugi no Hanayome, here is the list of plaintext .tjs files.

Code:
appconfig.tjs
append_setup_1.tjs
config.tjs
custom.tjs
default.tjs
deffontmap.tjs
embfontlist.tjs
envinit.tjs
particle_rain.tjs
particle_sakura.tjs
script_glow.tjs
script_lightedge.tjs
staffroll_type1.tjs
standcomment.tjs
storages.tjs
title_eff_stars.tjs
trans_crossfade.tjs

Now let's batch convert the remaining .tjs files.

Code:
cd C:\Users\User\Desktop\Tsumugi no Hanayome\tools\furikiri.cli_girigiri.cli_net8.0
set tool="C:\Users\User\Desktop\Tsumugi no Hanayome\tools\furikiri.cli_girigiri.cli_net8.0\girigiri.exe"
for /f %i in ('dir /b "tjs\*.tjs') do %tool% --disassemble tjs/%i

After it says "All done!" press Enter, and repeat for all 139 bytecoded tjs files. Yeah... If the above for loop does not work, open the spoiler to read more about Windows code pages for handing character encoding at the command prompt.

Windows command prompts can still work with characters that are not valid in the active command prompt encoding (code page) since Windows dynamically uses ? as a cosmetic placeholder instead of displaying the real value when running programs. This only works as long as there is no enumeration of those file names. That cosmetic substitution allows the real file names to be fed directly into a program as input despite the incorrectly displayed substitute character.

However, the batch scripts listed below process multiple files sequentially and need the real file names in a readable sequence in order to process each one in that sequence. In other words, commands like "dir /b" enumerate the file names into a list for processing which requires the characters output by dir to be valid in the code page the command prompt is using. Any values that are not valid in that code page will have that character replaced by ? as a literal ?, which is not the original file's name. This makes commands that work normally when entered directly into the command prompt no longer work when attempting to automate them in the batch language when using an invalid code page.

The most direct workaround is to use a command prompt encoding that supports the enumeration of the file names. This is possible by using either the Windows code page for utf-8 which is 65001 on newer versions of Windows 10 or newer such as Windows 10 1809+, or, for Japanese media, Windows code page 932, which is the code page for shift-jis. cp932 is available only after changing the Windows locale to Japanese, Japan, and rebooting. Changing the locale is not necessary if the command prompt uses utf-8/65001 encoding already on a new enough version of Windows.

To check the current encoding, type "chcp" at the command prompt and press enter. That will print out the active code page.

Code:
chcp

will print out something like

Code:
Active code page: 437

To change the active code page, type the number of the code page after "chcp" as in "chcp 65001", "chcp 932", or "chcp 437". The command prompt requires an appropriate locale be set for Windows before the command prompt can change to a code page specific to that locale.

While it is possible to rename the files temporarily or to proxy the commands using Python's subprocess library, for simplicity, this guide assumes either that Windows code page 65001 is the active Windws code page in the command prompt or the locale was changed to Japanese, Japan in order to use Windows code page 932.

Code:
mkdir tjs\asm
move tjs\*.tjsasm tjs\asm
for /f %i in ('dir /b "tjs\*.tjs') do %tool% --dec tjs/%i

girigiri.exe.decompile.png

girigiri.exe.decompile2.png


Disassembly should work with every .tjs script in bytecode, but decompiling will crash ...a lot. Just close the program as necessary and continue until it finishes processing every file. Then move any decompiled files into a subfolder.

Code:
mkdir tjs\dec
move tjs\*.dec.tjs tjs\dec

Of the 139 bytecoded tjs files, all 139 could be disassembled, but only 74 could be decompiled. The disassembled and decompiled files cannot be converted back into a format the game can read, but the files can provide hints as to how the game engine works to help us translate it.

Unfortunately the disassembled code is not very readable. It tends to look like this.

Code:
00000150	cp %2, %-6
00000153	spd %-2.*11, %2 // *11 = (string)"_ask"
00000157	gpd %1, %-2.*4 // *4 = (string)"_issave"
00000161	tt %1
00000163	jnf 00000170 // goto const %2, *13
00000165	const %2, *12 // *12 = (string)"上書きしてよろしいですか?"
00000168	jmp 00000173 // goto spd %-2.*14, %2
00000170	const %2, *13 // *13 = (string)"ロードしてよろしいですか?"
00000173	spd %-2.*14, %2 // *14 = (string)"_message"
00000177	gpd %1, %-2.*4 // *4 = (string)"_issave"
00000181	tt %1
00000183	jnf 00000191 // goto gpd %2, %-2.*16
00000185	gpd %2, %-2.*15 // *15 = (string)"onSave"
00000189	jmp 00000195 // goto spd %-2.*17, %2
00000191	gpd %2, %-2.*16 // *16 = (string)"onLoad"
00000195	spd %-2.*17, %2 // *17 = (string)"_action"

The information we are looking for right now is the exact name of the archive.xp3 this game uses to apply patches, if any. That name should be present in Initalize.tjs. Luckily, initalize.tjs did decompile so we do not have to look through the disassembled output. Here is the relevant section in the decompiled initialize.tjs file.

Code:
useArchiveIfExists("patch.xp3");
for (var v3 = 2; Storages.isExistentStorage(v4); v3++)
{
    var v4 = System.arcPath + "patch" + v3 + ".xp3";
    Storages.addAutoPath(v4 + ">");
    continue;
}

var v3 = patch_aftercall.count;
for (var v4 = 0; v4 < v3; v4++)
{
    var v5 = patch_aftercall[v4];
    if (v5 instanceof "Function")
    {
        v5();
    }
    
}

delete patch_aftercall;

Humm. That looks for patch.xp3, then patch2.xp3, then patch3.xp3 and so forth. However, our patch.xp3 is not being read either alone or after adding KiriKiriTools's version.dll with a patch.xp3 created using Xp3Pack.exe.

With this information, we can tentatively conclude that the issue is with the game ignoring the patch.xp3 archive completely despite it having the correct name of "patch.xp3".

Since the KiriKiriTools method of patching the game engine is not working despite the correct name for the archive, let's try another method.

Other methods listed in the Koakuma-chan case study were crskycode's KrkrPatch and bynejake's KrkrPatch and Galpatch. bynejake's two projects share the same code meaning they are logically the same project. Galpatch is just the KrkrPatch repository merged with another repository for another game engine and will be the one developed going forward.

Let's try both crskycode's and bynejake's projects to see how they each work. Here is the KrkrPatch readme.md.

Kirikiri Z File Patch

The universal patch extension for some new Kirikiri game.

How To Use

You need to create KrkrPatch.json with the following format.

Code:
{
  "gameExecutableFile": "your_game.exe",
  "gameCommandLine": "",
  "logLevel": 0,
  "patchProtocols": [
    "arc://",
    "archive://",
    "psb://"
  ],
  "patchArchives": [
    "your_patch.xp3"
  ],
  "patchNoProtocol": false
}

Put KrkrPatchLoader.exe and KrkrPatch.dll and KrkrPatch.json to your game folder.

Use GARbro create your patch.xp3 with No Crypt and keep directory structure.

Finally, Run the KrkrPatchLoader.

Notes

You can rename KrkrPatchLoader or change the icon and version information with some tool like Resource Hacker.

Don't rename KrkrPatch.dll and KrkrPatch.json.

Set patchNoProtocol to true if some files not patched.

So the following three files need to be in the game's folder.

- KrkrPatchLoader.exe
- KrkrPatch.dll
- KrkrPatch.json

Here is the KrkrPatch.json based on the template provided in the readme that was customized for Tsumugi.

Code:
{
  "gameExecutableFile": "Tsumugi.exe",
  "gameCommandLine": "",
  "logLevel": 0,
  "patchProtocols": [
    "arc://",
    "archive://",
    "psb://"
  ],
  "patchArchives": [
    "patch.xp3"
  ],
  "patchNoProtocol": false
}

After downloading and moving KrkrPatch.dll, the loader, the .json, and the garbro created patch.xp3 to the game directory, here is the result.

proof_of_concept.KrkrPatch.png

What did we learn? With KrkrPatch, we might be able to name the patch.xp3 anything we want, but will always need those four files for the patch to work. In order for the user to run the patch, any user would need to run a separate executable to apply the patch which is always a questionable thing to ask a user to do.

That is technically enough for a proof of concept for the dialogue for this game, but let's try GalPatch now anyway. Here is the GalPatch readme.md.

KrkrPatch

Auto load patch for krkr game.

Usage

- Rename krkr-version.dll to version.dll then put into game root dir.
- Put patch files into unencrypted folder without directory structure.
-- (Optional) Pack unencrypted folder to unencrypted.xp3 archive, use GARbro or Xp3Pack
- Put unencrypted | unencrypted.xp3 | both(priority : folder > archive) into game root dir.
- Supports multiple patches : unencrypted, unencrypted2~9 (larger seq, higher priority).
-- Picontinuous sequences are not necessary, sample : unencrypted3.xp3, unencrypted7.

WafflePatch

Patch for waffle game to prevent crashes.

Usage
- Rename waffle-version.dll to version.dll then put into game root dir.

Merged

- KrkrPatch
- WafflePatch

In other words, we need krkr-version.dll and patch.xp3 renamed to unencrypted.xp3. There is also a claim that loading files from folders works. Unlike KrkrPatch, Galgame hardcodes the name of the folder and patch to load custom assets to "unencrypted" and "unencrypted.xp3", but it does not require additional configuration .json files or a custom executable.

Does it actually work though? Here is what happens when copying krkr-version.dll to the game directory as version.dll and renaming the garbro created .xp3 to unencrypted.xp3

proof_of_concept.GalPatch.png

It seems to work.

When removing unencrypted.xp3 and using a folder named "unencrypted", the line is still translated. GalPatch adds support for both unencrypted patch.xp3 archives and for reading assets directly from folders. Every KirikiriZ game needs these features and GalPatch does it by only adding one file, version.dll.

The biggest downside is that the instructions explicitly state that Galpatch patch files must be "without [a] directory structure", but that is a pretty minor downside all things considered.

This case study and my actual patch will use GalPatch for this game, but feel free to use KrkrPatch instead.
 

Attachments

  • furikiri.cli_girigiri.cli_net8.0.7z
    206.8 KB · Views: 0
  • furikiri.sourcecode.2024-10-05.7z
    3.7 MB · Views: 0
Last edited:
Part D) Translating images

Next, we need to translate

- images
- the title
- game engine strings

And then automate all of it as much as possible.

There is not much new to say about translating images since the workflow for now is exactly the same as Koakuma-chan's.

- Organize the files under Tsugumi no Hanayome/extracts/Tsugumi_extracts using cli wildcards like *.tlg
- Use Garbro to find all files that need translation
- Use Garbro to batch convert all of the .tlg files that need conversion. The FreeMote Toolkit 4.0+ can also do this with EmtConvert, and so can Garbro.
- Copy the .tlg -> .png converted images to Tsugumi no Hanayome/MTL/images/
- Edit each of them in Krita or another image editing program
- Convert the translated .png images back to .tlg using the image format converter tool in the Kirikiri2 SDK kr2_s3r2.zip/kirikiri2/tools/krkrtpc.exe
- Copy the .tlg images or set the output of krkrtpc.exe to Tsumugi no Hanayome/bin/Tsumugi no Hanayome/unencrypted/

Remember to extract files from the Kirikiri2 SDK archive by using 7z.exe and the -mcp=shift-jis flag to avoid corrupt filenames. This is necessary for .zip archives created using Windows Explorer because Explorer always corrupts non-ascii filenames when creating .zip files at least up to and including Windows 10 22H2 and likely newer.

Code:
7z.exe x -mcp=shift-jis kr2_232r2.zip

Here is a translation for the image format converter tool.

krkrtpc-exe-translate-jpg.54868


And here is proof of concept for images. As usual, I am using Krita to edit the images, but other image editing programs should also work, probably.

krita.warning.png

proof_of_concept.images.png

When translating copyright notices and content warnings, always use Yandex OCR. Compared to other more accurate translators, Yandex consistently produces the most hilariously appropriate translations for such content.

For more detailed instructions for translating images in kirikiriZ games, see the Koakuma-chan case study.
 
Last edited:
  • Like
Reactions: Eir
Part E) Translating the title and game engine strings

Next is the title.

Given all of the bytecoded tjs2 files, this should have been a long difficult process. But actually, before trying to figure out how to alter the bytecoded tjs2 files, let's look through the tjs\plaintext files first. The title is actually found in Tsumugi no Hanayome\tools\furikiri.cli_girigiri.cli_net8.0\tjs\plaintext\appconfig.tjs. In other words, it is one of the very few plaintext .tjs files. The relevant line in AppConfig.tjs is this one.

Code:
global.ENV_GameName  = "紬の花嫁";

Just copy the file to Tsumugi no Hanayome/bin/Tsumugi no Hanayome/unencrypted/AppConfig.tjs and update it.

Code:
global.ENV_GameName  = "Tsumugi no Hanayome";

AppConfig.tjs.title.png


Done with updating the title. That was surprisingly simple!

While we are here, at Tsumugi no Hanayome\tools\furikiri.cli_girigiri.cli_net8.0\tjs\plaintext, Storages.tjs in the same folder has some very interesting information about the subfolders in the .xp3 to look at. Information about how the game organizes its files are striped from the metadata in the .xp3 files, but that information is also present in the plaintext Storages.tjs script. That makes Storages.tjs a good reference for restoring that structure later.

Next, we need to translate the game engine strings. Tsumugi no Hanayome\tools\furikiri.cli_girigiri.cli_net8.0\tjs\plaintext\default.tjs, a plaintext .tjs script, has a significant number of translatable game engine strings. Those are mostly the ones for the shortcut buttons when dialogue is playing.

After that, we finally have to deal with the bytecoded .tjs scripts head on.

TJS2 Documentation: https://a-rabin.github.io/krkr2doc-en/tjs2doc/contents/index.html
Documentation for bytecode/disassembled TJS2 VM: https://gist.github.com/uyjulian/1433ac019acaa6f3582a44efaadfb5bf

One way to alter bytecoded .tjs scripts is marcussacana's KrKrZSceneManager, the associated string extraction project called SacanaWrapper, and the related "translation platform".

Freemote understands e-mote files, but not bytecode TJS2 files. The SacanaWrapper program with all of the plugins can do both. This approach is not recommended for dialogue because

1. The extracted strings loose all context which makes guessing if they can be translated or not outright impossible from just the output. That makes it extremely easy to make a mistake and cause the game to crash.
2. It is not possible to recreate any intermediary structures appropriately in the format the game expects.

For Tsumugi, the translatable lines in the emote .txt.scn files look like this.

Code:
[[null,[[null,"人は、罪を犯しながら生きている。",16]],null,208,{...}],

That looks really cryptic, but basically that means this.

Code:
[[character_name_a,[[character_name_b,"dialogue",length_of_dialogue]],null,208,{...}],

The length_of_dialogue is an integer, a number, that displays the length of the string that makes up the dialogue. From my testing, it is not necessarily clear what this being wrong actually affects for Tsumugi, but giving the game wrong information only opens the door to potential crashes.

Does SacanaWrapper correctly recreate this information? Who knows, but almost certainly not. With manual parsing using the FreeMote toolkit, it is possible to toggle this on and off during parsing to see if it matters since the FreeMote toolkit gives much greater control over the data.

Another example is that the game also supports a custom name for one of the characters and has specific rules for counting the length of strings that include that raw custom name. Parsing the .json manually allows for applying these special rules for calculating the length correctly.

There are also some phonechat entries from the characters texting each other and many duplicate entries for those texts that do not need to be translated. Parsing the .json makes it clearer what is happening in both cases.

For the reasons above, I would only recommend SacanaWrapper and other blind/regex based string extraction tools if all other attempts at extracting the contents of the files has failed.

The tool itself is poorly documented without any usage instructions or way to get it working offline. Using anything written in c# online is questionable because Microsoft embeds spyware in .Net.

- https://github.com/dotnet/sdk/tree/main/src/Cli/dotnet/Telemetry
- https://learn.microsoft.com/en-us/dotnet/core/tools/telemetry
- https://learn.microsoft.com/en-us/dotnet/core/diagnostics/observability-with-otel
- https://devblogs.microsoft.com/dotnet/what-weve-learned-from-net-core-sdk-telemetry/

Also,
- Compiling anything takes like 5 GB of space + Visual Studio on MS Windows because all practical use of c# is tied to .Net and requires the Windows Development SDK.
- c#, although there is some automatic garbage collection to deal with more common errors, is not entirely a memory safe language because it supports manipulating memory and pointers directly.

But it only affects code that is flagged as unsafe you say? Guess what is in StringTool.exe.config.

Code:
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>

So you are not getting any of the memory safety, but you do get all of the object oriented requirements, all of the compiling baggage, and all of the Microsoft garbage including having to use windows. Adding all the downsides to using anything written by Microsoft together with the downsides for compiled languages, I just do not get development on c# at all.

And the cross platform promise of .net is mostly a lie too since developers tend to not publish either binaries or instructions to compile their software for other platforms. And even if they did, they would still require visual studio on windows, right? sigh Moving on.

So unlike the c# developer for girigiri, Ulysses, the c# developer, marcussacana, was kind enough to provide a binary of their stuff so we do not have to compile it from source, but also not kind enough to include usage instructions, the necessary dependencies, or any plugins, all of which are required to actually use the software. Sigh. The signs, moans, and groans just never stop with c#. Anyway.

Not quite official usage instructions for using SacanaWrapper.

1 - Download the Plugin Manager and SacanaWrapper from the github "Releases" page
2 - Extract both to the same directory
3 - Open the PMan.exe (Plugin Manager)
4 - Search for NUT, and install the NUTEditor plugin [and all other plugins]
5 - Drag&Drop your NUT [and other] files to the stringtool.exe [to extract the strings]

If you want to skip all of that, here is SacanaWrapper_2.1.5_with_plugins.7z. It is also available as an attachment to this post. Just extract and use it. For more help, read the readme.txt.

Code:
Name:     SacanaWrapper_2.1.5_with_plugins.7z
Size:     8413407 bytes (8.02 MiB)
CRC32:    28FB26AB
CRC64:    807872665305C687
MD5:      192cabadbfec2861ba8269e33fe3158f
SHA1:     cd7af8123de94394cd7805cf750cb886927af58c
SHA256:   eeed473735281c27b247c06d385105687846f09ae94cc43719bd9c891590c878
SHA512:   4d7d22ddb6f52448eaa8255617939588d1e39e9a62097975b1370c0aa34ffc9373cb000d02243e7b1c371f54c3d39a1728268d67bd49ee98493fd60024874e3c
BLAKE2s:  e36f6cd4e443ef197dc12df12f6ae7bce51368afeb938718a9c8f0252ecf5ffe
XXH64:    61AF72D32A103975

Extract it to Tsumugi no Hanayome/tools/SacanaWrapper_2.1.5/. Then install the .net 8.0 requirement. Click on "Run console apps - Download x64" which downloads "dotnet-runtime-8.0.8-win-x64.exe" or a newer minor version. Then install it and run StringTool.exe --help.

Code:
StringTool.exe --help

Usage:
StringTool -Dump {InputScript} [OutputText]
StringTool -Insert {InputScript} [InputText] [OutputScript]
StringTool -Wordwrap {InputScript} [InputText] [OutputScript]
0 Tasks to be Executed.

Now let's batch extract all of the translatable strings in the bytecoded .tjs files. Open a command prompt and enter the following.

Code:
set tool=C:\Users\User\Desktop\Tsumugi no Hanayome\tools\SacanaWrapper_2.1.5\StringTool.exe
cd "C:\Users\User\Desktop\Tsumugi no Hanayome\tools\furikiri.cli_girigiri.cli_net8.0\tjs"
for /f %i in ('dir /b *.tjs') do "%tool%" -Dump "%i" "%i.txt"
mkdir txt
move *.txt txt

It would be nice to be able to revert back any changes we make in case something breaks which it inevitably will when translating game engine strings. Create a backup of Tsumugi no Hanayome\tools\furikiri.cli_girigiri.cli_net8.0\tjs\txt. One way is to create an archive txt.7z and rename it to _txt.7z.backup.7z.

Now, some super happy fun time with the Windows Snipping Tool and ocr.py begins.

ocr.py should already be set up from the Koakuma-chan case study. If not, refer to the case study instructions to get it working. codeberg/translation_tools/ocr.py has also been updated to output properly formatted .csv to make it more convenient when using it together with romaji.py and sugoi_mtl.py. It also also been moved to codeberg/ocr_tools and renamed to ocr_text_recognition.py, so check the updated repository for it.

Snip something easy to find. In my case, I did the top left menu.

mainmenu.string1.png


Then run ocr.py.

Code:
cd Desktop\Tsumugi no Hanayome\tools
python translation_tools\ocr.py ..\MTL\ocr\main_menu\mainmenu.string1.png -o

or for ocr_text_recognition.py

Code:
cd Desktop\Tsumugi no Hanayome\tools
python ocr_tools/ocr_text_recognition.py ..\MTL\ocr\main_menu\mainmenu.string1.png -o

That outputs mainmenu.string1.png.ocr.txt which has ファイル inside. To output a .csv, run ocr.py in batch mode, instead of a specific .png file, input a folder.

Code:
python translation_tools\ocr.py ..\MTL\ocr\main_menu
#or
python ocr_tools/ocr_text_recognition.py ..\MTL\ocr\main_menu

It is somewhat pointless doing it for a single file, but if done, here is the output.ocr.csv

Code:
ocr_text,file
ファイル,..\MTL\ocr\main_menu\mainmenu.string1.png

Humm. Maybe I should resolve the relative file paths to absolute ones to get rid of the .. in the next version of ocr.py. Or maybe removing the entire path and keeping only the filename would be better? Anyway...

Next, open Tsumugi no Hanayome\tools\*.txt Notepad++ and search for the result from ocr.py, ファイル. Ctrl+f, enter the string, and "Find All in All Opened Documents".

notepadplusplus.search.system.png

Finding the "correct" instance of the string is really annoying. Unfortunately, there is not really a way to get this right the first time. For ファイル in the main menu, the one that worked to translate the main menu option was the instance in Override.tjs. Override.tjs actually happens to have a lot of the translatable strings.

To translate it, change the string to the translated version.

Code:
開始メニューの表示(&S)
onBootStrapKicked(
systemMenu
ファイル(&F)
セーブ(&S)
SystemAction._save()
menu.save
ロード(&L)
SystemAction._load()

Change that to

Code:
開始メニューの表示(&S)
onBootStrapKicked(
systemMenu
File(&F)
セーブ(&S)
SystemAction._save()
menu.save
ロード(&L)
SystemAction._load()

Then update the translation.

Code:
set tool=C:\Users\User\Desktop\Tsumugi no Hanayome\tools\SacanaWrapper_2.1.5\StringTool.exe
cd "C:\Users\User\Desktop\Tsumugi no Hanayome\tools\furikiri.cli_girigiri.cli_net8.0\tjs"
"%tool%" -Insert override.tjs txt\override.tjs.txt override.tjs.translated.tjs

Move the .translated.tjs file to Tsumugi no Hanayome/bin/Tsumugi no Hanayome/unencrypted/ and rename it to remove the extra .translated.tjs extension. Here are some changes to Override.tjs after copying that script to the game's patch directory with the updated translations.

StringTool.proofofconcept.png


Then repeat the process for every string. Unfortunately, there is not a lot of automation possible here to determine which strings are translatable since every change needs to be manually checked to see if it makes the game crash. Even if the game does not crash right away, if it is not the correct string obtained from ocr.py, then the change needs to be reverted (ctrl+z in notepad++) in order to not risk a crash, and the process repeated.

There are some small shortcuts though. While StringTool strips most context, there is some context from the name of the file and which strings are adjacent to one another. Take this example in Override.tjs.txt.

Code:
menu.vreplay
characterMenu
文字表示(&C)
chSpeedMenu
表示速度(&C)
chSlowMenuItem
遅い(&S)
speed
chSpeeds
slow
chNormalMenuItem
普通(&N)
normal
chFastMenuItem
速い(&F)
fast

There is a pattern to the above strings in Override.tjs.txt since they seem to be next to ch-something-MenuItem and the English word for a specific speed surrounds them. That might be code for altering the speed of the displayed text. If one of those strings is translatable, then all of them that fit the same pattern will probably also be translatable.

By the same token, some may seem like there are translatable, but are not. If one is not, then the rest following the pattern or that are very close to it are probably also not translatable.

Note that even the disassembled tjs2 code provides more context than the above difficult to work with StringTool output. "caption" and "setTextSpeed" both appear next to the translatable strings in the assembler code but not in the StringTool output. Even if assembler is harder to read than decompiled code, it can be worth cross checking certain strings from StringTool against it to provide more context.

Unfortunately, I am not aware of any way to reassemble the diassembled.tjs files, so their use is currently limited to providing additional context for StringTool.exe.

Code:
00002921	global %4
00002923	gpd %5, %4.*286 // *286 = (string)"Dictionary"
00002927	new %4, %5()
00002931	const %5, *287 // *287 = (string)"name"
00002934	const %6, *288 // *288 = (string)"characterMenu"
00002937	spis %4.%5, %6
00002941	const %5, *289 // *289 = (string)"caption"
00002944	const %6, *290 // *290 = (string)"文字表示(&C)"
00002947	spis %4.%5, %6
00002951	const %5, *291 // *291 = (string)"children"
00002954	global %6
00002956	gpd %7, %6.*292 // *292 = (string)"Array"
00002960	new %6, %7()
00002964	const %7, *285 // *285 = (byte)0
00002967	global %8
00002969	gpd %9, %8.*286 // *286 = (string)"Dictionary"
00002973	new %8, %9()
00002977	const %9, *287 // *287 = (string)"name"
00002980	const %10, *293 // *293 = (string)"chSpeedMenu"
00002983	spis %8.%9, %10
00002987	const %9, *289 // *289 = (string)"caption"
00002990	const %10, *294 // *294 = (string)"表示速度(&C)"
00002993	spis %8.%9, %10
00002997	const %9, *291 // *291 = (string)"children"
00003000	global %10
00003002	gpd %11, %10.*292 // *292 = (string)"Array"
00003006	new %10, %11()
00003010	const %11, *285 // *285 = (byte)0
00003013	global %12
00003015	gpd %13, %12.*286 // *286 = (string)"Dictionary"
00003019	new %12, %13()
00003023	const %13, *287 // *287 = (string)"name"
00003026	const %14, *295 // *295 = (string)"chSlowMenuItem"
00003029	spis %12.%13, %14
00003033	const %13, *289 // *289 = (string)"caption"
00003036	const %14, *296 // *296 = (string)"遅い(&S)"
00003039	spis %12.%13, %14
00003043	const %13, *297 // *297 = (string)"group"
00003046	const %14, *298 // *298 = (byte)1
00003049	spis %12.%13, %14
00003053	const %13, *299 // *299 = (string)"exp"
00003056	gpd %14, %-2.*300 // *300 = (string)"setTextSpeed"
00003060	spis %12.%13, %14
00003064	const %13, *301 // *301 = (string)"prop"
00003067	global %14
00003069	gpd %15, %14.*286 // *286 = (string)"Dictionary"
00003073	new %14, %15()
00003077	const %15, *302 // *302 = (string)"speed"
00003080	gpd %16, %-2.*303 // *303 = (string)"chSpeeds"
00003084	gpd %17, %16.*304 // *304 = (string)"slow"
00003088	spis %14.%15, %17
00003092	spis %12.%13, %14
00003096	spis %10.%11, %12
00003100	inc %11
00003102	global %12
00003104	gpd %13, %12.*286 // *286 = (string)"Dictionary"
00003108	new %12, %13()
00003112	const %13, *287 // *287 = (string)"name"
00003115	const %14, *305 // *305 = (string)"chNormalMenuItem"
00003118	spis %12.%13, %14
00003122	const %13, *289 // *289 = (string)"caption"
00003125	const %14, *306 // *306 = (string)"普通(&N)"
00003128	spis %12.%13, %14
00003132	const %13, *297 // *297 = (string)"group"
00003135	const %14, *298 // *298 = (byte)1
00003138	spis %12.%13, %14
00003142	const %13, *299 // *299 = (string)"exp"
00003145	gpd %14, %-2.*300 // *300 = (string)"setTextSpeed"
00003149	spis %12.%13, %14
00003153	const %13, *301 // *301 = (string)"prop"
00003156	global %14
00003158	gpd %15, %14.*307 // *307 = (string)"Dictionary"
00003162	new %14, %15()
00003166	const %15, *302 // *302 = (string)"speed"
00003169	gpd %16, %-2.*303 // *303 = (string)"chSpeeds"
00003173	gpd %17, %16.*308 // *308 = (string)"normal"
00003177	spis %14.%15, %17
00003181	spis %12.%13, %14
00003185	spis %10.%11, %12
00003189	inc %11
00003191	global %12
00003193	gpd %13, %12.*307 // *307 = (string)"Dictionary"
00003197	new %12, %13()
00003201	const %13, *309 // *309 = (string)"name"
00003204	const %14, *310 // *310 = (string)"chFastMenuItem"
00003207	spis %12.%13, %14
00003211	const %13, *311 // *311 = (string)"caption"
00003214	const %14, *312 // *312 = (string)"速い(&F)"
00003217	spis %12.%13, %14
00003221	const %13, *297 // *297 = (string)"group"
00003224	const %14, *298 // *298 = (byte)1
00003227	spis %12.%13, %14
00003231	const %13, *299 // *299 = (string)"exp"
00003234	gpd %14, %-2.*300 // *300 = (string)"setTextSpeed"
00003238	spis %12.%13, %14
00003242	const %13, *301 // *301 = (string)"prop"
00003245	global %14
00003247	gpd %15, %14.*307 // *307 = (string)"Dictionary"
00003251	new %14, %15()
00003255	const %15, *302 // *302 = (string)"speed"
00003258	gpd %16, %-2.*303 // *303 = (string)"chSpeeds"
00003262	gpd %17, %16.*313 // *313 = (string)"fast"
00003266	spis %14.%15, %17
00003270	spis %12.%13, %14
00003274	spis %10.%11, %12
00003278	inc %11

Of course, context is just a heuristic. Ultimately every translated string needs to be tested for crashes. Here are all of the .tjs scripts that I found with translatable strings. There were still a few that I missed, so this is not comprehensive.

Code:
AppConfig.tjs
default.tjs
keybinder.tjs
movieqsel.tjs
nocfchkdialog.tjs
override.tjs
snapcapture.tjs
speech.tjs
stopDeactive.tjs
uigame.tjs
uimain.tjs

While Override.tjs had quite a few, even with the above scripts included, that was not enough to translate all of the game engine's strings. These game engine strings were not translated.

- The infamous を実行します, screenshot, string and related strings in the menus
- Font selection menu in main menu and font names
- Text to speech menu in main menu
- Gamepad button assignment menu
- Help tips

If anyone knows how to translate the screenshot string or the help tips, please comment on how exactly. I could not find the strings in the bytecoded .tjs scripts, the .ks scripts with macros, or the plaintext .tjs scripts.

The help tips not being found is especially puzzling. I suspect some of the menu ones, like the screenshot string, are in .dll plugins. The fonts ones, text to speech, and gamepad strings are probably in the bytecoded .tjs files, but I did not look very hard for those since I doubted anyone would use those. There is another more obvious font selection menu in the config screen already and TTS is kinda obscure.

For completeness, there were some strings found in these associated screens as images that I did not translate for the patch. They may get translated later but those are a lot of strings to ocr and translate for a negligible impact on the player experience.

- Extras - music tracks
- Extras - character pose screen

So far, we can run the following snippet to generate the translated .tjs files.

Code:
"%tool%" -Insert override.tjs txt\override.tjs.txt override.tjs.translated.tjs

Then the .translated.tjs files can be moved to unencrypted/ and renamed to omit the .translated.tjs extensions so the game read them. However, that process is overly cumbersome, so let's automate it while we are here.

Let's create a new folder called Tsumugi no Hanayome/tools/scripts and create a new text file inside of it called "update_gamestrings.cmd".

Code:
@echo off
set working_directory=C:\Users\User\Desktop\Tsumugi no Hanayome
set tool=%working_directory%\tools\SacanaWrapper_2.1.5\StringTool.exe

set raw_tjs_directory=%working_directory%\tools\furikiri.cli_girigiri.cli_net8.0\tjs
set translated_txt_directory=%raw_tjs_directory%\txt
set patch_directory=%working_directory%\bin\Tsumugi no Hanayome\unencrypted

"%tool%" -Insert "%raw_tjs_directory%\keybinder.tjs" "%translated_txt_directory%\keybinder.tjs.txt" "%patch_directory%\keybinder.tjs"
"%tool%" -Insert "%raw_tjs_directory%\movieqsel.tjs" "%translated_txt_directory%\movieqsel.tjs.txt" "%patch_directory%\movieqsel.tjs"
"%tool%" -Insert "%raw_tjs_directory%\nocfchkdialog.tjs" "%translated_txt_directory%\nocfchkdialog.tjs.txt" "%patch_directory%\nocfchkdialog.tjs"
"%tool%" -Insert "%raw_tjs_directory%\override.tjs" "%translated_txt_directory%\override.tjs.txt" "%patch_directory%\override.tjs"
"%tool%" -Insert "%raw_tjs_directory%\snapcapture.tjs" "%translated_txt_directory%\snapcapture.tjs.txt" "%patch_directory%\snapcapture.tjs"
"%tool%" -Insert "%raw_tjs_directory%\speech.tjs" "%translated_txt_directory%\speech.tjs.txt" "%patch_directory%\speech.tjs"
"%tool%" -Insert "%raw_tjs_directory%\stopdeactive.tjs" "%translated_txt_directory%\stopdeactive.tjs.txt" "%patch_directory%\stopdeactive.tjs"
"%tool%" -Insert "%raw_tjs_directory%\uigame.tjs" "%translated_txt_directory%\uigame.tjs.txt" "%patch_directory%\uigame.tjs"
"%tool%" -Insert "%raw_tjs_directory%\uimain.tjs" "%translated_txt_directory%\uimain.tjs.txt" "%patch_directory%\uimain.tjs"

Now we can just run that file every time we update a string and it will update the relevant file in Tsumugi no Hanayome/bin/Tsumugi no Hanayome/unencrypted so the game can read the changes. Unlike girigiri, isn't it nice to have cli tools that are automation friendly?

With the caveats mentioned above, that is enough to translate most of the game engine strings.
 

Attachments

  • SacanaWrapper_2.1.5_with_plugins.7z
    8 MB · Views: 0
Last edited:
  • Like
Reactions: Eir
Part F) Adding subfolders to unencrypted.xp3

That covers translating dialogue, phonetext, images, title, game engine strings, some level of game engine string automation to help with manual debugging. The next step is to dive more into automation.

However, there is one more thing I would like to do before diving into more automation. Let's try to make unencrypted.xp3 support subfolders to keep everything nice and organized.

For that, we need to know a little but more about how Galpatch's krkr-version.dll actually works. Let's experiment. Then we can determine whether it is possible or not.

- Loading an empty initialize.tjs makes the game not launch at all anymore.
- Loading the decompiled initialize.tjs results in a "String exception raised Cannot convert given narrow string to wide string" which is the same error generated as when we were messing with the various utf-16 encodings, bom vs no bom, le vs be, back in the Koakuma-chan workflow.
- Converting the decompiled initialize.tjs from utf-8 to utf-16-le-bom changes the error to "Script exception raised Syntax error (syntax error)".

In other words, Galpatch can alter the loading of initialize.tjs despite how early that file is in the boot process. It can also alter Storages.tjs which gets loaded before the code that tries to load "patch.xp3" which is "useArchiveIfExists("patch.xp3");".

storages.tjs.empty.png

With this information, there are a lot of games we can play now with how the files load. Let's try doing the same thing as the Koakuma-chan workflow to create subfolders. Here is the basic syntax.

Code:
Storages.addAutoPath(System.exePath + "Patch.xp3>system/");

After moving Storages.tjs to Desktop\Tsumugi no Hanayome\bin\Tsumugi no Hanayome\unencrypted, here is what the top of the file looks like.

Code:
//Storages.addAutoPath(System.exePath + "Patch.xp3>data/system/");

Storages.addAutoPath(System.exePath + "unencrypted/data/");
//Storages.addAutoPath(System.exePath + "unencrypted.xp3>data/");

However, after moving 紬の花嫁01 プロローグ.txt.scn, the dialogue script that translates the first line of the game, to the unencrypted/data folder, the first line of dialogue was no longer translated after loading the contents of the unencrypted folder and the unencrypted.xp3. The title from AppConfig.tjs loading, the Storages.tjs script was loading or the game would be crashing without it, and 紬の花嫁01 プロローグ.txt.scn was already tested to translate the first line, but the files from the subdirectory never loaded.

At the bottom, there is another function called addAutoPathRecursive(). However using that syntax did not worth either.

Code:
addAutoPathRecursive(System.exePath+"unencrypted/", true, tkdlVerbose);
addAutoPathRecursive(System.exePath+"unencrypted/", false, tkdlVerbose);
addAutoPathRecursive(System.exePath+"unencrypted.xp3>data/", true, tkdlVerbose);
addAutoPathRecursive(System.exePath+"unencrypted.xp3>data/", false, tkdlVerbose);

I also tried this syntax,

Code:
setupSubFolders([
    "unencrypted.xp3>data/",

Which gave this error.

xp3subfolder_1.png


Changing is to the following removed the error which implies the game could find the archive and so it launched successfully, but did not load any custom assets from the archive.

Code:
setupSubFolders([
    System.exePath+"unencrypted.xp3>data/",

Using crskycode's KrkrPatch with the archive named patch.xp3 had the same behavior meaning that this is not an issue with the patch software. Rather, this is an issue with the game's willingness to load assets from unencrypted archives and plain folders separately from the patch software's logic of loading files from the root of the .xp3 archives.

Thinking back to Koakuma-chan, arcusmaximus's KiriKiriTools did not have this issue. That is likely because it adjusted the game's loading of the patch.xp3 to allow loading of all assets from it dynamically by working with the game engine's built in asset loading mechanism.

However, bynejake's GalPatch and crskycode's KrkrPatch use different logic for loading assets from patches. At least in GalPatch's case, that updated logic allows replacing initialize.tjs, which is an improvement, but that logic also does not seem to patch the game's built in patching system for loading updated assets. So, we cannot use the game's built in asset loading system since that code still does not support loading unencrypted .xp3 archives.

The obvious thing to do is that we could technically modify the source code of krkr-version.dll and recompile it to forcefully add support for subfolders. But, besides compiling software always being a living hell, it would also alter the hash of the version.dll that we need to include with the final patch.xp3 from the one published on bynejake's Github repository.

version.dll contains executable code. Asking people to run executable code always presents a risk to that user. Even if we do not intend to run code maliciously, a third party could alter the contents of the .dll and republish the patch without our knowledge or consent.

That means the exact version of the .dll is important because it allows users to verify they are using an easily obtainable public .dll with a known hash which proves the file was not modified from the source repository version by a third party, including us, the patch provider.

A hash is a mathematical algorithm that always outputs the same number of bits as output regardless of the input. Crucially, the same input will always result in the same output. Among other uses, hashes can be used to prove that two files are identical even if they have different file names, extensions, created dates, modified dates, or other metadata differences. Hashes check the content of the data not the filesystem level metadata.

Code:
Name:     version.dll (renamed GalPatch's v1.2.5 krkr-version.dll)
Size:     53760 bytes (52.5 KiB)
CRC32:    72C92B59
CRC64:    5D32AE545362F5B2
MD5:      a5b1d8e71c154507a7eabd2474d56b68
SHA1:     bab02a321c4c2d746dc43932450c47801b164578
SHA256:   c2afd43203664d08f13187627ed1b7af4be2cb4a08be5871076533f5ea71b613
SHA512:   e2cc257a95b25d7c19ab5e3377ca4bc4b04b877ba19057c5c8288c3baadae7b2c0eeedb03d2c05371b52d436f01a6888e7ad89b3514e3623c0336896e03c9c43
BLAKE2s:  3b6de6887955beb4fd748ce1b1d5e0867b65a1a0a992bdadfffd6d246c357b3a
XXH64:    6699E9985EB4C5F8

Including version.dll is not technically needed to publish the final patch.xp3 at all. version.dll is only provided with the patch for convenience. But we still need some justification for asking a user to take that risk of running that file. We can prove convenience is that reason by making sure the included version.dll is the exact same file as in the official repository for GalPatch.

Essentially, being able to publish a patch using the original .dll from the publicly available repository means any user can check the hash of the provided .dll with the original .dll from the repository to verify for themselves that the file was not changed. While this is not flawless protection against persons who hijack our patch for malicious purposes, using provably unmodified files published by the source developer goes a long way towards establishing trust in our translation patch. It also speaks to motive that there was a genuine attempt to minimize the user's exposure to arbitrary code execution to what was genuinely necessary to translate the game.

If the hashes do not match proving they are different files, then that convenience argument falls apart and so does our credibility as the patch provider by opening up the user's exposure to additional arbitrary code execution.

What was changed? Where is the source for those changes? Why is the patch.xp3 now dependent on this specific .dll instead of the official version? Was it really necessary to change it, or was there any possible way the publicly accessible version.dll could have worked?

While those questions have answers, it is better to not create a situation where they need to be asked in the first place. That makes compiling version.dll from source an unrealistic solution to this very minor problem of not having organized subfolders in the patch archive. Unless there is a technical reason that makes it genuinely necessary to compile version.dll ourselves, we are better off using the official version.dll. Mere better organization does not meet that high bar where we can justify exposing users of the patch to higher risk because there is an alternative that poses less risk.

It should be noted that the patch.xp3 includes altered bytecoded .tjs files which are fundamentally unverifiable. They are not human readable as bytecode, not guaranteed to decompile into normal tjs2, and the disassembled code is difficult to interpret at best. However, at least that danger is confined to the patch.xp3 file itself and a sufficiently technically literate user can always cut out all the .tjs scripts if they feel that is too much risk to them. The justification of being "necessary to translate the UI" is also quite reasonable for a patch that is meant to be a translation.

Since that failed and recompiling is ill-advised, the best thing to do is to add an alternative code path for the storages subsystem as a function that does the same thing but actually works for unencrypted archives and folders. Ideally, that should be written entirely in plaintext tjs2 to make sure it is portable and readable. Unfortunately, while such a thing is undoubtedly possible, I do not actually have any code to actually do that. For the time being, the patch will have to go without organized subfolders.
 
Last edited:
  • Like
Reactions: Eir
Part G) Automating dialogue extraction, translation, and insertion

The next step is to automate the extraction, translation, and reinsertion of dialogue.

The basic goal when automating stuff is to make sure one operation works, and then to automate the process for all of the files using that syntax that was tested to work. That syntax in CLI apps can be automated by using a shell script with variables for all of the inputs.

In my case, I am using a folder called Desktop\Tsumugi no Hayayome\MTL to contain the intermediary files related to translating the dialogue and splitting every aspect of it into subfolders. The actual .cmd files, however, are placed at tools/scripts since they are more tools meant to manipulate static assets than static assets themselves.

One alternative to subfolders is to keep everything besides the final output in the same folder, but that confuses me since that mixes different types of content. I would rather have all of the files in seperate folders since the folder names can be used to clarify the nature of the files inside of it. Separate folders also simplifies selecting the correct files while automating everything with batch scripts.

The structure I am using for translating the dialogue inside the e-mote files is

Code:
MTL/
MTL/dialogue_scn_raw/
MTL/dialogue_scn_json/
MTL/csv/
MTL/dialogue_scn_json_translated/
MTL/dialogue_scn_translated/

- dialogue_scn_raw - the raw KrKrDump extracted e-mote .txt.scn dialogue scripts
- dialogue_scn_json - the dialogue scripts converted from e-mote format (.txt.scn) scripts to txt.json using FreeMote
- csv - the spreadsheets in .csv format created by the ScriptExtractInsertTool.py that contain the dialogue extracted from the json files. The dialogue in these .csv files can translated using romaji.py and sugoi_mtl.py
- dialogue_scn_json_translated - the output of ScriptExtractInsertTool.py inserting the translated dialogue from the spreadsheet.csv files back to .json
- dialogue_scn_translated - the final translated .txt.scn e-mote scripts created using FreeMote

If following this guide, be sure to create the above folders and copy all of the dialogue .txt.scn scripts to MTL/dialogue_scn_raw.

As was mentioned earlier, automating stuff with the batch language requires the command prompt to be able to enumerate the characters in the file names correctly. Otherwise, it will not be able to find the files to work on them due to interpreting '?' literally. That means the following scripts will only work in the command prompt code pages for utf-8, 65001, or shift-jis, 932. Check the current code page by entering chcp into the command prompt wiindow.

Code:
chcp
Active code page: 932

To change the code page, either change the locale to Japanese,Japan or enter either "chcp 932" or "chcp 65001". Changing to chcp 932 also requires changing the locale to japanese,japan.

We need a script to actually perform the above actions using the syntax discovered earlier in this case study using the data in the above folders.

Let's create Desktop/Tsumugi no Hanayome/tools/scripts/extract.txt.scn_to_json.cmd

If following this guide, the paths or script names might need to be adjusted since the game specific tools were moved from codeberg/translation_tools/ to codeberg/translation_projects/ after this guide was written. I also normally put all .cmd files at tools/scripts/*.cmd The *.cmd scripts below are also all available at codeberg/translation_projects/games/Hending/Tsumugi no Hanayome/scripts.

Code:
@echo off

set working_directory=%userprofile%\Desktop\Tsumugi no Hanayome
:: this converts the .txt.scn to .txt.scn.json
set tool=%working_directory%/tools/FreeMoteToolkit/PsbDecompile.exe

set dialogue_scn_raw=%working_directory%\MTL\dialogue_scn_raw
set dialogue_scn_json=%working_directory%\MTL\dialogue_scn_json
set csv_directory=%working_directory%\MTL\csv
set dialogue_scn_json_translated=%working_directory%\MTL\dialogue_scn_json_translated

echo 'dir /b "%dialogue_scn_raw%\*.txt.scn"'
for /f "delims=;" %%i in ('dir /b "%dialogue_scn_raw%\*.txt.scn"') do "%tool%" "%dialogue_scn_raw%/%%i"

if not exist "%dialogue_scn_json%" mkdir "%dialogue_scn_json%"
if not exist "%dialogue_scn_json%/resx" mkdir "%dialogue_scn_json%/resx"

echo move /y "%dialogue_scn_raw%\*.resx.json" "%dialogue_scn_json%\resx"
move /y "%dialogue_scn_raw%\*.resx.json" "%dialogue_scn_json%\resx"
echo move /y "%dialogue_scn_raw%\*.txt.json" "%dialogue_scn_json%"
move /y "%dialogue_scn_raw%\*.txt.json" "%dialogue_scn_json%"

Now lets open a command prompt and run extract.txt.scn_to_json.cmd.

Code:
cd Desktop\Tsumugi no Hanayome\tools\scripts
extract.txt.scn_to_json.cmd

If all of the paths were correct, that should create a lot of .json files and move them to dialogue_scn_json. Next, we need to extract out the translatable strings from the .json files.

Most of the two weeks from the game's release date to the patch release date was spent writing a script to extract and insert the dialogue from those .txt.scn.json files without missing lines or generating errors.

Getting the phone texts translated also made the process take twice as long as it should have especially since the developer used a koala emote which took quite a while to figure out how to deal with. Koalas are not text! In the end, I just gave up and hardcoded some fixes for the koala.

But now that it has been done, it is just a matter of using that script in the same way as the Koakuma-chan version.

From my Codeberg, download translation_tools or translation_projects/games/Tsumugi no Hanayome/TsumugiNoHanayome_ScriptExtractInsertTool.py. A link is in my signature for signed in users. On Codeberg, click on the name of the script to preview the contents and then click on the "Download file" icon next to "History" to download it. Git also works.

Code:
set working_directory=%userprofile%\desktop\Tsumugi no Hanayome
cd %working_directory%
cd tools
git clone https://codeberg.org/entai2965/translation_projects.git

The script needs Python3.7+ or CPython3.6+ to run. It should already be installed from the Koakuma-chan workflow and from the ocr.py/ocr_text_recognition.py script used above.

Code:
python "TsumugiNoHanayome_ScriptExtractInsertTool.py" --help
usage: TsumugiNoHanayome_ScriptExtractInsertTool.py [-h] [-s SPREADSHEET]
                                                    [-o OUTPUT]
                                                    [-cn CHARACTER_NAMES]
                                                    [-c COLUMN] [-w WORDWRAP]
                                                    [-cd CSV_DIALECT] [-v]
                                                    [-d]
                                                    mode inputfile

Extract and insert text into kirikiriz .txt.scn files after they have been
extracted from archive.xp3. Any folders in the paths must already exist. For
usage, 'python tool.py -h' version=0.1.0

positional arguments:
  mode                  must be extract or insert
  inputfile             the source file to extract strings from and insert
                        them into

options:
  -h, --help            show this help message and exit
  -s SPREADSHEET, --spreadsheet SPREADSHEET
                        the file name to read and write the extracted strings
                        to and from, he first row is reserved for column
                        headers, must be .csv if pyexcel is not installed with
                        'python -m pip install pyexcel pyexcel-xls pyexcel-
                        xlsx pyexcel-ods pyexcel-ods3 pyexcel-odsr
                        openpyxl==3.0.10'
  -o OUTPUT, --output OUTPUT
                        the output file name for the resulting file, only used
                        for mode=insert
  -cn CHARACTER_NAMES, --character_names CHARACTER_NAMES
                        a .csv or spreadsheet mapping the raw character name
                        to a translation, the first row is reserved for column
                        headers
  -c COLUMN, --column COLUMN
                        the column number in the spreadsheet to use as
                        replacements, only used for mode=insert, starts from
                        1, column A is the 1st column, so enter 1, column D is
                        the 4th column, so enter 4, column C is 3, 0 is a
                        special flag that uses the right most column, default
                        is 0
  -w WORDWRAP, --wordwrap WORDWRAP
                        word wrap setting, enter the number of characters per
                        line, word wrap assumes a maximum of 3 lines, default
                        is 50
  -cd CSV_DIALECT, --csv_dialect CSV_DIALECT
                        specify the csv dialect when reading spreadsheet.csv
                        files, normal settings are used otherwise, ignored for
                        non .csv formats, valid options are unix, excel,
                        excel-tab
  -v, --version         print version information
  -d, --debug           print debug information

The interface was copied from the Koakuma-chan script, so it should look very familiar. The basic syntax to extract and insert translatable text is this.

Code:
python script.py extract dialogue.scn.txt.json
python script.py insert dialogue.scn.txt.json

And then add the spreadsheet.

Code:
python script.py extract dialogue.txt.scn.json --spreadsheet ..\MTL\dialogue.txt.scn.json.csv

To see it working, run the string extraction tool on one of the .txt.scn files.

Code:
cd Desktop\Tsumugi no Hanayome\tools
python "translation_tools\games\Tsumugi no Hanayome\TsumugiNoHanayome_ScriptExtractInsertTool.py" extract "C:\Users\User\Desktop\Tsumugi no Hanayome\MTL\dialogue_scn_json\紬の花嫁01 プロローグ.txt.json"

That should create "dialogue_scn_json\紬の花嫁01 プロローグ.txt.json.csv" and complain that a lot of character names were not found.

extracttool1.png

Let's fix that by translating the character names. Create a text file called character_names.csv, TsumugiNoHanayome_characternames.csv, or similar with these contents.

Code:
Japanese name,latin name
葵,
山吹,

So, who are 葵 and 山吹? I don't know (IDK). Those names were output from the tool.py. Running "python romaji.py 葵" outputs "Aoi". "python romaji.py 山吹" outputs "Yamabuki". Let's assume that is correct for now.

Code:
Japanese name,latin name
葵,Aoi
山吹,Yamabuki

We could leave it at that, but let's check the vndb page. It looks like vndb uses the same spelling for the character names as UniDic which is a good sign that we can continue to use the output of romaji.py for the rest of the character names. From the character information on vndb, we can also add entries for 吐瀉丸, Toshamaru, and 速水 遥, Hayami Haruka.

Code:
Japanese name,latin name
葵,Aoi
山吹,Yamabuki
吐瀉丸,Toshamaru
遥,Haruka

Then rerun the above command with the updated character dictionary. With the character names it becomes.

Code:
python script.py extract dialogue.txt.scn.json --spreadsheet ..\MTL\dialogue.txt.scn.json.csv --character_names character_names.csv

so with variables it should look like

Code:
set working_dir=C:\Users\User\Desktop\Tsumugi no Hanayome
set tool=python "%working_dir%\tools\translation_tools\games\Tsumugi no Hanayome\TsumugiNoHanayome_ScriptExtractInsertTool.py"

set character_names=%working_dir%\TsumugiNoHanayome.characternames.csv

set file=%working_dir%\MTL\dialogue_scn_json\紬の花嫁01 プロローグ.txt.json
set spreadsheet=..\MTL\dialogue.txt.scn.json.csv

%tool% extract "%file%" --spreadsheet "%spreadsheet%" -cn "%character_names%"

There should not be any missing character name errors when processing 紬の花嫁01 プロローグ.txt.json.

The above assumes the commands get entered directly into the command prompt. If there are issues running the commands as a .cmd file, then it may be an encoding issue. If the filename, 紬の花嫁01 プロローグ.txt.json, becomes distorted on older versions of windows (<Windows 10 1803+), then use shift-jis encoding for .cmd files. Once it extracts correctly, extract the next one.

Code:
set file=%working_dir%\MTL\dialogue_scn_json\紬の花嫁02 吐瀉丸(ev213放尿あり).txt.json
%tool% extract "%file%" -cn "%character_names%"

Code:
??? nested character name not found
おかあさん character not found
としゃまる nested character name not found

From here, we can either continue to run the script on the files one by one fixing the errors in the character names as we go or processes all of the files at once. Both approaches are valid.

Since we mostly already have the code we need to processes all of the files together, let's just move on. Here is what the characternames.csv looks like when complete.

Code:
Japanese name,latin name
葵,Aoi
山吹,Yamabuki
山吹ちゃん,Yumabuki-chan
吐瀉丸,Toshamaru
???,"???"
としゃまる,Toshamaru
おかあさん,Okaa-san
イキリカメラマン,Smug Cameraman
ローアングラーマン,Low Angle Man
ローアングラー,Low Angler
ヘビィボウガンマン,Hebibougan Man
運営スタッフ,Operations Staff
有象無象カメラマン,Riffraff Cameraman
おっさんカメラマン,Os-san Cameraman
吐瀉丸_葵,Toshamaru_Aoi
遥,Haruka
菖蒲,Shoubu
女生徒,Schoolgirl
女生徒A,SchoolgirlA
女生徒B,SchoolgirlB
店員,Clerk
でかちんDQN,Dekachin DQN
DQN,DQN
DQN2,DQN2
DQN3,DQN3
吐瀉丸_山吹,Toshamaru_Yamabuki
刺客,Shikaku
雅,Miyabi
ショップのお姉さん,Shop's Onee-san
楓,Kaede
パパ,Papa
雅子供,Kid Miyabi
葵・山吹,Aoi・Yamabuki
唄穂,Utaho
無言スマホマン,Silent Smartphone Man
伊達,Date
取り巻き達,Entourage
取り巻き1,Follower1
取り巻き2,Follower2
取り巻き3,Follower3
取り巻き4,Follower4
種付けおじさん,Mating Oji-san
女優,Actress
女聖騎士,F. Holy Knight
オーク,Oak
地味子女優,Plain Child Actor
昌継,Masatsugi

Some of those translations are not so good, but without any context, it is difficult to know what is accurate and what is not, especially the DQN ones.

In particular, ヘビィボウガンマン, Hebibougan Man might be heavy bone man? Some of the translation engines I put that into transliterated it to Heavy Bow Man and Heavy blowgun man. If the bone version is what is intended, then maybe something like "Large boned man" would be an accurate translation? IDK, so let's just leave it as the raw romaji for now.

We can fix quality issues later. For now let's keep automating. Here is a script that can automatically extract the translatable dialogue.

Code:
@echo off

set working_directory=%userprofile%\Desktop\Tsumugi no Hanayome
:: this converts the .txt.scn to .txt.json
::set tool=%working_directory%/tools/FreeMoteToolkit/PsbDecompile.exe
:: this converts the .txt.json to .txt.scn
::set tool=%working_directory%/tools/FreeMoteToolkit/PsbBuild.exe
:: this converts the .txt.json to csv and back
set tool=python "%working_directory%/tools/translation_tools/games/Tsumugi no Hanayome/TsumugiNoHanayome_ScriptExtractInsertTool.py"

set dialogue_scn_raw=%working_directory%\MTL\dialogue_scn_raw
set dialogue_scn_json=%working_directory%\MTL\dialogue_scn_json
set csv_directory=%working_directory%\MTL\csv
set dialogue_scn_json_translated=%working_directory%\MTL\dialogue_scn_json_translated
set character_names=%working_directory%/TsumugiNoHanayome.characternames.csv

if not exist "%csv_directory%" mkdir "%csv_directory%"

echo 'dir /b "%dialogue_scn_raw%\*.txt.json"'
for /f "delims=;" %%i in ('dir /b "%dialogue_scn_json%\*.txt.json"') do %tool% extract "%dialogue_scn_json%/%%i" -cn "%character_names%" -s "%csv_directory%/%%i.csv"

Once the paths are fixed, running it works just like the koakuma-chan automation scripts, just with more steps.

Start a command prompt.

Code:
set working_directory=%userprofile%\Desktop\Tsumugi no Hanayome
cd "%working_directory%"
tools\scripts\extract.json_to_csv.cmd

The phonechats needed special handling compared to regular dialogue, but I automated almost all of it. That means just running the scripts is enough to translate the dialogue mostly. There are some name tags in the phone chats that are not translated.

This does assume that the paths in the *.cmd files are updated to work with the local environment and the scripts are invoked in the right environment where cutlet with Unidic and a sugoi compatible server are both available. However, that should both already be set up. If not, refer to the romaji.py, sugoi_mtl.py and the automation section in the Koakuma-chan guide.

Once the local environment is set up and the paths to the tools adjusted, it is just a matter or running the scripts.

For koakuma-chan, there were 4 scripts, extract, add romaji, add sugoi, insert. Since Tsumugi no Hanayome has an extra set of converting the files .txt.scn <-> .txt.json, there are 2 extra scripts to handle that, for a total of 6 that are part of the main dialogue. There is also a 7th script for updating game strings, update_gamestrings.cmd.

1. .txt.scn -> .txt.json

Code:
extract.txt.scn_to_json2.cmd

2. .txt.json -> .csv

Code:
extract.json_to_csv.cmd

3. add romaji to .csv's

Code:
@echo off

set working_directory=%userprofile%\Desktop\Tsumugi no Hanayome

set tool=python "%working_directory%/tools/translation_tools/romaji.py"

set dialogue_scn_raw=%working_directory%\MTL\dialogue_scn_raw
set dialogue_scn_json=%working_directory%\MTL\dialogue_scn_json
set csv_directory=%working_directory%\MTL\csv
set dialogue_scn_json_translated=%working_directory%\MTL\dialogue_scn_json_translated
set character_names=%working_directory%/TsumugiNoHanayome.characternames.csv

for /f "delims=;" %%i in ('dir /b "%csv_directory%\*.csv"') do %tool% "%csv_directory%/%%i" -s

4. add sugoi translations to the .csv's

Code:
@echo off

set working_directory=%userprofile%\Desktop\Tsumugi no Hanayome

set tool=python "%working_directory%/tools/translation_tools/sugoi_mtl.py"

set dialogue_scn_raw=%working_directory%\MTL\dialogue_scn_raw
set dialogue_scn_json=%working_directory%\MTL\dialogue_scn_json
set csv_directory=%working_directory%\MTL\csv
set dialogue_scn_json_translated=%working_directory%\MTL\dialogue_scn_json_translated
set character_names=%working_directory%/TsumugiNoHanayome.characternames.csv

for /f "delims=;" %%i in ('dir /b "%csv_directory%\*.csv"') do %tool% "%csv_directory%/%%i" -s

5. .csv -> .txt.json

Code:
@echo off

set working_directory=%userprofile%\Desktop\Tsumugi no Hanayome

:: this converts the .txt.json to csv and back
set tool=python "%working_directory%/tools/translation_tools/games/Tsumugi no Hanayome/TsumugiNoHanayome_ScriptExtractInsertTool.py"

set dialogue_scn_raw=%working_directory%\MTL\dialogue_scn_raw
set dialogue_scn_json=%working_directory%\MTL\dialogue_scn_json
set csv_directory=%working_directory%\MTL\csv
set dialogue_scn_json_translated=%working_directory%/MTL/dialogue_scn_json_translated
set character_names=%working_directory%/TsumugiNoHanayome.characternames.csv

for /f "delims=;" %%i in ('dir /b "%dialogue_scn_json%\*.txt.json"') do %tool% insert "%dialogue_scn_json%/%%i" -cn "%character_names%" -s "%csv_directory%\%%i.csv" -o "%dialogue_scn_json_translated%/%%i" --column 0

6. .txt.json -> .txt.scn

Code:
@echo off

set working_directory=%userprofile%\Desktop\Tsumugi no Hanayome

:: this converts the .txt.json to .txt.scn
set tool="%working_directory%/tools/FreeMoteToolkit/PsBuild.exe"

set character_names=%working_directory%/TsumugiNoHanayome.characternames.csv

set dialogue_scn_raw=%working_directory%\MTL\dialogue_scn_raw
set dialogue_scn_json=%working_directory%\MTL\dialogue_scn_json
set csv_directory=%working_directory%\MTL\csv
set dialogue_scn_json_translated=%working_directory%\MTL\dialogue_scn_json_translated
set dialogue_scn_translated=%working_directory%\MTL\dialogue_scn_translated

pushd "%dialogue_scn_translated%"
for /f "delims=;" %%i in ('dir /b "%dialogue_scn_json_translated%\*.txt.json"') do %tool% -v 3 "%dialogue_scn_json_translated%/%%i"
echo renaming files...
for /f "delims=;" %%i in ('dir /b "%dialogue_scn_translated%\*.txt.psb"') do ren "%dialogue_scn_translated%\%%i" "%%~ni.scn"
popd

The scripts above use the same syntax as was discovered during the dialogue proof of concept above. There are still some automation specific optimizations we make though and some other final notes about the workflow.

We do not really want to send newlines to Sugoi translator. Sugoi will understand the line as a contigious idea better without new lines. That is also true for other translators too, including DeepL and LLMs. in translation_tools/sugoi_mtl.py there is this line

Code:
        data_to_translate.append(row[0].replace('\n',' ').replace('  ',' '))

replace it with

Code:
        data_to_translate.append(row[0].replace('\n',' ').replace(r'\n',' ').replace('  ',' '))

and that should make sure new lines are not sent to Sugoi.

Also note that there are 67 txt.scn files but only 66 have translatable dialogue. "紬の花嫁61_4_staff.txt.json" does not have any text dialogue to translate. That is probably the staff credit roll at the end of the game as opposed to regular dialogue. Since the tool.py is coded to only extract dialogue and phonechats, it will not extract anything from that .json file.

The 66 .txt.scn files with translatable text will generate 70 .csv files because 4 of the files have phonechat conversations, and those produce their own .csv files.

The nameplate for the koala emote was also never automated, so that has to be fixed manually for the final patch.

fixme_final.jpg

disclaimer, The image editing for the above .png file was done in mspaint.exe and saved to .jpg to ensure no .png files were harmed in the creation of that image.

Just like with Koakuma-chan, let the game run with the skip setting to 'on' to see if it crashes. If everything is translated and nothing crashes, then the automation workflow is done, otherwise troubleshoot as needed.

The final patch also does not seem to work if Tsumugi.exe.sig is still in the parent folder.

The .sig/signature file is only used to check if the file was modified before loading it, so perhaps GalPatch's version.dll changes the signature of Tsumugi.exe which results in the game aborting its own load code?

However, without the .sig file present, the game just works normally since it has nothing to verify itself against.

It took ages to figure out the Tsumugi.exe.sig issue since I only ever worked on the repackage version with does not have any .sig files at the root of the main game at all. The repackage would always work, but the download versions would not, so fixing it took obtaining a download version and comparing hashes against the repackage version with translation_tools/hashes_organize.py.
 
Last edited:
  • Like
Reactions: Eir
With that, this guide is done. Looking back, a lot of software from a lot of different developers, some working for organizations, was used to create a translated version of Tsumugi no Hanayome. Here is an enumeration of that.

General software:
- The internet (anime-sharing.com, search engines, vndb.org, github, codeberg, mediafire, etc)
- Microsoft Windows + the windows command prompt/batch scripting
- various Windows utilities (mspaint.exe, image viewer, snipping tool)
- Microsoft Visual Studio 2022 Community + Win 10 SDK for compiling stuff, especially Furikiri
- CPython, cross platform open source core dependency for a lot of software
- Notepad++ for editing all text files
- wxHexEditor for debugging
- Krita for image editing

VN or translation specific software:
- crskycode's Garbro fork that was forked from Morkt's version
- UlyssesWu's Furikiri/girigiri for debugging and FreeMote for e-mote (.txt.scn) conversion
- Marcussacana's ScandaWrapper/StringTool.exe which builds on their other repositories.
- translation_tools/romaji.py which implements polm's cutlet which itself uses unidic
- translation_tools/sugoi_mtl.py which implments a Sugoi client for use with Sugoi Toolkit by MingShiba, or the associated repackage
- ocr_tools/ocr_text_recognition.py which implements kha-white's MangaOCR library and the manga-ocr-base model which itself was built on all sorts of other ocr related libraries built by other hobbyists

Software for this particular game:
- The game itself by Hending vndb.org page which was made using kirikiriZ with an mtwo.co.jp's e-mote plugin and other plugins, like for .ogg vorbis and fonts
- whoever made the no-DVD patch and girlcelly for releasing that patch
- translation_projects/games/Tsumugi no Hanayome/tool.py

Even that is a partial list. The translation patch would not have been published without the above software and the work put into developing it done by the developers involved. I sincerely thank everyone involved for their contributions that helped this project finish in a mere 2 weeks!
 

Users who are viewing this thread

Latest profile posts

BlazeOrc wrote on Nihonjaki90's profile.
Hey 😙

A new version 2.0 has come out for this game おさわり×ばつげーむ, can you re-upload it ? Both PC and android, thank you.
Paine wrote on Nihonjaki90's profile.
Jelly-filled Donut wrote on Otokonoko's profile.
Sorry, Otokonoko. Do you still have these old contents to reupload?
いっぱいしちゃお
ラブesエム
Jelly-filled Donut wrote on Esan's profile.
Hi, Esan. Do you still have these ancient games to reupload? Sorry, I know it's a lot, so I'll be patient.
南国搾乳☆みるくアイランド娘
OH!!マイクロマン
せーえき瞬間
They're all from the same dev. Please take your time.