Entai2965

Moderator
Moderator
Elite Member
Dec 28, 2023
123
63
title.png


This is part 3 of the kirikiri game engine series. If you have not already done so,
- read part 1, Koakuma-chan,
- and then part 2, Tsugumi no Hanayme.

Read those guides from start to finish prior to reading this one.

While they use the more complicated successor engine, KirkiriZ, and monobeno uses the older, simpler, kirikiri2 engine, those guides introduce many core concepts of the kirikiri game engine that will be completely glossed over here.

In addition, this workflow will heavily focus on automation from the start, unlike the previous guides, including extensive use of the windows command prompt even for basic tasks. This makes the workflow significantly harder to understand for those lacking a solid foundation for how to already work with the kirikiri game engine in detail and with general automation concepts for the Windows operating system.

Read the previous two guides on kirikiri first!

- random kirikiri2 source tree reference.

A certain someone volunteered their time and money to AI TL this game, but they lacked the technical knowledge on the kirikiri game engine to translate it, especially in a reasonable amount of time. For my part, I now have the technical skills, but lack the $ to do so, so we collaborated on the release.

My takeaway from this release is increased automation, especially when working with images and game engine strings. Why is the focus of this guide on automation, not translating the first line of dialogue?

Most games, especially older Japanese ones, can be translated to at least English, yet most never are. Even the ones that are translated sometimes/usually only get unedited MTL NMT translations. Very low effort work.

While part of that is related to commercial interests, the mountain of issues related to working with the Japanese language, or game engines, the fundamental reason is that even if there is a way to translate them or improve their translations, it is simply too much work to actually do so.

The intent of these guides is to show how media can be translated to another language in very practical terms and lower the bar for the technical skills required to do so. They deliberately assume a relatively low-ish technical level and explain any concepts necessary during the guide. People who do not need those extra explanations can just skip them.

Yet the focus of this guide is not to show how to translate Monobeno, but rather how to automate a quality-based translation of Monobeno.

That very different focus makes sense because
1) this is part 3 of the kirikiri game engine series meaning that part 1 and 2 already show how to work with this game engine in a step-by-step manner
2) this guide tackles the core issue still plaguing game translations or lack thereof, especially quality ones, that it is simply too much work to translate a game from Japanese, even with automation

Too much work = not enough automation. If something is too much work, then automate it until it becomes managable.

With sufficent automation, there should be no or few practical differences between translating a 1 hour long game and a 70 hour long game like Monobeno. Making a basic translation patch should be just a matter of translating the first line, setting up the automation, and then letting it all process automatically. Then, where it really matters, additional steps can be taken to improve the quality and the automation should support that.

That is why quality-based automation the goal of this specific guide, since I still consider the lack of sufficent automation to be a major barrier to producing quality translations of Japanese media.

This guide will not solve that completely of course, but it will chip away at the problem from various angles.

Table of Contents

- Introduction
- Learning more about Monobeno
- Hikari Field's Monobeno release on Steam
- Setting up the translation project
- Translating the first line of dialogue
- Testing dialogue word wrapping
- Automating the dialogue translation 1
- Automating the dialogue translation 2
- Image processing theory
- Image processing workflow
- Manually fixing automated image processing mistakes
- Testing SLR Translator's SEP addon
- Updating the title
- Adding subfolders to the patch
- Automating game engine string translations
- Improving automated wordwrappng quality
- Improving the MTL to an ATL MTL
- Porting the translation patch to other versions

Highlight the section you would like to browse to, copy it, then use ctrl+F to go there.
 
Last edited:
Learning more about Monobeno

With that out of the way, let's learn more about Monobeno. We can do that by reading up on the various releases documented on vndb.org and looking at the title on commercial sites.

vndb monbeno (original game) https://vndb.org/v12392
vndb monobeno happy end (remake) https://vndb.org/v12392
dmm https://www.dmm.co.jp/dc/doujin/-/detail/=/cid=d_274849/
Japanese Base game https://www.dlsite.com/pro/work/=/product_id/VJ007039.html
Japanese -happy end- DLC only https://www.dlsite.com/pro/work/=/product_id/VJ012792.html
Japanese complete collection https://www.dlsite.com/pro/work/=/product_id/VJ012820.html
Steam w/Hikari Field publisher https://store.steampowered.com/app/831660/

Monobeno guide, https://seiya-saiga.com/game/lose/monobeno.html
Monobeno happy end guide, https://seiya-saiga.com/game/lose/monobeno_he.html
monobeno happy end guide2,

Okay, it looks like the story is about survival life in the mountains/rural japanese town called Monobeno and a disease that affects aging? Apparently, that is a good basis for an eroge. Well, whatever.

While there are a lot of H scenes, it does not seem to be a nukige. The number of characters is 26, and the length is 70-80h. Those pieces of information are both somewhat terrifying, so let's do our best to ignore what that implies for now.

There are also quite a few versions, humm... Which version should we use for creating a translation patch? The Happy End version is a partial rewrite of the original story with a lot of extra content, so let's focus on that version. The most complete sounding of the Happy End versions is the complete set.

Monobeno Complete Set

Includes Monobeno and Monobeno -happy end- with all the appends already applied. This set also includes a total of 14 hours of voice content, including voice drama, system voices, ringtones, etc. It also includes a vocal collection with all 9 Monobeno vocal songs and 17 remixes of them, for a total of 26 songs in WAV format, and 6 types of wallpapers.

In addition, as a FANZA-exclusive bonus limited to this release, a total of 105 newly-drawn Monobeno illustrations have been included in their original size at the time of production.

That version seems promising, but are there any differences in content or packaging between this version and previous versions sold on DLSite? The DLsite version does not say it supports chinese, but the steam version does. How different are they really?

People who have one version downloaded already might be pretty annoyed to have to download yet another version to apply a patch. I certainly would be, so creating patches for at least the two main versions might be best here. At the very least, one for the steam version and one for the DLSite verison if they happen to be very different.

Also, I am not supporting the base game explicitly. The game is old enough and the happy end versions are common enough that it just makes sense to translate that one instead of the original since it is semi-accurate to say that the happy end version has a superset of the content of the original.

Do not do this. While trying to figure out which version to use, I looked at these versions, old torrent, new torrent, archive.org, girlcelly's first release version, and lebedev's version, and the hikarifield/steam version.
old torrent (gone), https://sukebei.nyaa.si/view/2642426, mirror, mirror2
new torrent,
archive.org version, link
girlcelly's version,
[url="https://www.anime-sharing.com/threads/2023-lose-monobeno-complete-set-update.1387876/"]lebedev's version
, mirror
steam's hikarifield version with the optional happy end dlc, deluxe version, f95 mirror

It is not clear which version is which especially since lebedev's version is labled as the Nakayoshi Set and vndb.org only lists combined Maitetsu-Monobeno as part of it. Huh? Is it the complete set or not? It does not seem like it, but it is documented as such?

Well, let's figure this out. Let's create hashes for all of the files in every release and also a directory tree for all of the releases. I will spare you the nitty gritty details of how I did that besides saying that it involved hashes_organize.py, but here is the final summary of all versions compared.

Comparing hashes in a spreadsheet, https://ethercalc.net/egdvjl81207j (make this read only or .png) [create .csv]

These are instructions/tips for working with hashes_organize.py loaded into libre office.

-rename the files with 1., 2., 3., 4. in front to set the header order
-run hashes_organize.py on the folder
-open up the output.csv in libre office

it is distracting to see cell contents bleed, so

ctrl + a to select all cells
right-click->format cells->alignment

Code:
--Text Alignment
    --Vertical: Top
--Properties
    --Wrap text automatically: checked

Press OK.
That will mess up the horizontal and vertical spacing for every cell in the spreadsheet.
ctrl+a again, or click on the blank space in the header row left of the A to select all cells
Right click on a row number->Row Height->check Default value->OK
Right click on a column letter->Column Width->check Default value->OK
Then expand the first column until the file names are fully visible.

Now if a cell is blank, that means the file does not exist. and it is easier to compare the left-aligned hashes to each other too.

In the append disk from lebedev's version and the archive.org version that has the append patches for monobeno happy end, there was a readme that said there were 3 different versions of Japanese Monobeno happy end and 3 different tactics to employ to get them all up to date.

1. the original Japanese physical package version of Monobeno can be updated to include the happy end dlc and all previous dlc/appends
2. the Japanese download version of monobeno can be updated to include the happy end dlc and all previous dlc/appends
3. the monobeno happy end complete set that has both happy end and all the dlc/appends applied to it already

There are also versions of monobeno with dlc that do not include the happy end dlc btw. The new torrent version includes a 405mb patch_sp.xp3 as part of the non-happy end Monobeno base game which is a file that does not exist or seem to do anything? when loaded into the monobeno happy end version of the game.

Based upon that readme and the data above from comparing all of the hashes, we can conclude

- the following files are all the same for monobeno happy end (besides for the base without the happy end dlc and the hikari field version)
bgimage.xp3
bgm.xp3
data.xp3
evimage.xp3
evimage2.xp3
fgimage.xp3
voice.xp3
voice2.xp3
- both of the old torrent and the new torrent are the exact same version. The new torrent has 2 additional suplementary files, but no differences in included content.
- the archive.org (ものべの -happy end- DL版 v12392.rar) one and the one uploaded by lebedev (version.iso) are the exact same version. The one on archive.org was extracted from the disc and not installed properly prior to being uploaded. I am assuming this is the complete set for now.
- and of course the base version without the happy end dlc and the hikari field version use completely different archives/files

Now that all of the versions are organized, which one should we translate? Which version is the basis for our future work?

I would rather use the complete set as base because it has fewer files which makes that version easier to manage during translation. There are other minor reasons to use that version, but that reason is the most important one.

With kirikiri's patching system, if a file is marked for translation, it might have been updated in one of the append.xp3 files. If there are 14 of them, that potentially means checking 14 of them for other versions of that file to make sure the one marked for translation is the most updated version. In contrast, the complete set only contains 3 appends which are probably the same content as 14 files from the torrent versions. If they are not, then we can deal with that later.

I checked the 100% save included on the new torrent version with the installable version and it worked, so I doubt we are dealing with any severe incompatibilities between versions though. Really, we could base it on either version. However, using the complete set would make our work easier. Always make life easier for yourself I say!

That said, we do not really know if the one on archive.org/lebedev's upload really is the complete set, so we might have to use a different version later. A worthwhile risk I say!

If the translation patch we produce for that version is not compatible with the torrent version, then it should be simple enough to port the translation to it by caching the AI translations locally and re-running the automated workflow on the new files. As long as everything is sufficently automated, switching versions and applying the same translation to different versions should take very little effort.

In other words, we are up to possibly making 3 patches 1) torrent version 2) lebedev/archive.org/complete set, 3) steam/hikari field version. I am really hoping the same patch works on both the torrent version and complete set without any modifications, but getting it to also work on the steam version without any changes is highly unrealistic.

The repackage_v1 is based on the monobeno happy end installable version.

Future me talking here. Later on, the archive.org/levdev/installed version was discovered to be missing a few scenes, so I had to redo the repack using the DLC.iso those versions included. The end result was a repackage_v2 where all of the hashes for the game files matched the old and new torrents. That cut down the number of versions possible to only the Japanese Package/Download version and the Steam version. So back to two.

Loading patch_sp.xp3 from the monobeno base game (the preorder bonus dlc?) in the happy end version did not seem to unlock any new content. Looking through it, the images/scenes present inside of the patch_sp.xp3 archive seem to already be present in the happy end dlc, so it is excluded from the repackage, for now since it seems highly or entirely redundant.

Technically, the repackage_v2 is based on the installed version again, but I added the DLC from the append including overwriting the DLC/append files from the original monobeno happy end installation. There might be a repackage v3 if overwriting the first three appends turns out to not be a good idea later.

The repackage is a hash match of the new torrent version except for 2 main changes

- Fixed/restored support for the built in hash-check mechanism. supporttools.exe does not work properly unless the game is installed, and sigchk.exe cannot work unless included. The presence of sigchk.exe also adds an extra option to the supporttools.exe menu. I included the installed version of the support tools to retain as much of the functionality it was intended to have as reasonably possible since keeping a simple included file structure was just impossible. That ship sailed somewhere between the first and 14th DLC.xp3. As a side effect of including all of the intended files, the sigcheck tool can be used to verify all of the checksums of the repackage which proves the files in the repackage that are from the original game are 100% unmodified.
- I also changed the encoding of the -happyend- readme.txt from shift-jis to utf-8, which is why that hash no longer matches anything before that, but the contents are the exactly the same. Only the encoding changed, and it is a text file of course, so there is no risk posted to anyone by changing that hash.

There is still one option missing in supporttools.exe, the option to open the save data folder while under the influence of the .cf file. However, that is unnecessary if that folder is also one with the savedata in it. The original .cf file is in the backups/ directory along with a ~100% save. Creating new save data or using the 100% save will create a new savadata/ .cf/.cfu file. Including the vanilla .cf file in the base directory of the game will tell the game to create/use savedata under %userprofile%/Documents instead of in the game directory.

The repackage_v2 also contains 100% unmodified game files which means that the included sigcheck.exe tool should work correctly to prove the files were downloaded and extracted correctly. A complete save is available in the backups/ directory. The monobeno developer renamed sigcheck.exe to "ファイル破損チェックツール.exe", but reverting the change means that SupportTools.exe will no longer list the option to check the hashes as an option, so I left it alone ...this time.

How do I know the sigcheck.exe file was renamed? Right-click on "ファイル破損チェックツール.exe"->properties.

[sigchk.exe_properties]

Here is the table of contents for the included content. This assumes the included 100% save data unlocks everything.

Scenes (Main Menu)
Prologue
Scene 1-11
HScene 1-4

Sumi
Scene 1-18
Past 1-4
After 1-21
HScene 1-24

Alice
Scene 1-17
After 1-14
HScene 1-18

Natsuha
Scene 1-22
After 1-20
HScene 1-11

CGViewer
79 (prologue)
138 (sumi)
107 (alice)
120 (natsuha)
230 (H)
482 (all?) -> 483

SceneViewer
30 (Natsuha)
15 (sumi)
18 (alice)
23 (other)
20 (bonus)

The number of available CGs to view in the CGViewer changed from 482 to 483, but that might be an error because the formatting for the CGs became partly distorted. No scenes were added, and I did not see any changes just from that, but I also did not look around very much.

Let's just assume the old dlc file from the monobeno base game does not do anything useful when added to the monobeno happy end version. I'll continue to exlude it from the repackage until some information besides that one number suggests differently since it seems to distort the cg gallery.

There are also the original DLCs from the installation version to compare with the repackage version, but they seem to be a subset of the DLCs included in the extras disk/torrent version. Those DLCs were the main difference when going from repackage_v1 to repackage_v2.

At some point, it may become necessary to do a comparison of those vanilla DLCs included with the installed version of monobeno happy end by checking them against the above table of contents. Comparing no dlc at all on top of that would also allow figuring out what is going on with each individual file and allow comparing and contrasting with Steam/Hikari Field's version of Monobeno Happy End (censored to remove all 18+ content) before and after adding the 18+ content restoration patch.
 
Hikari Field's Monobeno release on Steam

This post is just summarizing information on the Hikari Field version which will not be used in this guide, until maybe the very end, so feel free to skip it.

The steam versions are heavily censored.
The DLC is not especially for 18+. It is only an expansion of story. All scenes related to 18+ has been cut from the Steam version.
The original Japanese version containing over 100 18+ scenes is available at DLSite "ものべの 全部入り".
An alternative way is using this patch for Steam version:
http://appendingpulse.jp/dl/monohe_patch/
But it only contains parts of all the 18+ scenes(about 50).
--by tetradecane1

There are 2 versions on steam, both are Hikari Field's chinese version. This one is the base game + optional happy end DLC, similar to the Japanese version we are using. Hikari field also published a deluxe/collectors version that has both the game + DLC sold together.

According to the publisher, HikariField, in the community page for the base Monobeno game,
Question: is there any different between this version and the deluxe one?
-Hoshiyomi
The differences between the two versions [regular vs deluxe] are as follows:

1. This version [regular base game version] needs to purchase additional DLC "Happy End" before it can be exactly the same as the deluxe version.
2. The unlocking order of this version of the story route is different from that of the deluxe version.
3. The steam Trading Cards and background configured in this version are different from those in deluxe version.

There is someone, Teriteri, maintaining patches, for the chinese versions of monobeno since the patch for the steam version keeps getting updated. The latest one at time of writing is 2025.

The patches are only applicable to the chinese translation for the Hikari Field version of the game. There are 4 patches in total.
- 1 chinese simplified for the base game
- 1 chinese traditional for the base game
- 1 chinese simplified for the game + happy end dlc
- 1 chinese traditional for the game + happy end dlc

Hikari Field explaining that the Base game + DLC is the same exact as their Deluxe version explains why there are 4 patches instead of 6 (2x base + 2x (base+dlc) + 2x deluxe=6). There are no content differences between the normal+dlc and the deluxe versions, so 4 patches makes sense here. Hopefully that did not involve too much wizardry by Teriteri.

Here are the descriptions for them and some translations.

https://appendingpulse.jp/dl/mono_patch/
"Shigeru Strange Talk" Scene Plot Patch(2019-01-25)

Content description
1. This patch is only applicable to the international Chinese version of "Mao Shen Qi Tan This Article". Please download the patches for the "Shinki Tale Sequel" DLC and the "Shinki Tale -HAPPY END- Collection Edition" from here.
2. After using this patch, the plot of the following chapters will change:

Alice:
Scene.09 Alice takes the initiative!
Scene.13 Alone with Alice

Qimei:
Scene.10 Spend time with you
Scene.12 The wish of the beauty

Summer Leaf:
Scene.12 Toru's uneasiness
Scene.16 The uneasiness of blending
Scene.21 Heart-to-heart connection

3. After using this patch, the "Plot Appreciation" function will be added to the appreciation mode, and the "CG Appreciation" function will add (or modify) related content.

How to use:
1. Download this patch and unzip it to get the patch.xp3 file.
2. Start the STEAM client and make sure you have updated "Mao Shen Qi Tan" to the latest version. 3.
Put the patch.xp3 file into the installation folder of "Mao Shen Qi Tan" and restart the game.
(Default installation path: C:\Program Files(x86)\Steam\steamapps\common\monobeno)

Warning statement
1. Please confirm that you are at least 18 years old before using this patch.
2. Please make sure that this patch does not violate the laws of your country or region before using this patch.
3. Please do not disseminate this patch file in violation of the laws of your country or region.

Download address

https://appendingpulse.jp/dl/monohe_patch/
Happy End-Scene Plot Patch (Updated on 2025-06-14)

Latest updates
2025-06-14 update:
- The Collection Edition of "Mao Shin Ki Tan -HAPPY END-" on Steam needs to be updated to be used for the 2025-06-14 update.
- The main story of "Mao Shen Qitan" + the DLC version of the future talk does not need to be updated to be used with the patch.

Content description
1. This patch is applicable to the main story of "Mao Shen Qi Tan" + the DLC version of "Mao Shen Qi Tan -HAPPY END-" collection version.
Please download the patch for this part of "The Tale of Mashinobu" here.
2. After using this patch, the plot of the following chapters will change (or add new chapters):

Alice:
Scene.09 Alice takes the initiative!
Scene.13 Alone with Alice
After.06 Give Alice sex education
After.08 Alice is doing sex education!?
After.11 Intentional intercourse is reproduction
After.13 My boobs

Qimei:
Scene.10 Spend time with you
Scene.12 The wish of the beauty
After.04 Play house as a baby
After.15 Beloved wife care

Summer Leaf:
Scene.12 Toru's uneasiness
Scene.16 The uneasiness of blending
Scene.21 Heart-to-heart connection
After.04 Massage to recover from fatigue!?
After.07 for the brother, for the bottle
After.09 Small wedding
After.11 Cat Princess Night
After.13 Sleeping summer leaves
After.15 The warmth of mineral springs
After.17 Natsuha's night raid
After.19 The long-awaited safety period

3. After using this patch, the "Plot Appreciation" function will be added to the appreciation mode, and the "CG Appreciation" function will add (or modify) related content.

How to use:
1. Download this patch and extract the patch2.xp3 file by unzipping it.
2. Start the STEAM client and make sure that "Mao Shen Qi Dan -Happy End-" has been updated to the latest version.
3. Put the patch2.xp3 file into the installation folder of "Happy End-" and restart the game.
(DLC version default installation path: C:\Program Files(x86)\Steam\steamapps\common\monobeno, Collection version default installation path: C:\Program Files(x86)\Steam\steamapps\common\monohe)

Warning statement
1. Please confirm that you are at least 18 years old before using this patch.
2. Please make sure that this patch does not violate the laws of your country or region before using this patch.
3. Please do not disseminate this patch file in violation of the laws of your country or region.

Download address
Patch Download (20250614 Update)
[...]

Any patch we make for the Hikari Field version would likely need to be done on top of the existing fan-made scene restoration patch since the base game on steam without the fan patch is uh... there is no nice way to refer to it.

Even assuming we are not supporting the base game without DLC, how many patches could be made for Hikari Field's Monobeno Happy End theoretically?
1. Maybe one for the japanese translation in the hikari field version, (no r18 content, jpn->eng translation)
2. then a second patch for the chinese translation, (no r18 content, chi->eng translation)
3. then a third patch for the chinese translation + the fan made patch. (some r18 content, chi->eng translation)

Doing a Chinese -> English translation seems like a terrible idea to me, but without that, there is no way to work with the existing patch.

How was that fan patch created anyway?

If Hikari field did not translate those scenes, then there is nowhere to port the scenes from except for the Japanese version which means the patch provider must have done a Japanese->Chinese(x2) translation of those scenes before adding them.

So theoretically, when/if we get around to creating a patch for the hikari field version, we should have (jpn->eng) AI translation mappings already for those scenes that were previously jpn->chi, which means we just need to map the chinese to the original japanese and recreate the cache based on that information. That would allow for chinese->eng translation that actually uses the AI translation from jpn->eng. That might be overly difficult to maintain though, so it might be a one off.

Well, I need to think about that more. Supporting the Hikari Field version is low priority since their version is defective anyway. Even their "deluxe" version is defective. Here is Hikari Field's description on steam for their deluxe version.

"Mao Shen Qi Tan -HAPPY END- Collection Edition" integrates "Mao Shen Qi Tan This Article" and "Mao Shen Qi Tan Sequel" into a whole. The Chinese version is produced using the final optimized story version, and includes both the PSV version of "Monobeno -pure smile-" and the PC version of "Monobeno -happy end-". It can be said to be the version that has collected the content of "Mao Shen Qitan" so far.

"the collected the content of 'Mao Shen Qitan' so far"? Lies! If it was comprehensive, then a patch to restore all the missing stuff would not be needed.

They probably meant that it is comprehensive to the story only version of Monobeno which is why they mentioned the pure smile version which is the heavily censored version Lose released on the Playstation Vita (PSV).

So anyway, that is pretty comprehensive basic information for the steam version, or rather, that is as much as I can get without peaking into the internals of the files and the fan patch. It is already known that the archives are very different from the japanese version from the comparison made when making the repackage.

Next I need to look at the DLSite versions to figure out what is going on there, but I will save that for later.
 
Setting up the Translation Project

So, let's create the following directory structure somewhere. The main "Monobeno" folder can go anywyere, in my case the desktop at %userprofile%/desktop. Use this exact structure for the subfolders or it will be overly difficult to follow along with this guide.

Code:
Monobeno
Monobeno\bin
Monobeno\extracts
Monobeno\MTL
Monobeno\MTL\scenario
Monobeno\MTL\images
Monobeno\MTL\images\raw
Monobeno\tools
Monobeno\tools\scripts

- 'bin' is short for 'binary' as in the location of files that are meant to execute on the processor and are associated with this title, e.g. the main game. The downloaded repackage belongs at "Monobeno\bin\Monobeno Happy End.7z" and "Monobeno\bin\Monobeno Happy End" (extracted), such that the main executable (.exe) is at "Monobeno\bin\Monobeno Happy End\ものべの-happy end-.exe". Lets download it to there and also download Garbro to Monobeno/tools.
- Monobeno\extracts has files extracted from Monobeno\bin.
- Monobeno\MTL is where all altered assets must be placed into.
- Monobeno\tools these are various executable tools that help with all aspects of translating this title.
- Monobeno\tools\scripts these are batch language .cmd files created to automate all aspects of the workflow required to translate this title. The goal is to have a script for every part, meaning that it should be possibe to fully reproduce all automated aspects of translating this title by running scripts.cmd in a command prompt consecutively.

Some aspects are not fully automatable yet, like image editing. Having ideal quality translated gui images is still a very manual process in 2025. If we are willing to drop the quality by a lot, then fully automated solutions do exist, like SLR Translator 2.0's SEP addon, AnyTrans, and the various commercial services compared by AnyTrans.

Quality patches can live forever, but poor quality ones will need to be replaced later as technology gets better or when a person willing and able to manually do quality work becomes involved. What sort of patches should we make? Should we focus on quality more or automation more? Just food for thought.

So anyway, let's figure out what engine this game is using. Garbro might be able to provide us with some hints or even a definitive answer. That said, the presence of .xp3 files strongly hints that this is some version of the kirikiri game engine. Which version exactly?

While in Garbro, Double click on Monobeno\bin\Monobeno Happy End\ものべの-happy end-.exe to attempt to read metadata from the executable file. Then RT_VERSION\00001 has this.

Code:
BLOCK "041103A4"
{
    VALUE "CompanyName", ""
    VALUE "FileDescription", "TVP(KIRIKIRI) 2 core / Scripting Platform for Win32"
    VALUE "FileVersion", "2.31.2013.330"
    VALUE "InternalName", "tvp2/win32"
    VALUE "LegalCopyright", "(KIRIKIRI core) (C) 1997-2008 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 "LegalTrademarks", ""
    VALUE "OriginalFilename", ""
    VALUE "ProductName", ""
    VALUE "ProductVersion", "1.0.0.0"
    VALUE "Comments", ""
    VALUE "PrivateBuild", ""
    VALUE "SpecialBuild", ""
}

So this is a kirikiri2 game, not kirikiriZ. I see. I see. That makes sense too. If I remember correctly, KirikiriZ was not released until 2015 and the earliest version of this game was released back in 2012/2013. They never updated the engine with any of the subsequent kirikiri game engine releases. There was no need to at that point since the game was already released.

Hopefully the game being kirikiri2 means this title also uses the older KAG3 (.ks) format for the scenario scripts. Those are really easy to work with compared to the newer e-mote (.psb/.txt.scn) format. Then again, the newer format is utf-8 compatible which makes it much easier to translate into languages with complex character sets, so the added complexity would help us translators a lot in the end despite being initially more work.

Next, let's browse around the archives. Our next task is to find... wait wait wait!

Let's run the game first. Always test to make sure the game works before moving further. Completely forgot to do that, so let's do it now. There is no point in going any further if the game does not work after all.

Close Garbro. Launch the game using ものべの-happy end-.exe with a Japanese locale emulator or with the region set to Japanese (Japan). After clicking around, this is the first line displayed. Let's save it to MTL\first_line.png.

[first_line]

The game works, and the first line was saved. Now we can close the game and browse around the .xp3 archives at our leasure.

Selecting bgimage.xp3 with no encryption and selecting one of the .jpg files makes it appear in Garbro's preview pane. That proves the archive was not encrypted. How convenient. Hopefully loading custom assets works the same way.

There are staff credits at data.xp3\image\staffroll that contains some translatable images. There are even more images in that folder that might need translation later.

How big is data.xp3? ~900 MB. That is large but not unreasonable to just extact every file. data.xp3 contains scenario\, AppConfig.tjs, startup.tjs, scn\, and uipsd\. We will need to either translate the files in those directories or at least look at them to get a handle on how the game's control flow is like, so let's just extract the entire data.xp3 folder to Monobeno\extracts\data.

Open data.xp3 in garbro -> ctrl + A to select all -> right-click -> extract. Save images as "as-is".

Why extract the images "as-is"?

If the images in the various formats were converted to .png and we translated them, then we would put the image in an otherwise working patch. At that point, we might wonder why the image is not loading when everything else is working.

The answer is because the image was not originally in .png format. The game might be expecting a .tlg image format or whatever. Which files were in .png and which ones are not? Are the files that were extracted as .png in their original format or were they converted into .png format by Garbro? We would have to check again for every single file. No thanks.

To prevent this ambiguity, it is better to exact files exactly unchanged. We can still preview any .tlg image files in garbro and convert them later if and only if necessary. We can organize them into files that need to be converted to .tlg and native .png files by using folders one time before processing the images later if needed.

While browsing around, I noticed that Monobeno Happy End\patch_append1.xp3 has a .scn.txt file at the very bottom. That is not really an e-mote file is it? Either way, Japanese characters in a scenario script file means that we are going to need to process the image with that name. Can the japanese name be displayed normally?

open a command prompt
windows key + R
cmd
[Enter]

Code:
chcp
[Enter]

if it prints 65001, then it is in utf-8 mode, so the command prompt can render japanese characters correctly if it is not then the two obvious options are to enable utf-8 mode at the command prompt or to change the locale of the computer to enable to command prompt to use an ansi code page that can display shift-jis encoded characters, cp932. The locale that can use cp932 is Japanese (Japan) if the local command prompt does not support utf-8, or whatever.

Using a locale emulator is not enough. The command prompt must be able to display file names correctly in order to work with them in automated workflows. For Monobeno, that means it must use code page 65001 or 932.

Here are some instructions for changing the command prompt to utf-8/65001 on Windows 10 1807 or newer, Windows 11. (recommended)
And here are DLSite's instructions for changing the region to Japanese (Japan). (alternative)
Pick either one of the options above. Remember to reboot to apply the settings.

Now lets get back on track. Using Garbro, we have identified the presence of a .scn.txt file with a japanese name and taken steps to make sure the local command prompt can display that file name correctly. That will probably be useful later when processing it.

Looking around some more, it does not look like there are any translatable files in the appends besides the .txt.scn, just lots of .ogg (audio) and .tlg (image) files, so I am just going to extract those to Monobeno\extracts\patch_append1, patch_append2\, and so forth. Right-click->extract

while extracting them, I noticed there was oftentimes a file called 'instreadme.txt'. That file contains a single string like '「ものべの -more smile- for Sumi」アペンドHシーンをインストールします。' 'more smile' is the name of some of the listed DLC. That provides us a way to reconcile the patch_append.xp3 files with vndb.org releases.

After extracting them all, I copied the files at extracts\data\scn to MTL\scenario\dialogue_scn_raw, but a problem cropped up while copying the patch_append\.txt.scn files.

We do not really want to loose the information of where each of the append\.txt.scn files comes from but that information is being lost by copying everything to the target dialogue_scn_raw folder. Most of the files in dialogue_scn_raw are from extracts\data\scn, so that is clear, but the same is not true for the appends. The information is important because the final patch should ideally have the files sorted inside of it using folders names after the source directories to keep everything nice and organized.

To keep track of the source directory information, we can either create lots of subfolders under MTL\scenario\dialogue_scn_raw\ to have the presence of those file system directories keep track of it, which would require adjusting the workflow scripts, or we could manually keep track of where every source file came from.

Creating a manual mapping is easier in this case.

1. Create a new directory under extracts\.
2. move all of the patch_append folders into that directory
3. Open up a command prompt (cmd.exe)
4. change into that directory

Code:
cd %userprofile%\desktop\monobeno\extracts\New Folder
tree /f > output.txt

- 'tree' will list the directory structure of whatever directory it is given. If used without a directory, it will start from the current directory and map all sub directories.
- '/f' means to also list all of the files within the subdirectory
- '>' means to redirect output from the previous command to a file, output is normally output to standard output or std.out but using > will redirect it to a file
- 'output.txt' is the name of the file for rediected output

If we open up output.txt, it contains that mapping that we will need later.

Code:
Folder PATH listing for volume Windows
Volume serial number is [redacted]
C:.
│  output.txt
│  
├─patch_append1
│      星辰ひめみや_sh_ご開祖ちゃんは委員長!.txt.scn
│      
├─patch_append10
│      夏葉中とありす_sh_ものべの、夜の大運動会.txt.scn
│      
├─patch_append11
│      夏葉とすみ_sh_ネココーロ.txt.scn
│      
├─patch_append12
│      ありす_sh_ありすのリハビリ☆チアガール!.txt.scn
│      ありす_sh_タイニィありすinアリス!.txt.scn
│      
├─patch_append13
│      夏葉と星辰ひめみや_sh_バス停の長い夕暮れ.txt.scn
│      
├─patch_append14
│      すみ_sh_可愛げなリュックと帽子.txt.scn
│      
├─patch_append2
│      つみ_sh_たそがれ、よあけ.txt.scn
│      
├─patch_append3
│      夏葉_sh_水着水着っ!.txt.scn
│      
├─patch_append4
│      すみ_sh_ヘクセンバナー∴すみ.txt.scn
│      
├─patch_append5
│      夏葉_sh_はじめてのブルマー.txt.scn
│      夏葉_sh_オオトメカマにわすれもの.txt.scn
│      夏葉_sh_残り物なら福袋.txt.scn
│      
├─patch_append6
│      ありす_sh_サージカルヒーラー†ありす.txt.scn
│      
├─patch_append7
│      ありす_sh_放課後アルトリコーダー.txt.scn
│      
├─patch_append8
│      すみ_sh_えみの洋服を選ぼう!.txt.scn
│      すみ_sh目隠し妻.txt.scn
│      
└─patch_append9
        ありすとすみ_sh_連・鎖.txt.scn

Open it up in Notepad++, convert the encoding to utf-8 if needed, and maybe delete that line that says output.txt. That is not a very good name because it does not describe its contents. Maybe a better name is something like dlc_mapping.txt?

The reason for the subdirectory is to not capture the directory structure of the data folder. We already know the directory structure of the data folder, just look at the files there, but feel free to run the tree command there too if you want. Now that the subdirectory has served its purpose, we can move the DLC folders back up one level and delete the subfolder.

Let's copy the extracts\patch_append\*.txt.scn files to MTL\scenario\dialogue_scn_raw if that has not been done already.

At this point, if not done already, it is time to open one of the .txt.scn files in Notepad++ and find this.

[notepad_scntxt]

A PSB file header. These really are [e-mote] files. *sigh*

Interestingly, this game is kirikiri2 but uses the newer e-mote dialogue script format. I have never seen that combination before actually. All other games that I have come across that have used e-mote dialogue scripts have been kirikiriZ titles, so Lose was ahead of their time.

In a way, this a good thing. The e-mote format is also UTF-8 compatible which makes international translations easier. Kirikiri2 usually only supports utf-16 le bom or shift-jis encoded .ks files otherwise. However, it does make working with them much more complicated than the comparatively simple .ks dialogue script format.

Thankfully, we worked with e-mote files before in the Tsumugi no Hanayome title, so we can just copy the general workflow from the project_files.7z, especially the tools/scripts, and update all of the paths. That should give a nice template to work from.

While we are adding files to the tools/ directory, download FreeMote. An updated version 4.1 was released at the time of writing this workflow guide. The Hanayome title was created with FreeMote 3.8 which means we may need to update the scripts in case there were any API or behavior changes since then.
 
Translating the first line of dialogue

So which of the extracted files contains the first line anyway? Let's open up the one with the lowest integer in the filename that does not appear to be from the happy end dlc, 'ありす_01_早老症の影.txt.scn'.

We *could* use the batch scripts to decode all of the files at once, but with the verison change from Freemote and adjusting the working_directory in all of the files, that is asking for problems. I would rather verify the behavior of Freemote Toolkit 4.1 seperately from the scripts to determine if they are still compatible before I run them.

The workflow in the hanayome scripts is .txt.scn -> .json -> .csv -> .json -> .txt.scn That means first step in that chain is this file.

Code:
@echo off

set working_directory=%userprofile%\Desktop\Monobeno
:: this converts the .txt.scn to .txt.scn.json
set tool=%working_directory%/tools/Ulysses-FreeMoteToolkit-v4.1.1/PsbDecompile.exe

set dialogue_scn_raw=%working_directory%\MTL\scenario\dialogue_scn_raw
set dialogue_scn_json=%working_directory%\MTL\scenario\dialogue_scn_json
set csv_directory=%working_directory%\MTL\scenario\csv
set dialogue_scn_json_translated=%working_directory%\MTL\scenario\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%"

So, the syntax is, PsbDecompile.exe file
There is also some code to move the *.resx.json files and *.txt.json to MTL\scenario\dialogue_scn_json. Is that behavior still appropriate for FreeMote toolkit v4.1?

So let's open up a command prompt to check.

Code:
cd %userprofile%\desktop\monobeno
set file=ありす_01_早老症の影.txt.scn
set working_directory=%userprofile%\Desktop\Monobeno
set tool=%working_directory%/tools/Ulysses-FreeMoteToolkit-v4.1.1/PsbDecompile.exe
"%tool%" "%file%"

FreeMote PSB Decompiler
by Ulysses, [email protected]
18 Plugins Loaded.

Done.

Hummm. Nothing happend? It says "Done." Maybe it created the output relative to the target? The script does say this.

Code:
move /y "%dialogue_scn_raw%\*.resx.json" "%dialogue_scn_json%\resx"

So extract.txt.scn_to_json2.cmd is moving files from the MTL\scenario\dialogue_scn_raw to MTL\scenario\dialogue_scn_json. If we open up MTL\scenario\dialogue_scn_raw, there is nothing there. Oh right, right. The command needs the correct path to the file, not just the filename.

Code:
echo "%tool%" "%file%"

prints

"C:\Users\User\Desktop\Monobeno/tools/Ulysses-FreeMoteToolkit-v4.1.1/PsbDecompil
e.exe" "ありす_01_早老症の影.txt.scn"

which means that it will not find the correct file while the command prompt is at C:\Users\User\Desktop\Monobeno without more information or changing the current directory. A better error message than 'Done.' would have been appreciated, but whatever. Let's fix that. The script fixes that issue by prepending %dialogue_scn_raw% to the file, so let's do the same thing.

Code:
set temppath=%working_directory%/MTL/scenario/dialogue_scn_raw
echo "%tool%" "%temppath%/%file%"

prints

"C:\Users\User\Desktop\Monobeno/tools/Ulysses-FreeMoteToolkit-v4.1.1/PsbDecompil
e.exe" "C:\Users\User\Desktop\Monobeno/MTL/scenario/dialogue_scn_raw/ありす_01_
早老症の影.txt.scn"

That should work now. Remove the echo in front of the previous command and run it again

Code:
"%tool%" "%temppath%/%file%"

Decompiling: ありす_01_早老症の影.txt
Done.

And both ありす_01_早老症の影.txt.json and ありす_01_早老症の影.txt.resx.json now exist at MTL\scenario\dialogue_scn_raw. Let's move those to MTL\scenario\ temporarily. The .resx.json just contains metadata.

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

CryptKey: none. Is that true?

If we open up ありす_01_早老症の影.txt.json, it appears to be in plaintext which makes working with the e-mote scripts easier. Remember that e-mote .psb files support weak encryption, so they potentially require having to figure out the encryption keys via brute force or some other method. These are not encrypted, so we can just skip that step.

What was the first line again?

[first_line]

Does that line appear in the file? Well, we cannot search for it as long as that line is a picture, so lets ocr it using manga_ocr using ocr_text_recognition.py as a wrapper for that python library. If you have not already, then set up manga_ocr as per these or these instructions. Previously, that script was called ocr.py, but I recently created ocr_tools to store text detection and text recognition scripts and ocr.py was renamed during that process.

Code:
cd %userprofile%\desktop/Monobeno\tools
git clone https://codeberg.org/entai2965/ocr_tools.git
cd ocr_tools
python --version
python ocr_text_recognition.py --help
python ocr_text_recognition.py
set working_directory=%userprofile%\Desktop\Monobeno
python ocr_text_recognition.py "%working_directory%\mtl\first_line.png" -o

That prints out 『ここからのペの』なんかにもないねーっ. Does that string appear in ありす_01_早老症の影.txt.json? No it does not, and neither does either half of the string. Humm. Maybe we have the wrong file? Since the workflow is mostly validated now, let's try running the batch script and see if it works. That would allow us to search in N++ easier by allowing us to search multiple files at once.

Code:
set workingdir=%userprofile%\desktop\monobeno
cd %workingdir%\tools\scripts
dir
extract.txt.scn_to_json2.cmd

Some of those individual scripts took a very long while to process (>1 second). That is mildly terrifying for how long this game might be. Then again... vndb.org did try to warn us.

So anyway, MTL\scenario\dialogue_scn_json should have lots of .json files now, 204 of them. There is a maximum number of files that N++ can open at once per instance of 100. So just be aware that selecting them all and opening then in N++ will start ~3 instances, each containing a subset of the files. Now let's search for 『ここからのペの』なんかにもないねーっ again. Some of those searches took a while, ~2 seconds, but no hits for either the full string or each half of the substrings.

Is the ocr even correct? It is kind of a strange font. Looking at first_line.png again, some of the characters are clearly not correct. Let's try piecing together the search string together based upon the ocr.

Quite a few of the characters inside of the quotes are incorrect, so let's search using the second half of the string which is, なんかにもないねーっ. Let's search for ないねー. That was the largest number of consecutively correct characters that I found.

Searching for that gave a few results. The one in 共通_01_プロローグ.txt.json looks the most promising.

[notepad_search_firstline]

Code:
Line 32767:       "texts": [["夏葉",null,"「ここがものべの? なぁんにもないねー」",[{

If we manually check each character from the search result against the image, we have a match! 共通_01_プロローグ.txt.json contains the first line of the game. Is that the same file we demo'd earlier? Earlier we searched ありす_01_早老症の影.txt.json which is not the same file. So we were way off and searching in the wrong file.

In total, there are 207 scripts in MTL\scenario\dialogue_scn_raw. 207 is from 189 scripts at data.xp3\scn and + 18 files from the 14 DLC/appends. Some of the DLCs contain multiple .txt.scn scripts. The 207 files produce 204 .json files when going from .txt.scn to .txt.json. Maybe charvoice.scn, classlist.scn, and scenelist.scn need different processing or something? Hopefully they do not contain translatable strings, so we can keep ignoring them, but if something in the game ends up not translated, then we might have to figure out how to work with those three files.

In total, that comes out to 639MB worth of dialogue scripts when in .json format. Yikes. While I desire to ignore the length of this game, the reality of the game's length is becoming impossible to ignore now that we have started processing the scripts.

Now that we have the correct file, the correct string, and the correct line, we can test translating it. Let's copy 共通_01_プロローグ.txt.json to MTL\scenario temporarily and translate it.

Code:
Line 32767:       "texts": [["Natsuha",null,"「Is this the place? I don't see anything.」",[{

- Mapping 夏葉 to Natsuha came from vndb's character page.
- English text should use double quotation marks for dialogue instead of 「 」, but we can deal with that later. Let's just get the proof of concept working.

The workflow looks like

data.xp3->raw.txt.scn->raw.txt.json->.csv->translated.txt.json->translated.txt.scn->patch.xp3

For the proof of concept, we skipped the .csv step. and which means we are at the translated.txt.json->translated.txt.scn step. How do we have to convert .txt.json to .txt.scn again? Here is the insert.json_to_psb.cmd from the Hanayome translation with adjusted variable paths.

Code:
@echo off

set working_directory=%userprofile%\Desktop\Monobeno

:: this converts the .txt.scn to .txt.json
::set tool="%working_directory%/tools/Ulysses-FreeMoteToolkit-v4.1.1/PsbDecompile.exe"
:: this converts the .txt.json to .txt.scn
set tool="%working_directory%/tools/Ulysses-FreeMoteToolkit-v4.1.1/PsBuild.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"
:: this converts the strings in the csv from kanji/kana to romaji
::set tool=python "%working_directory%/tools/translation_tools/romaji.py"
:: this translates the strings in the csv
::set tool=python "%working_directory%/tools/translation_tools/sugoi_mtl.py"

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

set character_names=%working_directory%/Monobeno.character_names.csv

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

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

There is some code for working with Tsumugi no Hanayome left in the script since the tool to replace it does not actually exist yet. The main logic of the script is

Code:
%tool% -v 3 "%dialogue_scn_json_translated%/%%i"

The -v 3 part is probably the .psb version. For monobeno, let's change that to 2 since that is what the .resx.json said earlier and create a small demo based on the above script's logic.

Code:
set working_directory=%userprofile%\Desktop\Monobeno
set tool="%working_directory%/tools/Ulysses-FreeMoteToolkit-v4.1.1/PsBuild.exe"
set file=共通_01_プロローグ.txt.json

%tool% -v 2 "%working_directory%/MTL/scenario/%file%"

That prints out this.

Code:
C:\Users\User>%tool% -v 2 "%working_directory%/MTL/scenario/%file%"
FreeMote PSB Compiler
by Ulysses, [email protected]
18 Plugins Loaded.

Compiling 共通_01_プロローグ.txt ...
Compile 共通_01_プロローグ.txt done.
Done.

C:\Users\User>echo %tool% -v 2 "%working_directory%/MTL/scenario/%file%"
"C:\Users\User\Desktop\Monobeno/tools/Ulysses-FreeMoteToolkit-v4.1.1/PsBuild.exe
" -v 2 "C:\Users\User\Desktop\Monobeno/MTL/scenario/共通_01_プロローグ.txt.json"

So it says it did something, but nothing changed at Monobeno/MTL/scenario. However, a file was created at %userprofile% called "共通_01_プロローグ.txt.psb".

- PsbDecompile.exe produced output relative to the target.
- PsBuild.exe produced output relative to the current directory.

That difference is quirky, inconsistent behavior. I do remember vaguely that FreeMote toolkit 3.8 also had this strange behavior too. Looking closer, PsbDecompile starts with "Psb" but PsBuild starts with "Ps". It should be named "PsbBuild.exe" for consistency. So both the behavior and the names are inconsistent.

Maybe it was kept for backwards compatibility? However, fixing strange behavior like this could have been done when changing the freemote major release from 3.x to 4.x according to semantic versioning and common sense.

Maybe the developer does not consider this inconsistent behavior to be a bug?

This storage behavior is probably intentional since there are people who want to keep the source files and the unpacked files in the same directory and then run the PsBuild.exe script on top of that. If PsBuild.exe produced output relative to the target, then it would overwrite the original files, which is not great, but then again, neither is running everything from the same folder, so that should not really be a concern for the original developer, certainly not enough to introduce and maintain an inconsistent interface. Even then, doing what the user said to do is not an incorrect decision from a programming perspective, even if the original files end up getting overwritten.

Perhaps the developer is one of those people who prefers mixing different types of files in the same directory and wrote their tool in a way to not mess up their own workflow?

Putting everything in the same folder
- seems incredibly messy which makes it hard to understand where each file is in the translation pipeline
- makes it more difficult to undo any errors created when a script fails part way through a batch
- and makes it difficult to regenerate files from a previous step as opposed to from scratch

That is why I split everything into different folders. But well, as long as it works I guess. Moving on.

The code in insert.json_to_psb.cmd printed above also has code for changing to the destination folder, and then renaming the produced files. We can mimic that behavior by just renaming the file and moving it to Monobeno\MTL\scenario\.

Next, we need to repack this file in a way for the game engine to read it. Luckily, kirikiri supports patches, so this is very simple. What should the patch be named though, how does this game load assets anyway, and in what order? What is the control flow for Monobeno?

Remember from the previous guides that the default order for the kirikiri game engine if the developer did not customize it is start.tjs->status.tjs->initialize.tjs. However, kirikiri is an open source game engine and developers are free to do things however whatever they want, so do not take that load order as absolute.

extracts\data\system\Initialize.tjs is a great start and so is reading data\startup.tjs and scenario\start.ks. Monobeno does not appear to have a status.tjs at first glance. This is from Initialize.tjs.

Code:
// パッチアーカイブ(※パッチの中身は平坦展開でパッチ内サブフォルダは使えない)
useArchiveIfExists("patch.xp3");

// 追加のパッチ用アーカイブの検索
for(var i = 2; ; i++)
{
    // パッチ用アーカイブ patch2.xp3, patch3.xp3 ... がある場合はそちらを
    // 優先して読み込むように
    if(Storages.isExistentStorage(System.exePath + "patch" + i + ".xp3"))
        Storages.addAutoPath(System.exePath + "patch" + i + ".xp3>");
    else
        break;
}

[...]

if (convertPackMode) {
    Scripts.execStorage("convertPackMain.tjs");
} else if (convertMode) {
    //kag.visible = false;
    Scripts.execStorage("convertMain.tjs");
} else {
    kag.process("first.ks");
}

The part after[...] is at the very end of the file, so it looks like first.ks may or may not exectute after Initalize.tjs. Does it? That depends on if convertPackMode and convertMode evaluate to true or false. Let's ignore that for now.

The key line here is the second one where the game engine says it wants the patch named patch.xp3. How vanilla. I also started looking around and found data\main\Storages.tjs, always an interesting read. At the bottom of Storages.tjs is this interesting snippet of code.

Code:
@if (PACKED)
{
    // 予約特典用
    useArchiveIfExists("patch_sp.xp3");

    // 追加シナリオ用
    for (var i = 1; i < 100; i++) {
        useArchiveIfExists(@"patch_append${i}.xp3");
    }
}
@endif

In other words, this is the code that loads the patch_append1.xp3, patchappend2.xp3 files. It looks hardcoded to check the special case of patch_sp.xp3 and then every file from patch_append1.xp3 to patch_append99.xp3 regardless of any do not exist in between. We could name our patch as patch_append99.xp3 if we wanted it to load and not conflict with any of the existing dlc which only goes up to 14.

After the game starts, if we load our translated .txt.scn too early, then the files could potentially get overwritten by the game loading the append .xp3 files, at least for .txt.scn dialogue scripts that translate the DLC. What is the order of those patches loading? In other words, when does Storages.tjs get executed which loads those patches?

That information is probably in the Initialize.tjs file. Initialize.tjs has this.

Code:
// カスタムフォルダ登録処理スクリプトを実行(※パッチの前に評価すること)
if(Storages.isExistentStorage("Storages.tjs"))
    Scripts.execStorage("Storages.tjs");

// パッチアーカイブ(※パッチの中身は平坦展開でパッチ内サブフォルダは使えない)
useArchiveIfExists("patch.xp3");

// 追加のパッチ用アーカイブの検索
for(var i = 2; ; i++)
{
    // パッチ用アーカイブ patch2.xp3, patch3.xp3 ... がある場合はそちらを
    // 優先して読み込むように
    if(Storages.isExistentStorage(System.exePath + "patch" + i + ".xp3"))
        Storages.addAutoPath(System.exePath + "patch" + i + ".xp3>");
    else
        break;
}

So the game engine loads Storages.tjs prior to loading any of the patch.xp3, patch2.xp3, etc. files. That means the load order is Storages.tjs->patch_append[].xp3-> patch.xp3 -> patch2.xp3 -> patch3.xp3. The patch.xp3 loading code will also break if the next patch in the series is not found starting from 2, meaning that patch3.xp3 will not load unless patch2.xp3 is found but patch.xp3 and patch2.xp3 will always load, unlike the append loading code which was unconditional for all .xp3.

The append/DLC loading code probably loads the patches unconditionally because Lose wanted people to who bought mis-matched DLC, like DLCs 3 and 9, to still have their DLC work even if they had not purchased every previous DLC.

If we did load our patch as patch_append99.xp3, it would load prior to any other patch.xp3. Do we want that? Humm. Well, we can decide that later. For now, let's load everything into patch.xp3 and hope for the best.

The original files are not encrypted, so let's try to create the patch in the same way.

1. Open Garbro.
2. navigate to Monobeno\MTL\scenario
3. right click or F3 on 共通_01_プロローグ.txt.scn-> create archive
4. Archive format: xp3
5. And lets set the output to where the main game is located at Monobeno\bin\Monobeno Happy End\patch.xp3

[garbro_createxp3_unencrypted]

Then start the game with a Japanese local emulator or while the system's region is set to Japanese (Japan).

[dialogue_translation]

Progress!
 
Testing dialogue word wrapping

Here are a couple nitpicks about the previous screenshot.

- the translation is not displayed using double quotes as in the norm in english. Let's try changing that by escaping the double quotes in the .json by prepending \ to them as in \".
- Does xp3pack.exe from KirikiriTools work for this title? Garbro works, but xp3pack.exe has a command line interface which makes it easier to automate using a shell script. Garbro probably has a cli too to create xp3 files, but I can't be bothered to figure out how to use it yet, not when xp3pack.exe is readily available, has a good chance of working, and I know how to use it already.
- how many characters can be included per line for a typical sentence when using the default font at the default point size? This is useful for applying monospace based word wrap automatically which is very important information for me since I have not written the dialogue script extraction tool yet and I need that information to write it.

Anyway, let's close Monobeno again, and see if this works

Code:
Line 32767:       "texts": [["Natsuha",null,"\"Is this the place? I don't see anything. Is this the place? I don't see anything. Is this the place? I don't see anything. Is this the place? I don't see anything. Is this the place? I don't see anything. Is this the place? I don't see anything.\"",[{

Code:
::recreate the .txt.psb file with the updated translation
%tool% -v 2 "%working_directory%/MTL/scenario/%file%"
::rename the .txt.psb to .txt.scn and move it to MTL/scenario/New Folder
::open up a new command prompt
cd desktop\Monobeno
tools\xp3pack\Xp3Pack.exe
tools\xp3pack\Xp3Pack.exe "mtl\scenario\new folder"
dir mtl\scenario
move "mtl\scenario\new folder.xp3" "bin\Monobeno Happy End\patch.xp3
y + Enter

Start Monobeno again.

[first_line_debug]

Only 2 lines? That is incredibly stingy since English tends to be longer than Japanese. That might present some problems for translation actually.

That font is so odd too. It is possible to change the font in the config screen.

[first_line_debug2]

But most fonts display even less text though. Also, if we change the font for better word wrapping purposes, then we would need to document that change so people know to use that particular font. It might be possible to integrate that change into the patch, but changing game engine code is not ideal unless truly required.

The history log shows more at least.

[first_line_debug_historylog]

Code:
python -c "print(len(\"Is this the place? I don\'t see anything. Is this the place? I don\'t see anyt\"))"
#prints 76

76 characters is fairly generous. Hanayome and Koakuma-chan were only at about 50/51 characters per line, but had a max of 3 lines. Assuming 2 lines wrapped at 70-72 to provide some wiggle room for lines with lots of captial letters, that becomes 140-144 characters total per dialogue line. The previous Kirikiri titles allowed 50*3 = ~150-153 characters per dialogue box, so Monobeno's max characters per dialogue box is not that much shorter really. I still want that third line, but we can deal with that later since changing the game engine scripts would likely be necessary to add that feature.

[first_line_debug3]

Not necessary! It looks like the engine just displays lines 2 at a time and clears the screen before appending the rest. Let's call this feature 'line continuation'. That does raise some very complicated questions about how Monobeno's line continuation system will interact with word wrap though.

Is there a way to say, "always break here" in a way that is compatible with the built in line continuation algorithm? In Hanayome's e-mote format, that was done by putting \n in the dialogue, but there was no line continuation there, the text would just run off the screen. A few lines later is a text entry like this.

Code:
          }],[null,null,"駅舎から飛び出した夏葉が、とんでもないことを大声で言う。\n周りにちらほら人もいるのに――",null,192,{

It looks like the convention of using \n to indicate a new line also works here. Well since \n for newline works, I can test different word wrap methods until it all works properly.

However, getting word wrap perfect is more of a nitpick before the final release of the patch than something that needs to be perfected at this stage. As long as the characters display on screen, we can move on for now to outline the other key aspects of translating this game.

Later testing revealed that the line continuation code works harmoniously with \n in the e-mote files to split the lines into pairs. No problems in sight! I really have to hand it to Lose here. They did a very good job implementing their automatic new line system + the line continuation code to also be compatible with hardcoded newlines in the e-mote dialogue script files. It all works together as a human thinks it should. That level of polish is pretty rare among kirikiri developers.

Here is the code I used to test word wrap if anyone is curious. Add \n to the dialogue line, save the .txt.json file, and run the script to see the changes reflected right away.

Code:
::this script does not work on older command prompts unless the text file is encoded as shift-jis because the data in %file% gets distorted
@echo off

set working_directory=%userprofile%\Desktop\Monobeno
set game_directory=%working_directory%\bin\Monobeno Happy End
set tool=%working_directory%/tools/Ulysses-FreeMoteToolkit-v4.1.1/PsBuild.exe
set pack_exe=%working_directory%/tools\xp3pack\Xp3Pack.exe
set scripts_dir=%working_directory%/mtl/scenario
set file=共通_01_プロローグ

::pushd does not support / outside of quotes
pushd "%scripts_dir%"

::recreate the .txt.psb file with the updated translation
echo "%tool%" -v 2 "%working_directory%/MTL/scenario/%file%.txt.json"
"%tool%" -v 2 "%working_directory%/MTL/scenario/%file%.txt.json"

::rename the .txt.psb to .txt.scn and move it to MTL/scenario/New Folder
if not exist "%file%.txt.psb" (echo .psb output does not exist
goto end)
move /y "%file%.txt.psb" "New Folder\%file%.txt.scn"

echo "%pack_exe%" "new folder"
"%pack_exe%" "new folder"

if not exist "new folder.xp3" (echo "new folder.xp3" does not exist
goto end)
echo move /y "new folder.xp3" "%game_directory%\patch.xp3"
move /y "new folder.xp3" "%game_directory%\patch.xp3"

echo updated patch.xp3

:end
popd
 
Automating the dialogue translation 1

The next step is to fully automate the workflow for extracting and removing strings. That will allow producing the initial MTL that is only useful for debugging. That will probably take more than a reasonable amount of time given the length of Monobeno which I am slowly coming to terms with. After that, we canis translating the title, game engine strings, and gui images, including the curved ones. *sigh* This might be a while. For now, I have to go write Monobeno_DialogueExtractInsertTool.py...

And we are back once sentence later, and the extract portion of MonobenoHappyEnd_DialogueExtractInsertTool.py is now written. Time travel is amazing, right?

open a command prompt
Code:
cd %userprofile%\desktop\monobeno\tools
git clone https://codeberg.org/entai2965/translation_tools

If already downloaded, the git repository can also be updated like this

Code:
cd translation_tools
git add *
git stash
git pull

Unfortunately, the above commands do not actually work, at least for me, since the tool has not been uploaded yet. There are some downsides to time travel it seems.

The Monobeno dialogue tool should be at "translation_tools/games/Monobeno Happy End/MonobenoHappyEnd_DialogueExtractInsertTool.py" Feel free to inspect the source, or rather, please read it yourself to make sure it is safe before running it. Once you are happy, run it with --help for the usage help.

Code:
python translation_tools/games/Monobeno Happy End/MonobenoHappyEnd_DialogueExtractInsertTool.py --help

Code:
usage: MonobenoHappyEnd_DialogueExtractInsertTool.py [-h] [-s SPREADSHEET]
[-o OUTPUT]
[-cn CHARACTER_NAMES]
[-c COLUMN] [-w WORDWRAP]
[-wf WORDWRAP_FONT]
[-wfe WORDWRAP_FONT_ENCODING]
[-wp WORDWRAP_POINT_SIZE]
[-cd CSV_DIALECT] [-v]
[-d] [-t]
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 or folder 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, the 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', for batches, add the spreadsheet
extension to change the output format ['.csv', '.ods',
'.tsv', '.xls', '.xlsx']
-o OUTPUT, --output OUTPUT
the output file name or folder 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 sane values are 30-80 characters, if accurate
word wrapping is enabled using wordwrap_font, this is
instead interpreted as the maximum pixel length, sane
values for max pixel length are 400+
-wf WORDWRAP_FONT, --wordwrap_font WORDWRAP_FONT
optional, enable accurate word wrapping by providing
the full path to the .ttf font that will display the
text, requires the PIL image library at >= 8.x, python
-m pip install pillow
-wfe WORDWRAP_FONT_ENCODING, --wordwrap_font_encoding WORDWRAP_FONT_ENCODING
optional, the encoding of the .ttf font, can be unic
for Unicode, sjis for shift-jis, big5, see more
options at https://hugovk-pillow.readthedocs.io/en/sta
ble/reference/ImageFont.html#PIL.ImageFont.truetype
-wp WORDWRAP_POINT_SIZE, --wordwrap_point_size WORDWRAP_POINT_SIZE
optional, for accurate word wrapping, this is the
point size to use with the .ttf font, default is 12
-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
-t, --test read input and process data but do not write any
output

The syntax is
- python tool.py extract file.json
- python tool.py insert file.json

Now we just have to wrap it in a shell script and run it the command prompt. However, it is always more than just mildly scary to run batch scripts on data directly, especially without confirming the invocation syntax first.

Handling paths correctly is oddly difficult for developers, but even if the program works perfectly, messing up a path at runtime can occasionally delete or overwrite lots of files, or put them in the wrong place. Personally, I go out of my way to not overwrite files, but I trust nothing without testing it first, especially my own code.

Let's iron out the exact syntax, and whether the program works or not so I can fix the bugs while writng this guide, by processing a subset of the data. This is similar to how we used PsbDecompile.exe and PsbBuild.exe earlier. It was only after confirming the syntax and behavior that we ran the .cmd files.

open a command prompt

Code:
set working_dir=%userprofile%/desktop/monobeno
set tool=python "%working_dir%/tools/translation_tools/games/Monobeno Happy End/MonobenoHappyEnd_DialogueExtractInsertTool.py"
set file=%working_dir%/mtl/scenario/dialogue_scn_json/ありす_01_早老症の影.txt.json
%tool%
%tool% extract
%tool% extract "%file%" --test
%tool% extract "%file%"
%tool% extract "%file%"

Code:
C:\Users\User\Desktop>%tool% extract
MonobenoHappyEnd_DialogueExtractInsertTool.py: error: the following arguments ar
e required: inputfile

C:\Users\User\Desktop>%tool% extract "%file%" -t
reading C:\Users\User/desktop/monobeno/mtl/scenario/dialogue_scn_json/ありす_01_
早老症の影.txt.json
reading json ありす_01_早老症の影.txt
no scene title found for scene number 0
scene title 早老症の影 for scene 1
unable to find the following speakers dict_keys(['すみ', '夏葉大', '夏葉', 'あり
す', '透', '飛車角', '尚武', '村人', '案内音声'])
len(extracted_string_list), 457

C:\Users\User\Desktop>%tool% extract "%file%"
reading C:\Users\User/desktop/monobeno/mtl/scenario/dialogue_scn_json/ありす_01_
早老症の影.txt.json
reading json ありす_01_早老症の影.txt
no scene title found for scene number 0
scene title 早老症の影 for scene 1
unable to find the following speakers dict_keys(['すみ', '夏葉大', '夏葉', 'あり
す', '透', '飛車角', '尚武', '村人', '案内音声'])
len(extracted_string_list), 457
wrote C:\Users\User/desktop/monobeno/mtl/scenario/dialogue_scn_json/ありす_01_早
老症の影.txt.json.csv
wrote C:\Users\User\desktop\monobeno\mtl\scenario\dialogue_scn_json/character_na
mes.csv

C:\Users\User\Desktop>%tool% extract "%file%"
spreadsheet already exists, C:\Users\User/desktop/monobeno/mtl/scenario/dialogue
_scn_json/ありす_01_早老症の影.txt.json.csv

--test/-t means to read files and process input but to not make any changes to the filesystem.

So it wrote ありす_01_早老症の影.txt.json.csv and character_names.csv at Monobeno\MTL\scenario\dialogue_scn_json. The tool creates output relative to the target and will refuse to run if the target spreadsheet already exists. -s should work to change the output path of the spreadsheet.

Code:
%tool% extract "%file%" -s "%working_dir%/mtl/scenario/csv"

Code:
unrecognized spreadsheet extension, /csv from input.spreadsheet C:\Users\User\desktop\monobeno/mtl/scenario/csv

So it wants the full path to the spreadsheet.csv, not just the path to the folder.

Code:
C:\Users\User\Desktop>%tool% extract "%file%" -s "%working_dir%/mtl/scenario/csv/ありす_01_早老症の影.txt.json.csv"

Code:
wrote C:\Users\User/desktop/monobeno/mtl/scenario/csv/ありす_01_早老症の影.txt.json.csv
wrote C:\Users\User\desktop\monobeno\mtl\scenario\csv/character_names.csv

This version supports batches, so it should process all of the .json files if %file% is actually a directory. Let's create another script at monobeno/tools/scripts/extract.json_to_csv.cmd. This one was also copied from the Hanayome project files, but the for loop that was used for processing input was removed since that logic is now integrated into the script.py.

In the following wrapper_script.cmd for the tool.py script, %dialogue_scn_json% gets overwritten and instead points to a temporary folder at monobeno\MTL\scenario\New Folder (2). New Folder (2) has some (2) of the scripts from MTL\scenario\dialogue_scn_json. The idea is to process a small subset of the data before running it on the entire dataset to validate tool.py and script.cmd. -s also points to %csv_directory% instead of a file for batch processing.

Code:
@echo off

set working_directory=%userprofile%\Desktop\Monobeno
set tool=python "%working_directory%/tools/translation_tools/games/Monobeno Happy End/MonobenoHappyEnd_DialogueExtractInsertTool.py"

set dialogue_scn_raw=%working_directory%\MTL\scenario\dialogue_scn_raw
set dialogue_scn_json=%working_directory%\MTL\scenario\dialogue_scn_json
set dialogue_scn_json=%working_directory%\MTL\scenario\New Folder (2)
set csv_directory=%working_directory%\MTL\scenario\csv
set dialogue_scn_json_translated=%working_directory%\MTL\scenario\dialogue_scn_json_translated

set character_names=%working_directory%/mtl\scenario\csv\character_names.csv

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

%tool% extract "%dialogue_scn_json%" -cn "%character_names%" -s "%csv_directory%"

The script wants a character_names.csv spreadsheet. It output a template for one during the previous command, so let's reuse it. It looks like this right now.

Code:
raw_name,localized
すみ,
夏葉大,
夏葉,
ありす,
透,
飛車角,
尚武,
村人,
案内音声,

Let's quickly add romaji as psudo translations.

romaji.py is part of translation_tools. If you were following the guides up until now, it should have already been set up and unidic downloaded. If not, set it up now.

start a command prompt
Code:
cd %userprofile%\desktop\monobeno
python tools\translation_tools\romaji.py --help
python -m pip install cutlet fugashi unidic
python -m unidic download
python tools\translation_tools\romaji.py --help

once cutlet is installed and unidic has finished downloading, run romaji.py on the character_names.csv spreadsheet

Code:
cd %userprofile%\desktop\monobeno
python tools\translation_tools\romaji.py --help
python tools\translation_tools\romaji.py mtl\scenario\csv\character_names.csv -s

Code:
wrote mtl\scenario\csv\character_names.csv

That produces this as output.

Code:
raw_name,localized,cutlet_hepburn
すみ,,Sumi
夏葉大,,Kayoudai
夏葉,,Kayou
ありす,,Arisu
透,,Tooru
飛車角,,Bisha kaku
尚武,,Shoubu
村人,,Murabito
案内音声,,Annai onsei

Remember that a comma is a new column and the output for character_names.csv should be in the second column. That means the output is off by one since the cutlet_hepburn output from romaji.py is currently in the third column. Let's open the file in LibreOffice Calc and swap the order of the second and third columns. Other spreadsheet software might work too.

To change the order of columns in Libre Office Calc, do the following.

1. Left click on the letter of the column that should be moved. The entire column should now be selected.
2. Hold the left Alt button on the keyboard.
3. While holding the left Alt button, left click and hold on any cell in the selected column.
4. Drag the column to the correct slot as indicated by the vertical line on left.
5. Let go of the left mouse button.
6. Let go of the alt button on the keyboard.

Code:
raw_name,cutlet_hepburn,localized
すみ,Sumi,
夏葉大,Kayoudai,
夏葉,Kayou,
ありす,Arisu,
透,Tooru,
飛車角,Bisha kaku,
尚武,Shoubu,
村人,Murabito,
案内音声,Annai onsei,

Okay, now we can run the script to extract.json_to_csv.cmd.

Code:
cd %userprofile%/desktop/monobeno
tools\scripts\extract.json_to_csv.cmd

Code:
C:\Users\User\Desktop\Monobeno>tools\scripts\\extract.json_to_csv.cmd
reading character_names.csv
reading C:\Users\User\Desktop\Monobeno\MTL\scenario\New Folder (2)/ありす_01_早老症の影.txt.json
reading json ありす_01_早老症の影.txt
no scene title found for scene number 0
scene title 早老症の影 for scene 1
len(extracted_string_list), 457
wrote C:\Users\User\Desktop\Monobeno\MTL\scenario\csv/ありす_01_早老症の影.txt.json.csv
reading C:\Users\User\Desktop\Monobeno\MTL\scenario\New Folder (2)/ありす_02_病気と向き合う姿勢.txt.json
reading json ありす_02_病気と向き合う姿勢.txt
no scene title found for scene number 0
scene title 病気と向き合う姿勢 for scene 1
scene title 病気と向き合う姿勢 for scene 2
unable to find the following speakers dict_keys(['???', '菜穂子'])
len(extracted_string_list), 497
wrote C:\Users\User\Desktop\Monobeno\MTL\scenario\csv/ありす_02_病気と向き合う姿勢.txt.json.csv
reading character_names.csv
moved C:\Users\User\Desktop\Monobeno\character_names.csv to C:\Users\User\Deskto
p\Monobeno\character_names.csv.backup.csv
wrote C:\Users\User\Desktop\Monobeno\character_names.csv

C:\Users\User\Desktop\Monobeno>tools\scripts\extract.json_to_csv.cmd
spreadsheet already exists, C:\Users\User\Desktop\Monobeno\MTL\scenario\csv/ありす_01_早老症の影.txt.json.csv
spreadsheet already exists, C:\Users\User\Desktop\Monobeno\MTL\scenario\csv/ありす_02_病気と向き合う姿勢.txt.json.csv
all spreadsheets already exist

So it wrote .csv output for 2 files to /mtl/scenario/csv and updated character_names.csv If run again, it did nothing since the spreadsheets it was trying to create already all exist. Here is what the updated character_names.csv looks like now.

Code:
raw_name,cutlet_hepburn,localized,
すみ,Sumi,,
夏葉大,Kayoudai,,
夏葉,Kayou,,
ありす,Arisu,,
透,Tooru,,
飛車角,Bisha kaku,,
尚武,Shoubu,,
村人,Murabito,,
案内音声,Annai onsei,,
???,,,
菜穂子,,,

Getting character_names.csv to update correctly while also avoiding unnecesary writes took such a long time.

Anyway, now that we are confident the script.py and the .cmd are both working as intended, we can run the script on the real data. We can do that by commenting out or deleting the line that overrides dialogue_scn_json in extract.json_to_csv.cmd. That will cause %dialogue_scn_json% to point back to the main data.

Code:
set dialogue_scn_json=%working_directory%\MTL\scenario\dialogue_scn_json
::set dialogue_scn_json=%working_directory%\MTL\scenario\New Folder (2)
set csv_directory=%working_directory%\MTL\scenario\csv

and run the script again

Code:
tools\scripts\extract.json_to_csv.cmd

It is reading and processing 200+ files, so it should take a while to finish, like a few seconds.

When it finishes, MTL\scenario\csv should now have 204 files. character_names.csv was also updated again and now includes 144 names in total, listed in 145 rows (data + header row). The structure of the file is the same as what was already shown above, just with more blank entries.

The issue with the currently generated spreadsheets is that they do not have the character names translated. Now that we know the names of every speaker in Monobeno, we can delete the spreadsheets at MTL\scenario\csv and create them again after the speakers have localized names.

Let's update character_names.csv with more romaji names.

Code:
python tools/translation_tools/romaji.py character_names.csv -s

Code:
raw_name,cutlet_hepburn,localized
すみ,Sumi,
夏葉大,Kayoudai,
夏葉,Kayou,
ありす,Arisu,
透,Tooru,
飛車角,Bisha kaku,
尚武,Shoubu,
村人,Murabito,
案内音声,Annai onsei,
???,???,
菜穂子,Naoko,
ラジオアナウンサー,Radio announcer,
セールス,Sales,
救急隊員,Kyuukyuu taiin,
[...]

Eventually, we will need the localization column filled. For a better sample size before filling that it, let's MTL translate them. Start a sugoi server using Sugoi Toolkit, or use [url="https://forums.fuwanovel.moe/topic/27430-tool-sugoi-offline-translator-repackage/"]my repackage. Then use sugoi_mtl.py.

Code:
python tools/translation_tools/sugoi_mtl.py --help
python tools/translation_tools/sugoi_mtl.py character_names.csv -s

Code:
raw_name,cutlet_hepburn,localized,sugoi_mtl
すみ,Sumi,,Sumi
夏葉大,Kayoudai,,Natsuha Hiroshi
夏葉,Kayou,,Natsuha
ありす,Arisu,,Arisu
透,Tooru,,Tou
飛車角,Bisha kaku,,Rook Horn
尚武,Shoubu,,Naotake
村人,Murabito,,Villager
案内音声,Annai onsei,,Guided voice
???,???,,???
菜穂子,Naoko,,Nahoko
ラジオアナウンサー,Radio announcer,,Radio announcer
セールス,Sales,,Sales
救急隊員,Kyuukyuu taiin,,Paramedics.
看護師,Kangoshi,,Nurse.
救命医,Kyuumeii,,Lifesaving doctors
南雲教授,Nagumo kyouju,,Professor Nagumo
星辰姫宮,Seishin himemiya,,Hoshitatsuhimemiya
ありす小,Arisushou,,Arisuko
女の子,Onnanoko,,A girl.
ありす?,Arisu?,,Arisu?
ご開祖様,Gokaisosama,,Your Holiness.
ご開祖ちゃん,Gokaisochan,,Founder!

So now we have to finally decide what goes into the empty localized column. To aid us in filling out that column, we have the hepburn romaji, a sugoi MTL, vndb.org's character page, and the official artbook.

That will take a while, so let's take a shortcut instead. Our immediate objective is to finish the automated workflow for this title. We can work on the quality of the translation later. Right now, we just want the full workflow to be fully automated first to confirm the automation actually works. To that end, let's just use the romaji or sugoi_mtl column for now to simulate a fully localized character_names.csv.

Delete all previously generated .csv files besides character_names.csv and run extract.json_to_csv.cmd again.

Code:
cd %userprofile%/desktop/monobeno/mtl/scenario/csv
dir
del *.csv
cd %userprofile%/desktop/monobeno
tools\scripts\extract.json_to_csv.cmd

That should produce files with this sort of output.

Code:
texts,character,metadata
「ここがものべの? なぁんにもないねー」,Kayou,1_0
「って、夏葉っ!?」,Tooru,1_1
"駅舎から飛び出した夏葉が、とんでもないことを大声で言う。
周りにちらほら人もいるのに――",,1_2
"くそっ、荷物重い!
夏葉になかなかおいつけない。",,1_3

That is definitely the wrong name to use for Natsuha. Natsuha's name is Natsuha, not Kayou, but the important part is that we can move on for now. Let's add romaji and sugoi_mtl translations to the spreadsheets. Let's apply the same policy as before where we run the scripts on one file first, and then we can run it on batches.

Code:
cd %userprofile%/desktop/monobeno
set tool=python "tools/translation_tools/romaji.py"
%tool% --help
%tool% "mtl/scenario/csv/共通_01_プロローグ.txt.json.csv"

wrote mtl/scenario/csv/共通_01_プロローグ.txt.json.csv.romaji.csv

Open Monobeno/MTL/scenario/csv/共通_01_プロローグ.txt.json.csv.romaji.csv in LibreOffice or other spreadsheet or text editing software to verify that it exists and the formatting is correct. Once satisfied, delete it and recreate it with the -s option.

Code:
%tool% "mtl/scenario/csv/共通_01_プロローグ.txt.json.csv" -s

wrote mtl/scenario/csv/共通_01_プロローグ.txt.json.csv

Open it again, verify it, close it again. Now let's do a small batch. Copy a few of the .csv files into a new folder. For me that is "Monobeno\MTL\scenario\csv\New folder" and lets add that folder as an override for the csv directory in the Hanayome script add.romaji.cmd.

Make sure to use backslashes for the override path. dir and for loops do not like / in that part of the logic. The override path is the duplicate path in the script that overrides the previous line to point to a new path for debugging purposes.

Code:
@echo off

set working_directory=%userprofile%\Desktop\Monobeno

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

set dialogue_scn_raw=%working_directory%\MTL\scenario\dialogue_scn_raw
set dialogue_scn_json=%working_directory%/MTL/scenario/dialogue_scn_json
set csv_directory=%working_directory%\MTL\scenario\csv
set csv_directory=%working_directory%\MTL\scenario\csv\New folder
set dialogue_scn_json_translated=%working_directory%/MTL/scenario/dialogue_scn_json_translated

set character_names=%working_directory%/Monobeno.character_names.csv
set character_names=%working_directory%/character_names.csv

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

Now let's run it.

Code:
tools\scripts\add.romaji.cmd

[...]
wrote C:\Users\User\Desktop\Monobeno\MTL\scenario\csv\New folder/共通_11_村の孤立.txt.json.csv

He is what a random file in that folder looks like now.

Code:
texts,character,metadata,cutlet_hepburn
尚武さんの、医者らしく細長い指が伸ばされる。,,1_0,"Naotakesan no, isharashiku hosonagai yubi ga nobasareru."
「ほう、なかなか見事な出来栄えだね」,Shoubu,1_1,"""hou, nakanaka migoto na dekibae da ne"""
"「顔つきのくじらさんは、なっちゃんお手製。
うふふ、かわいらしいでしょ?」",Naoko,1_2,"""kaotsuki no kujirasan wa, nacchan otesei. ufufu, kawairashiidesho?"""
「ふむ」,Shoubu,1_3,"""fumu"""
"菜穂子さんが尚武さんにと取っておいたクッキー皿へと、
医師らしい繊細な指が伸びる。",,1_4,"Naokosan ga Naotakesan ni to totte oita cookie sara e to, ishirashii sensai na yubi ga nobiru."

That seems to have worked as designed.

With the growing evidence that romaji.py and the .cmd script are working as intended with both single files and in batches, we can now have a basis to conclude that processing the main data is likely safe and can be reasonably expected to work.

By the way, the reason we are being so coy about running the script.py scripts and the shell .cmd scripts is because it is generally best to assume things will fail. A single wrong character from a typo or old variable name somewhere can and will ruin our day until the problem is located and fixed. Being pleasantly surprised when things work is a better mindset than being constantly upset when stuff fails.

Also, this way, every step is checked and works so there is never ambiguity over which step in a 20-step workflow failed. I would rather not get into that situation of figuring out which step in 20 failed in the first place even if initial progress to finish the workflow is "slower" by comparison. The end result is more trustworthy that way too, and there is less troubleshooting overall since issues are fixed as they are encountered.

Perhaps you like to forge ahead and throw caution to the wind? Feel free to work like that! You be you! Just let me be me.

So anyway, lets comment out or delete the 2nd "set csv_directory" line in add.romaji.cmd and re-run it.

Code:
set csv_directory=%working_directory%\MTL\scenario\csv
::set csv_directory=%working_directory%\MTL\scenario\csv\New folder
set dialogue_scn_json_translated=%working_directory%/MTL/scenario/dialogue_scn_json_translated

and then run it.

Code:
tools\scripts\add.romaji.cmd

After spot checking a few files, it seemed to work. Now let's do a Sugoi MTL by using the same process.

Code:
cd %userprofile%/desktop/monobeno
python tools/translation_tools/sugoi_mtl.py
python tools/translation_tools/sugoi_mtl.py "MTL/scenario/csv/共通_01_プロローグ.txt.json.csv"

C:\Users\User\Desktop\Monobeno>python tools/translation_tools/sugoi_mtl.py "MTL/
scenario/csv/共通_01_プロローグ.txt.json.csv"
reading MTL/scenario/csv/共通_01_プロローグ.txt.json.csv
number of entries to translate 446
wrote MTL/scenario/csv/共通_01_プロローグ.txt.json.csv.sugoi_mtl.csv

Let's open sugoi_mtl.csv and double check it worked correctly.

The headers are texts,character,metadata,cutlet_hepburn,sugoi_mtl and the sugoi_mtl column. The sugoi_mtl column has this for the first few lines.

「This is the room? Nothing's wrong.」
「Wait, Natsuha!?」
Natsuha leaps out of the station building and yells something outrageous. Even though there are people here and there around her...
Dammit, the bags are so heavy! I can't put them on Natsuha.
「There's no buildings, no bookstores, no supermarkets, no drugstores! Wow, it's so empty, and it's really stretched out.」
「Natsuha...」
Natsuha twirls her arms wide and repeats her happy turn. The passersby are all smiling at her.
Thank goodness, the other passengers... I don't think the locals care about Natsuha's nonsense at all.
「Amazing, it's completely different from Tokyo. Right, Onii-chan?」

That looks fine. The headers are in the correct order. The initial scene is about Natsuha arriving at Monobeno together with Tooru and that appears to be the dialogue for it. Now let's delete it!

[delete_sugoi_test]

And re-run it with -s.

Code:
python tools/translation_tools/sugoi_mtl.py "MTL/scenario/csv/共通_01_プロローグ.txt.json.csv" -s

reading MTL/scenario/csv/共通_01_プロローグ.txt.json.csv
number of entries to translate 446
wrote MTL/scenario/csv/共通_01_プロローグ.txt.json.csv

Opening it up again, and the same dialogue is there. Looking good so far. Now for the real test, the .cmd file.

Code:
@echo off

set working_directory=%userprofile%\Desktop\Monobeno

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

set dialogue_scn_raw=%working_directory%\MTL\scenario\dialogue_scn_raw
set dialogue_scn_json=%working_directory%/MTL/scenario/dialogue_scn_json
set csv_directory=%working_directory%\MTL\scenario\csv
set csv_directory=%working_directory%\MTL\scenario\csv\New folder
set dialogue_scn_json_translated=%working_directory%/MTL/scenario/dialogue_scn_json_translated

set character_names=%working_directory%/character_names.csv

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

Again, there is a dummy "set csv_directory" which masks the actual data to a subset for debugging purposes.

Code:
tools\scripts\add.sugoi_mtl.cmd

reading C:\Users\User\Desktop\Monobeno\MTL\scenario\csv\New folder/共通_09_七星祭.txt.json.csv
number of entries to translate 702
wrote C:\Users\User\Desktop\Monobeno\MTL\scenario\csv\New folder/共通_09_七星祭.txt.json.csv
reading C:\Users\User\Desktop\Monobeno\MTL\scenario\csv\New folder/共通_10_川遊び.txt.json.csv
number of entries to translate 825
wrote C:\Users\User\Desktop\Monobeno\MTL\scenario\csv\New folder/共通_10_川遊び.txt.json.csv
 
Automating the dialogue translation 2

Those line counts are somewhat scary. Okay, very scary if I am being honest.

With CUDA, sugoi toolkit and my repackage will finish processing a file in about 5-10s, depending on if the model is reloaded after each batch with the model loading from an SSD each time. With 204 files*5-10s that is about fifteen minutes to a half an hour of processing time. That is considered a very long time given how efficient CUDA is at running NMT models.

The length of this game is... well, I am going to try to not think about it. 15 minutes! It's just 15 minutes of processing! Without CUDA, it may need to run overnight btw.

If we run the add.sugoi_mtl.cmd script again as-is, then we get this as output.

reading C:\Users\User\Desktop\Monobeno\MTL\scenario\csv\New folder/共通_01_プロローグ.txt.json.csv
reading C:\Users\User\Desktop\Monobeno\MTL\scenario\csv\New folder/共通_02_大土地の町.txt.json.csv
reading C:\Uers\User\Desktop\Monobeno\MTL\scenario\csv\New folder/共通_03_不至.txt.json.csv

sugoi_mtl.py will not submit any entries that already have translations. In addition, the sugoi repackage uses server software, discovered from a post on sugoitoolkit's discord, that will cache the results, so there is no wasted processing on either the client side or server side, unlike with the official toolkit.

As before, let's remove or comment out the line allowing us to run sugoi_mtl.py on a subset of the data in add.sugoi_mtl.py.

Code:
set csv_directory=%working_directory%\MTL\scenario\csv
::set csv_directory=%working_directory%\MTL\scenario\csv\New folder
set dialogue_scn_json_translated=%working_directory%/MTL/scenario/dialogue_scn_json_translated

And then we can run add.sugoi_mtl.cmd again.

Code:
tools\scripts\add.sugoi_mtl.cmd

That will take a while to process, so let's keep working. Assuming those translations work as intended, the next step is to reinsert the translated strings from the .csv back to the .txt.json. We can do that using the MonobenoHappyEnd_DialogueExtractInsertTool.py

Start a new command prompt.

Code:
set working_directory=%userprofile%/desktop/monobeno
cd %working_directory%
set tool=python "tools/translation_tools/games/Monobeno Happy End/MonobenoHappyEnd_DialogueExtractInsertTool.py"
%tool%

So the basic syntax is

%tool% insert file.txt.json

Code:
%tool% insert "MTL/scenario/New folder (2)/ありす_01_早老症の影.txt.json"

unable to find spreadsheet MTL/scenario/New folder (2)/ありす_01_早老症の影.txt.json.csv

So it looks like it normally wants the spreadsheet to be in the same folder as the inputfile. We are using a dedicated folder for that over at mtl/scenario/csv, so let's use -s to point to it. Since we are running the tool on file instead of directory, we need the full path to the spreadsheet instead of just the correct folder.

Code:
%tool% insert "MTL/scenario/New folder (2)/ありす_01_早老症の影.txt.json" -s "mtl/scenario/csv/New folder/共通_01_プロローグ.txt.json.csv"

assert (len(spreadsheet_as_list)-1) == cumulative_line_count_from_scenes #sp
readsheet_as_list -1 due to spreadsheet header
AssertionError

Wrong spreadsheet! Or wrong file. There is some logic to make sure the inputfile is matched up with the correct spreadsheet which prevents data corruption.

The input file is ありす_01_早老症の影.txt.json
But the .csv is 共通_01_プロローグ.txt.json.csv

That combination is incorrect.

We can change either the input or the spreadsheet so they match. Since 共通_01_プロローグ.txt.scn has the first line of the game, let's do that one by changing the inputfile to 共通_01_プロローグ.txt.json and moving that file to the correct folder for testing at MTL/scenario/New folder (2).

Code:
copy "mtl/scenario/dialogue_scn_json/共通_01_プロローグ.txt.json" "mtl/scenario/New folder (2)/共通_01_プロローグ.txt.json"

The system cannot find the file specified.
0 file(s) copied.

copy does not seem to like /. Let's try \. Why do I keep using / when it fails half the time if \ will always work on Windows? Because I want to know the exact situations where it fails. Curiousity is a wonderful thing, no?

Code:
copy "mtl\scenario\dialogue_scn_json\共通_01_プロローグ.txt.json" "mtl\scenario\New folder (2)\共通_01_プロローグ.txt.json"

1 file(s) copied.

Now let's rerun the command with the updated inputfile.

Code:
%tool% insert "MTL/scenario/New folder (2)/共通_01_プロローグ.txt.json" -s "mtl/scenario/csv/New folder/共通_01_プロローグ.txt.json.csv"

[...]
wrote MTL/scenario/New folder (2)/共通_01_プロローグ.txt.json.translated.json

So the good is that it produced output, but it also yelled at us a lot about missing character names.

夏葉 character not found at 445 [...]

Let's delete 共通_01_プロローグ.txt.json.translated.json and try running the tool again after adding character_names.csv option.

Code:
del "MTL/scenario/New folder (2)/共通_01_プロローグ.txt.json.translated.json"

The system cannot find the path specified.

Code:
del "MTL\scenario\New folder (2)\共通_01_プロローグ.txt.json.translated.json"
%tool% insert "MTL/scenario/New folder (2)/共通_01_プロローグ.txt.json" -s "mtl/scenario/csv/New folder/共通_01_プロローグ.txt.json.csv" -h
%tool% insert "MTL/scenario/New folder (2)/共通_01_プロローグ.txt.json" -s "mtl/scenario/csv/New folder/共通_01_プロローグ.txt.json.csv" -cn character_names.csv

Code:
reading character_names.csv
reading 共通_01_プロローグ.txt.json
reading 共通_01_プロローグ.txt.json.csv
reading 共通_01_プロローグ.txt.json.csv
using spreadsheet column 5
no scene title found for scene number 0
scene title プロローグ for scene 1
string at [ 27 ][ 5 ] has >150 characters, unable to word wrap
string at [ 70 ][ 5 ] has >150 characters, unable to word wrap
string at [ 94 ][ 5 ] has >150 characters, unable to word wrap
string at [ 99 ][ 5 ] has >150 characters, unable to word wrap
string at [ 133 ][ 5 ] has >150 characters, unable to word wrap
string at [ 423 ][ 5 ] has >150 characters, unable to word wrap
wrote MTL/scenario/New folder (2)/共通_01_プロローグ.txt.json.translated.json

So it worked to write output again, but we are now getting word wrap errors. It is also reading 共通_01_プロローグ.txt.json.csv twice and I am not sure why. Maybe I will fix that double-read later.

The default word wrap code assumes a maximum of 50 characters per line with a maximum of 3 lines. That is oddly half-accurate for most generic VNs. However, for Monobeno, the approximate maximum number of characters for a line when using the default font is 76 characters, or 70-72 if we allow for some lines being longer than others, and there is no maximum number of lines due to Monobeno's line continuation code. If we wanted to adjust this, we could use the --wordwrap option to input 70 and re-run the file.

Code:
del "MTL\scenario\New folder (2)\共通_01_プロローグ.txt.json.translated.json"
%tool% insert "MTL/scenario/New folder (2)/共通_01_プロローグ.txt.json" -s "mtl/scenario/csv/New folder/共通_01_プロローグ.txt.json.csv" -cn character_names.csv --wordwrap 76

reading character_names.csv
reading 共通_01_プロローグ.txt.json
reading 共通_01_プロローグ.txt.json.csv
reading 共通_01_プロローグ.txt.json.csv
using spreadsheet column 5
no scene title found for scene number 0
scene title プロローグ for scene 1
wrote MTL/scenario/New folder (2)/共通_01_プロローグ.txt.json.translated.json[/code]

What are the odds that MTL/scenario/New folder (2)/共通_01_プロローグ.txt.json.translated.json will work perfectly in the game? Personally, I have 0 confidence it will work because I am debugging the insert() function in the tool.py at the same time as writing this guide. Before automating further, let's test it to make sure it actually works first.

Remember that we can go from .txt.json to .txt.psb with PsBuild.exe. Here is the syntax again.

Code:
PsBuild.exe -v 2 %file%

open a command prompt

Code:
set working_directory=%userprofile%\Desktop\Monobeno
set tool=%working_directory%/tools/Ulysses-FreeMoteToolkit-v4.1.1/PsBuild.exe
set file=MTL/scenario/New folder (2)/共通_01_プロローグ.txt.json.translated.json
"%tool%" -v 2 "%file%"

FreeMote PSB Compiler
by Ulysses, [email protected]
18 Plugins Loaded.

Compiling 共通_01_プロローグ.txt.json.translated ...
Compile 共通_01_プロローグ.txt.json.translated done.
Done.

Let's rename that .translated.psb output to 共通_01_プロローグ.txt.scn and dump it into a patch.xp3. Here is the result.

[insert_debug1]

So it translated the game and did not crash. Progress!

Now I just need to fix word wrap. The text is getting output to the .json files as \\n, but it needs to be output as \n to work properly. At least double quotes are being represented properly as \".

After I fixed the above bugs so that you don't have to, here is what it looks like.

[insertion_test_post_debug_non_proportional]

So it translated the game and did not crash. Progress!

Now I just need to fix word wrap. I am getting a sense of déjà vu.

Anyway, let's keep working on automation. Now that we have tested that the insertion tool is working, let's automate it for a few files.

Code:
@echo off

set working_directory=%userprofile%\Desktop\Monobeno

:: this converts the .txt.json to csv and back
set tool=python "%working_directory%/tools/translation_tools/games/Monobeno Happy End/MonobenoHappyEnd_DialogueExtractInsertTool.py"

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

set character_names=%working_directory%/character_names.csv

%tool% insert "%dialogue_scn_json%" -cn "%character_names%" -s "%csv_directory%" --wordwrap 70 -o "%dialogue_scn_json_translated%"

There is an override line for %dialogue_scn_json%. open a command prompt

Code:
cd %userprofile%/desktop/monobeno
tools\scripts\insert.csv_to_json.cmd

C:\Users\User\Desktop\Monobeno>tools\scripts\insert.csv_to_json.cmd
python "C:\Users\User\Desktop\Monobeno/tools/translation_tools/games/Monobeno Ha
ppy End/MonobenoHappyEnd_DialogueExtractInsertTool.py" insert "C:\Users\User\Des
ktop\Monobeno\MTL\scenario\dialogue_scn_json_temp" -cn "C:\Users\User\Desktop\Mo
nobeno/character_names.csv" -s "C:\Users\User\Desktop\Monobeno\MTL\scenario\csv"
--wordwrap 70 -o "C:\Users\User\Desktop\Monobeno\MTL\scenario\dialogue_scn_json
_translated"
reading character_names.csv
reading 共通_01_プロローグ.txt.json
reading 共通_01_プロローグ.txt.json.csv
using spreadsheet column 5
no scene title found for scene number 0
scene title プロローグ for scene 1
wrote C:\Users\User\Desktop\Monobeno\MTL\scenario\dialogue_scn_json_translated/
共通_01_プロローグ.txt.json.translated.json
reading 共通_02_大土地の町.txt.json
reading 共通_02_大土地の町.txt.json.csv
using spreadsheet column 5
no scene title found for scene number 0
scene title 大土地の町 for scene 1
wrote C:\Users\User\Desktop\Monobeno\MTL\scenario\dialogue_scn_json_translated/
共通_02_大土地の町.txt.json.translated.json
reading 共通_03_不至.txt.json
reading 共通_03_不至.txt.json.csv
using spreadsheet column 5
no scene title found for scene number 0
scene title 不至 for scene 1
string at [ 120 ][ 5 ] has >210 characters, unable to word wrap
scene title 不至 for scene 2
wrote C:\Users\User\Desktop\Monobeno\MTL\scenario\dialogue_scn_json_translated/
共通_03_不至.txt.json.translated.json
reading 共通_04_茂伸神社.txt.json
reading 共通_04_茂伸神社.txt.json.csv
using spreadsheet column 5
no scene title found for scene number 0
scene title 茂伸神社 for scene 1
wrote C:\Users\User\Desktop\Monobeno\MTL\scenario\dialogue_scn_json_translated/
共通_04_茂伸神社.txt.json.translated.json

C:\Users\User\Desktop\Monobeno>tools\scripts\insert.csv_to_json.cmd
[...]
translated file already exists C:\Users\User\Desktop\Monobeno\MTL\scenario\dialo
gue_scn_json_translated/共通_01_プロローグ.txt.json.translated.json
translated file already exists C:\Users\User\Desktop\Monobeno\MTL\scenario\dialo
gue_scn_json_translated/共通_02_大土地の町.txt.json.translated.json
translated file already exists C:\Users\User\Desktop\Monobeno\MTL\scenario\dialo
gue_scn_json_translated/共通_03_不至.txt.json.translated.json
translated file already exists C:\Users\User\Desktop\Monobeno\MTL\scenario\dialo
gue_scn_json_translated/共通_04_茂伸神社.txt.json.translated.json
all output files already exist

Did that work? It created 4 files. How do we know that those files were created without errors? We need to test them. If the text displays properly when loading the patch.xp3, only then can we know they were created without errors.

Let's move on to the next part of automation which is converting .txt.json to .txt.psb and test creating the patch. Here is the next script.

Code:
@echo off
setlocal ENABLEDELAYEDEXPANSION

set working_directory=%userprofile%\Desktop\Monobeno

:: this converts the .txt.json to .txt.psb which can be renamed to .txt.scn
set tool=%working_directory%/tools/Ulysses-FreeMoteToolkit-v4.1.1/PsBuild.exe

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

set character_names=%working_directory%/character_names.csv

if not exist "%dialogue_scn_translated%" mkdir "%dialogue_scn_translated%"

pushd "%dialogue_scn_translated%"
for /f "delims=;" %%i in ('dir /b "%dialogue_scn_json_translated%\*.txt.json.translated.json"') do %tool% -v 2 "%dialogue_scn_json_translated%/%%i"
echo renaming files...
for /f "delims=;" %%i in ('dir /b "%dialogue_scn_translated%\*.txt.json.translated.psb"') do (
set temp=%%~i
ren "%dialogue_scn_translated%\!temp!" "!temp:~0,-20!.scn"
)
popd

There are only the 4 files in that folder from above, so we do not need to override %dialogue_scn_json_translated% in insert.json_to_psb_scn.cmd this time.

The renaming is also a bit more complicated since every file is getting .translated.json appended to it by the insertion script. When running it in single file mode, -o is used as the actual output path, but for processing batches, it needs to be a folder, so there is no option to remove the .translated.json bit in the tool.py. I might change that behavior in the tool.py later. For now, let's run the .cmd script.

Code:
tools\scripts\insert.json_to_psb_scn.cmd

C:\Users\User\Desktop\Monobeno>tools\scripts\insert.json_to_psb_scn.cmd
[...]
Compiling 共通_01_プロローグ.txt.json.translated ...
Compile 共通_01_プロローグ.txt.json.translated done.
Done.
[...]
Compiling 共通_02_大土地の町.txt.json.translated ...
Compile 共通_02_大土地の町.txt.json.translated done.
Done.
Compiling 共通_03_不至.txt.json.translated ...
Compile 共通_03_不至.txt.json.translated done.
Done.
[...]
Compiling 共通_04_茂伸神社.txt.json.translated ...
Compile 共通_04_茂伸神社.txt.json.translated done.
Done.
renaming files...

Isn't it sort of weird that PsBuild.exe does not support batches? Maybe that should be a feature request. Thinking about batches, I should probably also change romaji.py and sugoi.py to also support batches of files now that tool.py supports them.

Code:
C:\Users\User\Desktop\Monobeno>tools\Ulysses-FreeMoteToolkit-v4.1.1\PsBuild.exe
-v 2 C:\Users\User\Desktop\Monobeno\MTL\scenario\dialogue_scn_json_translated
FreeMote PSB Compiler
by Ulysses, [email protected]
18 Plugins Loaded.

Done.

MTL\scenario\dialogue_scn_translated now has 4 files.

Having to move the updated files to a new folder, and then also integrate new translated ui images and scripts constantly is going to get annoying, so let's automate that now too. Thankfully, we already verified that Xp3Pack.exe works, so we can just keep using that.

Let's create a new folder, MTL/patch. This folder will represent where our finished files will go.

Code:
@echo off

set working_directory=%userprofile%\Desktop\Monobeno

:: this creates .xp3 files from folders
set tool=%working_directory%/tools/KiriKiriTools-1.7/Xp3Pack.exe

set dialogue_scn_raw=%working_directory%\MTL\scenario\dialogue_scn_raw
set dialogue_scn_json=%working_directory%\MTL\scenario\dialogue_scn_json
set csv_directory=%working_directory%\MTL\scenario\csv
set dialogue_scn_json_translated=%working_directory%\MTL\scenario\dialogue_scn_json_translated
set dialogue_scn_translated=%working_directory%\MTL\scenario\dialogue_scn_translated
set patch_directory=%working_directory%\MTL\patch
set game_directory=%working_directory%\bin\Monobeno Happy End

set character_names=%working_directory%/character_names.csv

if not exist "%patch_directory%" mkdir "%patch_directory%"

::move updated translations to MTL/patch
echo copying from "%dialogue_scn_translated%" to "%patch_directory%"
xcopy /i /q /y "%dialogue_scn_translated%" "%patch_directory%"

::archive the folder
"%tool%" "%patch_directory%"

move /y "%working_directory%\MTL\patch.xp3" "%game_directory%\patch.xp3"

echo updated patch.xp3

The patch at Monobeno\bin\Monobeno Happy End\patch.xp3 is 371 KB (380,863 bytes). If we make a patch using that folder's contents with garbro and the "Compress contents" option checked in the ui, the resulting patch is 371 KB (380,884 bytes) meaning they are about the same size, only a few bytes difference. That means there isn't any reason to use Garbro to create patches over xp3pack.exe for our current workflow.

Now we can start Monobeno and try to get it to crash.

Assuming the prologue plays the files in the order we translated them, if we hit the skip button and Japanese appears after a while, then we have enough evidence to claim that the four translated files did not cause the game to crash.

Is that assumption about the game playing the files in-order correct? Let's go back over to Monobeno/MTL/scenario/csv/共通_04_茂伸神社.txt.json.csv. These are the final four poorly translated lines in that file.

Kayou: 「Playing!? Were you just talking about going out?」
Sonchou: 「Hey, hey, hey.」
Natsuha, wake up.
Even though you're this exhausted, you're still jumping to your feet to talk about fun... Tomorrow's schedule is... decided by playing in the mountains.

If we can find the above lines while skipping through all of the text from the very start of the game and the lines after these are in Japanese, then that probably means patching the game's first four scenario files did not cause it to crash. The scripts also have about ~500 lines each, so these four lines are about 2k lines into the game. We will be skipping for a while...

The game will not actually let us skip unread lines, but there is probably an option in the settings somewhere to enable that, probably. Personally, I copied the 100% savedata from the backups/ folder instead to enable skipping for debug purposes.

Here is the last line from above and the line after that.

[fast_forward_test1]
[fast_forward_test2]

To my surprise, it works!

Now let's enable the rest of the files. Let's go back to insert.csv_to_json.cmd. It had an override line for %dialogue_scn_json%. Let's delete or comment out that line and run the script again.

Code:
set dialogue_scn_json=%working_directory%\MTL\scenario\dialogue_scn_json
::set dialogue_scn_json=%working_directory%\MTL\scenario\dialogue_scn_json_temp
set csv_directory=%working_directory%\MTL\scenario\csv
set dialogue_scn_json_translated=%working_directory%\MTL\scenario\dialogue_scn_json_translated
set dialogue_scn_translated=%working_directory%\MTL\scenario\dialogue_scn_translated

Now lets run the .cmd script.

Code:
C:\Users\User\Desktop\Monobeno>tools\scripts\insert.csv_to_json.cmd

And it took a minute or so to produce 204 .json files totaling 1.77 GB over at MTL\scenario\dialogue_scn_json_translated. Just storing the uncompressed dialogue of this game is larger than most other VNs. It must be pure maddness to translate it without automation. Even just writing it in the original Japanese must have taken years.

[dialogue_scn_json_translated.size]

Code:
C:\Users\User\Desktop\Monobeno>tools\scripts\insert.json_to_psb_scn.cmd

Compiling 飛車角pre_sh_なぐさめ.txt.json.translated ...
Compile 飛車角pre_sh_なぐさめ.txt.json.translated done.
Done.
renaming files...
A duplicate file name exists, or the file
cannot be found.
A duplicate file name exists, or the file
cannot be found.
A duplicate file name exists, or the file
cannot be found.
A duplicate file name exists, or the file
cannot be found.

It created but did not successfuly rename the four files we worked on earlier in the folder mtl\scenario\dialogue_scn_translated. Let's just delete those .translted.psb files manually. We could update the script so it deletes the old versions of the translated .txt.scn files in order to not get this error, but deleting stuff automatically is mildly terrifying to me, so that is not happening. I would rather deleting stuff involve the user.

Code:
pushd C:\Users\User\Desktop\Monobeno\MTL\scenario\dialogue_scn_translated
del *.translated.psb
popd

Next, we just repackage the files into the .xp3.

Code:
cd C:\Users\User\Desktop\Monobeno
tools\scripts\update_patch.from_dialogue.cmd

copying from "C:\Users\User\Desktop\Monobeno\MTL\scenario\dialogue_scn_translate
d" to "C:\Users\User\Desktop\Monobeno\MTL\patch"
204 File(s) copied
1 file(s) moved.
updated patch.xp3

At some point, we will need to update update_patch.from_dialogue.cmd to honor the folder structure of the appends and to put the scenario files into a patch.xp3\data\scenario subfolder.

The resulting patch.xp3 is 11.1 MB in size. How did 1.77 GB of uncompressed .json become just 11.1 MB? There must be some sort of error. However, if we open Garbro, it does list all 204 .scn.txt files and the game does work.

[garbro.patch_dialogueonly.size]

Clearly, text compression technology is actually witchcraft by any other name.

This also puts into perspective how wasteful it is to translate kirikiri games by repackaging data.xp3 in situations when there is no technical need to do so. Repackaging data.xp3 for translating just the Monobeno dialogue would result in a ~900 MB file (~500-600MB compressed). Contrast that to a patch.xp3, that does exactly the same thing of translating only the dialogue, which weighs in at only a mere 11.1 MB. In addition, if the patch.xp3 file is moved, the game goes back to being in Japanese, so we retain the full functionality of the original game by making it trivial to restore it to its original state.

The next time you are downloading a translation patch for a kirikiri game that is 200 MB+ and find out it was because the patch developer repackaged data.xp3, remember this moment.

That convers translating the dialogue + automation. The next steps remaining for the patch are to
1) Increase the quality of the MTL, especially fixing character_names.csv
2) Translate all images that are part of the ui
3) translate all game engine strings that are part of the UI

For 1), now that automatically extracting and inserting the game's dialogue is complete, if a human translator becomes involved in this project later, then we can easily incorporate their work and transition the project from an MTL to a FanTL or edited MTL. Until then, we can settle for the third best alternative which is to use an AI to translate the dialogue. Since AIs can incorporate context, unlike NMT models, that should boost the translation quality if and only if we are willing to provide that context to the LLM. The second best option is for a human editor to edit an AI TL but that still requires an AL TL first.

1) is the next logical step, but in practical terms, that means running OpenAI_MTL.py, a script that does not actually exist yet and is earmarked by another developer. That leaves us with 2) and 3).

Automating 3) requires creating a new .py that can extract and insert game engine strings based upon a predefined list and dynamically chosen algorithims. That seems complicated, but more importantly, interesting. Since that is interesting, that is what I would normally work on next, but @Shisaye wanted feedback for some automated image processing. For that reason, 2) is next.
 
Image processing theory

Let's automate the translation of all images that are part of the ui as much as possible, and then compare the result with SLR Translator's fully automated workflow.

As stated earlier, translating images using optical character recognition (ocr) consists of 3 distinct steps.

1. running ocr software on the image
2. translating based upon the ocr text
3. inserting the translation into the original image

For the second step, we have romaji.py and sugoi_mtl.py, so that is almost fully automated already. We can also just manually double check the translations during the thrid step if anything seems incorrect.

For the third step, we have these options for automation.
- There is kra-py which can automate some parts of creating krita .kra files. This takes the existing approach in the Koakuma-chan guide of fully manually editing image files in Krita and automates the more egregiously repetitive, non-creative parts of image editing like loading the canvas and layering the translated text. Further processing beyond that is still entirely manual.
- SLR Translator 2.0's will eventaully have a non-beta public purely offline option called SEP for reinserting strings into images. However, the emphasis of SLR is and will always be full automation. Don't expect Krita integration for exporting important images that could use improvements. The output image processing quality will also never be better than AnyTrans and the UI assumes you will be using all aspects of the integrated pipeline for image processing rather than just one part in the middle or end modularly like we intended to.
- AnyTrans has an interesting algorithim to reinsert text into images which is potentially very high quality. Unfortunately, it requires a lot of online LLM integration and seems to be both language-pair limited and character length limited. While promising, that means using it would take a lot of work to convert their algorithim into something working locally, to remove those odd limitations, and the results would, in the end, be only mildly better than SLR Translator 2.0's image processing. In other words, AnyTrans is more of a tech demo than anything genuinely useful. AnyTrans could work, but only with a lot of extra software development time put into it because it is currently purely just an algorithim showing what is possible, not an actual viable solution. That means putting that development time into SLR would be better investment in our time. Only once SLR Translator's SEP addon is working perfectly would it make sense to integrate the AnyTrans algorithim into it.
- There are also commercial cloud services available from Google, Microsoft, and Apple. They were compared by AnyTrans in their repository.
- Yandex also exists. Their quality speaks for itself.

The main benefit of SLR Translator 2.0's image processing add-on, AnyTrans's algorithm, and the commercial services is that they perform all steps related to OCR and inserting a translation into images in a fully automatic way. The emphasis is not quality, but automation.

But what if we are not willing to sacrifice quality in our automation attempts? How useful are these solutions to us then?

The above solutions emphasize a fully automated pipeline. They are not intended to be modular, that is, to allow us to fix any problems in one step before moving on to the next. That strongly implies that quality is strictly, and at best, a secondary concern. A notable exception is that some parts of SLR Translator image processing code, SEP, are modular which means the software shows potential to be useful for quality-focused workflows ...one day eventually.

Thinking back to the dialogue automation above, what makes automation of that nature truly useful for both pure MTL patches (for dubugging) and for quality focused translators that are manually translating or editing is because of the modularity of it. Each individiaul step does not affect the others, so there is loose coupling. Loose coupling which allows one or more parts of the workflow to be fully replaced or altered as needed. That allows adjusting the quality vs automation dynamic to suit different use cases of each individual part in real time.

For example, if the sugoi_mtl.py did not work for our use case, such as translating to a non-English language or we wanted an MTL AI TL instead of an MTL NMT TL, then we could alter that step to use a different model, various online services, or even a human translator without affecting the other parts of the workflow.

Does Yandex lets us insert our own translations into the image before integrating that translation into the image? Does Google? Does Microsoft? Does Apple? Can we use completely different ocr models of our choosing if the base model is lousy for our dataset? Can we export .kra files at some or all parts of the workflow to allow us to manually process key gui images where only quality matters while automating away as much as possible as well?

Such things are flatly impossible with the commerical cloud services for image translation. Moduarity is not how these very large companies offer their online services. While some have APIs that allow us to build the otherwise modular software similar to what we are considering, then we are back to AnyTrans's fatally flawed tech-demo like architecture that mixes both local software with required online processing. That approach has all of the downsides of both approaches simultanteously.

That approach is not fully local, hence non-private, requires internet access, accounts, and also typically charges for users uploading their data and processing which adds all sorts of unrelated complications to the specifics of the problem in front of us like processing limits, privacy concerns, and limited availablity.

I want to edit some images. Why is my credit card and other personality identifiable information necessary to do that? That is the practical reality of cloud computing. That is the AnyTrans and commercial services approach, to either mix local and online processing which requires API keys, or to move it all online and pay a provider to store and process customer provided data.

Businesses that process sensitive data tend to be hesitant to use these types of services, and all business data is sensitive to a particular business by definition. While such extraneous complications are simply the cost of doing business for commercial entities, they certainly make this type of cloud-only commercial software utterly worthless to us since we want to automatically process images locally without batch limits, privacy complications, or any other limitation imposed by some corporation somewhere.

Fundamentally, what we need are the tools to build fully automated image processing pipelines locally. That will allow us to maintain high quality when we decide it matters and focus on full automation otherwise. Integral to that approach are loosly coupled tools where each tool can function independently from the others, unix style. That allows us to split up the problem of translating images into as many small steps as possible so the output of each step can be validated before moving on and so we can redo each step as needed in a fully automated way.

We want the automated dialogue translation process we built earlier, but for images.

That leaves the remaining approaches for reinserting text into images as (one day) SLR Translator's SEP, and other software that can be used locally in .py files, like kra-py, or the automation technologies that are part of the software we are using like krita scripting, and photoshop macros.

Besides SLR, in recent years there has been a lot of work put into document ocr by commerical organizations like PaddlePaddle, docTR, and easyOCR and into general ocr by many hobbyists. Commercial grade document OCR is about converting images that represent documents into machine readable web, text, or markdown files. As a side effect of that work, for various complex reasons, some of the associated ocr models and software for text detection, text recognition, document and line pre and post processing have been released online.

Each model only solves only part of the problem. That is, these are part of the tools required to build fully automated document recognition pipelines that these commercial services offer as part of their commercial web services. Accurately recognizing documents is an emerging field, especially AI integrations, so the reality is that most commercial grade solutions require more than just a little manual tweaking to both the model used the intermediary processing to make recognition for particular organization's documents ubiquitous. These commercial services offer those tweaks, which require highly specialized human expertise, as part of their commerical offerings, but many also offer the unoptimized models and software for anyone who wants to run them locally.

Unlike cloud services, these are potentially useful for a fully automated approach. One complication is that every entity releasing such work has their own idea for how ocr and/or document recognition should work. Their software and models tend to be very focused on their particular use cases. While that is a complication, that is not a negative. It means we can tailor all of the tools available to our purposes, specifically strip out the parts we do not need and extend the parts where we need more control.

So why did I rant for so long about image processing? Because there are no fully automated quality-focused local workflows to translate images right now.

Unlike text translations which has mostly settled on LLM translations with manually crafted context as the perfect blend that brings together both reasonably high automation and very high quality, image translation and processing is a much more developing field.

I would encourage looking around to see what tools and services are available regardless of what is written in this guide which will undoutedly not age well as more automated approaches become viable and software is further developed. The technology is changing fast and there is a lot of work being done by myself and others to improve both the quality and the level of automation possible for image translations.

Software tends to be designed around a particular emphasis. The emphasis of SLR is automation and improving quality only to the extent that it does not interfere with automation. Creating a high quality translation of Monobeno requires a workflow that emphasizes quality at every step and automates only to the extent that it does not interfere with quality.

If the emphasis of the software does not match what you need, then you may need to keep looking for alternative tools, improve the existing tools, or even build your own.

Let's backtrack a moment and talk about step 1.

1. running ocr software on the image

the literal problem of ocr can be thought of as divided into two main tasks, text detection and text recognition.

Text detection is figuring out which parts of the image have text and marking those areas in the picture for processing in some way, usually by drawing boxes around them. In the Koakuma-chan guide, the Windows Snipping Tool was used to extract parts of images into bits of translatable text that were dumped into a folder as .png files for later processing. That was manual text detection.

Text recognition is figuring out the glyphs of a language found in the image. In the previous Koakuma-chan guide, we used MangaOcr's model to recognize japanese glyphs in cropped images. Text glyphs change as languages change since natural languages use different character sets, so text recognition models are always natural language centric, that is, text recognition models list which languages they work for It made sense to use MangaOcr when recognizing Japanese text because it was trained on image data consisting of Japanese glyphs. For a different source language, we would need a different text recognition model trained on images of that language's text. Basically, use different text recognition models for different languages.

Instead of 3 broad steps of translating images, let's divide our workflow further into as many discrete steps as possible. Each step should be potentially automatable using a single AI model, or software.

Here are the general steps to perform image translations that we have been using.

1. running ocr software on the image
2. translating based upon the ocr text
3. inserting the translation into the original image

Now that we know that running ocr software on the image is actually 2 steps, let's rewrite the steps.

1. running text detection ocr software on the image
2. running text recognition ocr software on the image
3. translating based upon the ocr text
4. inserting the translation into the original image

How do we know that we need to run text detection and text recognition software on an image? We also need to convert any .tlg images to .png for processing and back to .tlg at the end. Let's also break down what inserting the translation into the original image actually means. It means creating a new canvas based on the original image, inpainting, and text overlay.

Inpainting is filling in part of an image with context dependent information. Inpainting in the context of ocr means removing the original text which then allows us to overlay the translation cleanly so the end result looks nice and professional instead of a horribly illegible text-on-text mess.

- identify images that need ocr
- convert to .png if necessary (from .tlg)
- run text detection ocr software on the image
- run text recognition ocr software on the image
- translate the text based upon the ocr result
- create a new canvas based upon the original image
- inpaint the detected areas from text detection
- overlay the text onto the image
- save the result as a .kra file and export to .png
- convert to the original format (.tlg, .jpg, etc)
- move the converted images appropriately (such as to a patch folder used to generate patch.xp3 )

For reference, here are three different automated document workflows that we can take ideas from for how images should be processed.

- PaddlePaddle's PP-OCRv5.
- EasyOCR's Implementation Roadmap.
- mindee's docTR architecture and automation workflow.

Why nothing from google, microsoft, or apple? Because my life does not currently include those companies, or only to the minimum extent, and I would like that state of affairs to continue. Feel free to use their stuff if you want.

There are some parts of full document recognition workflows that are cut from the above because different source documents need different workflows. when translating a small amount of gui images with a focus on quality, we do not need to worry about document orientation/rotation, document warping, line rotation, or merging ocr'd lines into paragraphs before translation.

If those are necessary, we can fix the images prior to processing them. As long as we can quality check the translation and the workflow is automated, the exact point at which quality checking is done is not particularly important because we can just recreate everything after that automatically.

Unfortunately for us, it is not really possible to fully automate the entire workflow right now. Even basic automated .tlg -> .png -> .tlg conversion is unknown. Software to do this probably exists somewhere. Maybe even Garbro can do it at a command line or perhaps we could force it with some sort of gui automation tool?

However, ultimately automating that would result in minimal time savings because converting .tlg files to .png is actually quite fast, even if done manually.

The biggest immediate barrier to automation that would result in the most time savings is actually the lack of automated local inpainting software.

The automated ones, as seen in the AnyTrans example below, are of questionable quality. Some options like SLR, blur the background and hope for the best. SLR Translator's SEP addon in particular removes the original text by applying a 2 stage gaussian blur. For comparison, Microsoft's attempt just lazily applies a grey background for their 'inpainting' step.

In other words, even using commercial grade OCR software does not gurantee quality-based inpainting.

On the other end of the spectrum, there are also a lot of very high options if we want to inpaint with AI manually. Here is a random guide for inpainting on civitai that assumes we are using sd-webui.

Fully manual AI inpainting in sd-webui, comfyui, and manual inpainting in Krita and Photoshop are very high quality, but I have not found any fully automated solutions that are even close to comparable quality. That is not to say quality based inpainting software does not exist, but rather that I have not looked into it very much.

The closest is AnyTran's algorithim. Here are some leads for further development and my subjective interpretation of the level of promise they hold for getting them to work someday.

- AnyTrans's inpainting algorithim is very promising and near ideal, but I need to look into this more to remove all cloud API calls.
- This very old pytorch model template? on github. This PyTorch inpainting template has a lot of interesting forks in their network graph. Unlikely to ever work and also based on very old (6 year+) algorithims, so low motivation to work on this.
- It may be possible with ComfyUI somehow. Wiki. I really hope this works. It would be absolutely perfect but likely require high end hardware.
- chaiNNer might work. Promising.
- openmodeldb.info[/urk] has lots of pytorch models which might have something useful for inpainting maybe. Seems hopeless.

The biggest drawback to working on inpainting right now is that even if we fully automate inpainting, we would need an fallback from automated AI inpainting back to manual inpainting in order to maintain the highest quality workflow possible. One way or another, the workflow has to involve Krita or Photoshop, at least as a fallback option, or there is a deliberate decision to sacrifice quality for increased automation.

The most we can do now to not drop quality is to automate more and more of the existing Photoshop/Krita workflow while keeping everything modular to allow for drop in improvements to be made later. If we find a decent a better inpainting strategy, algorithim, or model for inpainting later, then we can integrate it then when the software is ready. As everything gets written in a modular way, there should be no problems integrating it later.
 
Image processing workflow

I have recently been working on automating text detection, text recognition, but not text overlay. To that end, let's time travel again to a week from now.

Here we are a week later! Some work has been done to overlay text onto images automatically. The resulting work is published together with the text detection and text recognition work over at ocr_tools. Here is the resulting workflow.

- manually identify images from data.xp3 that need ocr and move them to subfolders under mtl/images/data
- manually convert to .png if necessary (from .tlg)
- automatically run text detection ocr software on the images at mtl/images/data
- automatically run text recognition ocr software using a language appropriate text recognition model
- automatically translate the text based upon the ocr result
- manually create a new canvas based upon the original image
- automatically overlay the text onto the image
- manually quality check the translated text
- manually adjust the overlayed text so it looks native
- manually inpaint the detected areas from text detection
- manually export save the result as both .kra and .png
- manually convert to the original format (.tlg, .jpg, etc)
- manually move the final images to the appropriate folder (such as to a patch folder used to generate patch.xp3)

There is not a lot of automation is there? Well, at least that means there is plenty of room for improvement and there is a clear roadmap for future development. Let's get to work. First, let's fully install ocr_tools to get ocr_text_detection.py to work which implements PaddlePaddle's PP-OCRv5 architecture and MangaOCR.

start a command prompt
download ocr_tools

Code:
set working_directory=%userprofile%\Desktop\Monobeno
cd "%working_directory%/tools"
git clone https://codeberg.org/entai2965/ocr_tools.git
cd ocr_tools

if ocr_tools is already downloaded, then just update it instead

Code:
cd "%working_directory%/tools/ocr_tools"
git pull

if there are changes, then stash the changes before updating it

Code:
cd "%working_directory%/tools/ocr_tools"
git add *
git stash
git pull

install pyenv to help manage the dependency hell that is PP-OCRv5
start powershell

Code:
Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/pyenv-win/pyenv-win/master/pyenv-win/install-pyenv-win.ps1" -OutFile "./install-pyenv-win.ps1"; &"./install-pyenv-win.ps1"

- wait until it says 'pyenv-win is successfully installed.'
- optional, Move to or create directory junctions for any existing Python installations to %userprofile%/.pyenv/pyenv-win/versions to have pyenv become aware of them
- optional, on old versions of Windows (< Windows 10 1807), leave the code page as the local ansi page, open `%userprofile%/.pyenv/pyenv-win/bin/pyenv.bat` and comment out line at the top with `chcp 65001` so it looks like `::chcp 65001 >nul 2>&1`

next, it's time to install a local python environment dedicated to handle the unique requirements of PP-OCRv5

start a new command prompt

Code:
set working_directory=%userprofile%\Desktop\Monobeno
cd "%working_directory%/tools/ocr_tools"
pyenv --version
pyenv versions
pyenv install --list
pyenv install 3.10
pyenv versions
python --version[code]

- If pyenv --version does not work, fix your %path%. Make sure pyenv appears first in the local %path%. Adjusting the system and user path variables may be required for that. [url="https://www.wikihow.com/Change-the-PATH-Environment-Variable-on-Windows"]Instructions[/url].
- If it says on Windows "No global/local python version has been set yet. Please set the global/local ver
sion by typing:", then define a system python version first by entering the command it says to, like pyenv global 3.10.11
- To use a preexisting installed version of Python as the global version, move it to the right folder so pyenv can manage it, that folder is listed above. alternatively, create a directory junction in pyenv\versions folder to the existing Python version
- See the [url="https://codeberg.org/entai2965/ocr_tools"]ocr_tools[/url] for more detailed installation instructions or if you encounter any problems, especially for how to fix pyenv switching to utf-8 on older Windows versions (<1807) that do not support utf-8, and the syntax for creating directory junctions.

[code]set working_directory=%userprofile%\Desktop\Monobeno
cd "%working_directory%/tools/ocr_tools"
python --version
pyenv versions
#adjust this next command as needed
pyenv local 3.10.11
python --version
pyenv versions
python -m pip install pip==24.0.0
python -m pip install -r requirements.txt
python -m pip install torch==2.3.1 torchvision==0.18.1 torchaudio==2.3.1 --index-url https://download.pytorch.org/whl/cpu
choco install rust --version=1.77.2
rustc --version
python -m pip install paddlepaddle paddlex paddleocr manga-ocr
#fix all the broken versions, welcome to dependency hell, this will also take core of installing compatible versions for tokenizers==0.19.1 and pydantic-core==2.14.1
python -m pip install transformers==4.44.2 safetensors==0.4.2 numpy==1.26.4 pydantic==2.5.0
#optional ccache dependency
choco install ccache
#check if the ticking time bomb was installed
python -m pip index versions hf_xet
#if it was installed, then remove it to fix huggingface_hub, huggingface_hub is used to download the models btw, so it's important
python -m pip uninstall hf_xet
python ocr_text_detection.py --help
#(optional), make sure they import correctly
python
import paddleocr
paddleocr.__version__
import manga_ocr
manga_ocr.__version__
exit()

download the default PP-OCRv5 ocr models, which are the detection model, PP-OCRv5_server_det, and the recognition model, PP-OCRv5_server_rec .

Code:
python ocr_text_detection.py --help
python ocr_text_detection.py -dl_hub

Now that ocr_tools has been installed and assigned its own special python version using pyenv, it will not mess up the system's Python installed packages. However, using it requires always opening up a command prompt to Monobeno/tools/ocr_tools before invoking python and about 2.4-2.8 GB of space. The actual environment it invokes is stored at ~/.pyenv/pyenv-win/versions/3.10.11 for future reference.

Now that ocr_tools is installed, let's copy all images that have text from Monobeno/extracts/data/uipsd to Monobeno/MTL/images/data/uipsd, excluding logos. After moving them, the count is at 245 ui files that contain text, all of them already in .png format. A lot of them are very small with trasparency. That count includes ui files that already have embedded english translations and files that appear to be duplicates.

After exlcuding files that already have embedded english translations by moving them to a subfolder called "eng", since there is no need to translate them when doing a japanese->english translation, the final count is at 180. The files weigh in at a combined total of 1.79 MB.

There are additional images files with text at extracts/data/image and extracts/data/main/ui. The ones at data/main/ui are logos which already say "Monobeno", so I am excluding them for image proessing.

Copying the images with text, excluding logos, from Monobeno/extracts/data/image to Monobeno/MTL/images/data/image results in 9 files, all .jpg. Before automating anything, lets set up a single file as a test case, just like before. That means copying extra_cg@subtabs%cref;normal.png to Monobeno/MTL/images/ocr/test.

Code:
python ocr_text_detection.py -i "%working_directory%/MTL/images/ocr/test/extra_cg@subtabs%cref;normal.png"

input_folder C:\Users\User\desktop\monobeno/MTL/images/ocr/extra_cg@subtabscref;
normal.png does not exist

% is a reserved character in the command prompt's batch langauge but also a valid character for filenames. There are ways to enter it, like ^% or %%, but a lazier workaround is to enter the folder as input instead.

Code:
python ocr_text_detection.py -i "%working_directory%/MTL/images/ocr/test

autodetected text_detection_model from PP-OCRv5 0 PP-OCRv5_server_det found at ~
\.cache\huggingface\hub\models--PaddlePaddle--PP-OCRv5_server_det\snapshots\ca86
7c897ecbca8873081573a802ad70d499cb94
autodetected text_recognition_model from PP-OCRv5 0 PP-OCRv5_server_rec found at
~\.cache\huggingface\hub\models--PaddlePaddle--PP-OCRv5_server_rec\snapshots\b2
6c3587fda8da3c8ec0ce357214b4d661ff1558
using device cpu
processing images as containing horizontal text
wrote ~\desktop\monobeno\MTL\images\ocr/test/extra_cg@subtabs%cref;normal.png.json
wrote ~\desktop\monobeno\MTL\images\ocr/test/extra_cg@subtabs%cref;normal.png.csv

The .json has the all of the information output from ocr_tools, including coordinates. The .csv has a summary of the output in a more human readable form that also allows for batch processing using the existing translation_tools scripts.

Here is the .csv, slightly truncated for readability
Code:
text,reserved,score#model_group#image_path#crop_path
Hシーンのみ,,0.99577    PP-OCRv5[...]
ありすルート,,0.98538    PP-OCRv5[...]
すみルート,,0.93413    PP-OCRv5[...]
全て表示,,0.99792    PP-OCRv5[...]
夏葉ルート,,0.99454    PP-OCRv5[...]
共通ルート,,0.88955    PP-OCRv5[...]

We can just run romaji.py and sugoi_mtl on it like normal.

open a new command prompt, or change the current one to not be at Monobeno\tools\ocr_tools

Code:
set working_directory=%userprofile%\desktop\monobeno
cd %working_directory%
#note the double % below to escape it
set file=extra_cg@subtabs%%cref;normal.png
python tools/translation_tools/romaji.py mtl/images/ocr/test/%file%.csv -s
python tools/translation_tools/sugoi_mtl.py mtl/images/ocr/test/%file%.csv -s

and the resulting translation.csv, truncated for readability

Code:
cutlet_hepburn,sugoi_mtl
H scene nomi,H-scene only
Arisu route,Arisu Route
Sumi route,Sumiruto
Subete hyouji,All displayed.
Kayou route,Natsuha Route
Kyoutsuu route,Common route

The next step is to add these translations to the images.

So it turns out that kritapy used to create .kra files is pre-alpha software that does not work even in the most simplistic case, the documentation is incorrect, and it still did not work even with tons of fixes applied to it.

At first, I was thinking about using Photoshop's .psd instead, but then learned more about it, it is not an interchange format, which is a format meant to be read many different types of by image processing software, not just Photoshop or Krita. Krita's support documents explicitly say that editable text would never be supported when reading from .psd files.

OpenRaster .ora, unlike the previous two options, is a proper interchange format with a specification and everything, and the library does work, and has proper documentation, but the .ora format specification does not seem to support vector or text layers? Including text seems to imply rendering that layer into a raster layer, so any created .ora file with layers would not have editable text. That is disappointing. I hope I am wrong about this.

At that point, I started thinking that there is probably some way of getting .ora to work with Krita using metadata that Krita understands or creating a script to get Krita to extract that metadata from the specially created .ora and almost created a demo for that, but obviously only Krita would understand that specially crafted data. So... at that point, it just makes more sense just to switch to scripting directly in Krita since any code written would be exclusive to Krita anyway, not directly compatible with Photoshop/GIMP or other image processing software.

That is what happened and that is the rational for why there is now a krita script that integrates ocr_text_detection.py's output into krita.

For future development, I could go back to the .ora with metadata idea or get .ora to store vector or text layers, and work to get it working in GIMP or whatever for a more ideal and generalized solution to overlaying text with image processing software, but this works for now. Krita, unlike Photoshop, is free and open source software, so we know it is not disappearing into the void that is closed source software anytime soon. That makes it safe to invest our time learning to use it.

The plugin itself is under ocr_tools/ocr_tools_import. Read the ocr_tools/ocr_tools_import_for_krita/README.md if interested in additional documentation. There are 2 ways to use it.

- 1) Run it directly as a script with or without a GUI. Open Krita->Tools->Scripts->Scripter->File->open->select either of the loose .py files at ocr_tools/ocr_tools_import.

The one named with .gui.py has a GUI, so it should be easier to use.
The other is the pure logic of the program, lacking any of the Qt logic for a GUI, and changing any of the settings requires changing the source code under main() that looks like this.

Code:
#get input
csv_dialect=None
spreadsheet_encoding='utf-8'
font_size=22
font_family="'DejaVu Sans', sans-serif"
fill_color=Qt.black
lock_background=True
default_text="Placeholder Text"

While some of the options are easy to understand and change like the spreadsheet_encoding, font_size, and placeholder text, it is less clear what the alternatives to font_family and Qt.black are. Which fonts are available on the local system? What is csv_dialect? Alternatives to Qt.black requires reading some cryptic Qt documentation that does not have the right syntax for PyQt.

The script.gui.py option may be more useful outside of debugging the main logic. the non-gui.py script also has no debug checkbox since it has no gui at all.

- 2) Run it as a plugin. Either
copy the files at ocr_tools/ocr_tools_import to %appdata%/krita/pykrita, restart Krita, and then activate the plugin under Settings->Configure Krita->Python Plugin Manager->ocr_tools import
or
do Krita->Tools->Scripts->Import Python Plugin. The .zip can be downloaded from ocr_tools's releases page. The files there may be more out of date than the source files. Just remember that importing .zip installs krita plugins to %appdata%/krita/pykrita. Update or delete the files there to update the plugin in the future.

My recommendation is to run ocr_tools import as a gui script at first.

If you are happy with it and want to keep using it, then install it as a plugin later so it does not have to be manually loaded every time, and so that Krita works normally even with the ocr_tools import gui open. The script versions must be closed before Krita's gui can respond to the keyboard/mouse again, but the plugin fixes that. I am pretty sure that is just how scripts work? The scripting school did not say how to fix that.

Anyway, let's open extra_cg@subtabs%%cref;normal.png in Krita.

Tools->Scripts->Scripter->File->open->select ocr_tools/ocr_tools_import/ocr_tools_import_for_krita_script_gui.py

Then click on the Run button. Do not click on debug, debug will always crash Krita on Windows. Remember that Krita traces its initial development back to KDE Linux, so while the debug button works on Linux, probably, it does not on Windows. The gui should look very similar to this.

[ocr_tools_import1]

Notice how the 'debug' option in ocr_tools import is checked, not to be confused with the debug button in the Krita Scripter window. Click on Apply, then close.

It should print some debug information in the Output tab, and do nothing else.

Code:
scope Active Image
font_size 20
font_color 2
font_family Noto Sans
placeholder_text Placeholder Text
lock_background True
spreadsheet_dialect None
spreadsheet_encoding utf_8
processing C:/Users/User/Desktop/Monobeno/MTL/images/ocr/test/extra_cg@subtabs%cref;normal.png
with C:\Users\User\Desktop\Monobeno\MTL\images\ocr\test/extra_cg@subtabs%cref;normal.png.json
len(temp_boxes) 6
processing box 0 - H-scene only 439.0 27.0
processing box 1 - Arisu Route 231.0 27.0
processing box 2 - Sumiruto 129.0 27.0
processing box 3 - All displayed. 553.0 28.0
processing box 4 - Natsuha Route 339.0 28.0
processing box 5 - Common route 20.0 28.0
close clicked

It printed the path to the .json it was using to detect where each box of text is. It does not say so, but it is also reading the .csv and using the right-most column as translations which are listed as processing ox [box_number] - [translation] [coordinates]. The .csv is considered optional input. Maybe I should print that out too if it is found? here's what it looks like when the .csv is not found or has no translated data

Code:
processing box 0 - Hシーンのみ 439.0 27.0
processing box 1 - ありすルート 231.0 27.0
processing box 2 - すみルート 129.0 27.0
processing box 3 - 全て表示 553.0 28.0
processing box 4 - 夏葉ルート 339.0 28.0
processing box 5 - 共通ルート 20.0 28.0

Since it could not find any translated data from the .csv, because I deleted the .csv file, it just used the text from the .json instead. If the .json does not have any 'text' for a box entry, then the value of placeholder_text would be used instead as a last resort, which is "Placeholder Text" unless changed. If the boxes.json is not found at all, then the current image is skipped for processing completely.

'Sumiruto' for processing box 2 is obviously wrong. Deleting that cell in the sugoi_mtl column in the .csv results in ocr_tools import using 'Sumi route' from the romaji column instead.

Code:
processing box 2 - Sumi route 129.0 27.0

Better. That fallback logic is beautiful. However... Sumi route... Is there a 'Sumi route' as in a 'route' for 'Sumi' in this game? If there is a route for them, that would imply a character named 'Sumi' too, right? Is there a character named Sumi in this game? Here is the vndb.org entry. Yes, yes there is. Sumi really exists apparently. News to me!

Now that we are happy that ocr_tools import is reading from and processing the data from the .json and the .csv correctly, we can click on the Run button again, uncheck 'debug' this time, and then hit Apply + Close.

That should have added all of the layers from boxes.json and the translations from boxes.csv. The text is too large and the black text is hard to see. It needs to be smaller and white.

Running ocr_tools import again will do nothing because there is more than one layer in the current file. As a precaution, it refuses to run on files where there are already layers. I really do not want the code to ever mess with any work that is in progress, ever, which is why that restriction exists.

So let's run the tool again. That means we need to delete all of the existing layers except for the background so the file can be processed again. Remember to uncheck debug.

There should probably have a feature where it saves its own settings and loads them again, but that is more of a version 2 feature than a version 0.1.0 feature. So here is the output

[ocr_tools_import2]

At this point, we can save the image as extra_cg@subtabs%cref;normal.png.kra. Keeping the original .png extension in front the .kra filename is very important! Krita incorrectly renames the file to extra_cg@subtabs%cref;normal.kra which removes the original .png extension. That information is critical because it is used by ocr_tools to match the .kra file to the original image name. Without it, ocr_tools import cannot match the open image to the original filename anymore which means it will never process the file!

When exporting as .png, Krita will also incorrectly it as extra_cg@subtabs%cref;normal.png.png which has a double .png.png extension. That is not the correct filename! Kirikiri is not looking for that filename!

I do not understand the rationale behind this destructive behavior of both removing the original extension and then refusing to double check for obviously wrong double extensions, but just be aware of this not sane behavior. Having incorrect names will mess everything up after all, so be extra careful handling them because Krita will try to mess them up.

A better way to work with Krita so it does not mess up the file names is to always save as .png when the file loaded is a .png. During saving, there is a small checkbox on the .png saving screen to always save as .kra too. That will save the .kra file as .png.kra which is ideal. That means to work with the image again after closing it, open the .png.kra file and immediately the file as .png. Every export after that should work correctly without messing up the filename.

With the discussion on saving out of the way, now we just have to make the layers invisible, manually inpaint over the background layer, make the text layers visible again, style the text appropriately, and export that image. Then we can start the next image in a cycle until all of the images are done.

Here are some more hints for additional automation.

- ocr_text_detection.py works on batches of images at a time. It will produce files literally named 'boxes.json' and 'boxes.csv' when run on batches of images.
- ocr_tools import can import data from 'boxes.json' and 'boxes.csv' transparently, just use 'image.png.json' style names, even if not all of the images in boxes.json are loaded into Krita.
- ocr_tools import, when installed as a native plugin into Krita, allows leaving it open in the background while working in Krita. This also allows fine tuning different text overlay settings without having to reinput everything in the gui from scratch since it can just be left open. Closing it will reset all the settings back to their defaults unless I decide to implement a save data feature.
- Technically, ocr_tools import can also work on all images loaded into Krita at the same time by changing the scope to All Images, but since each image tends to need its own style adjustments, it is probably less work to import them one at a time. Otherwise, that would require deleting and reimporting a lot to fix the font_size, color, and maybe font family. However, that may be ideal for groups of images that are very similar, they can be processed in Krita all at once that way.

start a new command prompt

Code:
set working_directory=%userprofile%\desktop\monobeno
cd %working_directory%
cd tools/ocr_tools

python ocr_text_detection.py -i %working_directory%\MTL\images\data\image
python ocr_text_detection.py -i %working_directory%\MTL\images\data\uipsd
python ocr_text_detection.py -i %working_directory%\MTL\images\data\uipsd\eng

cd ../..

python tools\translation_tools\romaji.py MTL\images\data\image\boxes.csv -s
python tools\translation_tools\romaji.py MTL\images\data\uipsd\boxes.csv -s
python tools\translation_tools\romaji.py MTL\images\data\uipsd\eng\boxes.csv -s

python tools\translation_tools\sugoi_mtl.py MTL\images\data\image\boxes.csv -s
python tools\translation_tools\sugoi_mtl.py MTL\images\data\uipsd\boxes.csv -s
python tools\translation_tools\sugoi_mtl.py MTL\images\data\uipsd\eng\boxes.csv -s

Remember that invoking the correct isolated python environment for ocr_text_detection.py requires changing into the ocr_tools folder first.

That should detect all text in the gui images, add romaji, and sugoi translations for them. Isn't automation great?
 
Manually fixing automated image processing mistakes

Unfortunately, automation is never perfect. Even if most of the images process correctly, every once in a while there are images that require special care and are worth the effort.

One of those images is the main configuration screen. The configuration screen is one of the main highly visible images in the gui. It is worth spending a lot of time on it, several hours or more, to get it perfect. Here is the raw image.

[opt_snd@caption%layer]

It looks simply enough, does it not? It is just a .png with text on a transparent background. Here is the output from the automated workflow found in the mtl/images/data/uipsd/boxes.csv, truncated for readability.

Code:
text,reserved,score#model_group#image_path#crop_path,cutlet_hepburn,sugoi_mtl
text,reserved,score#model_group#image_path#crop_path,cutlet_hepburn,sugoi_mtl
自頻度,,0.68924 PP-OCRv5 .0,Jihindo,Self-frequency
CGての老化描写,,0.81288 PP-OCRv5 .1,CG te no rouka byousha,CG depictions of aging
速,,0.52039 PP-OCRv5 .2,Soku,Hayao
レント送択,,0.66509 PP-OCRv5 .3,Rent okutaku,Rentt's choice
場面シークバーの表示,,0.94268 PP-OCRv5 .4,Bamen seek bar no hyouji,Scene sequencer display
常に最前面に画面を表示する,,0.95792 PP-OCRv5 .5,Tsune ni saizenmen ni gamen wo hyouji suru,It always shows the screen at the front.
6,,0.28906 PP-OCRv5 .6,6,Six.
ローショ,,0.51094 PP-OCRv5 .7,Roosho,Rosho
非アクティプ時にも動作する,,0.92869 PP-OCRv5 .8,Hiakutipu toki ni mo dousa suru,It works even when it's not active.

That data is complete and utter garbage. Both the raw OCR results and the translations are just nonsense. There are even some very low text recognition scores, 0.28906 and 0.51094. Why? The image does not look hard to read. What is going on?

First, let's replicate the issue by running the image through the automated workflow manually.

Code:
set working_directory=%userprofile%\desktop\monobeno
set file=%working_directory%\mtl\images\data\uipsd\opt_sys@caption%%layer.png

The file name is opt_sys@caption%layer.png but since % is a reserved character in the batch language, we have to double it up whenever it appears in the filename to escape it properly.

Code:
cd %working_directory%
cd tools/ocr_tools
python ocr_text_detection.py --help

#remember to double up any % in the path
C:\Users\User\Desktop\Monobeno\tools\ocr_tools>python ocr_text_detection.py -i C:\Users\Us
er\Desktop\Monobeno\MTL\images\data\uipsd\opt_sys@caption%%layer.png -odb -oif

-odb means --output_detected_boxes, output the detected boxes as loose png files in the output folder, if subfolders are enabled, a image.png_boxes directory will be created for each processed image that will contain the loose png files

-oif means --output_intermediary_files, output miscellaneous intermediary files useful for visualization, if output subfolders are enabled, this will write all intermediary files in a nested subfolder under output_folder/image.png_boxes/intermediary

That should have written lots of files to ~/Desktop/Monobeno/MTL/images/data/uipsd/opt_sys@caption%layer.png_boxes/. If we look in that folder, it has these images.

[opt_sys@caption%layer.png.14]

[opt_sys@caption%layer.png.15]

That is what the image looks like to the text recognition model. That color scheme seems like it would make it harder to detect text, not easier.

When an image with at transparent background and text is opened with open computer vision library (cv2), cv2 will automatically add a high contrast background unless it is explicitly told to keep the transparency. Adding a high contrast background is generally a good idea when detecting text, but it seems that failed here since it added a white background to contrast against white text. Incorrectly adding a white background instead of a black background likely happened since the text in the image has a grey shadow, which probably confused the algorithim somehow.

Let's fix that one part of the automated image processing chain manually and then feed the image back into the automated workflow after that.

Open the image in Krita, new layer, move the layer under the base image,edit->fill with background color -> file -> save as -> mtl/images/ocr/sys_config/opt_sys@caption%layer.png

"fill with background color" should add a black background. Note that the file above was saved to a different path than the source image. Now let's run ocr_tools again.

Code:
C:\Users\User\Desktop\Monobeno\tools\ocr_tools>python ocr_text_detection.py -i "%working_directory%/mtl/images/ocr/sys_config/opt_sys@caption%%layer.png" -odb -oif

Unlike before, the images under MTL/images/ocr/sys_config/opt_sys@caption%layer.png_boxes/ look normal this time which is a very good sign. However, here is the detection.png under opt_sys@caption%layer.png_boxes/intermediary/.

[opt_sys@caption%layer.png.detection]

That is still not quite correct, is it? PP-OCRv5's detection model will try to merge text as much as possible because that is generally the correct behavior. That behavior created a few errors in this system configuration screen image. In this image, sometimes text appears in the same horizontal line that is actually a completely different text entry like "CGての老化描写目パチ頻度" is actually "CGての老化描写" and "目パチ頻度". That text should not be merged.

One not automated way is to use the Windows Snipping Tool to cut these problematic entries into their own seperate boxes before running ocr_text_recognition.py and handling them seperately from the main .csv. Another option that uses the uses the existing .csv is to manually put "CGての老化描写" and "目パチ頻度" on different lines and then run romaji.py/sugoi_mtl.py.

Both of the above approaches are valid, but here is a third option. ocr_tools actually allows us to draw our own boxes by using the mouse if it is given the correct command line switch

-m, --manual_text_detection, enable manual box detection mode, when selected, images will be shown so text boxes can be drawn using the mouse, press Spacebar to continue once all missing boxes on an image have been selected, press Esc to quit

Let's delete MTL/images/ocr/sys_config/opt_sys@caption%layer.png.json and .csv and run ocr_text_detection.py again with --manual_text_detection.

C:\Users\User\Desktop\Monobeno\tools\ocr_tools>python ocr_text_detection.py -i "%working_directory%/mtl/images/ocr/sys_config/opt_sys@caption%%layer.png" -odb -oif -m[/code]

That will print this and bring up an image window.

Code:
[INFO] Select 4 points to draw a box
[INFO] Left-click: create a point
[INFO] Right-click: delete the last point
[INFO] CTRL + C: interrupt the program while the terminal is the active window
[INFO] ESC: cancel all manual input while the image is the active window
[INFO] Spacebar: continue to next image while the image is active

Let's draw boxes over every text entry that should be seperated. The boxes that need to be split should overlap with the original boxes that incorrectly merged nearby text into one box. 5 entries were merged incorrectly for this file which means 10 manually drawn boxes. Press spacebar when done.

[sys_config.manual]

Code:
wrote ~\desktop\monobeno\mtl\images\ocr\sys_config/opt_sys@caption%layer.png.json
wrote ~\desktop\monobeno\mtl\images\ocr\sys_config/opt_sys@caption%layer.png.csv

The top of the .csv is the same as before but near the bottom are some new entries.

Code:
最速
最速
無し
テキストウィンドウ不透明度
オートモード待ち時間
テキスト表示速度
目パチ頻度
CGての老化描写
無し
メニュー表示ボタンの不透明度

Better. Much better. If we open the .json, here is what the manually detected boxes look like in the raw data.

Code:
"31": {
    "distance_from_top": 176,
    "left": 23,
    "right": 25,
    "bottom": 204,
    "height": 28,
    "top_left": [23,176],
    "top_right": [260,176],
    "bottom_right": [254,199],
    "bottom_left": [25,204],
    "angle_top": 0.0,
    "angle_top_radians": 0.0,
    "angle_bottom": -1.251,
    "angle_bottom_radians": -0.02183,
    "angle_left": 85.914,
    "angle_left_radians": 1.49949,
    "angle_right": -75.379,
    "angle_right_radians": -1.31561,
    "detection_model_group": "user",
    "detection_model": "user",
    "detection_score": 1.0,
    "vertical_text": false,
    "text": "メニュー表示ボタンの不透明度",
    "recognition_score": 0.93326,
    "recognition_model_group": "PP-OCRv5",
    "recognition_model": "b26c3587fda8da3c8ec0ce357214b4d661ff1558"
}

The detection_model_group and detection_model are both 'user' and have a perfect detection score of 1.0.

After detecting the box manually, the recognition model group PP-OCRv5's PP-OCRv5_server_rec was used to recognize the text in that box.

The metadata for the recognition_model entry is messed up right now. It just outputs the folder name which is not particularly helpful if the folder name is a commit hash since the model is in the huggingface_hub cache. I might fix this cosmetic issue later.

Anyway, this is a very important picture. It is vital we get both the OCR correct and translation correct since it will dramatically impact the resulting quality of any translation patch. To that end, let's say we wanted to use manga-ocr-base as the recognition model instead of PP-OCRv5_server_rec so we could have a second opinion when we later edit it in Krita. One way would be to run ocr_tools again with the MangaOCR model group. We can find all of the known model groups and models by doing

Code:
python ocr_text_detection.py --list

Under 'known OCR recognition models', it lists MangaOCR as a valid model group and [ 0 ] manga-ocr-base as the only known model for that group.

Code:
MangaOCR ['ja']
[ 0 ] manga-ocr-base

There is only one model, manga-ocr-base, for the text_recognition_model_group, MangaOCR, so we do not have to worry about supplying an index. Then we could append the model group to our previous command.

Code:
python ocr_text_detection.py --text_recognition_model_group MangaOCR

However, that would require deleting the .json and .csv we generated before and also re-drawing all of the boxes manually so they can be recognized using the new recognition model. Drawing the boxes again would be highly annoying. No thanks.

A better way is to run the mangaocr model by using the previous output from -odb. The -odb command from earlier outputs all of the detected boxes to MTL/images/ocr/sys_config/opt_sys@caption%layer.png_boxes/ including our manually drawn boxes. Even better, we can alter the images there manually before running recognition software on them, including outright deleting any incorrect ones or editing out any overzealous padding.

The files in that folder are meant to be used this way to ensure a manual fallback option exists for any images that need it. This should ensure the highest quality input possible for the recognition model since we are manually fixing any errors in the detection stage. This is the flexibility that a quality focused workflow should enable.

Aside. Eventually, I should fully merge the ocr_text_detection and recognition scripts and make detection optional. Until then, ocr_text_recognition.py can be used to always invoke the MangaOCR recognition model on groups of images using a simpler syntax than ocr_text_detection.py. Right now, ocr_text_detection.py insists on running text detection which is not entirely correct behavior because that is too inflexible. What if we already ran the detection and only want to run a different recognition model on the same data like we want to do right now? I may fix this issue later. For now, let's just use ocr_text_recognition.py instead.

Code:
python ocr_text_recognition.py -h
python ocr_text_recognition.py "%working_directory%/MTL/images/ocr/sys_config/opt_sys@caption%layer.png_boxes" -o

Unable to find file: C:\Users\User\desktop\monobeno/MTL/images/ocr/sys_config/op
[email protected]_boxes

right, right... Double up on %.

Code:
python ocr_text_recognition.py "%working_directory%/MTL/images/ocr/sys_config/opt_sys@caption%%layer.png_boxes" -o

wrote: C:\Users\User\desktop\monobeno/MTL/images/ocr/sys_config/opt_sys@caption%layer.png_boxes/output.ocr.csv

Now we can add romaji and sugoi translations to both opt_sys@caption%layer.png.csv (PP-OCRv5's PP-OCRv5_server_rec) and to output.ocr.csv (MangaOCR's manga-ocr-base).

Code:
cd %working_directory%\tools

python translation_tools/romaji.py "%working_directory%/MTL/images/ocr/sys_config/opt_sys@caption%%layer.png.csv" -s
python translation_tools/sugoi_mtl.py "%working_directory%/MTL/images/ocr/sys_config/opt_sys@caption%%layer.png.csv" -s

python translation_tools/romaji.py "%working_directory%/MTL/images/ocr/sys_config/output.ocr.csv" -s
python translation_tools/sugoi_mtl.py "%working_directory%/MTL/images/ocr/sys_config/output.ocr.csv" -s

Code:
ocr_text,file,cutlet_hepburn,sugoi_mtl
速,,Soku,Hayao
フォント選択,,Font sentaku,Font selection
場面シークバーの表示,,Bamen seek bar no hyouji,Scene sequencer display
常に最前面に画面を表示する,,Tsune ni saizenmen ni gamen wo hyouji suru,It always shows the screen at the front.
ロパクアニメーション,,Ropakuanimeeshon,Ropaku Animation
非アクティブ時にも動作する,,Hiactive ji ni mo dousa suru,It also works when it's inactive.
場面ジャンプの際に確認する,,Bamen jump no sai ni kakunin suru,Check when jumping scene
目バチアニメーション,,Me bachi animation,Mebachi Animation
クリックでオートモード解除,,Click de auto mode kaijo,Click to release auto mode.
データ削除時に確認する,,Data sakujo ji ni kakunin suru,Check when you delete the data
エフェクト瞬間表示,,Effect shunkan hyouji,Effect instant display
未読スキップ,,Midoku skip,Unread Skip
データ上書き時に確認する,,Data uwagaki ji ni kakunin suru,Check when overwriting the data
データロード時に確認する,,Data road ji ni kakunin suru,Check when you load the data.
データセーブ時に確認する,,Data save ji ni kakunin suru,Check when you save data.
確認ダイアログ設定,,Kakunin dialogue settei,Confirming dialogue settings
画面モード,,Gamen mode,Screen mode
はい,,Hai,"Yes, sir."
...,,...,...
無し,,Nashi,No.
テキストウィンドウ不透明度,,Text window futoumei do,Text window opaque
オートモード待ち時間,,Auto mode machi jikan,Auto-mode Waiting Time
テキスト表示速度,,Text hyouji sokudo,Text display speed
目パチ頻度,,Me pachi hindo,Eye blink frequency
CGでの老化描写,,CG de no rouka byousha,Description of aging in CG
無し,,Nashi,No.
メニュー表示ボタンの不透明度,,Menu hyouji button no futoumei do,The opaque button on the menu display

Beautiful. Not flawless, but still much better than our original output.

Next we can open MTL/images/ocr/sys_config/opt_sys@caption%layer.png in Krita, and use ocr_tools import to overlay the translated text automatically. Once it looks mostly correctly positioned and the redundant layers are deleted, we can move all of the remaining layers over to MTL/images/data/uipsd/opt_sys@caption%layer.png, also opened in Krita, and have a much more reasonable base to work from. Best of all, we can also refer to output.ocr.csv for alternative translations based on using the MangaOCR recognition model.

The sound configuration screen, opt_snd@caption%layer.png, will need the same treatment as above.

Editing images means exporting them from Krita as .jpg or .png, copying them to Monobeno/MTL/patch, packing that folder into a .xp3 file, and moving the .xp3 to Monobeno/bin/Monobeno Happy End. That is really annoying to test minor edits, especially the packing part, so here is a script that automates the packing. It was based on the previous update.cmd script.

Code:
@echo off

set working_directory=%userprofile%\Desktop\Monobeno

:: this creates .xp3 files from folders
set tool=%working_directory%/tools/KiriKiriTools-1.7/Xp3Pack.exe

set dialogue_scn_raw=%working_directory%\MTL\scenario\dialogue_scn_raw
set dialogue_scn_json=%working_directory%\MTL\scenario\dialogue_scn_json
set csv_directory=%working_directory%\MTL\scenario\csv
set dialogue_scn_json_translated=%working_directory%\MTL\scenario\dialogue_scn_json_translated
set dialogue_scn_translated=%working_directory%\MTL\scenario\dialogue_scn_translated
set patch_directory=%working_directory%\MTL\patch
set game_directory=%working_directory%\bin\Monobeno Happy End

set character_names=%working_directory%/character_names.csv

if not exist "%patch_directory%" mkdir "%patch_directory%"

::archive the folder
"%tool%" "%patch_directory%"

move /y "%working_directory%\MTL\patch.xp3" "%game_directory%\patch.xp3"

echo updated patch.xp3

Here is what the almost fully translated system configuration screen looks like.

[sys_config.translated]

Not everything is translated because those are internal game engine strings, not images. I also have no clue what "Ropaku animation" refers to, so I left it as-is for now. Here is the raw data.

opt_sys@caption%layer.png.csv
Code:
ロパクアニメーション,0.99229 PP-OCRv5,Ropakuanimeeshon,Ropaku Animation

output.ocr.csv
Code:
ロパクアニメーション,,Ropakuanimeeshon,Ropaku Animation

The ocr agrees from both PP-OCRv5_rec and manga-ocr-base, so that is a good sign. An alternative translation is "Lopaku anime" or "Ropac Animation", and it is right below 'eye animation'. I think it is trying to convey that it is some sort of animation option, but I am not sure what for what exactly. It might be english as in "Low-Pack Animation." DeepL also suggested "low-quality animation." Anyway, maybe we can figure it out before publishing the final patch.xp3, but let's move on for now.

Future speaking here! I went back later and figured it out.

Since is was not obvious from the text alone but is clearly some sort of animation, let's try disabling the option, playing a few lines, going back to the start, enabling the option, and playing a few lines again. Notice any differences? The mouth flapping animation stops when the option is disabled, but works if it is enabled.

Some candidates are calling it, Character animation speaking, speaking animation, mouth flap animation, animated mouths, animated speaking, or animated speakers? "Speaking Animation" is probably the most clear without being overly long, but maybe "Mouth Animation" works too.
 
Testing SLR Translator's SEP addon

That is the most automation for image processing we can do right now without dropping quality, at least until a quality centric context aware inpainting model can used to perform inpainting automatically.

So let's try dropping quality next!

We could not really justify dropping quality before because we had no solution for when the automation fails. Now that fixing each individual image with a higher degree of automation than before is possible, we can try to get automated inpainting to work without being penalized if the automation does not work. We are no longer relying on full automation to always be accurate basically, and our "manual" fallback option is somewhat automated now.

SLR Translator 2.0's SEP addon blurs the background and then overlays the translated text over the image. That means it is not a particularly high quality solution, but its primary benefits are being fully local and automated. Blurring the background is also a better quality solution than some commercial OCR providers. The quality should also improve over time once a better automated inpainting solution like AnyTrans, is implemented, one day, maybe.

Let's extract SLR Translator to Monobeno/tools/SLR Translator.

Launch Monobeno/tools/SLR Translator/SLR Translator.exe
Start a new project.
SEP
Select Monobeno/MTL/images/data/image -> select folder
Start project

Code:
Creating a new project via SEP : C:\Users\User\Desktop\Monobeno\MTL\images\data\image

Log started!
You can view the more detailed information on console log (F12)
Creating a new project via SEP : C:\Users\User\Desktop\Monobeno\MTL\images\data\image
[SEP] Cleaned up temporary directory.
[SEP] generating trans data.
[SEP] Temporary directory created at: C:\Users\User\Desktop\Monobeno\tools\SLR Translator\www\addons\SEP\tmp
[SEP] Copying PNG data from C:\Users\User\Desktop\Monobeno\MTL\images\data\image to C:\Users\User\Desktop\Monobeno\tools\SLR Translator\www\addons\SEP\tmp
[SEP] Starting initial OCR data generation...
[SEP] Running ocr_text_detection.py with PP-OCRv5 model... This can take a while.
[ocr_text_detection.py] using text_detection_model with PP-OCRv5 from ~\Desktop\Monobeno\tools\SLR Translator\www\addons\SEP\PP-OCRv5_server_det
[ocr_text_detection.py] using text_recognition_model with PP-OCRv5 from ~\Desktop\Monobeno\tools\SLR Translator\www\addons\SEP\PP-OCRv5_server_rec
[ocr_text_detection.py] using device cpu
[ocr_text_detection.py] no images found at C:\Users\User\Desktop\Monobeno\tools\SLR Translator\www\addons\SEP\tmp
[SEP] ocr_text_detection.py completed successfully.
[SEP] PNG processing failed: error is not defined
[SEP] Failed to generate OCR data: error is not defined

Well, that failed catastrophically. What it supposed to fail that way? Yes, actually. This early version of SEP only works on .png files and MTL/images/data/image only has .jpg files.

SEP actually uses ocr_tools internally. ocr_tools also supports vertical text and is meant to support arbitrary image formats, including .jpg. At the very least, all image formats that PIL and cv2 support should theoretically also work with ocr_tools, although there might be some missing wildcards to pick them up due to how early ocr_tools is in its development. boxes.json was already also in the MTL/images/data/image which proves ocr_tools worked just fine on those images but asking SEP to process that same input causes SEP to ignore the already processed boxes.json which SEP could have reused and also to refuse to process the image files found in that folder. humm

In other words, SEP could also work on the .jpg files in that folder, but the developer has chosen to disable that functionality despite there being no technical need to do so. Wonderful. It is one thing to not support .tlg, which is an obscure image format unique to the kirikiri game engine, but .jpg is a very common image format.

Part of the reason for this seemingly arbitrary limitation is that SEP was initially designed for use with the RPGM game engine, so using it on non-RPGM sourced images is techncally outside the initial scope of the software. RPGM mostly only uses .png, so there was never any need to support anything outside of that.

In our current workflow, we can just use ocr_tools seperately from SEP, and then add both romaji, and Sugoi translations by using translation_tools like we already did, and it was all completely automated.

Ideally, with SEP, we could also get some automation on producing gaussian blur backgrounds and selecting the correct font size for text overlay, but gaussian blur backgrounds is an extremely low quality solution compared to both AI inpainting and manual inpainting, so we were already expecting a large quality drop by integrating SEP into our existing workflow. Font sizing is also very error prone as well, and SEP cannot even save images in the correct output format because the images are in .jpg format and SEP only supports .png.

One option now is to do manually perform .jpg->.png conversion for each file so that when SEP tries to copy .png files to 'SLR Translator\www\addons\SEP\tmp', it will actually find some .png files to copy, so that ocr_tools can then process them. Then once we have the final output, we can manually convert our images back to .jpg.

This "automated" solution is turning out to be very not automated, and due to the problems listed above, the expected output quality is much lower than our existing workflow. There is no gurantee that we would get any useful output out of SEP at all.

Another option is to just give up for now. I am not a fan of automation software that does not automate anything in particular. Software to automate stuff must automate stuff. If it doesn't, why use it?

Same thing with the scope. I am also not a fan of using software outside of its intended scope.

If the developer decided to only support RPGM, we cannot reasonably complain when it does not work on other game engines. That is not what the software was developed to support and tested against. While we could coerce either our input or the software to work anyway, the software has not shown that it is useful enough to warrant that level of effort yet. If we did coerce the software, the developer would likely not backport our changes because they are outside the scope of SEP. That is one of the many problems that occurs when using software outside of its intended scope and SEP's focus is simply too narrow to be useful to us, at least for this very early version of SEP.

This early version of SEP does not meet the minimum requirement of being automated sufficently to count as automated image processing software, at least for our use case of processing .jpg's. I'll check back in 6 months or so. Maybe .jpg support will be added by then.

Once the AnyTrans algorithim is properly engineered to be available using only local software, then SEP would potentially save us a lot of time because the expected quality would be dramatically higher. At that point, it would be worth taking the time to make sure SEP works with the AnyTrans algorithim and arbitrary image input formats.

However, at the time of writing this guide, I have already contributed enough development time to SEP currently. I would like the software I write to be used and useful, at least to me, before I continue developing it further, so I'll stick to just using ocr_tools for now.

Update, SLR Translator 2.004 includes an updated version of SEP that says it works with .jpg files.
 
Updating the title

[title_before]

extracts/data/AppConfig.tjs has a line like this.

Code:
global.ENV_GameName  = "ものべの-happy end-";

Such a line may be relevant to us updating the title because ものべの-happy end- is the same string that appears in both the title and game's main executable, ものべの-happy end-.exe. That is probably the title.

To update the title, we should first try to update either that file or the global.ENV_GameName variable. If we try to update the file, will our changes load? If they will not, then we will have to update the variable. To answer if changes to that file will stay, let's ask when does AppConfig.tjs get read relative to patch.xp3? Initialize.tjs probably has the answer to this.

If we browse data/system/Initialize.tjs and search for AppConfig.tjs, it looks like this.

Code:
// パッチアーカイブ(※パッチの中身は平坦展開でパッチ内サブフォルダは使えない)
useArchiveIfExists("patch.xp3");

// 追加のパッチ用アーカイブの検索
for(var i = 2; ; i++)
{
    // パッチ用アーカイブ patch2.xp3, patch3.xp3 ... がある場合はそちらを
    // 優先して読み込むように
    if(Storages.isExistentStorage(System.exePath + "patch" + i + ".xp3"))
        Storages.addAutoPath(System.exePath + "patch" + i + ".xp3>");
    else
        break;
}

[...]

/*
    AppConfig.tjs 読み込み
*/
if(Storages.isExistentStorage("AppConfig.tjs"))
{
    KAGLoadScript("AppConfig.tjs");
}

/*
    Config.tjs 読み込み
*/
if(Storages.isExistentStorage("Config.tjs"))
{
    KAGLoadScript("Config.tjs");
}

So the load order is Initialize.tjs->patch.xp3->AppConfig.tjs. So if we update AppConfig.tjs in patch.xp3, our changes should stay. Perfect.

If we search for "global.ENV_GameName" in all of the files, there is also one reference to it in MainWindow.tjs and another in Initialize.tjs again. Here is the Initialize.tjs part.

Code:
/*
    Config.tjs 読み込み
*/
if(Storages.isExistentStorage("Config.tjs"))
{
    KAGLoadScript("Config.tjs");
}
else if(Storages.isExistentStorage("Config.~new"))
{
    System.inform("Config.tjs が見つかりません。\nsystem フォルダにある "
        "Config.~new ファイルを Config.tjs に改名してください。");
    System.exit();
}
else
{
    throw new Exception("Config.tjs が見つかりません。");
}

if (typeof global.ENV_GameName != "undefined") {
    System.title = global.ENV_GameName;
}

The crucial line is the one that says

Code:
System.title = global.ENV_GameName;

It looks like System.title is also used for the title, but it's value comes from global.ENV_GameName, so if we update global.ENV_GameName before that, they should both be updated because the load order is Initialize.tjs->AppConfig.tjs->Config.tjs->reading the value of global.ENV_GameName in Initialize.tjs.

Surely that variable does not get changed in Config.tjs, right? Open up the file and look if you are curious.

Now that we have a basis to have a reasonable expectation that updating the title in AppConfig.tjs should work, we can test it and draw additional conclusions depending upon the test result.

Let's copy extracts/data/AppConfig.tjs to mtl/patch/ and update it.

Code:
global.ENV_GameName = "Monobeno Happy End";

After creating the patch again, here is the result.

[title_before]

Hummm. That was not as effective as I had hoped. Perhaps that file's contents are being read and stored in memory prior to getting invoked in Initialize.tjs, so the kirikiri game engine just returns that data instead of reading a fresh copy from patch.xp3?

Let's try the alternative approach of updating the title, updating the variables instead. Let's delete mtl/patch/AppConfig.tjs, and then copy extracts/data/main/Config.tjs to mtl/patch/ and update it by prepending the line above.

[title_after]

Well, that worked, but now we have the problem of hijaking Config.tjs. It works, but that is an inelegant solution.
 
Adding subfolders to the patch

The title is the first game engine string that we need to update, but there will be more, a lot more, like the line in the above window with the backwards E with is apparently a valid Japanese character. Copying arbitrary files .tjs from extracts/data to mtl/patch will become very messy, and it already is, honestly.

For Tsumugi no Hanayome, there was no option but to dump all of the files in the unencrypted.xp3 file since the patching .dll did not support subfolders, but for Koakuma-chan, we were able to give it the patch a meaningful structure. To try to keep everything organized and easily updateable, let's copy that same code from Koakuma-chan's Patch.xp3 to our Monobeno patch.xp3.

Code:
// update title
System.title = "Koakuma-chan no Yuuwaku!";
GAME_TITLE = System.title;
GAME_CAPTION = System.title;

// add new storages here
Storages.addAutoPath(System.exePath + "Patch.xp3>data/system/");
Storages.addAutoPath(System.exePath + "Patch.xp3>data/scenario/");
Storages.addAutoPath(System.exePath + "Patch.xp3>parts/frame/");

if(Storages.isExistentStorage(System.exePath + "Patch2.xp3")){
    Storages.addAutoPath(System.exePath + "Patch2.xp3>");
}
if(Storages.isExistentStorage(System.exePath + "Patch3.xp3")){
    Storages.addAutoPath(System.exePath + "Patch3.xp3>");
}

LoadScript("Utility.tjs");

Let's update that to work with Monobeno's structures.

Code:
//update title
global.ENV_GameName  = "Monobeno Happy End";

//add new storages
Storages.addAutoPath(System.exePath + "patch.xp3>data/image/");
Storages.addAutoPath(System.exePath + "patch.xp3>data/main/");
Storages.addAutoPath(System.exePath + "patch.xp3>data/scenario/");
Storages.addAutoPath(System.exePath + "patch.xp3>data/uipsd/");

//load original file
LoadScript("Config.tjs")

We did not need any of the Patch2.xp3 code from Koakuma-chan's Utility.tjs file because Monobeno, unlike Koakuma-chan has support for additional patch.xp3 files already, so that code was removed.

The above code for Config.tjs assumes a directory struture like this for monobeno/mtl/patch

Code:
patch/
patch/Config.tjs
patch/data/
patch/data/image/
patch/data/main/
patch/data/main/Config.tjs
patch/data/scenario/
patch/data/uipsd/

- patch/Config.tjs has the code above and patch/data/main/Config.tjs is the unaltered file. The original Config.tjs still needs to run to ensure we are not altering the game's scripts despite us using the name "Config.tjs" to execute our code right before it loads.
- patch/data/image/ has a few .jpg images.
- patch/data/scenario/ has all of the .txt.scn files.
- patch/data/uipsd/ has the bulk of the ui translated images as .png

[syntax_error]

Hummm. Where did we mess up? Hummm. Hummm. Hummm. We are using kirikiriZ's syntax for kirikiri2. Maybe it changed? What was the syntax for adding .xp3 files again? extracts/data/system/Initalize.tjs and extracts/data/main/Storages.tjs should have that.

Code:
function useArchiveIfExists(name)
{
    // name を正規化
    with (Storages) name = .chopStorageExt(.extractStorageName(name));
    // name が存在していたらそのアーカイブを使う
    var arcname = System.exePath + name + ".xp3";
    if(Storages.isExistentStorage(arcname)) {
        archive_exists[name.toLowerCase()] = true;
        Storages.addAutoPath(arcname + ">");
    }
}

function addArchive(name)
{
    // 検索パスを設定しつつ,name.xp3 が存在していたらそのアーカイブを使う
    Storages.addAutoPath(name + "/");
    useArchiveIfExists(name);
}

Before we try to integrate the above code, let's double check to make sure the syntax is the problem. Lets comment out all of the subfolders in mtl/patch/Config.tjs which means appending Config.tjs's contents to that file as well.

Code:
//update title
global.ENV_GameName = "Monobeno Happy End";

//add new storages
//Storages.addAutoPath(System.exePath + "patch.xp3>data/image/");
//Storages.addAutoPath(System.exePath + "patch.xp3>data/main/");
//Storages.addAutoPath(System.exePath + "patch.xp3>data/scenario/");
//Storages.addAutoPath(System.exePath + "patch.xp3>data/uipsd/");

//load original file
//LoadScript("Config.tjs")


//start original Config.tjs
// Config.tjs - KAG 設定

Creating the patch.xp3 again and launching the game gives this wonderful error.

[patch_subfolders_unicode_error]

Oops. Well, that was stupid, and I feel stupid. The error says something about ANSI and unicode. That points to an encoding error, which should have been obvious even before testing it. Config.tjs is encoded as utf-8 isn't it?

[patch_subfolders_unicode_error2]

Yes, yes it is. As far as I know, .tjs must always be encoded as shift-jis, even in KirikiriZ which can support utf-8 encoded .ks files sometimes. This older kirikiri2 engine however, definitely requires everything to be in shift-jis, outside of the e-mote files of course. Let's redo the encoding of Config.tjs to shift-jis.

- Create a new tab in Notepad++ (File->New)
- Encoding->Character sets->Japanese->Shift-jis
- Copy everything from Config.tjs to that new tab
- Close config.tjs
- save the new tab as Config.tjs, overriding the original.
- create patch.xp3 again

And it works again! Since none of the images or scripts are loading, only the title is translated. Let's uncomment the lines related to the storages first to double check just that syntax.

Code:
//add new storages
Storages.addAutoPath(System.exePath + "patch.xp3>data/image/");
Storages.addAutoPath(System.exePath + "patch.xp3>data/main/");
Storages.addAutoPath(System.exePath + "patch.xp3>data/scenario/");
Storages.addAutoPath(System.exePath + "patch.xp3>data/uipsd/");

//load original file
//LoadScript("Config.tjs")

//start original Config.tjs
[...]

That works now too to translate all of the ui and dialogue. That proves the Storages.addAutoPath() syntax we took from kirikiriZ works fine on this instance of kirikiri2. And finally, we can uncomment out LoadScript() and delete the original Config.tjs contents.

[syntax_error]

Humm. Uncommenting out LoadScript() causes that error. Since, Storages.addAutoPath() works just fine, what is it about restoring LoadScript() and removing the Config.tjs contents that causes this error? LoadScript() is the correct function to use here, right? What does Initialize.tjs say?

Code:
/*
    Config.tjs 読み込み
*/
if(Storages.isExistentStorage("Config.tjs"))
{
    KAGLoadScript("Config.tjs");
}

No! No, it's not! We need KAGLoadScript() instead. Let's update that and create the patch.xp3 again.

[syntax_error]

Damn! This is starting to get annoying now. The problem is definitely that KAGLoadScript() line. What else could be causing a syntax error?

Is the function defined? Copying the function definition "function KAGLoadScript(name)" code from Initialize.tjs to Config.tjs did not change the error, so it is probably not a function is not defined error.

What else could be causing a syntax error? ...It can't be. It's that stupid semi-colon at the end, isn't it?

Code:
//update title from ものべの-happy end-.exe
global.ENV_GameName = "Monobeno Happy End";

//add new storages
Storages.addAutoPath(System.exePath + "patch.xp3>data/image/");
Storages.addAutoPath(System.exePath + "patch.xp3>data/main/");
Storages.addAutoPath(System.exePath + "patch.xp3>data/scenario/");
Storages.addAutoPath(System.exePath + "patch.xp3>data/uipsd/");

//load original file
KAGLoadScript("Config.tjs");

Repacking that into patch.xp3 made the game work again. It was the semi-colon! The evil semi-colon was the issue. That took way too long to convert a script from kirikiriZ to kirikiri2, but at least it works now.

We are not done updating the patch folder yet though.

Remember way back when, a few weeks ago for me, or a few posts ago for you, that we took care not to erase the information of where the dlc scenario files came from by using tree to map their relative paths on the filesystem?

We just copied all of the scenario .txt.scn files to patch/data/scenario/ without regard for their true source, the dlc .xp3 archives, and even before that by copying all of the scenario files to mtl/patch. If we use the monobeno/tools/scripts/update_patch.from_dialogue.cmd automation script to update the patch folder again from the translated.json files, it will not place the scenario files in the correct folder.

In other words, it is now time to use extracts/dlc_mapping.txt to update mtl/patch/ and then backport all of our changes to the automation script.

As temping as it is to create a patch/dlc/ folder for all of the patch_append folders, there is no dlc folder in the original monobeno happy end folder or the .xp3 archives, and I would like to honor the original structure as much as possible which means patch looks like this now.

Code:
patch/Config.tjs
patch/data/
patch/patch_append1/
patch/patch_append2/
patch/patch_append3/
patch/patch_append4/
patch/patch_append5/
patch/patch_append6/
patch/patch_append7/
patch/patch_append8/
patch/patch_append9/
patch/patch_append10/
patch/patch_append11/
patch/patch_append12/
patch/patch_append13/
patch/patch_append14/

That is both messy and neat, in different ways, at the same time. All of those dlc folders are empty right now. Intead of moving the files from patch/data/scenario/ into the correct folders, lets update the automation script and have it do it for us. That way, we do not have to do it twice.

Code:
@echo off

set working_directory=%userprofile%\Desktop\Monobeno

:: this creates .xp3 files from folders
set tool=%working_directory%/tools/KiriKiriTools-1.7/Xp3Pack.exe

set dialogue_scn_raw=%working_directory%\MTL\scenario\dialogue_scn_raw
set dialogue_scn_json=%working_directory%\MTL\scenario\dialogue_scn_json
set csv_directory=%working_directory%\MTL\scenario\csv
set dialogue_scn_json_translated=%working_directory%\MTL\scenario\dialogue_scn_json_translated
set dialogue_scn_translated=%working_directory%\MTL\scenario\dialogue_scn_translated
set patch_directory=%working_directory%\MTL\patch
set patch_scenario_directory=%patch_directory%\data\scenario
set game_directory=%working_directory%\bin\Monobeno Happy End

set character_names=%working_directory%/character_names.csv

if not exist "%patch_directory%" mkdir "%patch_directory%"
if not exist "%patch_scenario_directory%" mkdir "%patch_scenario_directory%"

::move updated translations to MTL/patch/data/scenario
echo copying from "%dialogue_scn_translated%" to "%patch_scenario_directory%"
xcopy /i /q /y "%dialogue_scn_translated%" "%patch_scenario_directory%"

echo move /y "%patch_scenario_directory%\星辰ひめみや_sh_ご開祖ちゃんは委員長!.txt.scn" "%patch_directory%\patch_append1"
move /y "%patch_scenario_directory%\星辰ひめみや_sh_ご開祖ちゃんは委員長!.txt.scn" "%patch_directory%\patch_append1"

move /y "%patch_scenario_directory%\つみ_sh_たそがれ、よあけ.txt.scn" "%patch_directory%\patch_append2"

move /y "%patch_scenario_directory%\夏葉_sh_水着水着っ!.txt.scn" "%patch_directory%\patch_append3"

move /y "%patch_scenario_directory%\すみ_sh_ヘクセンバナー∴すみ.txt.scn" "%patch_directory%\patch_append4"

move /y "%patch_scenario_directory%\夏葉_sh_はじめてのブルマー.txt.scn" "%patch_directory%\patch_append5"
move /y "%patch_scenario_directory%\夏葉_sh_オオトメカマにわすれもの.txt.scn" "%patch_directory%\patch_append5"
move /y "%patch_scenario_directory%\夏葉_sh_残り物なら福袋.txt.scn" "%patch_directory%\patch_append5"

move /y "%patch_scenario_directory%\ありす_sh_サージカルヒーラー†ありす.txt.scn" "%patch_directory%\patch_append6"

move /y "%patch_scenario_directory%\ありす_sh_放課後アルトリコーダー.txt.scn" "%patch_directory%\patch_append7"

move /y "%patch_scenario_directory%\すみ_sh_えみの洋服を選ぼう!.txt.scn" "%patch_directory%\patch_append8"
move /y "%patch_scenario_directory%\すみ_sh目隠し妻.txt.scn" "%patch_directory%\patch_append8"

move /y "%patch_scenario_directory%\ありすとすみ_sh_連・鎖.txt.scn" "%patch_directory%\patch_append9"

move /y "%patch_scenario_directory%\夏葉中とありす_sh_ものべの、夜の大運動会.txt.scn" "%patch_directory%\patch_append10"

move /y "%patch_scenario_directory%\夏葉とすみ_sh_ネココーロ.txt.scn" "%patch_directory%\patch_append11"

move /y "%patch_scenario_directory%\ありす_sh_ありすのリハビリ☆チアガール!.txt.scn" "%patch_directory%\patch_append12"
move /y "%patch_scenario_directory%\ありす_sh_タイニィありすinアリス!.txt.scn" "%patch_directory%\patch_append12"

move /y "%patch_scenario_directory%\夏葉と星辰ひめみや_sh_バス停の長い夕暮れ.txt.scn" "%patch_directory%\patch_append13"

move /y "%patch_scenario_directory%\すみ_sh_可愛げなリュックと帽子.txt.scn" "%patch_directory%\patch_append14"

::archive the folder
"%tool%" "%patch_directory%"

move /y "%working_directory%\MTL\patch.xp3" "%game_directory%\patch.xp3"

echo updated patch.xp3

If using cp932 instead of cp65001 at the cli, remember to make sure update_patch.from_dialogue.cmd is shift-jis encoded.

move also has this strange behavior where it does not really know what you mean when copying a file sometimes. If the destination patch_append folders do not already exist, move may interpret

Code:
move /y "%patch_scenario_directory%\すみ_sh_可愛げなリュックと帽子.txt.scn" "%patch_directory%\patch_append14"

to mean move すみ_sh_可愛げなリュックと帽子.txt.scn to mtl/patch/ and rename it to patch_append14 (no extension), which would create a file called patch_append14, no extension, instead of moving すみ_sh_可愛げなリュックと帽子.txt.scn into a folder called patch_append14.

That is not really a bug. move is just trying to infer what we mean when we say move x to y, and it will assume we are moving files unless we give it a reason to assume otherwise. But if the target, y, is a folder that already exists, then move can correctly infer that we mean "move this file inside of that folder without renaming it" intead of "move this file to there and rename it".

Just make sure all of the folders exist already to prevent that somewhat useful but still quirky behavior.

Code:
C:\Users\User\Desktop\Monobeno\tools\scripts>update_patch.from_dialogue.cmd

copying from "C:\Users\User\Desktop\Monobeno\MTL\scenario\dialogue_scn_translate
d" to "C:\Users\User\Desktop\Monobeno\MTL\patch\data\scenario"
204 File(s) copied
move /y "C:\Users\User\Desktop\Monobeno\MTL\patch\data\scenario\星辰ひめみや_sh_
ご開祖ちゃんは委員長!.txt.scn" "C:\Users\User\Desktop\Monobeno\MTL\patch\patch_
append1"
1 file(s) moved.
1 file(s) moved.
1 file(s) moved.
1 file(s) moved.
1 file(s) moved.
1 file(s) moved.
1 file(s) moved.
1 file(s) moved.
1 file(s) moved.
1 file(s) moved.
1 file(s) moved.
1 file(s) moved.
1 file(s) moved.
1 file(s) moved.
1 file(s) moved.
1 file(s) moved.
1 file(s) moved.
1 file(s) moved.
1 file(s) moved.
updated patch.xp3

Now we need to update mtl/patch/Config.tjs to also read those folders. start a command prompt

Code:
python
prefix='Storages.addAutoPath(System.exePath + "patch.xp3>patch_append'
postfix='/");'

for i in range(15):
    print(prefix+str(i)+postfix)

Copy that output into Config.tjs. Remember to delete the patch_append0 line manually.

Code:
//update title from  ものべの-happy end-
global.ENV_GameName = "Monobeno Happy End";

//add new storages
Storages.addAutoPath(System.exePath + "patch.xp3>data/image/");
Storages.addAutoPath(System.exePath + "patch.xp3>data/main/");
Storages.addAutoPath(System.exePath + "patch.xp3>data/scenario/");
Storages.addAutoPath(System.exePath + "patch.xp3>data/uipsd/");

Storages.addAutoPath(System.exePath + "patch.xp3>patch_append1/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append2/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append3/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append4/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append5/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append6/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append7/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append8/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append9/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append10/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append11/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append12/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append13/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append14/");

//load original file
KAGLoadScript("Config.tjs");

After testing that, it works like a beautiful butterfly. Now that we have cleaned up the folder structure of mtl/patch and backported the changes, we can start to translate game engine strings.

On extra nitpick is that our patch includes the original unmodified Config.tjs. Is that really necessary? It shouldn't be, right? After all, the game already has that file available at data.xp3/main/Config.tjs. Is there any way to revert the Storages pointer for Config.tjs back to data.xp3/main/Config.tjs after updating it to patch.xp3/Config.tjs? If we could, then we would not need to include the original Config.tjs in the patch at patch.xp3/main/Config.tjs.

Before we try changing things to get things to work, how do we know the original Config.tjs loaded or not when the game loads? Let's try to produce an error to check for that failure state. Let's update patch/Config.tjs to comment out the lines that load the original Config.tjs so Config.tjs never loads.

Code:
[...]
//Storages.addAutoPath(System.exePath + "patch.xp3>data/main/");
[...]
//KAGLoadScript("Config.tjs");

Then we can update the patch.

Code:
set working_directory=%userprofile%\Desktop\Monobeno
cd %working_directory%
tools\scripts\update_patch.no_changes.cmd

1 file(s) moved.
updated patch.xp3

When we launch Monobeno, we get this error.

[no_config.tjs.png]

That is perfect. Now when we launch Monobeno, we have a clear idea as to what happens if the game is not able to find Config.tjs. If our changes show that error, we know what the problem is.

Now let's update our patch/Config.tjs to try to point to the internal data.xp3/main/Config.tjs.

Code:
//Storages.addAutoPath(System.exePath + "patch.xp3>data/main/");
[...]
Storages.addAutoPath(System.exePath + "data.xp3>data/main/Config.tjs");
KAGLoadScript("Config.tjs");

Update the patch again.

Code:
tools\scripts\update_patch.no_changes.cmd

[no_config.tjs2]

After struggling with OCR a bit, here is the output.
パス名の最後には>またはを指定してください
吉里吉里22.19beta14より一カイの区切り記号が''か>に変わりました

At the end of the pass name, please designate > or
Kichiri kisato 22.19 beta14 to ichikai's stop sign has changed to ka.

I think it is trying to say that the path name has to end in a > when loading kirikiri archives. However, we also know that "patch.xp3>data/main/" is also valid syntax. That might imply that the function is always trying to load multiple files from a folder, not a single file. We probably cannot say "update only this file from that archive".

Well, if we can only load folder paths, not individual files, then how do we update only that one file?

If we were to do "data.xp3>data/main/"); and then load Config.tjs, while that might update Config.tjs correctly, it would also override all of the pointers to our translated assets, thus possibly un-translating the game. The translated assets need to load last. In that case, maybe just moving the "data.xp3>data/main/" line up before the translated asssets load might work?

Code:
//update title from  ものべの-happy end-
global.ENV_GameName = "Monobeno Happy End";

//fix Config.tjs
Storages.addAutoPath(System.exePath + "data.xp3>data/main/");

//add new storages
Storages.addAutoPath(System.exePath + "patch.xp3>data/image/");
//Storages.addAutoPath(System.exePath + "patch.xp3>data/main/");
Storages.addAutoPath(System.exePath + "patch.xp3>data/scenario/");
Storages.addAutoPath(System.exePath + "patch.xp3>data/uipsd/");

Storages.addAutoPath(System.exePath + "patch.xp3>patch_append1/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append2/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append3/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append4/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append5/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append6/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append7/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append8/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append9/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append10/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append11/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append12/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append13/");
Storages.addAutoPath(System.exePath + "patch.xp3>patch_append14/");

//load original file
KAGLoadScript("Config.tjs");

After it fully utilizes a single CPU core for a while, and then this displays.

[no_config.tjs.3]

That likely created an infinite loop which caused the program to crash. I am out of ideas at this point. Let's revert the changes back to the known working syntax and we can come back to this problem later when we learn more about kirikiri2's engine internals.

Code:
[...]
//fix Config.tjs, this causes an EStackOverflow error
//Storages.addAutoPath(System.exePath + "data.xp3>data/main/");

//add new storages
Storages.addAutoPath(System.exePath + "patch.xp3>data/image/");
Storages.addAutoPath(System.exePath + "patch.xp3>data/main/");
[...]
 
Automating game engine string translations

While I intended to write some software to automate extracting, translating, and inserting the strings, I have not a clue on how to locate arbitrary translatable strings besides what we have been doing so far. That means

- looking at the gui
- OCR'ing the string
- searching for that string in the .tjs files
- translating it temporarily
- seeing if the game crashes
- if it passes, add it to the automated list

One minor detail is that the automated list does not actually exist yet. I should get started on writing it then. At least ocr can be done in batches now thanks to ocr_tools automating detection somewhat. As long as the text is entirely vertical, then PPOCR-v5's text detection model and algorithm will not attempt to merge the entries which means this ocr step can be done in batches now.

[ocr_batches]

This increased automation is a minor improvement, but still an improvement. Considering how long ocr_text_detection.py took to write and document and how long the game_string.py will take to write and document, just let me be happy here, okay? ;_;

Code:
set working_directory=%userprofile%\desktop\monobeno
cd "%working_directory%/tools/ocr_tools"

python ocr_text_detection.py -i "%working_directory%\MTL\images\ocr\top_menu"

cd ..

python translation_tools\romaji.py "%working_directory%\MTL\images\ocr\top_menu\boxes.csv" -s
python translation_tools\sugoi_mtl.py "%working_directory%\MTL\images\ocr\top_menu\boxes.csv" -s






Code:
set working_directory=%userprofile%\desktop\monobeno
cd %working_directory%
set configfile=C:\Users\User\Desktop\Monobeno\MTL\images\ocr\top_menu\config.csv

python tools\game_engine_strings.py extract "%configfile%" -d

-d means debug which means to print a lot of information

wrote C:\Users\User\Desktop\Monobeno\MTL\images\ocr\top_menu\config.csv.csv

Code:
set working_directory=%userprofile%\desktop\monobeno
cd %working_directory%

set spreadsheet=C:\Users\User\Desktop\Monobeno\MTL\images\ocr\top_menu\config.csv.csv

python tools\translation_tools\romaji.py "%spreadsheet%" -s
python tools\translation_tools\sugoi_mtl.py "%spreadsheet%" -s
 
Improving automated wordwrappng quality

The phrase "approximate maximum number of characters" was used when describing the the character length when wordwrap in the tool.py because the maximum number of characters is not actually determined by the number of characters but rather by the width of the characters drawn onto the screen.

Excluding monospace fonts, different characters have different widths. CAPITAL letters typically take more horizontal space than lowercase letters, spaces use less width than lowercase letters. A line with many upper characters can fit less characters. A line with many lowercase characters and spaces can fit more characters.

Different fonts use different widths for the same characters. Remember that unicode text is stored as an abstract representation of text called "code points". Actually displaying unicode text requires a way to render those abstract code points into glyphs. Fonts describe that mapping from code points to glyphs and different fonts are thus free to describe the and render the same character using different widths.

Even when using the same font, the width of each character will still differ depending upon the font's point size which is not necessarily uniform for all text.

The kirikiri game engine uses the actual width of the characters to word wrap up to a maximum pixel length (before UI scaling). The actual width that determines which character kirikiri applies word wrapping to in a line is determined by these 4 pieces of information.

1. the literal characters in the line
2. the font
3. the font size
4. the maximum number of pixels per line set by the game developer

The tool.py contains an experimental feature called proportional_word_wrap() that tries to accurately calculate word wrapping independently of the kirikiri game engine. To work, it needs the same information the kirikiri game engine and also the python image library (PIL/pillow).

Code:
python -m pip install pillow
python -m PIL

Pillow 10.2.0
Python 3.10.13 [...]

We could probably guess #3, font size, and #4, max pixel length, with enough experimentation. We also have #1 during runtime when applying the word wrapping built into the tool.py. That leaves the only critical piece of information missing as the font itself.

Let's search for the font in data.xp3.

[search_font]

2 fonts show up at data.xp3\sysscn, MTLmr3m.ttf and MTLc3m.ttf. Let's preview them.
search for font,
kirikiri font window

click on each one. Neither is the one we are looking for.

Inside of the game, at the bottom left is a license button. Fonts are commercial in nature, so clicking on it will include the licenses to use each font.

If we compare the names of the fonts with that list, which is a very slow process to us people who cannot read japanese characters.

Some kirikiri developers can also mess with fonts by converting them into pre-rendered bitmaps before embedding them into the game. In that case, there is no way we will find the font in the game since it literally isn't there anymore because the developer converted the font to an image.

That means that no matter how much we search, we may never find it. Still, let's try. If we browse Monobeno/extracts/main/, there are some very suspicious files.

Code:
font4_21.tft
font5_21.tft
font6_21.tft
font7_21.tft
font8_21.tft
namefont_20.tft

Did the developers just give up on naming that last font? I mean, no one would ever notice that, right?

[tvp_prerendered_font]

The files appear to have a "TVP pre-rendered font" header which implies they used the prerendering technique. I am not aware of any technical benfits to prerendering fonts. Wouldn't it also be easier (not faster) to dynamically load whatever the user has installed on their local computer and whatever the engines finds in the data file and let the user pick whatever they want?

I mean, the developers included the non-rendered fonts MTLmr3m.ttf and MTLc3m.ttf, so why could they not have done that to all of the fonts they wanted to include?

Perhaps that they were trying to work around the same problem we are trying to solve now? Maybe by prerendering the font and then scaling it, they can hard code word wrapping into the .txt.scn files? That sort-of makes sense I guess? It seems simpler and more ideal to me to just calculate the word wrapping dynamically so the user can adjust the size of the font without breaking anything. With Lose's line continuation code, that should have worked perfectly. The kirikiri engine is calculating the actual width anyway, so just reuse that code and make it entirely dynamic so word wrapping works perfectly with any font at any nearly any font size.

Another possible reason is that maybe there was some sort of licensing restriction on distributing the raw font.ttf files. Fonts can be commercially licensed after all, and both the of the included .ttf fonts do have associated licenses, but I could not find one for the .tft fonts.

Moving on. There should be away to un-render the .tft/tvp fonts into something useful theoretically, but I am not familiar with any way of doing that, and, more importantly, it is not really necessary.

Let's keep searching for the font elsewhere. Since the default font is not bundled with the game in an easily accessible .ttf format, we will have to find the font another way.

Let's search the internet! To search the internet, we usually need texual data, so let's convert the name of the default font into text.

Here is the kirikiri fonts window split up into a million pictures by using the Windows Snipping Tool. I put them at Monobeno\MTL\images\ocr\fonts.

[fontconfig_clippings]

Now let's run ocr_text_recognition.py.

Code:
C:\Users\User\Desktop\Monobeno>python tools\ocr_tools\ocr_text_recognition.py C:
\Users\User\Desktop\Monobeno\MTL\images\ocr\fonts -o

That will create output.ocr.csv with the ocr results. Let's run romaji.py on it.

Code:
C:\Users\User\Desktop\Monobeno>python tools\translation_tools\romaji.py C:\Users
\User\Desktop\Monobeno\MTL\images\ocr\fonts\output.ocr.csv -s

Then sugoi_mtl.py.

Code:
C:\Users\User\Desktop\Monobeno>python tools\translation_tools\sugoi_mtl.py C:\Us
ers\User\Desktop\Monobeno\MTL\images\ocr\fonts\output.ocr.csv -s
reading C:\Users\User\Desktop\Monobeno\MTL\images\ocr\fonts\output.ocr.csv
number of entries to translate 18
wrote C:\Users\User\Desktop\Monobeno\MTL\images\ocr\fonts\output.ocr.csv

Now that we have the actual data at MTL\images\ocr\fonts\output.ocr.csv, let's search online for Tsukushi B-maru Gothic, which is the sugoi MTL of the default Monobeno font. that takes us to adobe's website for one of their designers, Shigenobu Fujita.
There is one example of their work, FOT-TsukuARdGothic Std.
At the bottom, it says there are two versions of the font, FOT-TsukuARdGothic Std R, and FOT-TsukuARdGothic Std B. We probably want to play with the B version since Tsukushi B-maru Gothic has a B in it.

There is a "Sample Text" box which lets us preview text rendered in that font. Let's use it to enter the proof of concept text from earlier, "Is this the place? I don't see anything."

[font_compare]

Is that the same font? It does not look like it. The ? and t's are similar, but the other characters are different. The g is very different and also looks very unique. Hummm. Maybe the monobeno font is an earlier font by that developer, but that font is no longer on Adobe's main English site?

Let's un-translate the font name. The kanji from the ocr is 筑紫B丸ゴシック, or with a half-width B, 筑紫B丸ゴシック. That looks accurate to my blind eyes.

If we search for that, we get this site. At the top right we can change the language to English. There is that guy again, Shigenobu Fujita, 藤田 重信. He is following us around on the internet, and this time we even get a portrait of him. It looks like he is related to "Fontworks", which may imply that Fontworks might be related to Adobe somehow, like being Adobe's JP branch for something or another.

So down at the bottom there is a glyph section with some examples. to the right of "Glyph", we can click on "Alphabet". We can also see "Font family" and change that to "Li European lingues". If we compare the g and n, from the proof of concept screenshot to the Glyphs, the p is a little short in the glyphs section, but the Li European lingues section shows the same p. t is also very similar.

It is 120% not obvious, but the really big red box at the top is a textbox. You can enter whatever you like and the site will render it in that font as well as the predefined examples lower down.

Okay, we have a match. Based upon the title of the website in English, the accurate localization for this font is TsukuBRdGothic B | Monotype. Monotype... monotype... dammit. It's monotype! I feel stupid now. We don't need to calculate the width of the characters for this font because it's monotype dammit!

That means the earlier estimate of 76 characters per line was actually exact. We can just put --wordwrap 76 as a parameter and will always get an accurate result provided the text never exceeds 76 characters*3 lines = 228 characters total per translation.

Do I really think that always wrapping at 76 characters will work perfectly? No, not really, but we can test it.

The font back on Adobe's website was TsukuARdGothic std, but the one in the game is TsukuBRdGothic Std. Thinking back, the monotype width combined with the various serifs is probably why I found the font to be really strange initially. Maybe I will get to play with my proportional word wrapping function later, but that is not needed for Monobeno, probably. Humm.

Probably... probably... maybe we should test it, probably? Just to be sure, I mean. You never really know, unless you test it. So let's test word wrapping at 76 characters and see what we get.

Code:
set working_directory=%userprofile%\Desktop\Monobeno
cd %working_directory%
set tool=python "%working_directory%/tools/translation_tools/games/Monobeno Happy End/MonobenoHappyEnd_DialogueExtractInsertTool.py"
%tool% insert "MTL/scenario/New folder (2)/共通_01_プロローグ.txt.json" -s "mtl/scenario/csv/New folder/共通_01_プロローグ.txt.json.csv" -cn character_names.csv --wordwrap 76

[...]
wrote MTL/scenario/New folder (2)/共通_01_プロローグ.txt.json.translated.json

Code:
set tool=%working_directory%/tools/Ulysses-FreeMoteToolkit-v4.1.1/PsBuild.exe
set file=%working_directory%/MTL/scenario/New folder (2)/共通_01_プロローグ.txt.json.translated.json
"%tool%" -v 2 "%file%"

Rename to .txt.scn and repack. And we have this.

[insert_debug2]

It's trying to display this.

\"There's no buildings, no bookstores, no supermarkets, no drugstores! Wow,\nit's so empty, and it's really stretched out.\"

Is 76 characters really correct?

Code:
python
s='"There\'s no buildings, no bookstores, no supermarkets, no drugstores! W'
len(s) #prints 71

Later, this line has the same issue. It starts a new line after the y in passerby.

Natsuha twirls her arms wide and repeats her happy turn. The passersby are\nall smiling at her.

Code:
python
s2='Natsuha twirls her arms wide and repeats her happy turn. The passersby'
len(s2) #prints 70

And thus we have our true conclusion. This is not monospace at all! I feel lied to.

Maybe the Japanese glyphs are monospace, it is supposed to be a japanese font after all, but latin glyphs are variable width? The .ttf does not say it is monospaced either, only the website. Well, regardless of the reason, let's test it now with the proportional_word_wrap() function as we were intending to earlier before we were distracted by our attempts to falsify the monospace hypothesis.

It is downloadable here if you want to play with it. Download to Monobeno/MTL/scenario/fonts/TsukuBRdGothicStd. There are several versions of the font. Let's test the regular one first, TsukuBRdGothicStd-Regular.ttf.

We can test proportional_word_wrap() by supplying the .ttf font during runtime to the tool.py. That will make the tool use proportional_word_wrap() during insertion instead of the default apply_word_wrap() function. Here is the code from the tool.py

Code:
if font == None:
    translated_string_after_wordwrap=apply_word_wrap(translateddata_from_spreadsheet,wordwrap_length=wordwrap_length,index=spreadsheet_pointer+1,index2=column)
else:
    translated_string_after_wordwrap=proportional_word_wrap(text=translateddata_from_spreadsheet,font=font,max_pixel_length=wordwrap_length)

In other words, if font is not None, if the font.ttf exists as input, then it will use proportional_word_wrap() instead.

One more thing. When word wrapping with proportional_word_wrap(), the --wordwrap cli option is interpreted to mean 'width in pixels' instead of 'number of characters'. A sane starting point for pixel width is somewhere between 300-500 pixels, so 400 pixels is the somewhat arbitrary default value. The exact amount will change depending on the game.

start a command prompt

Code:
set working_directory=%userprofile%\Desktop\Monobeno
cd %working_directory%
set tool=python "%working_directory%/tools/translation_tools/games/Monobeno Happy End/MonobenoHappyEnd_DialogueExtractInsertTool.py"
set file=%working_directory%/mtl/scenario/New folder (2)/共通_01_プロローグ.txt.json
%tool%
set font=%working_directory%/MTL/scenario/fonts/TsukuBRdGothicStd/TsukuBRdGothicStd-Regular.ttf
%tool% insert "%file%" -s "%file%.csv" -cn character_names.csv --wordwrap 400 -wf "%font%"

set pack_tool=%working_directory%/tools/Ulysses-FreeMoteToolkit-v4.1.1/PsBuild.exe
set pack_file=%working_directory%/MTL/scenario/New folder (2)/共通_01_プロローグ.txt.json.translated.json

"%pack_tool%" -v 2 "%pack_file%"

Then rename the file to .txt.scn and repack it to patch.xp3. Here is the result.

[prop_ww_test1]

Is that correct? How do we know if that is the max pixel width for a line? If we are not getting errors, then it seems too short. We need to increase the length until it creates an error, then lower it only as much as it no longer produces word wrapping errors to get the true maximum pixel width. Let's test lots of different max pixel width breakpoints.

It is actually very annoying to re-run all of the previous commands after changing only the length, so let's automate that by creating a temporary new shift-jis encoded .cmd file at tools/scripts/insert.test_word_wrap.cmd. This is based on update_patch.cmd.

Code:
@echo off

set working_directory=%userprofile%\Desktop\Monobeno
pushd "%working_directory%"

set tool=python "%working_directory%/tools/translation_tools/games/Monobeno Happy End/MonobenoHappyEnd_DialogueExtractInsertTool.py"
set pack_tool=%working_directory%\tools\Ulysses-FreeMoteToolkit-v4.1.1\PsBuild.exe
set pack_tool2=%working_directory%/tools/KiriKiriTools-1.7/Xp3Pack.exe

set dialogue_scn_raw=%working_directory%\MTL\scenario\dialogue_scn_raw
set dialogue_scn_json=%working_directory%\MTL\scenario\dialogue_scn_json
set csv_directory=%working_directory%\MTL\scenario\csv
set dialogue_scn_json_translated=%working_directory%\MTL\scenario\dialogue_scn_json_translated
set dialogue_scn_translated=%working_directory%\MTL\scenario\dialogue_scn_translated
set patch_directory=%working_directory%\MTL\patch
set game_directory=%working_directory%\bin\Monobeno Happy End

set file=%working_directory%/mtl/scenario/New folder (2)/共通_01_プロローグ.txt.json
set file_to_pack=%working_directory%\MTL\scenario\New folder (2)\共通_01_プロローグ.txt.json.translated.json
set packed_psb_file=共通_01_プロローグ.txt.json.translated.psb
set renamed_file=共通_01_プロローグ.txt.scn
set font=%working_directory%/MTL/scenario/fonts/TsukuBRdGothicStd/TsukuBRdGothicStd-Regular.ttf

::delete the old translated file so a new one can be made
if exist "%file_to_pack%" del "%file_to_pack%"

::insert the translation
cmd /c %tool% insert "%file%" -s "%file%.csv" -cn character_names.csv --wordwrap 400 -wf "%font%"

::convert to .psb
"%pack_tool%" -v 2 "%file_to_pack%"

::move the .psb to the patch folder
move /y "%packed_psb_file%" "%patch_directory%\%renamed_file%"

::archive the folder
"%pack_tool2%" "%patch_directory%"

::move the .xp3 to the game directory
move /y "%working_directory%\MTL\patch.xp3" "%game_directory%\patch.xp3"

echo updated patch.xp3
popd

start a new command prompt

Code:
cd %userprofile%/desktop/monobeno
tools\scripts\insert.test_word_wrap.cmd

Here is what --wordwrap 700 looks like.

[prop_ww_test2]

So 400 is maybe too small, and 700 is about 60% larger than it should be on the second line based on the 's. Even though...' line. I could do some math to figure that out, but let's just keep experimenting instead.

First I tested 500, then 450, then 420, then 400 again, then back to 415, then 410. 410 seemed perfect for a while, but then I found an overflow in the prologue, so went down to 405. Adjusting the pixel increments by 5 resulted in 405 as the maximum pixel width for word wrapping.

405 is such a strange number. Shouldn't it be an even 400? That is a nice round number. 400. There are two zeroes there. That is much more round than a 0 and then a 5. It seems like a human being would choose 400 over 405 every day of the week.

Well, I live by experiments, not conjecture. 405 is the number determined by real world testing, so it is trustworthy ...for now. If we later find a word wrapping issue, then we can lower it further, but 405 seems correct based upon testing the initial scene and the fish conversation.

Now we need to backport the changes the insert.csv_to_json.cmd script. Here are the lines that need to be changed.

Code:
%tool% insert "%dialogue_scn_json%" -cn "%character_names%" -s "%csv_directory%" --wordwrap 70 -o "%dialogue_scn_json_translated%"

Code:
set font=%working_directory%/MTL/scenario/fonts/TsukuBRdGothicStd/TsukuBRdGothicStd-Regular.ttf

%tool% insert "%dialogue_scn_json%" -cn "%character_names%" -s "%csv_directory%" --wordwrap 405 -wf "%font%" -o "%dialogue_scn_json_translated%"

Then we can delete all of the .csv files, re-add romaji, and re-add sugoi_mtl translations.

After a lot of testing/reading later, I later found another error all the way in in Alice's afterstory route. It took quite a while to find that too!

[wordwrap_405_error]

Back to 400 it is! I was getting attached to that 405 number. I mean, it is 400, and 5. What's not to like? If 400 turns out to be too much, then it will have to drop to 395.
 

Users who are viewing this thread

Latest profile posts

watcherrrr wrote on zhonyk's profile.
Acidrain September Please
mailasdasd321 wrote on 404notfound's profile.
Hi can you please help fix the Music files for 明日ちゃんのセーラー服? I can't reference all the links here as there's limitations for character reply here, so I've sent you an alternative mail for reference. Many Thanks.