News: 11 March 2016 - Forum Rules
Current Moderators - DarkSol, KingMike, MathOnNapkins, Azkadellia, Danke

Author Topic: Time Twist Text Compression Code  (Read 3859 times)

Pennywise

  • Hero Member
  • *****
  • Posts: 2369
  • I'm curious
    • View Profile
    • Yojimbo's Translations
Time Twist Text Compression Code
« on: August 17, 2016, 06:38:58 pm »
So I decided to start looking into the text compression for Time Twist on the FDS. It doesn't use standard NES dictionary compression and uses something a little bit more complicated. I'm actually only familiar with dictionary and RLE compression, so I'd like to figure this out so I can learn something new. But I'm gonna need a bit of help to grasp whatever it is I'm not getting. I'm trying to do a partial disassembly of the code and I've got the end result figured out, but the middle is definitely confusing. I'm guessing it's relying heavily on bitmath to do its decompression. I'm also not even sure I've got all the code as this thing is looking beastly. Anyone curious or want to help me? If so, I'll post what I've got so far.

Gemini

  • Hero Member
  • *****
  • Posts: 2026
  • 時を越えよう、そして彼女の元に戻ろう
    • View Profile
    • Apple of Eden
Re: Time Twist Text Compression Code
« Reply #1 on: August 17, 2016, 07:41:30 pm »
Bitmath? Sounds like huffman.
I am the lord, you all know my name, now. I got it all: cash, money, and fame.

Pennywise

  • Hero Member
  • *****
  • Posts: 2369
  • I'm curious
    • View Profile
    • Yojimbo's Translations
Re: Time Twist Text Compression Code
« Reply #2 on: August 17, 2016, 08:38:47 pm »
That's just my best guess. It goes through a bunch of loops and whatnot with a few LSR's and ROL's.

abw

  • Hero Member
  • *****
  • Posts: 580
    • View Profile
Re: Time Twist Text Compression Code
« Reply #3 on: August 17, 2016, 08:43:54 pm »
I can't promise I'll have anything useful to offer, but I am curious. Post away!

Oh, this just in:
That's just my best guess. It goes through a bunch of loops and whatnot with a few LSR's and ROL's.
Any chance it's using an encoding that's not a multiple of 8 bits? You'll see lots of shift operations in e.g. Battle of Olympus, which uses multiple 5-bit character encodings, with some code points indicating an encoding change (a.k.a. table switch).

Pennywise

  • Hero Member
  • *****
  • Posts: 2369
  • I'm curious
    • View Profile
    • Yojimbo's Translations
Re: Time Twist Text Compression Code
« Reply #4 on: August 17, 2016, 10:29:40 pm »
Code: [Select]
00:8154:A2 00     LDX #$00
 00:8156:86 72     STX $0072
 00:8158:86 73     STX $0073
 00:815A:A9 80     LDA #$80
 00:815C:85 6C     STA $006C
 00:815E:A0 00     LDY #$00
 00:8160:84 3A     STY $003A
 00:8162:20 28 83  JSR $8328
 00:8165:B0 06     BCS $816D
 00:8167:20 28 83  JSR $8328
 00:816A:4C A3 81  JMP $81A3
 00:816D:20 28 83  JSR $8328
 00:8170:B0 03     BCS $8175
 00:8172:4C A3 81  JMP $81A3
 00:8175:20 28 83  JSR $8328
 00:8178:B0 03     BCS $817D
 00:817A:4C CC 81  JMP $81CC
 00:817D:20 28 83  JSR $8328
 00:8180:B0 03     BCS $8185
 00:8182:4C BE 82  JMP $82BE
 00:8185:A9 00     LDA #$00
 00:8187:85 3A     STA $003A
 00:8189:20 28 83  JSR $8328
 00:818C:20 28 83  JSR $8328
 00:818F:20 28 83  JSR $8328
 00:8192:A5 3A     LDA $003A
 00:8194:C9 07     CMP #$07
 00:8196:F0 03     BEQ $819B
 00:8198:4C 42 82  JMP $8242
 00:819B:A9 B5     LDA #$B5
 00:819D:9D 48 87  STA $8748,X
 00:81A0:4C 5E 81  JMP $815E
 00:81A3:20 10 81  JSR $8110
 //regular character routine
 00:81A6:A5 3A     LDA $003A
 00:81A8:C9 2E     CMP #$2E
 00:81AA:D0 05     BNE $81B1
 00:81AC:A9 30     LDA #$30
 00:81AE:4C B7 81  JMP $81B7
 00:81B1:C9 2F     CMP #$2F
 00:81B3:D0 02     BNE $81B7
 00:81B5:A9 31     LDA #$31
 00:81B7:18        CLC
 00:81B8:69 C0     ADC #$C0
 00:81BA:9D 47 87  STA $8747,X
 00:81BD:BD 48 87  LDA $8748,X
 00:81C0:C9 AC     CMP #$AC
 00:81C2:D0 02     BNE $81C6
 00:81C4:A9 AC     LDA #$AC
 00:81C6:9D 48 87  STA $8748,X
 00:81C9:4C 3D 82  JMP $823D
 00:81CC:A9 00     LDA #$00
 00:81CE:85 3A     STA $003A
 00:81D0:20 0A 81  JSR $810A
 00:81D3:A5 3A     LDA $003A
 00:81D5:C9 04     CMP #$04
 00:81D7:90 07     BCC $81E0
 00:81D9:C9 20     CMP #$20
 00:81DB:B0 09     BCS $81E6
 00:81DD:4C ED 81  JMP $81ED
 00:81E0:18        CLC
 00:81E1:69 20     ADC #$20
 00:81E3:4C 36 82  JMP $8236
 00:81E6:C9 25     CMP #$25
 00:81E8:B0 3C     BCS $8226
 00:81EA:4C 15 82  JMP $8215
 //special character routine
 00:81ED:A5 3A     LDA $003A
 00:81EF:C9 1E     CMP #$1E
 00:81F1:D0 05     BNE $81F8
 00:81F3:A9 2E     LDA #$2E
 00:81F5:4C B7 81  JMP $81B7
 00:81F8:C9 1F     CMP #$1F
 00:81FA:D0 05     BNE $8201
 00:81FC:A9 2F     LDA #$2F
 00:81FE:4C B7 81  JMP $81B7
 00:8201:BD 48 87  LDA $8748,X
 //dakuten check
 00:8204:C9 B5     CMP #$B5
 00:8206:D0 05     BNE $820D
 00:8208:A9 A3     LDA #$A3
 00:820A:4C 0F 82  JMP $820F
 // #$EE dakuten character
 00:820D:A9 EE     LDA #$EE
 00:820F:9D 48 87  STA $8748,X
 00:8212:4C A6 81  JMP $81A6
 00:8215:BD 48 87  LDA $8748,X
 00:8218:C9 B5     CMP #$B5
 00:821A:D0 05     BNE $8221
 00:821C:A9 A4     LDA #$A4
 00:821E:4C 23 82  JMP $8223
// dakuten character #2
 00:8221:A9 EF     LDA #$EF
 00:8223:4C 2F 82  JMP $822F
 00:8226:BD 48 87  LDA $8748,X
 00:8229:C9 B5     CMP #$B5
 00:822B:F0 02     BEQ $822F
 //blank Dakuten row
 00:822D:A9 AC     LDA #$AC
 00:822F:9D 48 87  STA $8748,X
//sets index to load from table
 00:8232:A5 3A     LDA $003A
 00:8234:29 1F     AND #$1F
 00:8236:A8        TAY
 //character table
 00:8237:B9 59 83  LDA $8359,Y
 //$8747 is where decompressed text is written to RAM
 00:823A:9D 47 87  STA $8747,X
 00:823D:E8        INX
 00:823E:E8        INX
>00:823F:4C 5E 81  JMP $815E
 00:8242:A5 3A     LDA $003A
 00:8244:C9 01     CMP #$01
 00:8246:D0 03     BNE $824B
 00:8248:4C EB 82  JMP $82EB
 00:824B:C9 02     CMP #$02
 00:824D:D0 03     BNE $8252
 00:824F:4C F0 82  JMP $82F0
 00:8252:C9 04     CMP #$04
 00:8254:D0 03     BNE $8259
 00:8256:4C F5 82  JMP $82F5
 00:8259:C9 03     CMP #$03
 00:825B:D0 03     BNE $8260
 00:825D:4C FA 82  JMP $82FA
 00:8260:C9 05     CMP #$05
 00:8262:D0 03     BNE $8267
 00:8264:4C 04 83  JMP $8304
 00:8267:C9 06     CMP #$06
 00:8269:D0 03     BNE $826E
 00:826B:4C FF 82  JMP $82FF
 00:826E:8A        TXA
 00:826F:C9 31     CMP #$31
 00:8271:90 0B     BCC $827E
 00:8273:C9 61     CMP #$61
 00:8275:90 1C     BCC $8293
 00:8277:C9 91     CMP #$91
 00:8279:90 2D     BCC $82A8
 00:827B:4C BA 82  JMP $82BA
 00:827E:A5 72     LDA $0072
 00:8280:D0 11     BNE $8293
 00:8282:E0 00     CPX #$00
 00:8284:D0 04     BNE $828A
 00:8286:A9 01     LDA #$01
 00:8288:85 73     STA $0073
 00:828A:A9 01     LDA #$01
 00:828C:85 72     STA $0072
 00:828E:A2 30     LDX #$30
 00:8290:4C BA 82  JMP $82BA
 00:8293:E0 30     CPX #$30
 00:8295:D0 08     BNE $829F
 00:8297:A5 73     LDA $0073
 00:8299:F0 04     BEQ $829F
 00:829B:A9 02     LDA #$02
 00:829D:85 73     STA $0073
 00:829F:A9 02     LDA #$02
 00:82A1:85 72     STA $0072
 00:82A3:A2 60     LDX #$60
 00:82A5:4C BA 82  JMP $82BA
 00:82A8:E0 60     CPX #$60
 00:82AA:D0 08     BNE $82B4
 00:82AC:A5 73     LDA $0073
 00:82AE:F0 04     BEQ $82B4
 00:82B0:A9 03     LDA #$03
 00:82B2:85 73     STA $0073
 00:82B4:A9 03     LDA #$03
 00:82B6:85 72     STA $0072
 00:82B8:A2 90     LDX #$90
 00:82BA:C8        INY
 00:82BB:4C 5E 81  JMP $815E
 00:82BE:A9 00     LDA #$00
 00:82C0:85 3A     STA $003A
 00:82C2:20 0D 81  JSR $810D
 00:82C5:A9 FF     LDA #$FF
 00:82C7:85 71     STA $0071
 00:82C9:A5 6A     LDA $006A
 00:82CB:48        PHA
 00:82CC:A5 6B     LDA $006B
 00:82CE:48        PHA
 00:82CF:A5 6C     LDA $006C
 00:82D1:48        PHA
 00:82D2:AD 16 A2  LDA $A216
 00:82D5:85 6A     STA $006A
 00:82D7:AD 17 A2  LDA $A217
 00:82DA:85 6B     STA $006B
 00:82DC:A5 3A     LDA $003A
 00:82DE:38        SEC
 00:82DF:E9 01     SBC #$01
 00:82E1:85 C2     STA $00C2
 00:82E3:A9 FF     LDA #$FF
 00:82E5:20 B1 80  JSR $80B1
 00:82E8:4C 5A 81  JMP $815A
 00:82EB:A9 13     LDA #$13
 00:82ED:4C 1D 83  JMP $831D
 00:82F0:A9 0F     LDA #$0F
 00:82F2:4C 1D 83  JMP $831D
 00:82F5:A9 09     LDA #$09
 00:82F7:4C 1D 83  JMP $831D
 00:82FA:A9 06     LDA #$06
 00:82FC:4C 1D 83  JMP $831D
 00:82FF:A9 17     LDA #$17
 00:8301:4C 1D 83  JMP $831D
 00:8304:A5 71     LDA $0071
 00:8306:F0 10     BEQ $8318
 00:8308:68        PLA
 00:8309:85 6C     STA $006C
 00:830B:68        PLA
 00:830C:85 6B     STA $006B
 00:830E:68        PLA
 00:830F:85 6A     STA $006A
 00:8311:A9 00     LDA #$00
 00:8313:85 71     STA $0071
 00:8315:4C 5E 81  JMP $815E
 00:8318:A9 03     LDA #$03
 00:831A:85 69     STA $0069
 00:831C:60        RTS -----------------------------------------
 00:831D:85 69     STA $0069
 00:831F:E0 00     CPX #$00
 00:8321:D0 04     BNE $8327
 00:8323:A9 03     LDA #$03
 00:8325:85 73     STA $0073
 00:8327:60        RTS -----------------------------------------
 //Start of text data read routine
 00:8328:B1 6A     LDA ($6A),Y
 00:832A:25 6C     AND $006C
 00:832C:D0 08     BNE $8336
 00:832E:20 3E 83  JSR $833E
 00:8331:18        CLC
 00:8332:26 3A     ROL $003A
 00:8334:18        CLC
 00:8335:60        RTS -----------------------------------------
 00:8336:20 3E 83  JSR $833E
 00:8339:38        SEC
 00:833A:26 3A     ROL $003A
>00:833C:38        SEC
 00:833D:60        RTS -----------------------------------------
 00:833E:A5 6C     LDA $006C
 00:8340:C9 01     CMP #$01
 00:8342:D0 12     BNE $8356
 00:8344:A5 6A     LDA $006A
 00:8346:18        CLC
 00:8347:69 01     ADC #$01
 00:8349:85 6A     STA $006A
 00:834B:A5 6B     LDA $006B
 00:834D:69 00     ADC #$00
 00:834F:85 6B     STA $006B
 00:8351:A9 80     LDA #$80
 00:8353:85 6C     STA $006C
 00:8355:60        RTS -----------------------------------------
 00:8356:46 6C     LSR $006C
 00:8358:60        RTS -----------------------------------------

When I was typing my initial post up, a different encoding not a multiple of 8 bits was what I was thinking, but couldn't remember the exact term. Anyhow $8328 is where the initial text data is read from and from there it ends up at either a routine for a regular character or a special misc character. $003A seems to be where the magic happens... Then the converted or decompressed data gets written to $8747 in PRG-RAM and eventually onto the screen.

Edit:

On a whim, I tried chopping down the binary to less than 8 bits for the first byte to see if anything would match the output, but no luck. Anyhow, let me get more specific. This little bit of code is where most of the action seems to happens.

Code: [Select]
LDA ($6A),Y @ $A4C2 = #$D7                   A:80 X:00 Y:00 S:F5 P:nvUbdiZc
AND $006C = #$80                             A:D7 X:00 Y:00 S:F5 P:NvUbdizc
BNE $8336                                    A:80 X:00 Y:00 S:F5 P:NvUbdizc
JSR $833E                                    A:80 X:00 Y:00 S:F5 P:NvUbdizc
LDA $006C = #$80                             A:80 X:00 Y:00 S:F3 P:NvUbdizc
CMP #$01                                     A:80 X:00 Y:00 S:F3 P:NvUbdizc
BNE $8356                                    A:80 X:00 Y:00 S:F3 P:nvUbdizC
LSR $006C = #$80                             A:80 X:00 Y:00 S:F3 P:nvUbdizC
RTS (from $833E) --------------------------- A:80 X:00 Y:00 S:F3 P:nvUbdizc
SEC                                          A:80 X:00 Y:00 S:F5 P:nvUbdizc
ROL $003A = #$00                             A:80 X:00 Y:00 S:F5 P:nvUbdizC
SEC                                          A:80 X:00 Y:00 S:F5 P:nvUbdizc
RTS (from $8328)

It basically repeats that over and over again until $006C = #$01. Then it resets everything and increments $006A and starts all over again. Anyhow, $003A ends up becoming #$2E, which is basically the end result. It's a special character, so it's used as an index to load the final character from a table. I guess I don't really understand what the ROL and the AND are doing here. Can anyone explain it me?
« Last Edit: August 19, 2016, 03:30:55 pm by Pennywise »

abw

  • Hero Member
  • *****
  • Posts: 580
    • View Profile
Re: Time Twist Text Compression Code
« Reply #5 on: August 19, 2016, 11:08:22 pm »
I'm also not even sure I've got all the code as this thing is looking beastly.

You're definitely missing a few bits of code in there - if you look at the targets of all the branches and jumps, you'll see that the code starting at $80B1, $810A, $810D, and $8110 isn't included in the disassembly.

I guess I don't really understand what the ROL and the AND are doing here. Can anyone explain it me?

It looks like the AND $006C is just a 1-bit mask on the contents of ($6A),Y (so the accumulator is left with some seven bits set to 0 and the other bit set to either 1 or 0 depending on the corresponding bit of ($6A),Y), and then the ROL $003A is basically shifting all the bits of $003A left by one in order to make room for a new bit and using CLC/SEC to set the new bit based on the results of the bitmask with ($6A),Y. After doing this a few times (how many times appears to be determined by the driving loop starting at $815E), the end result is that a string of bits from ($6A),Y gets copied into $003A.

If you're having trouble tracing through the ASM, it might be helpful to try rewriting it in pseudo code or any higher-level language you're already familiar with. Is something like this easier to read?

EDIT: Just for the sake of posterity, I've updated the below code based on the new information. It looks like the game uses a combination of 6-, 7-, and 9-bit strings.
Code: [Select]
var bitIndex; // this is $006C
var bitStringAddress; // this is 16-bit $006A,$006B and is presumably set by calling code
var curBitString; // this is $003A

// $0069 only gets set just before we return, so is likely some sort of status indicator
// $0071, $0072, $0073, $00C2, $7F78, $7F79, and 16-bit $A216,$A217 are also in use at various points for reasons that are not completely clear based on the available code
// $8359[Y] apparently indexes into font data
// $8747[X] and $8748[X] appear to be RAM buffers for, respectively, a character and its superscript (dakuten/handakuten/blank) before sending to VRAM
function main() {
X = #$00;
$72 = #$00;
$73 = #$00;
bitIndex = #$80;

while (true) {
Y = #$00;
curBitString = #$00;
if (getNextBit()) {
// curBitString == #$01
if (getNextBit()) {
// curBitString == #$03
if (getNextBit()) {
// curBitString == #$07
if (getNextBit()) {
// curBitString == #$0F
curBitString = #$00;
getNextBits(3);
// at this point, we've read a 7-bit string (111 1XXX)b
if (curBitString == #$01) {
$69 = #$13;
if (X == #$00) {
$73 = #$03;
}
return; // exit all of this code
} else if (curBitString == #$02) {
$69 = #$0F;
if (X == #$00) {
$73 = #$03;
}
return; // exit all of this code
} else if (curBitString == #$03) {
$69 = #$06;
if (X == #$00) {
$73 = #$03;
}
return; // exit all of this code
} else if (curBitString == #$04) {
$69 = #$09;
if (X == #$00) {
$73 = #$03;
}
return; // exit all of this code
} else if (curBitString == #$05) {
if ($71 == #$00) {
$69 = #$03;
return; // exit all of this code
} else {
pull bitIndex;
pull bitStringAddress;
$71 = #$00;
}
} else if (curBitString == #$06) {
$69 = #$17;
if (X == #$00) {
$73 = #$03;
}
return; // exit all of this code
} else if (curBitString == #$07) {
$8748[X] = #$B5; // indicate dakuten?
} else {
// curBitString == #$00
// looks like some sort of horizontal spacing
if (X < #$31 && $72 == #$00) {
if (X == #$00) {
$73 = #$01;
}
$72 = #$01;
X = #$30;
} else if (X < #$61) {
if (X == #$30 && $73 != #$00) {
$73 = #$02;
}
$72 = #$02;
X = #$60;
} else if (X < #$91) {
if (X == #$60 && $73 != #$00) {
$73 = #$03;
}
$72 = #$03;
X = #$90;
} // else: apparently we don't do anything special
}
} else {
// curBitString == #$0E
curBitString = #$00;
getNextBits(5);
// at this point, we've read a 9-bit string (1 110X XXXX)b
$71 = #$FF;
push bitStringAddress;
push bitIndex;
bitStringAddress = $A216,$A217;
$C2 = curBitString - #$01;
$7F79 = #$FF;
bitIndex = #$80;
if ($C2 == #$00) {
$7F78 = #$00;
return; // exit all of this code
} else {
// search through $A216,$A217 for the 7-bit string (111 1101)b, ignoring any 6- and 9-bit strings
while (true) {
curBitString = #$00;
getNextBits(2);
if (curBitString != #$03) {
getNextBits(4); // ignore 6-bit string
next;
}
getNextBits(2);
if (curBitString != #$0F) {
getNextBits(5); // ignore 9-bit string
next;
}
curBitString = #$00;
getNextBits(3);
// at this point, we've read a 7-bit string (111 1XXX)b
if (curBitString == #$05) {
if (bitIndex != #$80) {
bitStringAddress++;
}
$C2--;
if (bitStringAddress < #$D400) {
$7F79--;
if ($7F79 == #$00) {
return; // exit all of this code
} else {
bitIndex = #$80;
if ($C2 != #$00) {
next;
}
}
}
$7F78 = #$00;
return; // exit all of this code
}
} // end of inner while loop
}
bitIndex = #$80;
}
} else {
// curBitString == #$06
curBitString = #$00;
getNextBits(6);
// at this point, we've read a 9-bit string (1 10XX XXXX)b
if (curBitString < #$04) {
curBitString = $8359[curBitString + #$20];
} else if (curBitString >= #$20) {
if (curBitString >= #$25) {
if ($8748[X] != #$B5) { // if no dakuten
$8748[X] = #$AC; // blank dakuten row
}
} else {
// #$20 <= curBitString < #$25
if ($8748[X] == #$B5) { // if dakuten
$8748[X] = #$A4; // maybe dakuten character #1? handakuten?
} else {
$8748[X] = #$EF; // dakuten character #2
}
}
curBitString = $8359[curBitString & #$1F];
} else {
// #$04 <= curBitString < #$20
if (curBitString == #$1E) {
curBitString = #$E3; // write character to $8747[X] in RAM
} else if (curBitString == #$1F) {
curBitString = #$EF; // write character to $8747[X] in RAM
} else {
//dakuten check
if ($8748[X] == #$B5) {
$8748[X] = #$A3;
} else {
$8748[X] = #$EE;
}
}
}
$8747[X] = curBitString;
X += 2; // we wrote to $8747[X], so update X
}
next;
} // else: curBitString == #$02
} else {
// curBitString == #$00
getNextBit();
}
getNextBits(4);
// at this point, we've read a 6-bit string (0X XXXX)b or (10 XXXX)b, i.e. anything less than (11 0000)b
if (curBitString == #$2E) {
curBitString = #$F0; // write character to $8747[X] in RAM
} else if (curBitString == #$2F) {
curBitString = #$F1; // write character to $8747[X] in RAM
}
$8747[X] = curBitString;
X += 2; // we wrote to $8747[X], so update X
} // end of outer while loop
} // end of main()

// calls getNextBit the specified number of times
function getNextBits(numBits) {
for (var i = 1; i <= numBits; i++) {
getNextBit();
}
return;
}

// gets the bit of bitStringAddress[Y] specified by bitIndex, stores it into curBitString, and returns it
// also updates bitIndex
function getNextBit() {
var bit = bitStringAddress[Y] & bitIndex;
updateBitIndex();
curBitString = curBitString << 1 + bit;
return bit;
}

// shift bitIndex to the next bit; when bitIndex wraps, increment bitStringAddress
function updateBitIndex() {
bitIndex = bitIndex >> 1;
if (bitIndex == 0) { // if we've finished reading a byte, reset bitIndex and increment bitStringAddress
bitIndex = #$80;
bitStringAddress++;
}
}
« Last Edit: August 22, 2016, 08:08:36 pm by abw »

Pennywise

  • Hero Member
  • *****
  • Posts: 2369
  • I'm curious
    • View Profile
    • Yojimbo's Translations
Re: Time Twist Text Compression Code
« Reply #6 on: August 20, 2016, 04:13:13 pm »
I didn't post the whole thing because it's rather beastly. It's more important for me to focus me on the bits I think are important than the whole huge routine. I also don't have a programming background and do everything in assembly. It's what I'm used to and what's I've learned. But anyway, for completionist's sake, here is the rest of the code.

Code: [Select]
00:80AF:A9 04     LDA #$04
 00:80B1:8D 79 7F  STA $7F79
 00:80B4:A9 80     LDA #$80
 00:80B6:85 6C     STA $006C
 00:80B8:A9 00     LDA #$00
 00:80BA:C5 C2     CMP $00C2
 00:80BC:D0 03     BNE $80C1
 00:80BE:4C 4E 81  JMP $814E
 00:80C1:A0 00     LDY #$00
 00:80C3:84 3A     STY $003A
 00:80C5:20 28 83  JSR $8328
 00:80C8:B0 06     BCS $80D0
 00:80CA:20 28 83  JSR $8328
 00:80CD:4C 04 81  JMP $8104
 00:80D0:20 28 83  JSR $8328
 00:80D3:B0 03     BCS $80D8
 00:80D5:4C 04 81  JMP $8104
 00:80D8:20 28 83  JSR $8328
 00:80DB:B0 03     BCS $80E0
 00:80DD:4C FE 80  JMP $80FE
 00:80E0:20 28 83  JSR $8328
 00:80E3:B0 03     BCS $80E8
 00:80E5:4C 01 81  JMP $8101
 00:80E8:A9 00     LDA #$00
 00:80EA:85 3A     STA $003A
 00:80EC:20 28 83  JSR $8328
 00:80EF:20 28 83  JSR $8328
 00:80F2:20 28 83  JSR $8328
 00:80F5:A5 3A     LDA $003A
 00:80F7:C9 07     CMP #$07
 00:80F9:B0 0C     BCS $8107
 00:80FB:4C 1D 81  JMP $811D
 00:80FE:20 28 83  JSR $8328
 00:8101:20 28 83  JSR $8328
 00:8104:20 10 81  JSR $8110
 00:8107:4C C1 80  JMP $80C1
 00:810A:20 28 83  JSR $8328
 00:810D:20 28 83  JSR $8328
 00:8110:20 28 83  JSR $8328
 00:8113:20 28 83  JSR $8328
 00:8116:20 28 83  JSR $8328
 00:8119:20 28 83  JSR $8328
 00:811C:60        RTS -----------------------------------------
 00:811D:A5 3A     LDA $003A
 00:811F:C9 05     CMP #$05
 00:8121:F0 03     BEQ $8126
 00:8123:4C C1 80  JMP $80C1
 00:8126:A5 6C     LDA $006C
 00:8128:C9 80     CMP #$80
 00:812A:F0 0D     BEQ $8139
 00:812C:A5 6A     LDA $006A
 00:812E:18        CLC
 00:812F:69 01     ADC #$01
 00:8131:85 6A     STA $006A
 00:8133:A5 6B     LDA $006B
 00:8135:69 00     ADC #$00
 00:8137:85 6B     STA $006B
 00:8139:A5 C2     LDA $00C2
 00:813B:38        SEC
 00:813C:E9 01     SBC #$01
 00:813E:85 C2     STA $00C2
 00:8140:A5 6B     LDA $006B
 00:8142:C9 D4     CMP #$D4
 00:8144:B0 08     BCS $814E
 00:8146:CE 79 7F  DEC $7F79
 00:8149:F0 08     BEQ $8153
 00:814B:4C B4 80  JMP $80B4
 00:814E:A9 00     LDA #$00
 00:8150:8D 78 7F  STA $7F78
 00:8153:60        RTS -----------------------------------------

Anyhow, can you or someone else explain to me what a mask is? I think it might've been explained to me once, but I probably forgot it as I never have to use that stuff in my ASM hacks. Also, I understand that the bits are being shifted to the left, but where does the new bit come from? So, I understand the new bit is being set from the results of the bitmask, but which side is the bit being taken from? The right or the left?

abw

  • Hero Member
  • *****
  • Posts: 580
    • View Profile
Re: Time Twist Text Compression Code
« Reply #7 on: August 20, 2016, 07:47:26 pm »
But anyway, for completionist's sake, here is the rest of the code.

Thanks!

Anyhow, can you or someone else explain to me what a mask is? I think it might've been explained to me once, but I probably forgot it as I never have to use that stuff in my ASM hacks. Also, I understand that the bits are being shifted to the left, but where does the new bit come from? So, I understand the new bit is being set from the results of the bitmask, but which side is the bit being taken from? The right or the left?

Well, sure, I can take a stab at that. I apologize in advance if this is aimed too high or too low.

Wikipedia does a fairly decent job of explaining masking here. The "Querying the status of a bit" section is exactly what the Time Twist code is doing at $832A: pretend the first row of Wikipedia's example is ($6A),Y, the second row is $006C, and the third row is what's left in the accumulator (A) as a result. In the context of this section of code, masking basically means ignoring the bits you don't care about by setting them to 0.

On the NES, the AND operation also sets the zero processor flag if the result (A) is #$00, and that flag is what the BNE at $832C checks to decide what to do next. If A is not #$00 (which means it has some bit set, which in turn can only mean that the bit of ($6A),Y that we were checking with $006C was set), then it branches to $8336, where (in addition to updating $6A, $6B, and $6C via the JSR $833E) it does a SEC and a ROL $003A. The rightmost bit (a.k.a. the new bit, a.k.a. the lowest-order bit) in the ROL actually comes from the carry processor flag, not anything in A, so the SEC (SEt Carry) makes sure that the low bit of $003A does get set. On the other hand, if A is #$00 (which means it has no bits set, which in turn can only mean that the bit of ($6A),Y that we were checking with $006C was not set), then it doesn't branch and (in addition to running the exact same code for updating $6A, $6B, and $6C via the JSR $833E) instead does a CLC (CLear Carry) and a ROL $003A, which results in the low bit of $003A not being set. So in either case, the low bit of $003A becomes the same as the bit of ($6A),Y that we were checking with $006C.

If you haven't seen it already, Programming the 65816, a handy reference for both 65816 and 6502, has a nice little picture on the reference page for ROL that is probably a better explanation than anything I just wrote :p.

Anyway, that entire block of code from $8328 to $8358 is basically just reading one bit at a time from ($6A),Y and updating $003A accordingly. It gets called a lot, but (IMHO) it's fairly boring per se. I'm not sure exactly what part of Time Twist's text routine you're interested in most, but for me finding out what happens with $003A after $8328 - $8358 updates it is, well, not exactly exciting but at least closer to it ;).

mgp

  • Newbie
  • *
  • Posts: 2
    • View Profile
Re: Time Twist Text Compression Code
« Reply #8 on: May 11, 2021, 11:41:35 am »
A friend and I were thinking about Time Twist translation, and surfing around brought us here.

Did anyone make any more progress with this compression algorithm, or with any other part of translating Time Twist? Are there any files to be shared with the current state?

FWIW, between us we have a bit of Computer Science and a bit of Japanese.
« Last Edit: May 11, 2021, 01:30:06 pm by mgp »

Pennywise

  • Hero Member
  • *****
  • Posts: 2369
  • I'm curious
    • View Profile
    • Yojimbo's Translations
Re: Time Twist Text Compression Code
« Reply #9 on: May 11, 2021, 03:05:20 pm »
I never pursued the project and I don't know of anyone working on it. I'm not a big fan of working on the FDS even though I've done a few translations for the system. Depending on the game, the memory limitations can be quite severe. Anyhow, I gave up on the project to focus on other stuff to spare myself the potential (and likely) pain.

mgp

  • Newbie
  • *
  • Posts: 2
    • View Profile
Re: Time Twist Text Compression Code
« Reply #10 on: May 11, 2021, 04:16:21 pm »
OK, thanks.

I am guessing I'll only have a play with some of the disassembly tools for a bit and then put this down.