Mặc dù hồi nhỏ (gần 30 năm trước, tính từ 2016) toàn chơi Tsubasa 1, 2 trên FC và không mấy khi chơi Tsubasa 3, nhưng tính ra bản này dễ nghịch hơn nên thử vặn vẹo tí. Trước hết xác định kiểu encode text của game. Đầu tiên thử tìm một đoạn text dễ kiếm, có đặc trưng không bị trùng lặp. Chà, có vẻ như đã tìm được một cụm text độc đáo rồi. Giờ mở Rom bằng Yy-chr, xem thử font có bị nén hay mã hóa không. Sau một hồi cuộn lên cuộn xuống cũng tìm được. May thật, bộ font lộ thiên. Để coi, căn cứ trên trật tự xếp chữ trong bộ font này thì cụm text kia là... 6-2-13-41 Thử dò tìm tương đối thì được một kết quả tại $3D265. Thử kiểm tra kết quả này. Bật hex editor, tại địa chỉ này là giá trị 06. Thử đổi thành 09. Và kiểm tra lại kết quả... Vậy có nghĩa là đã tìm đúng. Nếu 09=け thì chữ tiếp theo, こ sẽ là 0A. Áp dụng cái logic này để lập table cho cả bộ font. Thử so sánh khi có và không có table. Tạm thời nhiêu này đã...
Cái bản Tsubasa 3 này là cái bản có Story dài hơi nhất trong Series. Hơi tiếc là chưa có bản dịch tiếng Anh hoàn chỉnh.
Hình như game Subasa 2 và 3 là niềm cảm hứng để Takahashi Yoichi sáng tác tiếp Hậu Subasa: Carlos Santana từ game > vào tuyện,Coimbra - siêu tiền đạo > vào sân từ Hiệp 2 chung kết với Nhật,đội Nhật luôn không có đội hình mạnh nhất khi thi đấu giải Châu Á,Lighting Tiger của Kojiro > Raiju Shot
Sau khi lập được table, dễ dàng xác định được text nằm đâu trong Rom. Từ đó tiến hành dump (xuất) text từ Rom ra file văn bản để dịch thuật. Đại khái trông như này. Việc tiếp theo cần phải làm là sửa bộ font từ tiếng Nhật thành tiếng Việt. YY-char, Tile molester, Tile Crystal,... là một số ứng dụng giúp ta làm việc này. Trong ví dụ này tôi dùng Yy-char. Khoan đã nào, một vấn đề nảy sinh ở đây là kích thường của font chữ. Tsubasa 3 dùng font 8x8 pixel. Tức mỗi chữ cái nằm gọn trong cái hình vuông 8x8. Ta có thể vẽ từng chữ cái La Tinh trong ô vuông này được, nhưng không thể vẽ thêm dấu (nằm trên đầu chữ cái) tiếng Việt, như cái hình dưới đây. Chả lẽ bó tay....? Khoan, quan sát coi 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. 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. Kiếm chữ Kana nào có dấu trọc âm, đặt read setpoint địa chỉ chữ cái đó trong debugger. 2. 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ý đó. Đại khái kết quả như này 3. 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. Việc cần làm là thêm LDY #$(giá trị mong muốn) vào và để Assembler xử lý... Nhưng phải lưu ý một điều là khi thêm vào 12 giá trị khác (14 dấu tiếng Việt, đã có sẵn 2) thì đoạn lệnh phía sau sẽ bị ghi đè, làm hư Rom. Do đó phải chuyển khối lệnh bắt đầu ở $85CA đi chỗ khác. Kiếm chỗ trống nào đó, hoặc mở rộng Rom và sử dụng vùng trống mới. Thay vì JMP $85CA ở $FF26 thì đổi thành JML $địa chỉ mới. Có sự khác biệt giữa JMP và JML, cứ tra Google sẽ rõ. Tạm thời hôm nay tới đây.
Hôm nay tôi đi vào phân tích đoạn code hiển thị dấu dakuten và handakuten, để từ đó làm cơ sở viết code cho phần hiển thị dấu tiếng Việt. Dùng debugger, đặt read breakpoint cho địa chỉ của một chữ cái bất kỳ 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 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 $00/85CB E2 30 SEP #$30 $00/85CD A0 00 LDY #$00 $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à パ. Tạm thời hôm nay tới đây.
Trong bài này, ta sẽ thử viết đoạn code bỏ dấu tiếng Việt, dựa trên code bỏ dấu ゛ (trọc âm) và ゜ (bán trọc âm) có sẵn trong game. Đầu tiên, thử sửa đổi font chữ từ Kana tiếng Nhật sang chữ La Tinh của tiếng Việt. Rõ ràng, với kích thước 8x8 điểm ảnh thì dường như việc vẽ thêm dấu (sắc, huyền) là rất khó, hoặc không thể (hỏi, ngã, mũ sắc, mũ huyền...). Do vậy ta cần làm viết code để dấu (xem như những ký tự khác) hiện trên đầu nguyên âm. Kết quả khi load game sẽ như này. Dĩ nhiên lúc này, dù chữ đã chuyển từ Kana Nhật sang ký tự La Tinh, nhưng trật tự của câu cú thì vẫn còn là trật tự của tiếng Nhật nên ta thấy kết quả câu lộn xộn vô nghĩa như hình trên. Để ý thấy, chữ u đã thay thế vị trí của dấu ゛ (trọc âm) nên giờ nó xuất hiện ở dòng trên đầu chữ khác mỗi khi giá trị của nó được gọi ra. Giờ thử áp dụng routine đã gốc của game để nhân rộng từ 2 dấu thành nhiều dấu. Nguyên bản của game: dùng register A để đọc giá trị của ký tự, và register Y để đọc giá trị của con dấu. Có nhiều cách bỏ dấu. Dưới đây là 2 ví dụ. Cách 1: Mã: 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. Mã: 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: Mã: 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. Còn đây là hình ảnh sau khi chỉnh lại trật tự câu chữ. Tạm thời hôm nay tới đây.
Xin hỏi với đoạn mã asm của "Cách 2" (Ví dụ): - Các lệnh như "org" hay "db" mình không tìm được mã hex tương ứng của các lệnh này (ví dụ : JMP $.. là 4C). - "jml $201234" : địa chỉ $201234 sẽ ở đâu trong file rom ? Dùng Luna Address thì địa chỉ này thuộc Snes Ram. Vậy phần code asm bên dưới nếu chuyển thành mã hex trong file rom thì sẽ phải ghi vào vị trí nào trong file rom?
org $(địa chỉ) là lệnh nội bộ của Assembler Xkas. Bạn gõ Org $abcxyz thì Xkas sẽ bắt đầu viết tại địa chỉ $abcxyz. $201234 là địa chỉ Snes, mình chỉ lấy ví dụ thôi. Vì Tsubasa 3 là Lorom nên nó tương ứng với $1E47 trong Rom khi ta mở bằng Hex editor trên PC. db $(giá trị) cũng là lệnh nội bộ của Xkas. db $1A sẽ ghi byte $1A vào vị trí chỉ định. Vì đoạn code mới thêm vào dài hơn code cũ nên nó sẽ ghi đè lên những phần sau. Để tránh việc này thì bạn có thể ghi code mới vào bất kỳ khu vực trống nào. Hoặc chắc ăn hơn là dùng Lunar Expand mở rộng Rom rồi ghi vào phần mới mở.
Tham khảo trang này http://wiki.superfamicom.org/snes/show/65816+Reference JML là 5C với DC, tuỳ vào addressing mode của nó. JML nhảy bất kỳ chỗ nào trong Rom cũng được. Còn $4C như bạn nói là JMP, nó chỉ nhảy trong phạm vi 128 byte thôi.
Thanks bác asm65816 đã chỉ dẫn. Có đề nghị nhỏ là nếu được thì tên các skill để thành tiếng Anh. Để sau này nếu có người cần hack thêm (không phải hack dịch thuật) file dịch tiếng Việt này thì cũng dễ đối chiếu với các tài liệu hack bằng tiếng Anh hơn (tên skill, tên cầu thủ, v.v..).
Mình dịch từ Rom tiếng Nhật nên chả biết tiếng Anh gọi là gì. Sau khi kết thúc cũng sẽ công bố file nguồn, ai thích sửa thế nào thì sửa.
Lần này tôi hướng dẫn một cách xác định text khác mà không cần dùng đến thủ thuật tìm kiếm tương đối. Cách này áp dụng kiến thức về phần cứng của SNES để tìm. 1.Bật Debugger Geiger, chạy game đến một đoạn text ở đầu game cho dễ xác định, chẳng hạn như cảnh này. Hình ảnh hiển thị trên màn hình SNES là kết quả tổng hợp của nhiều lớp (layer) đồ họa, gọi là Background (bối cảnh) chồng chất lên nhau. Các loại giả lập hiện nay đều có chức năng bật, tắt từng BG. Trong Geiger, mặc định khi nhấn phím 1 thì giả lập sẽ bật/tắt BG 1, phím 2 để bật tắt BG 2,.... Kết quả sau khi nhấn thử một loạt phím số thì biết được: - Cảnh nền (sân vận động) trong ảnh trên nằm ở BG2. - Phần chữ (text) nằm ở BG3. - Phình hình ảnh bình luận viên là sprite, và nó nằm ở BG5. Còn nhiều thông tin khác nữa, nhưng ta chỉ quan tâm tới BG3 chứa text. Bây giờ tắt mỗi BG2 đi, để bối cảnh không còn hiển thị phần sân vận động. 2. Nhấn vào tab "Show Hex" trên debug console của Geiger để bật Hex editor nội bộ của giả lập này. Tiếp tục, chọn dòng "Viewing" là "Vram" và click vào "Dump" để trích xuất toàn bộ Vram của thời điểm hiện tại. Có thể bật phần dump này bằng Yy-chr và ta thấy được những thành phần đồ họa đang được hiển thị trên màn hình. 3. Nhấn vào nút "What's used" trên debug console của Geiger để biết các thành phần đồ họa nào đang được sử dụng trong cảnh hiện tại. Ta có các thông tin sau: Mã: V-blank NMI enabled H-DMA 0 [1] 0x038E3C->0x2126 inc continue 0x7E8E3C indirect addressing H-DMA 1 [1] 0x038E51->0x2128 inc continue 0x7E8E51 indirect addressing DMA 7 0 0x7E2200->0x2104 Num: 544 inc VRAM write address: 0x7f1e(nByte), Full Graphic: 0, Address inc: 1 BG0: VOffset:0, HOffset:0, W:32, H:64, TS:8, BA:0x1800, TA:0x5000 BG1: VOffset:0, HOffset:65310, W:64, H:32, TS:8, BA:0x3800, TA:0x6000 BG2: VOffset:0, HOffset:0, W:32, H:32, TS:8, BA:0x7c00, TA:0x7000 BG3: VOffset:0, HOffset:0, W:32, H:32, TS:8, BA:0x0000, TA:0x0000 Main screen (always on): BG1,BG2,OBJ, Sub-screen (always on): Window 1 (0, 0, 13, 00): BG0(O-OR),BG1(O-OR),OBJ(O-OR), Window 2 (0, 0): Fixed colour: 000000 Dòng BG2 chứa các thông số về tileset, tilemap hiển thị ở BG3. Trong đó: - TS: chính là tile size, kích thước của tile. TS:8 cho biết BG này sử dụng tile có kích thước 8x8 pixel, tức kích thước font chữ của chúng ta. - BA: chính là tilemap. BA:0x7c00 cho biết tile map của chúng ta bắt đầu tại địa chỉ $7C00 tại Vram (Video ram, phần RAM hiển thị hình ảnh của SNES). - TA: chính là tileset. TA:0x7000 cho biết tileset của chúng ta bắt đầu tại địa chỉ $7000 trong Vram. Giải thích ý nghĩa: - Một đặc điểm của địa chỉ VRam của SNES là nó gấp đôi so với địa chỉ hiển thị. Nếu TA bắt đầu tại $7000 thì có nghĩa là nó bắt đầu tại $7000 x 2 = $E000 trong Vram. BA bắt đầu tại $7C00 thì trong Vram, địa chỉ của nó là $7C00 x 2 = $F800. - TA (Tileset): hiểu nôm na là một bộ các tile. Còn nhớ khi ta mở Rom bằng YY-chr để xem font? Mỗi một chữ cái là một tile, và nguyên cả bộ font đó là một tileset. Vốn nó là một phần dữ liệu trong Rom và được tải vào trong Vram để hiển thị. Đây là tileset trong Rom, bắt đầu tại $34800. Còn đây là tileset khi được tải vào Vram. Xem bằng cách dùng Yy-chr để mở file dump của Vram lúc nãy. Ta thấy địa chỉ của tileset bắt đầu ở $E000. - BA (Tilemap) được hiểu nôm na là một bản đồ phân bố, bố trí các tile trên màn hình. Màn hình SNES thể hiện được 32 tile trong một dòng, mỗi tile có kích thước 8 pixel theo bề ngang. Do vậy, bề ngang của màn hình SNES có độ phân giải: 32 x 8 = 256 điểm ảnh. Khi chụp ảnh màn hình giả lập, bạn sẽ thấy kích thước to hơn 256 là vì giả lập có chức năng upscale. Nếu bỏ chức năng này đi thì hình ảnh hiển thị sẽ thu nhỏ về 256 pixel. - Mỗi một tile được thể hiện bằng một byte, đi kèm với một byte khác thể hiện tính chất của tile đó. Do vậy, một tile trong Vram chiếm 2 byte, và một dòng trên màn hình SNES là 64 byte. - Ta đã biết BA của cảnh hiện tại bắt đầu tại $F800 trong Vram. Điều này có nghĩa, các tile được dùng để vẽ khắp màn hình từ góc trên cùng bên trái, xuống dưới cùng bên phải màn hình. Vị trí góc trên bên trái bắt đầu tại $F800. Bắt đầu từ đây, ta thấy một chuỗi giá trị 0000000000000.... Thử thay đổi byte đầu tiên (tại $F800) thành 01, và ta được kết quả sau. Nhìn lên góc trên, bên trái, ta thấy xuất hiện chữ あ. Như vậy, chữ này có giá trị là 01. Tiếp tục thử các giá trị khác, bằng cách này ta xây dựng được table mà không cần phải dò tìm tương đối. Cách này cho kết quả chuẩn xác hơn, nhanh chóng hơn. Tạm thời tới đây thôi.
Trích text Spoiler: Mở Sau khi lập được table, dễ dàng xác định được text nằm đâu trong Rom. Từ đó tiến hành dump (xuất) text từ Rom ra file văn bản để dịch thuật. Mục đích của việc dump text gồm: 1. Kiểm soát code nằm lẫn trong khối text 2. Phục vụ cho quá trình chèn text sau khi dịch Ngoài ra nó còn có tác dụng phụ là giúp việc đọc được tốt hơn so với khi đọc bằng Hex editor. Để trích text, nếu có khả năng thì bạn nên tự code một phần mềm chuyên dụng cho việc này. Tuy nhiên cũng có khá nhiều phần mềm có sẵn phục vụ cho mục đích này, và trong số đó thì Cartographer là phần mềm tốt nhất, có thể dump text với nhiều kiểu khác nhau như dump trực tiếp khối text, hay gián tiếp qua pointer. Và tất cả mọi phần mềm dump text đều làm việc trên một nguyên tắc chung, là quy đổi mã code thập lục thành chữ cái tương ứng mà ta quy định trong table. Giả dụ, trong table đã lập ở bước trên, ta có 01=あ thì khi gặp phải hex code 01 trong đoạn text cho sẵn, phần mềm sẽ xuất ra ký tự あ. Cách sử dụng Cartographer được hướng dẫn cụ thể trong file readme đi kèm. Tạo một file.bat để chung với thư mục Cartographer.exe và Rom, có nội dung như sau: Mã: cartographer Tsubasa3.smc dumpscript.txt abc -s Trong đó .smc là tên Rom đối tượng, .txt là tên file lệnh điều khiển Cartographer, abc là file text xuất ra (mặc định là .txt), -s/-m là tham số cho phép gộp nhiều kết quả vào một file hay tách thành nhiều file. Đây là kết quả dump một đoạn. Tạm thời hôm nay tới đây.
V. Sửa font Spoiler: Mở 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. 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. 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".
Spoiler: Mở 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 đó. 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,... 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 = $045FC, $25628 = $04628. Đặt một read break point tại $045FC, 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. Mã: $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: Mã: 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.
Trong khi chờ đợi bản tiếng Việt hoàn chỉnh. Xin được giới thiệu thêm 1 số cách hack Tsubasa 3 (không liên quan tới mảng dịch thuật - do mình không có kiến thức về mảng này). Ai quen với hack rom Tsubasa 2 sẽ thấy có nhiều điểm giống ở đây. Một số công cụ : Debugger (Snes9x Debugger, Bsnes Debugger,..) ; Hex Editor (Stirling, WinHex, HexWorkshop...) ; Lunar Address (dùng chuyển đổi các value Ram - Rom - PC File) ; Notepad++ (dùng biên tập, tìm kiếm nội dung trong các file log phát sinh từ Debugger); Calculator (windows) : dùng tính toán hay chuyển đổi giá trị Decimal và HexaDecimal. 0. Mở đầu : một số bảng value Ram - Rom (nguồn Google, ...) : - Một số bảng value Ram - dùng cho hack bằng value Ram khi sử dụng cheat engine tích hợp trong các trình giả lập snes) : Truy cập : http://www.gamefaqs.com/snes/562887-captain-tsubasa-3/faqs/60709 - xem từ mục 6 ( Curiosidades, Tips, Trucos y Códigos Especiales - phần Code Game Genie). Một số ví dụ sử dụng : ở đây thay đổi các giá trị trong bộ nhớ Ram, là vùng nhớ tạm nên chỉ có giá trị tức thời, không khuyến khích giữ nguyên các giá trị đã thay đổi để tiếp tục chơi game, vì dễ làm lỗi hay crash game. VD1 : thay đổi các skill sút : Giả sử Schneider đang có bóng, hiện tại vào đầu game Schneider chỉ có Shoot và Fire Shoot. +Thay dòng Shoot thành Shoot skill : 7E04B6 XX. Với XX là value của các skill shoot, tra bảng skill shoot (bên dưới - web) , chọn 0E (hexadecimal - hex) : Neo Tiger Sử dụng Snex9x. Alt+E hoặc chọn Cheat > Game Geine PAR code : Enter Cheat Code : 7E04B60E > OK Sau đó Apply Cheat, quay trở lại game và thực hiện Shoot. Lúc này dòng Shoot được thay bằng Neo Tiger. Tiếp tục chọn dòng Shoot ở menu con để thực hiện Neo Tiger. +Thay dòng Fire Shoot (dòng shoot skill đầu tiên - dòng 2 của menu shoot) thành 1 skill shoot khác : 7E0471 XX (nhớ Delete và Clear dòng cheat cũ ở trên đi). Chọn XX = 16 (Neo Cyclone ) Truy cập vào Menu cheat : nhập Cheat : 7e047116 > OK. Apply Cheat. Quay trở lại game và chọn Shoot, dòng Fire Shot được thay bằng Neo Cyclone. Chọn để thực hiện. VD2: thay đổi skill lửa : Giả sử Schneider đang có bóng. Mặc định Schneider chỉ có lừa thường. + Thay dòng lừa (Dribble) thường bằng Dribble "skill" : tương tự như trên dùng địa chỉ Ram : 7E04B6 XX : với XX là value của các skill "lừa", tra bảng skill "lừa" , ví dụ chọn 02 : Breaking Dribble (húc). Add code : 7E04B602 - Apply Cheat. Quay trở lại game và cho Schnider đối đầu với cầu thủ đội bạn. + Thay dòng Dribble Skill thứ nhất (dòng thứ hai trong menu Dribble) : ở đây xuất hiện nhược điểm là Schnider không có Skill Dribble, chỉ có Dribble thường. Thay Schnider bằng cầu thủ khác có Skill Dribble. Ví dụ Carlos. Mặc định các Carlos có Bunshin Dribble (dùng phiên âm nguyên chữ do mình không biết tiếng Nhật). Thay Skill Dribble (Bunshin Dribble) bằng Skill khác bằng cách dùng địa chỉ Ram : 7E0471XX, chọn XX = 01 (Heel Lìft của Tsubasa). Add code : 7E047101 và Apply Cheat. Ta có : Trong cả 2 trường hợp đều sử dụng 2 địa chỉ Ram 7E04B6 và 7E0471. Nếu giữ nguyên các giá trị đã chỉnh sửa trong 2 địa chỉ Ram này mà chơi tiếp tục thì có thể dẫn đến lỗi game hoặc crash game. Để tránh các trường hợp này, đòi hỏi phải chỉnh sửa trực tiếp trên file rom game, để các giá trị đã chỉnh sửa được giữ nguyên không gây ảnh hưởng game. Một số cheat khác trên các địa chỉ Ram có thể xem tiếp ở web trên. (còn tiếp)