Here comes level names. This was the most painful part of the whole translation hacking. First, they are made of sprites. Second, there is a limited number of them. And last but not least, the table that tells which sprites to use for a given level name can’t be expanded.
The LSB of the pattern indices for the first level are stored at $5196 (file offset) and $519C for the second. After putting a read breakpoint on $*4f96 (remember that there’s usually a 512 bytes header to PC Engine roms) and $*4f9C, I ended up at $8E3F. A quick look at the code show me there is a address table stored at $8F81 where each address starts 1 byte before the LSB pattern index list. The first byte indicates the number of sprites. Here’s the complete index list:
8F95 : 05 00 02 04 06 08
8F9B : 05 0E 02 12 06 08
8FA1 : 05 18 0A 16 06 08
8FA7 : 05 14 00 16 06 08
8FAD : 05 1C 02 20 06 08
8FB3 : 04 18 1A 06 08
8FB8 : 04 22 24 06 08
8FBD : 05 02 10 18 06 08
8FC3 : 04 26 28 06 08
At this point, I managed to avoid using compressed data. Unfortunatelly the whole sprites were compressed. In fact, there are 4 “decompression” schemes. The first one is a direct transfer to VRAM using the tia instruction. The second is using a simple RLE. The third one and the most tricky mixes RLE and some XOR. The last one simply copies the data to VRAM using a handcrafted loop.
f796: LDA <$7d ORA <$7e BEQ f7e1 JSR f888 CMP #$00 BNE f7ac ; #1 Transfer to VRAM using tia TIA $3700, $0002, $0020 BRA f7d2 f7ac: CMP #$02 BNE f7bc ; #2 RLE JSR f817 TIA $3700, $0002, $0020 BRA f7d2 f7bc: CMP #$03 BNE f7cf ; #3 RLE + XOR JSR f817 JSR f857 TIA $3700, $0002, $0020 BRA f7d2 f7cf: ; #4 Loop transfer JSR f803 f7d2: LDA <$7d SBC #$01 STA <$7d LDA <$7e SBC #$00 STA <$7e BRA f796 f7e1:
The RLE routine:
f817: LDA [$78] STA <$83 LDY #$04 LDA #$09 STA <$81 STZ <$80 CLX LDA #$20 STA <$82 f828: DEC <$81 BNE f83a PHY LDA #$08 STA <$81 INC <$80 LDY <$80 LDA [$78], Y STA <$83 PLY f83a: ROR <$83 ; the rle counter BCS f849 STZ $3740, X INX DEC <$82 BNE f828 JMP f7f6 f849: LDA [$78], Y STA $3740, X INY INX DEC <$82 BNE f828 JMP f7f6
The “XOR” routine:
f857: PHX PHY LDY #$07 CLX f85c: LDA $3740, X EOR $3742, X STA $3742, X LDA $3741, X EOR $3743, X STA $3743, X LDA $3750, X EOR $3752, X STA $3752, X LDA $3751, X EOR $3753, X STA $3753, X INX INX DEY BNE f85c PLY PLX RTS
The XOR is used here to maximize the number of repetitions in order to make the RLE more efficient. Well, that’s how I understood it. I didn’t make any benchmark to verify if it was really the case. The encoder was written pretty quickly. I reworked the font so that it more or less matches the ASCII set. This simplified the script encoder and the display routine.
Back to level names! I got help from a bunch of people to translate them. The last character of each level can be translated to country. Unfortunately that was far too big so I switched to “land”.
- Candy Land
- Toy Land
- Greenery Land
- Ice Land
- Time Land
- Water Land
- Sky Land
- Mirror Land
- Queen Land
Here’s the actual “font” made of 16×16 sprites.
The zigzag lines on the last 16×16 sprite are there to make the compressed data fits into 504 bytes. I must also keep an empty sprite for spacing.
I had the sprite index list done pretty quickly. But when I started working on the index pointer table, I realized that there were 9 entries. 9? Did I forgot one?
Yes, I forgot one. The water land… And I didn’t have enough room to put the missing letters (bloody size limit).
After a cautious review, it was decided to rename “Greenery land” into “Green land”. As There were already a “Queen land”, there’s now enough room for the missing “Water”.
The only issue remaining was that the sprite index list is 42 bytes long, and mine was 43. As each level name ends with the same sequence I can use the last index as the beginning of the next one (each sequence starts with the length of the index list). So I swapped the sprite for “nd” with the beginning of the one whose index was equal to a sequence length, namely “To” which was at index 6. Why 6? First because it’s the length of the longest index list and also because sprite indexes are always even.
Here’s the original index list (the fist 2 hexadecimal values are the values of the pointer table):
8F95 : 05 00 02 04 06 08
8F9B : 05 0E 02 12 06 08
8FA1 : 05 18 0A 16 06 08
8FA7 : 05 14 00 16 06 08
8FAD : 05 1C 02 20 06 08
8FB3 : 04 18 1A 06 08
8FB8 : 04 22 24 06 08
8FBD : 05 02 10 18 06 08
8FC3 : 04 26 28 06 08
The new one:
8F95 : 05 00 06 04 24 06
8F9B : 04 02 04 24 06
8FA0 : 05 08 0A 0C 24 06
8FA6 : 04 0E 10 24 06
8FAB : 05 12 14 28 24 06
8FB1 : 05 20 22 26 24 06
8FB7 : 04 16 04 24 06
8FBB : 06 18 1A 1C 28 24 06
8FC2 : 05 1E 0A 0C 24 06
Shortly after beating the evil level names I received the first version of the translated script. Unfortunately the first tests revealed that the current text layout was unreadeable. There were only 2 or 3 words per line. So I had to crawl brack into the rom and change the text box position and size. I was only some couples of values to change.
So the 24th of July 2011, the english patch was released. Nearly 2 years after I started working on it… Anyway, some days after the release a couple of angry frenchmen complained that we didn’t release a french patch. In order to silence those annoying creeters I had to modify the font and the script encoder in order to add accents. That was not a big deal. But I let the french translators handle the level names (with some kind of sadistic grin). After some weeks of hard work, they decided to give up and use the english names. The french translation was released in December 2011.
- English translation
- French translation