跳至内容

Jixun's Blog 填坑还是开坑,这是个好问题。

FC 魂斗罗汉化日记 (第三集)

第三集 - 过关对话汉化

(目录)

寻找修改点和上面的一样,没什么难度,照着之前写的代码抠出来就能用了。

不过遇到了一个问题:有的过关对话太长了。

因为过关场景使用了三个 PPU 显示图像,以及一个剩下来的 PPU 显示文字。而汉化能利用的 PPU 图案只有原本用于显示文字的那一块,满打满算也只能放 16 个不同的大字 (含符号)。

如果中文重用率没有那么糟糕也好啊…。

例如第五关的过场文字:

第五关过关后的对话

已抵达C点。

由于内部信号不良,
我们决定一举
击破敌方的
大本营。

算来算去,至少要消耗 2 个 PPU 才能刚好储存下来。

于是,我想了一个天才般的点子 - 就是把之前显示的文字擦掉,切换 PPU,再显示新的文字上去。

于是操刀改造代码,加入了自己用的一个控制符:FC,用于清空屏幕重新写入。

;  012C70:FF        UNDEFINED
; 04:AC60:FF        UNDEFINED
PlaceNextDialogPassChar:
.org $AC60
  DEC $0047
  BEQ _DO_PROC

_EXIT_WITH_CARRY:
  CLC
  RTS

_DO_PROC:
  LDA #$06    ; A = 06

; byte [0047] = 当前对话文字偏移
  STA $0047   ; [0047] = 06
  LDA $0030   ; A = [0030]
  ASL       ; A <<= 1
  TAY       ; Y = A

  LDA $A1D6,Y   ;
  STA $0000   ;

  LDA $A1D7,Y   ;
  STA $0001   ;

  LDY $0046   ; Y = [0046]
  LDA ($00),Y   ; 读取下一个要打印的字符
  CMP #$FF    ; 如果是结束符, 跳走
  BNE _CONTINUE_EXECUTE
    LDA #$00
    STA $07EE
    SEC
    RTS

_CONTINUE_EXECUTE:

  STA $0008 ; [0008] = A
  CMP #$FE
  BNE _SKIP_LINE_BREAK
    INY
    JSR _NEXT_LINE
    JMP _END_OF_CONTROL_CHAR

_SKIP_LINE_BREAK:
  CMP #$FD
  BNE _SKIP_ENABLE_CHINESE
    LDA #$02
    STA $07EE
  _LOAD_NEXT_CHAR:
    INY
    INC $0046
    JMP _END_OF_CONTROL_CHAR

_SKIP_ENABLE_CHINESE:
  CMP #$FC
  BNE _SKIP_CLEAR_SCREEN
    JSR _CLEAR_DIALOG

    LDA #$20        ; TODO: 清屏后的等待时间

  _SET_TIMER_AND_EXIT_WITH_CARRY:
    STA $0047
    INC $0046
    JMP _EXIT_WITH_CARRY

_SKIP_CLEAR_SCREEN:
  CMP #$FB
  BNE _SKIP_WAIT_TIMER
    LDA #$80        ; TODO: 清屏后的等待时间
    JMP _SET_TIMER_AND_EXIT_WITH_CARRY

_SKIP_WAIT_TIMER:
_END_OF_CONTROL_CHAR:

  LDX $0021   ; X = [0021]

  LDA #$01    ; 需要写出文字标记
  STA $0700,X

  LDA $004B   ; 写出位置
  STA $0701,X
  LDA $004A
  STA $0702,X


  ; Loop - 检查中文字符
_Loop_SearchForChinese:
  LDA ($00),Y   ; A = 当前字符
  AND #$C0    ; 检查第一位是不是 1
  CMP #$C0    ; 中文控制符
  BNE _END_CHINESE; 跳过中文字符处理

  ; 是中文控制符
  ; 加入两个字符,并,自循环
  ; 写出 2 个控制符
  TXA       ; 
  PHA       ; push X

  LDA ($00),Y   ; A = ChineseCtrlChar
  AND #$7     ; 
  TAX       ; X = A
  INY       ; Y++
  ; 此时的 stack: X
  ; 此时的值: X = PPU 序号
  ;           Y = 正常的序号

  LDA ($00),Y   ; A = NextChar
  STA $07F0,X   ; [07FX] = A (CHR_BANK_X)
  INY       ; Y++

  PLA
  TAX       ; pop X

  STY $0046   ; 储存对话偏移
  INC $0046

  JSR $FACE

  JMP _Loop_SearchForChinese

_END_CHINESE:
  LDA ($00),Y   ; 写出需要处理的字符
  STA $0703,X


  BEQ _SKIP_NO_SOUND    ; if char == 0 jump
    AND #$C0
    CMP #$C0
    BEQ _SKIP_NO_SOUND
      LDA #$10    ; Play sound
      JSR $F9BC

_SKIP_NO_SOUND:
  LDA #$FF
  STA $0704,X   ; 结束符号

  TXA       ; A = X
  CLC
  ADC #$05
  STA $0021

  INY
  STY $0046   ; 储存下一个偏移

  INC $004A
  LDA $0008

_EXIT:
  CLC
  RTS

; 清空屏幕字符
_CLEAR_DIALOG:
  JSR _RESET_POSITION

  ; 清除八行
  LDA #$10

; 循环八次
_LOOP_CLEAR:
  PHA
  JSR _ERASE_LINE
  PLA
  CLC
  SBC #$01
  BNE _LOOP_CLEAR

  ; 循环结束, 清理现场
  JSR _RESET_POSITION
  RTS


_ERASE_LINE:
  LDA #$10

_ERASE_NEXT_CHAR:
  LDY #$00

  STY $2007
  CLC

  STY $2007
  CLC

  STY $2007
  CLC

  STY $2007
  CLC

  STY $2007
  CLC

  STY $2007
  CLC

  STY $2007
  CLC

  CLC
  
  SBC #$01
  BNE _ERASE_NEXT_CHAR

  JSR _NEXT_LINE_8px
  RTS

_NEXT_LINE:
  ; 低位
  CLC
  LDA $0048
  ADC #$40
  STA $0048
  STA $004A

  ; 高位
  LDA $0049
  ADC #$00
  STA $0049
  STA $004B
  
  ; 设定等待, 不需要

  ; LDA #$30
  ; STA $0047
  RTS

; This will destory ACC register.
_RESET_POSITION:
  ; HI LO
  ; 21 11
  ;    ** [0048, 004A]
  ; **    [0049, 004B]

  LDA #$21
  STA $0049
  STA $004B
  STA $2006

  LDA #$11
  STA $0048
  STA $004A
  STA $2006

  RTS

_NEXT_LINE_8px:
  ; 低位
  CLC
  LDA $0048   ; Add new line
  ADC #$20
  STA $0048
  STA $004A

  ; 高位
  LDA $0049
  ADC #$00
  STA $0049
  STA $004B
  STA $2006

  ; 写低位地址
  LDA $0048
  STA $2006
  
  RTS

翻译之后:

最终分为两个屏幕来展示所有文字

第三日: 碎碎念

该说不愧是模拟器吗?连写数据到 PPU 总线的延迟都被模拟了。

清空屏幕的代码很迷,测试了很多遍终于能清空写在屏幕上的大多数字符了。

因为不是很熟悉清空区域屏幕的写法,所以是很笨的一行一行的擦除…

字模压缩思考

其实应该还有个方法,就是利用配色 + 压缩,让一个字模可以放入两个字符。

原理就是四色(无字符、A 配色显示、B 配色显示、A + B 均显示)。配色盘是可以自己定的。

不过不清楚怎么设定调色表,而且这 Workaround 也能用,就这样吧…

知识共享许可协议 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。

评论区