Skip to content

Instantly share code, notes, and snippets.

@lynn

lynn/ws.md Secret

Last active August 4, 2023 00:36
Show Gist options
  • Save lynn/aaab1ab4c8f6196c72735d157e2a95fb to your computer and use it in GitHub Desktop.
Save lynn/aaab1ab4c8f6196c72735d157e2a95fb to your computer and use it in GitHub Desktop.

Translating Wind's Seed

Background

  • I've translated one other PC-98 game before, so I got on my feet pretty quick with this one.
  • I read Japanese at an N2 level. I kinda know x86 assembly.
  • I'm armed with np2debug, wxmedit, Ghidra, and Python. Let's do this!
  • I had some old Python code lying around for reading .fdi files into a Python dictionary, so started from that.

Text rendering

  • The game stores 早く行こうよ as 2 4 - P. When it sees an ASCII character, it looks it up in a kana table.
  • We want to disable that code so we can actually print English strings without them becoming all kana.
  • But now there's another problem: the game only moves the text cursor in full-width (double) increments.
  • We change this to single increments, so our text doesn't look L i k e t h i s.
  • But now we have to double the X coordinate of a bunch text positions to compensate.
  • This involves a bunch of assembly patches, and seems to be where 46okumen put things down.
  • I found a nice place to insert a shl bl, 1 instruction that fixes most of it in menus.

Text in menus

  • In menus, only the last character of text was visible. Hmm...
  • Menu text rendering involves opaque backgrounds, unlike the dialogue.
  • I learned that when you ask Kanji ROM for an ASCII character, it gives it right-aligned in a 16×16px bitmap, so to speak.
  • So if we draw A with an opaque background, and then overlap B 8px to the right, we'll get… B.
    • We didn't notice this in the dialogue boxes because there's no opaque background.
  • To remedy this I replaced the code that bolds character bitmaps with code that moves them 8px to the left.
    • I replaced row |= row << 1 (how clever!) with row <<= 8, basically.
  • Then I disabled some instructions that draw the right half of a 16×16px background rectangle.

Digits

  • The kana compression was in charge of mapping !"#$%^&'() to fullwidth digits 0123456789.
  • So when we disabled kana compression, numbers on the screen reverted into crazy punctuation.
  • I tracked down some instructions like add al, '!' and replaced them with add al, '0' to get ASCII digits.

Non-story text translation

  • I wrote some strange code to try to find strings that look like Japanese all over the disk.
  • This worked pretty well! Here is everything it found.
  • I'm not sure why I translated the error messages and stuff. I was on a roll!!

Story

Dumping

  • I ported celcion's PowerShell code for dumping WSDATA.FCP to Python.
  • This is a simple archive format that contains all the game's graphics and text.
  • It contains STORY.DAT. This contains all the textbox dialogue in the game.
  • Figuring out the control codes (set portrait, newline, next text box) was not that hard.
  • The first 0x200 bytes of this file form an array ushort[256] of pointers into the file.
  • I dumped it into a human-editable format that's kind of arbitrary but plays nice with HTML syntax highlighting.
= event 0x0090 at 0x2207

<!-- <RB><RS TownLady>おそろしい世の中だねぇ<Next> -->
<!-- <End> -->
<RB><RS TownLady>Such a dreadful world we live in.<Next>
<End>

Reinserting

  • I wrote code to parse my own format and overwrite the STORY.DAT region of WSDATA.FCP.
  • That code automatically inserts newline commands to fit text in 22 columns.
  • There's not too much text in the game, so translating everything took only a few days.
    • There are a couple of really weird in-jokes. Maybe I'll write translator's notes later.

Size troubles

  • Uh-oh, the file became way bigger. It was 12,304 bytes, but in English it's almost 21,000 bytes!
    • English is often bigger than Japanese: think (2 bytes in SJIS) vs. forest (6 bytes).
    • With compressed kana, the difference is even larger.
  • So, I wrote code to repack WSDATA.FCP to fit a large STORY.DAT in it.
  • Then I wrote code to repack the floppy disk to fit a large WSDATA.FCP in it...
    • This was stupid and required me learning how FAT12 works. I should have probably used a library!
  • Crap, the game keeps the story in a 0x4000-byte array, but again, we have a little over 0x5100 bytes now.
  • I bumped the array size up to 0x6000, and moved a subsequent array further down RAM so they don't overlap.

Miscellaneous romhacking wisdom

  • One easy way to find code is to set breakpoints on data you know it reads.
  • Another is to guess instructions it probably executes and search for those! (Like add al, '!'.)
  • If you don't know what some code does, try replacing it with NOPs and running the game like that.

Testing

  • Testing a translation is pretty annoying without some form of cheats.
  • I don't want to worry about losing a boss battle if I just want to check if my translation looks good.
  • I poked at the save file format, and gave my party 999 HP, 999 attack, 999 defense etc.
  • Then I ran through the game, occasionally saving to make a "checkpoint", so I could access areas/scenes easily.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment