Đọc bản pdf tại đây (click)

V. Sửa font


Sau khi xuất text, ta có thể tiến hành dịch luôn. Nhưng có chèn bản dịch vào game thì cũng chưa thể xem là hoàn thành, vì còn rất nhiều việc phải làm. Một trong số đó là sửa bộ font. Bởi lẽ font chữ lúc này đang là chữ Nhật, cần phải sửa lại thành font La Tinh để hiển thị tiếng Việt.

Có khá nhiều phần mềm dành cho dân dịch game, có thể sửa được font chữ. Yy-char là một trong những phần mềm cho phép chỉnh sửa font chữ và hình ảnh trong game SNES.

Font chữ cho máy SNES thuộc loại 1bpp (1 bit per pixel/1 bit plane, mỗi 1 bit thể hiện 1 điểm ảnh) hoặc 2bpp. Captain Tsubasa III dùng font 1bpp, tức font đơn sắc và không có đổ bóng.

[​IMG]

Về kích thước, font chữ game SNES chỉ thuộc một trong các kiểu sau:
1. 8x8 pixel
2. 8x16 pixel
3. 16x16 pixel
4. Độ rộng thay đổi theo từng chữ

Đối với Captain Tsubasa III, game này dùng kiểu 8x8 pixel. Mỗi một chữ cái nằm gọn trong khung với kích thước 8x8 điểm ảnh. Ta có thể vẽ các chữ cái La Tinh dễ dàng trong ô vuông 64 pixel này, nhưng dường như không thể vẽ thêm dấu tiếng Việt, nhất là cái chữ có dấu mũ như â, ô, ê. Cách giải quyết vấn đề này sẽ bàn ở phần sau.

[​IMG]


Sau khi chỉnh sửa font chữ, dễ dàng nhận thấy rằng dù font đã được thay đổi nhưng trật tự của các chữ cái vẫn còn là của tiếng Nhật, và hiện tượng này được gọi là "tiếng mọi".

VI. Phân tích cơ chế bỏ dấu

Sau khi đã sửa bộ font và dump text, về cơ bản thì đã có thể tiến hành dịch. Nhưng trước đó cần phải giải quyết vấn đề dấu tiếng Việt như đã chỉ ra trước đó. Ta biết, Tsubasa 3 dùng font 8x8 pixel cho mỗi chữ và rất khó, nếu không muốn nói là không thể, vẽ thêm các dấu sắc, huyền, hỏi ngã, mũ, mốc,.... phía trên đầu các nguyên âm.
Chả lẽ bó tay....?
Thử quan sát, thấy trong tiếng Nhật, có dấu trọc âm (゛) và bán trọc âm (゜) đó. Các dấu này cũng nằm ngay bên trên chữ cái tiếng Nhật. Vậy chỉ cần áp dụng, biến 2 dấu này thành dấu tiếng Việt là được. Chẳng hạn như chữ S xuất hiện trên đầu chữ kana dưới đây.

[​IMG]

Nhưng gốc gác nó vốn chỉ có 2 dấu, trong khi tiếng Việt thì nhiều hơn (sắc, huyền, hỏi ngã, mũ, mốc, sắc mũ, huyền mũ, hỏi mũ, ngã mũ, sắc móc, huyền móc, hỏi móc, ngả móc) thì phải làm sao?

Tới đây phải dùng tới kiến thức Assembly để giải quyết, vì các thủ thuật hack rom thông thường không làm gì được.

1. Tìm một đoạn text có chứa chữ cái mang dấu trọc âm hay bán trọc âm. Xác suất xuất hiện của dấu trọc âm trong tiếng Nhật khoảng 80% trong một văn bản bình thường.
2. Xác định địa chỉ của chữ mang dấu trọc âm/bán trọc âm đó.
3. Chuyển đổi địa chỉ của dấu trọc âm đó sang địa chỉ SNES. Vì Tsubasa 3 là Lorom nên địa chỉ $00 mà ta thấy trong hex editor tương đương với $8000 trong bộ nhớ SNES. Có thể dùng Lunar Address để chuyển đổi.
4. Đặt Read break point địa chỉ đó trong debugger. Khi game đọc tới địa chỉ để hiển thị chữ cái đó thì nó sẽ dừng, click liên tục vào Step Into để đi vào khối code xử lý đó.

[​IMG]



Nhìn hình trên, ta thấy 9B và 9C là giá trị của 2 dấu trọc âm, bán trọc âm trong game. Khi thay bằng giá trị khác vào đoạn code này thì game sẽ hiển thị chữ cái có giá trị mà ta thay lên đầu của chữ Kana. Chẳng hạn, trong hình trên, tôi thay LDY #$9C (9C là giá trị của dấu bán trọc âm) tại dòng lệnh $0085DC bằng LDY #$93 (93 là giá trị của chữ S) thì dấu bán trọc âm xuất hiện trên đầu chữ ハ đã biến thành chữ S.
Tuy cơ chế đã có sẵn, nhưng mới chỉ có 2 dấu, trong khi tiếng Việt cần tới 14 dấu xuất hiện trên đầu. Do vậy ta cần viết thêm dựa trên cơ chế có sẵn.
Và để viết được thì đầu tiên cần hiểu rõ cơ chế bỏ dấu lên đầu chữ của game. 

Dùng debugger, đặt read breakpoint cho địa chỉ của một chữ cái bất kỳ như đã nói bên trên, và debugger sẽ dừng lại ngay khi đọc được code xử lý chữ cái đó. Ví dụ dưới đây là trường hợp chữ cái không có dấu dakuten hay handakuten, giá trị nằm trong khoảng 00 ~ 9F.

Đoạn code (routine) này bắt đầu ở $85CA và được viết lại như sau:

tại 85CA: php ; lưu giữ giá trị của register P vào stack, để dùng lại sau
tại 85CB: sep #$30
tại 85CD: ldy #$00 ; tải giá trị 00 vào register y. 00 là không thể hiện dấu gì
tại 85CF: cmp $a0 ; so sánh giá trị hiện tại của A với A0
tại 85D1: bcc $28 [$85fb] ; nhảy tới địa chỉ $85fb nếu carry = 0, nói cách khác là nếu giá trị trong A nhỏ hơn A0 thì nhảy tới $85FB
tại 85FB: PLP ; lấy lại giá trị của P được lưu trữ trong stack
tại 85FC: RTL ; kết thúc routine

[​IMG]

Nhìn vào đây, có thể thấy giá trị của A (tức là chữ cái mà A đang đọc) nhỏ hơn A0 nên bộ xử lý mới nhảy tới 85FB và kết thúc với Y = 00, tức không vẽ thứ gì lên đầu chữ cái đứng trước nó.

Giờ thử đặt read break point tại địa chỉ của một chữ kana với dấu handakuten (゜) trên đầu. Khi game đọc tới chữ này thì nó sẽ dừng và cho biết những thông tin tương tự.


$00/85CA 08 PHP ; lưu trữ giá trị của Status register
$00/85CB E2 30 SEP #$30 ; chuyển Register A, X, Y về chế độ 8 bit
$00/85CD A0 00 LDY #$00 ; tải giá trị 0 vào Y
$00/85CF C9 A0 CMP #$A0 ; so sánh A với A0 
$00/85D1 90 28 BCC $28 [$85FB] ; nếu A nhỏ hơn A0, nhảy tới 85FB
$00/85D3 A0 9B LDY #$9B ; tải giá trị 9B vào Y 
$00/85D5 C9 C8 CMP #$C8 ; so sánh A với C8 
$00/85D7 90 0C BCC $0C [$85E5] ; nếu A nhỏ hơn, nhảy tới 85E5
$00/85D9 A0 9C LDY #$9C ; tải giá trị 9C vào Y
$00/85DB E9 AE SBC #$AE ; trừ A cho AE 
$00/85DD C9 1F CMP #$1F ; so sánh kết quả cho 1F 
$00/85DF 90 1A BCC $1A [$85FB] ; nếu kết quả nhỏ hơn, nhảy tới 85FB
$00/85E1 E9 05 SBC #$05 ; trừ kết quả cho 05 
$00/85E3 B0 13 BCS $13 [$85F8] ; nếu kết quả >05, nhảy tới 85F8

$00/85F8 18 CLC reset carry về 0
$00/85F9 69 40 ADC #$40 A + 40
$00/85FB 28 PLP 
$00/85FC 6B RTL ; kết thúc routine

Giả sử, đặt

$85FB = label1
$85E5 = label2
$85F8 = label3

thì đoạn trên viết lại là

$00/85CA 08 PHP 
$00/85CB E2 30 SEP #$30 
$00/85CD A0 00 LDY #$00 
$00/85CF C9 A0 CMP #$A0 
$00/85D1 90 28 BCC label1
$00/85D3 A0 9B LDY #$9B 
$00/85D5 C9 C8 CMP #$C8 
$00/85D7 90 0C BCC label2
$00/85D9 A0 9C LDY #$9C 
$00/85DB E9 AE SBC #$AE 
$00/85DD C9 1F CMP #$1F 
$00/85DF 90 1A BCC label1
$00/85E1 E9 05 SBC #$05 
$00/85E3 B0 13 BCS label3 


Label 1:
PLP
RTL

Đoạn này chỉ có chức năng kết thúc routine, không làm gì hơn.

Label 2: đoạn hiển thị dấu dakuten trên đầu chữ.

Label 3:

CLC
ADC #$40
PLP
RTL

Đoạn này cộng giá trị của A với $40 và kết thúc routine.
Nhìn vào table, lấy giá trị của một chữ Hiragana bất kỳ và cộng với 40, ta sẽ được giá trị của chữ Katakana tương ứng.
Vd: 
0A=こ (ko, Hiragana)
4A=コ (ko, Katakana)

Vậy đoạn code này có tác dụng chuyển đổi chữ Hiragana thành Katakana.

Vậy, toàn bộ routine trên có thể diễn dịch như sau:

- Tải 00 vào Y (không dấu gì cả)
- So sánh A với A0 (từ A0 trở về sau là chữ có dấu ゛ hoặc ゜)
- Nếu A nhỏ hơn A0, nhảy tới label 1 (kết thúc routine, chữ vẫn không có dấu)
- Tải 9B vào Y (9B=゛, tức giờ chữ đã có dấu ゛)
- So sánh A với C8 (từ C8 về sau là chữ mang dấu ゜)
- Nếu A nhỏ hơn C8, nhảy tới routine 2 (mang dấu ゛)
- A lớn hơn hoặc bằng C8, tải Y với 9C (9C=゜, tức giờ đã có dấu ゜)
- Lấy A trừ AE 
- So sánh với 1F
- Nếu kết quả nhở hơn 1F, nhảy tới label1: kết thúc routine
- Trừ tiếp kết quả cho 05
- Nếu kết quả lớn hơn 05, nhảy tới label 3: biến Hiragana thành Katakana, giờ đã mang dấu ゜ và kết thúc routine.

Lấy ví dụ:
CD=パ, khi A đọc tới giá trị này thì đầu tiên nó:

- Vẽ chữ ハ mà không có dấu gì (Y = 00)
- So sánh A (CD) với A0
- Vì CD > A0 nên tiếp tục: vẽ thêm dấu ゛ (Y = 9B) vào, thành ra バ
- So sánh A với C8
- Vì CD > C8 nên tiếp tục: vẽ dấu ゜ (Y = 9C), thành ra パ
- Lấy A trừ cho AE: CD-AE=1F
- So sánh kết quả với 1F
- Vì kết quả không nhỏ hơn 1F nên tiếp tục: trừ kết quả này cho 05: 1F-05=1A (1A=は)
- Nhảy tới label 3: cộng A với 40: 1A + 40 = 5A = ハ, lúc này vẫn còn mang dấu ゜ (Y=9C) nên kết quả thể hiện là パ.

VII. Viết routine bỏ dấu tiếng Việt

Sau khi phân tích routine bỏ dấu nguyên bản của game ở phần trước, ta thấy game dùng register A để đọc giá trị của ký tự, và register Y để đọc giá trị của con dấu. Ở đây ta áp dụng cùng nguyên tắc này để viết cơ chế bỏ thêm các dấu cho nguyên âm. Có nhiều cách bỏ dấu. Dưới đây là 2 ví dụ.

Cách 1:



org $85CA ;bắt đầu viết code tại $85CA

jml $201234 ;nhảy tới vị trí trống
org $201234 ;bắt đầu viết code tại vị trí mới
php ;như cũ
sep #$30 ;như cũ
ldy #$00 ;như cũ, tải "không dấu" vào Y
cmp #$90 ;so sánh A với $90 (giá trị của nguyên âm 1 có dấu 1, chẳng hạn "á")
beq label_sắc ; nếu A = $90, nhảy tới label bỏ dấu sắc
cmp #$91 ;so sánh A với $91 (giá trị của nguyên âm 1 có dấu 2, chẳng hạn "à")
beq label_huyền ; nếu A = $91, nhảy tới label bỏ dấu huyền
cmp #$92 ;so sánh A với $92 (giá trị của nguyên âm 1 có dấu 3, chẳng hạn "ả")
beq label_hỏi ; nếu A = $92, nhảy tới label bỏ dấu hỏi
cmp #$93 ;so sánh A với $93 (giá trị của nguyên âm 1 có dấu 4, chẳng hạn "ã")
beq label_ngã ; nếu A = $93, nhảy tới label bỏ dấu ngã
cmp #$94 ;so sánh A với $94 (giá trị của nguyên âm 2 có dấu 1, chẳng hạn "é")
beq label_sắc ; nếu A = $94, nhảy tới label bỏ dấu sắc
cmp #$95 ;so sánh A với $95 (giá trị của nguyên âm 2 có dấu 2, chẳng hạn "è")

........
plp ;như nguyên bản
rtl ; kết thúc routine

Tương tự, viết hết các trường hợp kết hợp các nguyên âm và các dấu tiếng Việt. Cuối đoạn code, đừng quên định nghĩa các label.



label_sắc:
ldy #$70 ; tải giá trị $70 vào Y. Ở đây $70 là giá trị của tile có dấu sắc
jml $0085e5 ; nhảy tới vị trí thực hiện lệnh bỏ dấu lên đầu nguyên âm như nguyên bản

label_huyền:
ldy #$71 ; tải giá trị $71 vào Y. Ở đây $71 là giá trị của tile có dấu huyền
jml $0085e5 ; nhảy tới vị trí thực hiện lệnh bỏ dấu lên đầu nguyên âm như nguyên bản

label_hỏi:
ldy #$72 ; tải giá trị $72 vào Y. Ở đây $72 là giá trị của tile có dấu hỏi
jml $0085e5 ; nhảy tới vị trí thực hiện lệnh bỏ dấu lên đầu nguyên âm như nguyên bản

label_ngã:
ldy #$73 ; tải giá trị $73 vào Y. Ở đây $73 là giá trị của tile có dấu ngã
jml $0085e5 ; nhảy tới vị trí thực hiện lệnh bỏ dấu lên đầu nguyên âm như nguyên bản

Với cách này, bình thường game sẽ bỏ dấu "không có gì" (không dấu, Y=00) vào các giá trị của A khác với 90, 91, 92,.... Trong đó, ta gán 90=á, 91=à, 92=ả,...
Còn nếu giá trị của A=90, thì game sẽ bỏ dấu sắc (ở đây gán Y=71) lên đầu chữ cái đứng trước, và tương tự với các dấu còn lại. CMP (CoMPare A) là lệnh so sánh giá trị của A với một giá trị khác. BEQ (Branch if EQual) là lệnh phân nhánh nếu giá trị so sánh trước đó = 0.

Lưu ý: cách này sử dụng nhiều câu lệnh BEQ, trong khi lệnh phân nhánh chỉ có hiệu lực trong khoảng 256 byte kể từ vị trí lệnh. Tức game chỉ cho phân nhánh (nhảy tới vị trí) trong phạm vi 256 byte, nếu code dài quá khoảng này thì sẽ báo lỗi.


Cách 2:


org $85CA ;bắt đầu viết code tại $85CA
jml $201234 ;nhảy tới vị trí trống
org $201234 ;bắt đầu viết code tại vị trí mới
PHP ; như cũ, lưu giá trị của register P vào stack
pha ;lưu giá trị của A vào stack
SEP #$30 ;như cũ
tax ;chuyển giá trị của A cho X
lda table,x ;tải giá trị tại địa chỉ của table + giá trị của X vào A
tay ;chuyển giá trị của A cho Y
pla ;lấy lại giá trị của A lúc nãy cất giữ trong stack
plp ;lấy lại giá trị của P lúc nãy cất giữ trong stack
rtl ; kết thúc routine

table:
db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $28
db $00, $00, $00, $00, $00, $28, $29, $2a, $2b, $00, $32, $00, $2a, $28, $29, $2a
db $2b, $2c, $2d, $2e, $2f, $30, $2c, $40, $41, $42, $43, $44, $40, $28, $29, $2a
db $2b, $2c, $2d, $2e, $2f, $30, $2c, $28, $29, $2a, $2b, $28, $29, $2a, $2b, $2c
db $2d, $2e, $2f, $30, $2c, $28, $29, $00, $00, $00, $00, $00, $00, $2b, $28, $29
db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $2a, $2b, $00, $00, $00

db là lệnh chèn byte. Trong label mang tên "table" bên trên, ta chèn một loạt byte có giá trị lần lượt: 00, 00, 00,....2B, 2C, 2D,....

Giải thích: 
1. TAX chuyển giá trị của register A sang register X. Khi A đọc đến ký tự nào thì nó chuyển giá trị của ký tự đó cho X.
2. LDA table,x tải giá trị tại địa chỉ của label mang tên "table", cộng với giá trị của register X vào trong A. 
3. TAY chuyển giá trị mới của A vào Y.

Như vậy, giả sử ban đầu A=03, thì PHA lưu giữ giá trị này vào vùng nhớ gọi là stack.
Tiếp theo, nếu TAX khiến X lúc này có giá trị = 03.
Tiếp đó, LDA table,X tải giá trị tại địa chỉ tương đối của label "table" + giá trị của X. Chẳng hạn, nếu table nằm tại địa chỉ $206789 thì LDA table,X sẽ tải giá trị tại địa chỉ $206789 + $03= $20678C. Tại đây có giá trị 00 nên A lúc này sẽ mang giá trị 00 (nhìn vào bảng table). 
Kế đến, TAY chuyển giá trị 00 cho Y. Lúc này Y mang giá trị 00, tức không dấu.
PLA lấy lại giá trị 03 đã lưu trữ trước đó cho A.
Như vậy, đoạn code này cho thấy nếu A=03 (giá trị một ký tự nào đó) thì Y=00 và sẽ không có dấu nào được vẽ lên trên ký tự đó.

Giả sử A=4F, thì PHA lưu giá trị này (4F) vào vùng nhớ gọi là stack.
Tiếp theo, nếu TAX khiến X lúc này có giá trị = 4F.
Tiếp đó, LDA table,X tải giá trị tại địa chỉ tương đối của label "table" + giá trị của X. Chẳng hạn, nếu table nằm tại địa chỉ $206789 thì LDA table,X sẽ tải giá trị tại địa chỉ $206789 + $4F= $2067D8. Tại đây có giá trị 00 nên A lúc này sẽ mang giá trị 2A (nhìn vào bảng table).
Kế đến, TAY chuyển giá trị 2A cho Y. Lúc này Y mang giá trị 2A, tức có dấu (dấu gì thì tùy ta vẽ tại tile 2A).
PLA lấy lại giá trị 4F đã lưu trữ trước đó cho A. 

Như vậy, đoạn code này cho thấy nếu A=4f (giá trị một ký tự nào đó) thì Y=2A và sẽ vẽ lên trên ký tự đó con dấu có giá trị 2A.

Kết quả bỏ dấu cho hình như dưới đây.

[​IMG]

Còn đây là hình ảnh sau khi chỉnh lại trật tự câu chữ.

[​IMG]


VIII. Kết

Sau khi đã sửa font chữ và giải quyết vấn đề dấu tiếng Việt, ta có thể ung dung đến phần dịch thuật số text đã dump trước đó. 

[​IMG]

Tới đây coi như đã giải quyết xong 70% số việc cần làm. Vì sao chỉ mới có 70%? Vì trong quá trình dịch sẽ phát sinh rất nhiều vấn đề cần giải quyết, chẳng hạn như giới hạn không gian trống, chữ lệch hàng, mất phần hiển thị dấu,...

[​IMG]

Những vấn đề này không thể giải quyết bằng các thủ thuật thông thường, mà cần phải dùng đến kiến thức về Assembly. Một trong những vấn đề thường gặp nhất chính là không gian trống. Phần text hội thoại thường nằm trong một không gian hạn hẹp giữa code game như sau:

<code 1><text hội thoại><code 2>

Vì tiếng Nhật có tính cô đọng hơn các ngôn ngữ khác trong cả ngữ nghĩa lẫn các biểu thị ra con chữ, cùng một âm tiết thì tiếng Nhật chỉ dùng một chữ cái, còn các ngôn ngữ khác dùng nhiều chữ cái.
Chẳng hạn: âm "ka" (2 ký tự) được biểu thị bằng 1 ký tự か trong tiếng Nhật. Vì lý do này mà các bản dịch thường dài hơn bản gốc. Nếu phần text dịch dài hơn thì sẽ ghi đè lên phần code khác trong game, khiến hư hỏng Rom. Để giải quyết việc này thì phải can thiệp vào phần code điều khiển hội thoại.
Thử phân tích một ví dụ với khối text thể hiện tên giải đấu. Khối này bắt đầu tại $255FC cho pointer và $25628 cho phần text. Khối text này nằm trong không gian chỉ có vài trăm byte nên sẽ không đủ cho phần dịch thuật. Lúc này ta cần chuyển text ra vùng không gian trống khác, chẳng hạn vùng trống sau khi mở rộng Rom bằng công cụ Lunar Expand.

Giống như ở phần bỏ dấu, cách giải quyết vấn đề nằm ở kết quả debug. Chỉ có thông qua debug ta mới biết chính xác CPU xử lý cái gì, ra sao và từ đó mới đề ra được giải pháp.
Đầu tiên, đổi các địa chỉ trên sang địa chỉ Snes. $255FC = $04:D5FC, $25628 = $04:D628.
Đặt một read break point tại $04:D5FC, chơi đến khi vào đến màn hình hiển thị trên trận đấu. Lúc này CPU sẽ ngừng xử lý khi đến địa chỉ ta chỉ định, click vào "Step Into" để bám sát các lệnh CPU sẽ thực hiện.

[​IMG]

$02/E8DD B1 19       LDA ($19),y[$04:D5FC]   A:D500 X:004A Y:0000 P:envmXdIZc

$02/E8DF 85 19       STA $19    [$00:1819]   A:D628 X:004A Y:0000 P:eNvmXdIzc
$02/E8E1 A0 00       LDY #$00                A:D628 X:004A Y:0000 P:eNvmXdIzc
$02/E8E3 E2 20       SEP #$20                A:D628 X:004A Y:0000 P:envmXdIZc
$02/E8E5 B1 19       LDA ($19),y[$04:D628]   A:D628 X:004A Y:0000 P:envMXdIZc
$02/E8E7 C9 FC       CMP #$FC                A:D600 X:004A Y:0000 P:envMXdIZc
$02/E8E9 F0 10       BEQ $10    [$E8FB]      A:D600 X:004A Y:0000 P:envMXdIzc
Từ kết quả này thì hiểu được:

1. Game đọc pointer 2 byte từ vị trí $D5FC trong bank 04
2. Chứa giá trị của pointer đó vào $1819 trong Ram ($19 + giá trị của DP Register lúc này là 1800)
3. Reset Register Y về 0
4. Set Register A về chế độ 8 bit, để A chỉ đọc được 1 byte
5. Đọc giá trị tại $1819 + giá trị của Y, tức $D628 trong bank 04 vào trong A --> hiển thị chuỗi text tại $04D628 ra màn hình
6. So sánh A với FC (mã kết thúc chuỗi text)
7. Nếu A = FC thì nhảy tới $E8FB, xử lý kết thúc câu
8. Các xử lý khác


Có rất nhiều cách giải quyết vấn đề từ kết quả này. Một trong những cách đơn giản là biến dòng LDA ($19),y vốn đang đọc dữ liệu tại bank 04 sang đọc tại bank khác.
Giả dụ, ta muốn viết đoạn text này vào $105628 thay vì $25628 như nguyên bản. $105628 tức là $5628 tại bank 20 ($205628). Có thể viết đơn giản như sau:




org $02e8e5  ; bắt đầu ghi code tại $02E8E5
    jml $20e8e5      ; chuyển khối code tại $02E8E5 sang $20E8E5
  
org $20e8e5  ; bắt đầu ghi code tại $20E8E5
    phb         ; lưu giữ bank hiện tại (04)
    lda #$20   ; tải $20 vào A
    pha        ; lưu giữ A hiện tại ($20)
    plb        ; kéo giá trị của A ($20) vào bank
    lda ($19),y ; đọc giá trị tại bank (20) + $1819 + Y
    plb  ;  lấy lại giá trị bank cũ (04)
    CMP #$FC   ; so sánh A với $FC
    BEQ label   ; nếu bằng $FC, nhảy tới label
    INY         ; tăng Y lên 1
    PHY        ; lưu giữ Y
    jml $02E8ED   ; về địa chỉ trong khối code cũ

label:
    jml $02E8FB

Ngoài ra còn nhiều cách khác để giải quyết cùng một vấn đề. Song, tất cả đều dùng đến kiến thức Asm. Vì vậy, việc học hỏi về Assembly của một hệ CPU là điều cần thiết và rất quan trọng trong việc hack/dịch game hệ đó, nếu muốn làm một cách bài bản. Tất nhiên, trong nhiều trường hợp thì các thủ thuật dịch game thông thường như lập table, dump text, chèn text,... đều có thể thực hiện mà không cần đến kiến thức Asm. Nhưng để giải quyết được hết thảy mọi vấn đề nảy sinh, một cách triệt để, thì cần phải dùng đến Asm.

0 bình luận :

Post a Comment

 
Top