Zelda Densetsu SFC
1. Khái quát
Zelda no Densetsu - kamigami no toraifōsu (ゼルダの伝説 神々のトライフォース ), được tạm chuyển ngữ sang Việt văn là "truyền thuyết Zelda-tam bảo của chư thần", là phiên bản thứ 3 trong series Zelda no Densetsu do Nintendō phát hành. Phiên bản này dành cho máy Super Nintendō, xuất hiện vào cuối năm 1991 ở Nhật, đúng 1 năm sau khi hệ máy này ra đời. Bản dịch tiếng Anh chính thức được phát hành cho các hệ máy SNES Mỹ, SNES Âu châu ở các thị trường này xuất hiện sau đó vài tháng.
Tựa đề của bản tiếng Anh chính thức khác biệt so với bản gốc tiếng Nhật, cụ thể là "The Legend of Zelda: A Link to the Past". Ngoài tựa đề ra thì các phiên bản tiếng Anh cũng còn nhiều chỗ khác biệt nho nhỏ khác so với bản tiếng Nhật như lời thoại, hình ảnh xuất hiện trong game. Đây là kết quả của quá trình kiểm duyệt để tránh những vấn đề nhạy cảm như tôn giáo ở xã hội Âu Mỹ.
Về lối chơi, phiên bản này quay về với gốc rễ, bám sát với phiên bản Zelda đầu tiên năm 1986 trên máy Famicom (NES) sau khi phiên bản thứ hai năm 1987 cũng trên máy Famicom. Ở phiên bản SFC này xuất hiện nhiều Item mà sau đó đã trở thành những món đồ cố định trong series, như thanh kiếm "Master-sword", lọ thủy tinh,... Phiên bản này cũng là một hình mẫu ảnh hưởng đến nhiều dòng game sau này, chẳng hạn như Dark Souls. Bạn có thể thấy nhiều yếu tố mà Dark Souls học tập ở phiên bản Zelda này, như cách bố trí Item trong màn, chiếc cầu vô hình,...
2. Bản dịch Việt văn
Về mặt dịch thuật, bản dịch này dựa trên nền bản gốc tiếng Nhật, cố gắng bám sát tinh thần của bản gốc hết mức có thể. Sẽ có nhiều chỗ dị biệt nếu đối chiếu với bản Anh ngữ, bởi vì bản Anh ngữ vốn đã có những điểm dị biệt đó so với bản tiếng Nhật. Chẳng hạn như tựa đề của bản gốc mang nghĩa là "Triforce của các vị thần", trong đó "Triforce" là 3 món bảo vật hoàng kim của 3 vị thần sáng tạo nên thế giới trong series Zelda, tượng trưng cho trí tuệ, sức mạnh và dũng khí. Bản dịch Việt ngữ này dựa trên tinh thần của bản tiếng Nhật, có tựa đề là "Tam bảo của chư thần", hoàn toàn khác biệt với "A Link to the Past".
Về mặt kỹ thuật, bản dịch này cũng dựa trên Rom tiếng Nhật được dump trực tiếp từ băng cassette Super Famicom, ứng dụng kỹ thuật lập trình Assembly 65816 ở mức cao độ để phục vụ cho việc dịch thuật ở mức tối đa. Chẳng hạn như khung thoại được mở rộng, thay đổi màu khung thoại, dùng nhiều kiểu font chữ,... Bản dịch này còn có một số tính năng mà vốn đã bị ẩn đi ở phiên bản gốc, một số tính năng vốn không có trong phiên bản gốc. Đây cũng là kết quả của việc ứng dụng kỹ thuật Assembly 65816 trong quá trình dịch thuật.
Lưu ý: đây là bản dịch tiếng Việt, hay nói cách khác là bản chuyển ngữ Việt văn, bản Việt ngữ hóa. Đây hoàn toàn không phải là bản Việt hóa. Dịch sang tiếng Việt (Việt ngữ hóa) và "Việt hóa" là hai khái niệm hoàn toàn khác nhau, không nên nhầm lẫn.
Bản dịch Việt ngữ này hoàn toàn tương thích với phần cứng Super Famicom/SNES cũng như các loại giả lập mô phỏng cấu trúc của phần cứng này, chẳng hạn như ZSNES, SNES9X, BSNES, HIGAN, MESEN,.... Trong trường hợp dùng giả lập thì người dịch khuyến cáo nên dùng các loại giả lập được bôi đậm. Bản dịch cũng chạy tốt trên các loại phần cứng nhái của Super Famicom/SNES như SupaBoy của Hyperkin hay Super-NT của Analogue.
Hình ảnh bản dịch trên máy nhái Super NT với Tivi màn hình phẳng hiện đại
Hình ảnh bản dịch trên giả lập Mesen
Dung lượng bản gốc tiếng Nhật là 8 triệu bit, còn ở bản dịch tiếng Việt này là 16 triệu bit. Sở dĩ bản dịch có dung lượng lớn hơn là bởi vì nó được viết thêm khoảng 15 nghìn dòng code cải tiến về mặt kỹ thuật so với bản gốc, như mô tả dưới đây.
Cải thiện tốc độ (bản gốc: Slow Rom, bản dịch: Fast Rom)
Khung thoại được mở rộng để viết được nhiều chữ hơn.
Khung thoại có màu nền bán trong suốt, thay đổi ngẫu nhiên để cho hiển thị chữ tốt nhất, dễ nhìn dễ thấy nhất. Ở bản gốc thì màu nền của khung thoại là khung rỗng không màu, đôi khi gây khó nhìn vì lẫn với hình ảnh của Background.
Có thêm một số event ẩn mà bản gốc không có.
Có thêm nhiều lời thoại mới nhằm mang lại cảm giác tự nhiên cho mạch hội thoại. Ở bản gốc, các nhân vật NPC chỉ nói một câu duy nhất khi được hỏi, lặp đi lặp lại vô tận...như một cái máy. Còn ở bản dịch này, các NPC có thể nói nhiều câu khác nhau khi được hỏi, tăng thêm độ tự nhiên cho mạch thoại.
Bật chức năng Debug vốn có sẵn trong Rom, nhưng đã bị nhà sản xuất vô hiệu hóa trong bản gốc. Để sử dụng chức năng này thì chỉ cần nhấn đồng thời nút L + A, nhấn L + A lần nữa để giải trừ. Trong khi đang bật chức năng Debug mà nhấn R + A thì sẽ kích hoạt chức năng chạy 1 frame.
Có thể chọn nhanh Item ở màn hình chính mà không cần phải nhấn Start để truy cập vào Menu như ở bản gốc. Ở bản dịch này, chỉ cần nhấn L hoặc R là được.
Có thể Reset nhanh bằng cách nhấn L + R + Start mà không cần nhấn nút Reset trên phần cứng.
Nhấn nút bí mật vào thời điểm bí mật để kích hoạt chức năng bí mật, nhận Item bí mật,... Nút bí mật/thời điểm bí mật là gì thì tôi không mô tả ở đây nhưng chỉ cần nhìn mã nguồn kèm theo bản dịch là biết.
Toàn bộ chữ hiển thị trong bản dịch đều là proportional font, tức font chữ có độ rộng biến thiên. Trong khi ở bản gốc thì hầu hết text đều được hiển thị ở kiểu mono-spaced, tức font chữ có độ rộng cố định.
4. Quá trình dịch thuật
Phần này ghi lại những điểm kỹ thuật trong quá trình dịch thuật. Ai quan tâm có thể tham khảo.
A) Các bước để hoàn tất một bản dịch
Dump Rom
Debug để xác định font chữ, câu thoại và mọi thứ khác liên quan
Viết thêm code mới phục vụ cho việc hiển thị chữ, chèn code này vào Rom bằng xkas
Dump text gốc
Dịch text đã dump
Chèn text đã dịch vào Rom bằng Atlas
Trừ bước đầu và bước cuối có thứ tự không đổi thì các bước còn lại có thể xáo trộn trật tự, có thể bỏ qua tùy tình huống.
B) Những thứ cần có
Băng game: dùng để dump (trích xuất) Rom. Về mặt pháp lý, việc dump Rom từ băng vật lý để chơi bằng trình giả lập (Emulator) là việc hoàn toàn hợp pháp. Do vậy, đây là thứ bắt buộc phải có.
Rom dumper: có thể dùng máy dump Rom hoặc băng cassette có chức năng dump, chẳng hạn như Super UFO Pro 8. Băng cassette này có nhược điểm là chỉ đọc được LoRom và HiRom, không đọc được băng ExLoRom và ExHiRom. Nói cách khác, Super UFO Pro 8 chỉ đọc được băng game có dung lượng từ 32Mb (4MB) trở xuống. Băng Zelda này có dung lượng 8Mb (1MB) nên hoàn toàn tương thích với Super UFO Pro 8. Đây là thứ bắt buộc phải có, nếu không có thì ta buộc phải tự tạo một công cụ khác có chức năng tương đương.
Giả lập Super Famicom/SNES có chức năng debug. Ở đây tôi quen dùng Mesen-S (bản cũ của Mesen). Đây là thứ bắt buộc phải có, nếu không có thì ta buộc phải tự viết một công cụ khác có chức năng tương đương.
Một Hex-editor: dùng bất kỳ loại gì cũng được. Ở đây tôi quen dùng Stirling. Hex-editor không đóng vai trò quan trọng lắm, chỉ là để thuận tiện cho một số thao tác lặt vặt sẽ xuất hiện.
Phần mềm disassembler: dùng để "giải mã" dữ liệu số trong game thành những câu lệnh assembly. Tuy nhiên, do đặc thù của SNES CPU là dùng song song 2 chế độ là 8bit và 16bit nên người sử dụng phải hiểu rõ thời điểm chuyển đổi 2 chế độ này để có kết quả tốt nhất. Ngoài ra, phần mềm cũng không phân biệt đâu là code, đâu là dữ liệu. Người dùng phải tự hiểu rõ phần này để có kết quả tốt nhất. Có khá nhiều phần mềm disassembler cho hệ máy này, nhưng tôi quen dùng Dispel. Phần mềm này không bắt buộc phải có, nhưng nếu dùng nó thì bạn sẽ hình dung được chức năng của một đoạn code dễ dàng hơn. Có thể dùng luôn chức năng debug của giả lập để thay thế phần mềm này.
Xkas: phần mềm assembler của tác giả byuu (David Kirk Ginder, 1983~2021) giúp chuyển đổi mã assembly thành mã máy. Có khá nhiều phiên bản, ở đây tôi quen dùng xkas plus. Đây là thứ bắt buộc phải có, nếu không có thì ta buộc phải tự viết một công cụ khác có chức năng tương đương.
Cartographer: phần mềm command-line của tác giả RedComet, cho phép dump dữ liệu từ Rom ra dạng text. Khá thuận tiện cho việc dịch thuật nhưng không bắt buộc phải có, nếu như bạn hiểu rõ về cấu trúc cũng như từng câu thoại trong game.
Atlas: phần mềm command-line của tác giả Klarth, cho phép chèn text đã dịch và Rom. Đây là thứ bắt buộc phải có, nếu không có thì ta buộc phải tự viết một công cụ khác có chức năng tương đương.
Phần mềm chỉnh sửa hình ảnh (các dạng ảnh PNG, BMP): có khá nhiều loại có chức năng này. Nhưng cần lưu ý là chọn loại có khả năng lưu ảnh mà không phá vỡ cấu trúc pixel của nó. Tôi quên dùng Pixelformer của hãng Qualibyte Software. Những phần mềm làm thay đổi cấu trúc pixel như Microsoft Paint hay Photoshop mà không có plugin thì đều không dùng được. Phần mềm này không bắt buộc nếu như bạn hiểu rõ cách vẽ pixel.
Phần mềm chỉnh sửa dữ liệu hình ảnh của Super Famicom/SNES. Loại này hoàn toàn khác với cái vừa nêu bên trên. Phần mềm ở trên là loại dùng để xem/chỉnh sửa các định dạng ảnh trên máy tính, còn phần mềm ở đây là để xem/chỉnh sửa định dạng ảnh của máy Super Famicom/SNES. Có khá nhiều loại, ở đây tôi quen dùng YY-CHR. Phần mềm này không bắt buộc phải có nếu như bạn hiểu rõ về cách vẽ pixel, có thể tạo ra được hình ảnh chỉ từ những con số.
Phần mềm chuyển đổi dạng ảnh của máy tính (PNG, BMP) sang định dạng ảnh của Super Famicom/SNES. Có khá nhiều loại, nhưng tôi quen dùng SuperFamiconv, một phần mềm command-line có nhiều tính năng. Phần mềm này không bắt buộc phải có nếu như bạn hiểu rõ về cách vẽ pixel, có thể tạo ra được hình ảnh chỉ từ những con số.
Text editor: một trình soạn thảo văn bản dạng plain text, là công cụ chính yếu để ta viết code và dịch thuật. Có thể dùng Notepad có sẵn trong Windows cũng được. Ở đây tôi quen dùng Notepad++. Những phần mềm như Microsoft Word hoàn toàn không thể dùng được. Đây là thứ bắt buộc phải có, nếu không có thì ta buộc phải tự viết một công cụ khác có chức năng tương đương.
Phần mềm Lunar Address của tác giả FuSoYa’s Niche để tiện chuyển đổi giữa địa chỉ SNES và địa chỉ trên máy tính. Nếu bạn đã hiểu rõ về địa chỉ SNES thì có thể bỏ qua phần mềm này.
Phần mềm Lunar Expand của tác giả FuSoYa’s Niche để mở rộng dung lượng ROM. Nếu bản dịch không phình to hơn kích thước bản gốc thì có thể bỏ qua phần mềm này. Hoặc nếu bạn hiểu rõ cấu trúc của một Rom SNES thì có thể mở rộng dung lượng bằng cách dùng Hex-editor, không nhất thiết phải dùng Lunar Expand.
Kiến thức về bộ câu lệnh của CPU 65816, chức năng của từng câu lệnh. Có thể tìm hiểu qua Google. Tài liệu về mảng này không hiếm.
Kiến thức về phần cứng máy Super Famicom/SNES như địa chỉ SNES, khái niệm HiRom/LoRom, khái niệm DMA/HDMA, các loại Ram của SNES, chức năng của các Register, NMI/IRQ, Vblank, Force-blank,.... Có thể tìm hiểu qua Google. Tài liệu về mảng này không hiếm.
C) Quy trình
Đầu tiên là bước dump nội dung game từ băng cassette. Ở đây tôi dùng băng Super UFO Pro 8 để dump. Tháo nắp phía trên của Super UFO Pro 8 ra, ta sẽ thấy khe cắm băng. Cắm trực tiếp băng Zelda vào đó. Sau đó, cắm cả cụm này vào máy Super Famicom rồi khởi động máy. Trong menu BACKUP của Super UFO Pro 8, chọn mục GAME CART TO SDC. Chức năng của mục này là copy nội dung của băng game (Game cartridge) vào trong thẻ nhớ SD gắn trong Super UFO Pro 8.
Tại mục này, sau khi đặt tên game (tối đa 8 ký tự) thì file sẽ được lưu vào thẻ SD sau một khoảng thời gian tải. Lúc này ta có thể tháo thẻ SD ra khỏi băng Super UFO Pro 8 để cắm vào máy tính, kiểm tra xem trình giả lập có thể chạy được file vừa dump không. Nếu không chạy được thì quá trình dump đã gặp lỗi, cần lặp lại bước trên. Dung lượng đúng của Rom gốc là 1MB (1024KB).
Sau khi mọi thứ đã hoàn tất thì ta có thể debug Rom bằng chính debugger hoặc dùng phần mềm disassembler để có cái nhìn tổng thể về cấu trúc Rom. Nhưng để debug hay giải mã bằng disassembler thì đầu tiên ta cần biết địa chỉ của một số vector cơ bản như Reset, NMI, IRQ. Tất cả những thông tin này đều nằm ở phần header của Rom. Mọi Rom Super Famicom/SNES đều chứa những thông tin này. Ngay khi cắm băng vào máy rồi khởi động, việc đầu tiên máy Super Famicom/SNES làm là tìm kiếm những thông tin này trong băng. Do vậy, những thông tin khai báo trên buộc phải ở vị trí cố định.
Phần header của Rom SNES bắt đầu ở địa chị $00FFB0. Do Zelda là LoRom nên địa chỉ này tương ứng với $007FB0 khi xem bằng Hex-editor trên máy tính. Có thể dùng Lunar Address để chuyển đổi địa chỉ SNES $00FFB0 thành địa chỉ PC $007FB0. Phần header này chứa thông tin về mã game, mã nhà sản xuất, kiểu Rom là LoRom hay HiRom, có dùng chip đặc thù hay không, dung lượng Rom là bao nhiêu, có dùng pin không, có Sram hay không, mã vùng sản xuất là quốc gia nào, tên game là gì,... Tên game là chuỗi ký tự dài 0x15 byte (21 ký tự) bắt đầu tại địa chỉ $00FFC0, tức $007FC0 địa chỉ PC đối với LoRom. Đây là tên mà nhà sản xuất đăng ký với Nintendō chứ không nhất thiết là tên thương mại hay tựa đề xuất hiện ở màn hình game.
Sau phần thông tin này là đến thông tin khai báo địa chỉ của các vector. Ta cần quan tâm nhất là vector Reset và NMI. Vector NMI luôn nằm ở địa chỉ $00FFEA và $00FFFA, còn vector Reset ở địa chỉ $00FFEC và $00FFFC.
Khi đã biết được vector Reset chỉ về địa chỉ $008000 thì ta có thể đặt breakpoint trong debugger hoặc disassembler để nắm được máy sẽ chạy những lệnh gì khi khởi động.
Toàn bộ cấu trúc của Zelda như dưới đây. Phần màu xanh là các thủ tục định dạng Ram ban đầu, upload driver âm thanh. Phần màu hồng là vòng lặp chính của game.
80/8000: 78 SEI
80/8001: 9C0042 STZ $4200
80/8004: 9C0C42 STZ $420C
80/8007: 9C0B42 STZ $420B
80/800A: 9C4021 STZ $2140
80/800D: 9C4121 STZ $2141
80/8010: 9C4221 STZ $2142
80/8013: 9C4321 STZ $2143
80/8016: A980 LDA #$80
80/8018: 8D0021 STA $2100
80/801B: 18 CLC
80/801C: FB XCE
80/801D: C228 REP #$28
80/801F: A90000 LDA #$0000
80/8022: 5B TCD
80/8023: A9FF01 LDA #$01FF
80/8026: 1B TCS
80/8027: E230 SEP #$30
80/8029: 200189 JSR $8901
80/802C: 20C087 JSR $87C0
80/802F: A981 LDA #$81
80/8031: 8D0042 STA $4200
80/8034: A512 LDA $12
80/8036: F0FC BEQ $8034
80/8038: 58 CLI
80/8039: 8016 BRA $8051
80/803B: A5F6 LDA $F6
80/803D: 2920 AND #$20
80/803F: F003 BEQ $8044
80/8041: EED70F INC $0FD7
80/8044: A5F6 LDA $F6
80/8046: 2910 AND #$10
80/8048: D007 BNE $8051
80/804A: ADD70F LDA $0FD7
80/804D: 2901 AND #$01
80/804F: D009 BNE $805A
80/8051: E61A INC $1A
80/8053: 201E84 JSR $841E
80/8056: 22B58000 JSR $0080B5
80/805A: 20FC85 JSR $85FC
80/805D: 6412 STZ $12
80/805F: 80D3 BRA $8034
80/80B5: A410 LDY $10
80/80B7: B96180 LDA $8061,Y
80/80BA: 8503 STA $03
80/80BC: B97D80 LDA $807D,Y
80/80BF: 8504 STA $04
80/80C1: B99980 LDA $8099,Y
80/80C4: 8505 STA $05
80/80C6: DC0300 JMP [$0003]
Mặc dù đều cùng là sản phẩm của Nintendō nhưng cấu trúc của Zelda đơn giản hơn rất nhiều so với các phiên bản Fire Emblem sau này. Trong mỗi lần lặp, CPU đều xử lý routine $0080B5. Các phân đoạn trong game đều được xử lý ở routine này. Các phân đoạn này được quyết định bởi giá trị của địa chỉ Ram $10. Chẳng hạn, nếu giá trị của $10 là 0x00 thì game sẽ hiện màn hình đầu tiên (Nintendo presents), nếu giá trị là 0x01 thì là màn hình chọn nhân vật, giá trị 0x19 tương đương với cảnh căn phòng Triforce, giá trị 0x1A là cảnh ending,... Mỗi phân cảnh gồm những chuỗi sự kiện, hình ảnh, âm thanh nối tiếp nhau. Game đang xử lý tới "vị trí" nào trong phân cảnh đó là tùy thuộc vào giá trị của $11.
Lợi dụng giá trị của $10 và $11, ta có thể dễ dàng tìm hiểu xem từng phân cảnh được tạo ra như thế nào bằng cách đặt Read break point tại các địa chỉ này. Sau đó ta có thể chỉnh sửa phân cảnh đó tùy ý, bao gồm cả việc thay đổi hình ảnh, font chữ, lời thoại xuất hiện trong phân cảnh đó.
Chẳng hạn như để chuyển màn hình tựa đề game từ tiếng Nhật sang tiếng Việt, ta vẽ lại toàn bộ những thứ xuất hiện trên màn hình rồi chèn vào Rom. Cảnh này dùng chế độ hiển thị mode 1, gồm 2 thành phần:
Lớp Background 1: dòng chữ "Super Famicom" và tựa đề game "ゼルダの伝説". Lớp background này dùng 8 pallet, mỗi pallet 16 màu.
Lớp sprite: biểu tượng Triforce, dòng chữ "©1991 Nintendo" và phụ đề "神々のトライフォース". Những thành phần sprite này dùng 8 pallet, mỗi pallet 16 màu.
Việc cần làm ở đây là vẽ lại tileset và bố trí lại tilemap của lớp Background 1, cũng như vẽ lại lớp sprite nếu cần thiết. Sau khi vẽ lại thì chuyển dạng ảnh này thành dạng dữ liệu đồ họa của SNES rồi chèn vào Rom. Nhưng để làm được việc đó thì ta cần phải biết vị trí của tileset và tilemap trong Vram. Bằng cách dùng công cụ Tile Viewer của Mesen debugger (Bsnes cũng có công cụ tương tự), ta dễ dàng xác định được khối tileset tựa đề game nằm ở địa chỉ Vram $6000.
Đặt Write break point tại địa chỉ này trong Vram, ta xác định được thời điểm mà khối hình ảnh này được chuyển vào Vram là ngay sau khi dòng chữ "Nintendo presents" ở màn hình đầu tiên kết thúc, qua routine $00E310. Tại đây, các thành phần đồ họa của Background và sprite đều được lần lượt chuyển vào Vram qua cổng $002118 mà không thông qua kỹ thuật DMA nên tốc độ chuyển không được nhanh. Ta có thể thay thế bằng routine dưới đây. Trong đó zelda_chr là dữ liệu đồ họa của màn hình tựa đề mới mà ta đã vẽ bằng Pixelformer rồi chuyển đổi bằng SuperFamiconv.
PHP
SEP #$20
REP #$10
LDA.b #(zelda_chr>>16)
STA $4304
LDA #$80
STA $2115
LDX #$1801
STX $4300
LDX #(zelda_chr)
STX $4302
LDX #$18C0
STX $4305
LDX #$6000>>1
STX $2116
LDA #$01
STA $420B
LDX #$0000
_loop:
LDA zelda_map,x
STA $0B00,x
INX
CPX #$0700
BNE _loop
LDX #$0000
_loop2:
LDA zelda_map+1,x
CLC
ADC #$39
STA $0B01,x
INX #2
CPX #$0700
BNE _loop2
PLP
Phần _loop và _loop2 là chức năng copy tilemap của mành hình mới được chuyển đổi bằng SuperFamiconv. Tilemap được copy vào địa chỉ Ram $7E0B00 hoặc bất kỳ địa chỉ trống nào đủ 0x700 byte. Về lý thuyết, màn hình mode1 có kích thước 256x256 pixel, tương đương với tilemap 0x800 byte nhưng đặc thù của máy Super Famicom/SNES hệ NTSC là Tivi chỉ hiển thị đến scanline thứ 224 là hết, do vậy chiều cao chỉ là 224 pixel nên tilemap chỉ cần 0x700 byte là đủ. Bước copy tilemap mới vào địa chỉ Ram trống là để phục vụ cho bước DMA tilemap này vào Vram sau này.
Để xác định được vị trí của Background 1 tilemap trong Vram thì ta dùng công cụ Tilemap Viwer của debugger. Tilemap của Background 1 trong phân cảnh này bắt đầu tại $2000. Bằng cách đặt Write break point tại địa chỉ này thì ta biết được Tilemap được chuyển vào Vram ở địa chỉ $008BE2: JSR $92A1. Routine $92A1 được thực hiện trong khoảng thời gian Vblank, và là routine chung để cập nhật tilemap của Background 1 trong suốt game. Do vậy ở đây ta cần đặt điều kiện để routine mới chỉ được thực hiện trong phân cảnh tựa đề game để tránh ảnh hưởng tới những phân cảnh khác.
org $008BE2
JML freeram
freeram:
LDA $10
BNE +
PHP
SEP #$20
REP #$10
LDX #$1801
STX $4300
LDX #$0700
STX $4305
LDX #$0B00
STX $4302
LDX #$2000>>1
STX $2116
LDA #$7E
STA $4304
LDA #$80
STA $2115
LDA #$01
STA $420B
PLP
JML $008BE5
+
JSR _0092a1
JML $008BE5
Đoạn code trên DMA 0x700 byte tilemap ở địa chỉ $7E0B00 vào $2000 trong Vram chỉ khi game đang ở mode 0, tức màn hình đầu tiên. Sau đoạn code này thì ta được kết quả như dưới đây.
Xong màn hình mở đầu, tiếp theo là phần chính: text trong phần thoại chính của game. Game Super Famicom/SNES có 2 kiểu hiển thị text chính là render trong thời gian thực, và pre-render (render sẵn). Kiểu hiển thị pre-render có ưu điểm là không cần xử lý nhiều, và thường là chỉ render tilemap dựa trên tileset là bộ font chữ được chuyển vào Vram trước đó. Kiểu render này đã xuất hiện từ thời Famicom, thường được áp dụng với game không có nhiều text, hay game có bộ font chữ đơn giản. Còn nhược điểm của nó là không thể hiện được bộ font nhiều chữ, hay có chữ phức tạp, và không thể hiện được chữ có độ rộng biến thiên (proportional font). Còn kiểu render trong thời gian thực chỉ mới nở rộ từ thời Super Famicom, thường được dùng trong những game nhiều chữ như dòng Fire Emblem hay Final Fantasy. Kiểu render trong thời gian thực có ưu điểm là có thể vẽ chữ với độ phức tạp cao, do đó thể hiện được kiểu chữ có độ rộng biến thiên, ít tốn không gian Vram (vì nó không chứa sẵn bộ font trong Vram, mà chứa dữ liệu font trong Rom) nhưng lại có nhược điểm là đòi hỏi xử lý, tính toán khá nhiều.
Zelda 3 là game dùng kiểu render trong thời gian thực, bởi 3 bộ chữ được dùng trong game này là Hiragana, Katakana và Kanji nếu tính sơ sơ cũng vài trăm chữ, không thể chứa hết trong 64KB Vram được. Ngoài ra thì Vram còn chứa nhiều dữ liệu hình ảnh khác, không chỉ mỗi font chữ. Cho nên việc Zelda 3 sử dụng kiểu render trong thời gian thực là điều dễ hiểu. Tuy nhiên có một điểm đáng tiếc là font chữ trong game không phải kiểu chữ có độ rộng biến thiên. Chữ có độ rộng cố định chữ phù hợp với các ngôn ngữ có chữ vuông như Hàn, Nhật, Tàu mà thôi. Còn trong văn bản của các thứ tiếng dùng mẫu tự La Tinh thì đây là một điều cực kỳ khó chịu khi đọc. Thử tưởng tượng bạn sẽ cảm thấy như thế nào nếu chữ "i" có cùng độ rộng với chữ "w"? Do vậy ta cần phải viết thêm ít code để hiển thị kiểu chữ có độ rộng biến thiên.
Video bên dưới đây minh họa cho khái niệm render chữ trong thời gian thực.
Muốn viết thêm code để biến font chữ có độ rộng cố định thành font chữ có độ rộng biến thiên thì đầu tiên cần phải tìm được routine xử lý text. Bằng công cụ Tile Viwer và Tilemap Viewer của Mesen, ta xác định được đoạn thoại có tileset (phần hình ảnh của font chữ) được ghi vào địa chỉ Vram $F800, còn Tilemap (cách bố trí các mảnh tileset để tạo thành hình ảnh có nghĩa, cụ thể ở đây là tạo thành con chữ) được ghi vào Vram $C4CC.
Đặt write-breakpoint tại địa chỉ Vram $C4CC thì ta tìm được routine ghi dữ liệu tilemap vào Vram như dưới đây.
Đây cũng là routine được dùng khá nhiều để chuyển một số thành phần đồ họa vào Vram trong kỳ Nmi, trong đó có dữ liệu tilemap của phần text và phần khung thoại. Chức năng của routine này là ghi dữ liệu tại địa chỉ $7E1000 của Workram vào Vram. Cú pháp của routine này gồm: 16 bit địa chỉ Vram cần ghi, theo sau là tổng số byte cần ghi vào Vram - 1 byte, tiếp theo là đoạn dữ liệu cần ghi, kết thúc routine bằng $FFFF.
Hình ảnh bên trên là một đoạn dữ liệu được ghi vào Vram trong kỳ Nmi thông qua routine mà ta đã xác định bên trên. $6266 là địa chỉ Vram cần ghi ($C4CC), $0027 là kích thước của dữ liệu cần ghi (0x28 byte), theo sau đó là 0x28 byte dữ liệu cần được ghi vào Vram: 0x8039, 0x8139,.... Hết chuỗi này lại là địa chỉ Vram cần ghi: $6286, sau đó lại là 0x28 byte chỉ kích thước, nối tiếp là dữ liệu cần ghi: 0x9439, 0x9539,.... Chu trình này lặp lại cho tới khi đụng 2 byte 0xFFFF ở $7E1192.
Như vậy, muốn ghi bất kỳ dữ liệu nào vào Vram, ghi vào bất cứ vị trí nào trên màn hình thì chỉ cần ghi giá trị tương ứng vào địa chỉ $7E1002 trở đi.
Tương tự, khi đặt wite-breakpoint tại địa chỉ $F800 trong Vram thì ta tìm được routine ghi tileset của bộ font vào Vram. Routine ở địa chỉ $008CE4.
REP #$10
LDA #$80
STA $2115
LDX #$1801
STX $4300
LDY #$7C00
STY $2116
LDY #$0000
STY $4302
LDA #$7F
STA $4304
LDX #$0780
STX $4305
LDA #$01
STA $420B
SEP #$10
STZ $0710
RTS
Tương tự routine ghi dữ liệu tilemap, routine ghi tileset này cũng chỉ được thực hiện trong kỳ Nmi. Tuy nhiên, có điểm khác biệt là nếu routine trước ghi từng byte dữ liệu vào Vram (cách thức từ thời Famicom) thì ở routine này, một nhóm nhiều byte (cụ thể ở đây là 0x780 byte) được chuyển vào Vram với tốc độ cao thông qua kênh DMA #0. Dữ liệu được ghi từ địa chỉ Workram $7F0000. Thử dump CPU memory rồi xem bằng phần mềm YY-CHR thì có thể thấy hình ảnh của font chữ ở địa chỉ $7F0000.
Như vậy, ta cần truy lùng tiếp dữ liệu được ghi vào $7F0000 từ đâu, với cách thức như thế nào. Bằng cách đặt tiếp write-breakpoint ở $7F0000 thì sẽ tìm được routine dưới đây khi nhân vật nói chuyện.
Đây là routine chính để đọc các byte đại diện cho từng ký tự trong chuỗi text, sau đó liên kết từng byte đó với dữ liệu đồ họa của từng chữ cái (font) trong Rom, thực hiện các bước tính toán để ghi hình ảnh của từng chữ cái vào vùng Ram $7E0000, sau đó DMA cả khối dữ liệu hình ảnh này vào địa chỉ Vram $F800 trong giai đoạn Nmi. $1CD9 là địa chỉ chứa giá trị index trong chuỗi hội thoại. Chuỗi này bắt đầu ở địa chỉ Workram $7F1200, kết thúc bằng byte 0xFF. Như vậy, bằng cách thay đổi giá trị các byte trong vùng Workram từ $7F1200 trở đi, quan sát thay đổi tương ứng trên màn hình thì ta có thể lập được table thể hiện giá trị byte nào tương ứng với ký tự nào.
Như trên thì thấy mỗi ký tự trong game này chiếm 2 byte, chẳng hạn như 0x0000 tương đương với あ, còn 0x0001 tương đương với い. Tuy nhiên, thông thường thì đại đa số game trong thời Super Famicom đều dùng 1 byte để biểu diễn cho 1 ký tự Kana, còn đến khi biển diễn Kanji, do số lượng của chúng vượt quá phạm vi biểu diễn của 1 byte (0~255) nên người ta mới dùng tới kiểu 2 byte để biểu diễn. Còn ở đây lại dùng 2 byte để biểu diễn 1 ký tự Kana, thật là kỳ lạ.
Nếu tiếp tục đặt write-breakpoint ở $7F1200 để xác định những giá trị này được ghi vào vùng Ram này từ đâu, như thế nào thì ta sẽ thấy giá trị ID của mỗi chữ Kana trong Rom chỉ chiếm 1 byte mà thôi. Chỉ đến khi được ghi vào vùng Workram $7F1200 thì mới trở thành 2 byte. Mục đích của việc này là để đơn giản hóa quá trình đọc text, bởi vì các control code như code xuống dòng, code đổi màu chữ, code đổi tốc độ ghi chữ, code hiệu ứng âm thanh,... đều chiếm 2 byte, nếu code ký tự chiếm 1 byte thì việc xử lý sẽ phức tạp hơn. Vì vậy Nintendō đã biến tất cả code ký tự Kana từ 1 byte trong Rom thành 2 byte trong Ram cho đồng bộ với control code.
Sau khi đã xác định được table thì ta có thể xem vị trí của text trong Rom một cách trực quang với phần mềm Windhex, hoặc cũng có thể dump text với phần mềm Cartographer.
Cũng bằng cách đặt write-breakpoint tại địa chỉ Workram $7F1200, ta có thể xác định được text được ghi từ Rom vào Workram qua pointer ở $04.
Qua routine trên thì thấy được pointer chỉ đến một đoạn text xuất phát từ $04, mà giá trị tại đây lại xuất phát từ địa chỉ Ram $7E1CF0. Mọi đoạn hội thoại trong game đều xuất phát từ đây. Nếu muốn hiển thị đoạn hội thoại đầu tiên lên màn hình thì chỉ cần ghi giá trị 0x0000 vào địa chỉ Ram này, còn ghi giá trị 0x0001 để hiển thị đoạn thoại thứ hai.
Tuy nhiên, qua đoạn code trên thì cũng thấy được là pointer chỉ đến địa chỉ của text là 16 bit, nghĩa là toàn bộ lời thoại trong game phải được gói gọn trong một bank. Zelda 3 là game có kiểu mapping Lorom, tức một bank có dung lượng 32KB, trong khoảng $8000 đến $FFFF. Dung lượng 32KB là quá ít ỏi đối với một bản dịch tiếng Việt, bởi với cùng một câu thoại thì số lượng ký tự trong tiếng Việt thường nhiều hơn tiếng Nhật, chiếm nhiều byte hơn. Do vậy, với kiểu pointer 16 bit này thì không đủ để chứa hết lời thoại của bản dịch nên ở đây cần mở rộng kích thước pointer lên 24 bit, để có thể chỉ đến text ở bất kỳ vị trí nào trong Rom.
REP #$30
LDA $1CF0
PHA
ASL
CLC
ADC $01,s
TAX
PLA
LDA pointer_table,x
STA $04
INX
SEP #$20
LDA pointer_table,x
STA $06
Chỉ với đoạn code đơn giản trên là ta đã đổi pointer hội thoại thành 2 byte sang 3 byte. Dĩ nhiên cần phải thay đổi cả dữ liệu pointer và dữ liệu text trong Rom. Có thể dễ dàng làm việc này với phần mềm chèn text có hỗ trợ pointer như Atlas.
Tiếp theo là đến phần chính của vấn đề, làm thế nào để biến font có độ rộng cố định thành font chữ có độ rộng biến thiên? Tất cả đều phụ thuộc vào quá trình xử lý font mà ta sẽ viết sau đây.
1) Tạo một bộ font chữ cái La Tinh có dấu tiếng Việt. Có thể dùng YY-CHR hoặc các phần mềm xử lý ảnh dạng pixel, sau đó convert thành định dạng 2bpp. Bởi vì text trong Zelda 3 được hiển thị ở mode 1, trong đó lớp Background 3 được dùng để hiển thị text có độ sâu màu 2bpp, tức mỗi tile có thể hiển thị tối đa 4 màu.
2) Tại routine đọc ký tự text ở vùng Workram $7F1200, đọc từng giá trị tại chuỗi này. Nếu giá trị là control code thì không cần can thiệp, nếu giá trị là ký tự Kana thì xử lý tiếp các bước đánh số bên dưới.
REP #$30
LDX $1CD9
LDA $7F1200,x
CMP #$FF00
BEQ +
SEP #$30
LDA #$0C
STA $012F
REP #$30
+
LDA $1CDD
ASL
TAX
SEP #$20
LDA $1CDC //tile pro
STA $1301,x
STA $1303,x
STA $1305,x
STA $1329,x
STA $132B,x
STA $132D,x
JSR write_text_pos
3) Render ký tự
Đây là phần phức tạp nhất của toàn bộ quá trình, gồm các bước như dưới đây.
a. Đầu tiên là liên kết từ giá trị ID của mỗi ký tự đến phần tileset tương ứng của ký tự đó trong bảng font. Cách tạo liên kết phụ thuộc vào cách mà ta sắp xếp dữ liệu font trong Rom. Có khá nhiều cách xếp, nhưng phổ biến nhất là 2 cách xếp như dưới đây.
Ở hình trên, dữ liệu của chữ "A" được sắp xếp mặc định theo chế độ hiển thị của Super Famicom, trong khi những ký tự còn lại được sếp theo kiểu của máy Famicom. Với bất kỳ kiểu xếp dữ liệu nào thì cũng cần phải chọn kiểu xem dữ liệu tương ứng với nó trong YY-CHR để cho kết quả hiển thị đúng. Tương tự, cách xếp dữ liệu cũng ảnh hưởng tới cách mà ta vẽ chữ (render) ở bước sau, cũng như cách xếp tilemap. Chẳng hạn, bên trên là bộ font có kích thước 16 x 16 pixel cho mỗi ký tự, đủ để ta vẽ dấu tiếng Việt. Nếu coi phần bên trên của chữ "A" có địa chỉ tilemap là $00 thì đối với kiểu xếp mặc định, tile nửa bên dưới sẽ có địa chỉ là $10, còn tile thứ hai, bên trên của chữ "A" có địa chỉ là $01. Còn nếu xếp theo kiểu Famicom 16 thì tile nửa bên dưới có địa chỉ là $01, còn tile thứ hai bên trên có địa chỉ là $02,... Có thể vẽ tùy ý bằng YY-CHR rồi quan sát sự thay đổi của quỹ đạo byte bằng Hex editor để nắm quy luật.
Dưới đây là một ví dụ về cách liên kết từ giá trị ID của mỗi ký tự đến tileset của ký tự đó trong trường hợp xếp dữ liệu tileset theo kiểu Famicom 16.
REP #$20
LDA $7F1200,x
XBA
AND #$00FF
CMP #$00E8
BNE +
xử lý abcxyz...
+
ASL #6
TAX
LDY #$0000
SEP #$20
-
LDA font0,x
XBA
LDA font0+0x20,x
JSR shift_pix
ORA {char_buffer}+0x20,y
STA {char_buffer}+0x20,y
XBA
ORA {char_buffer},y
STA {char_buffer},y
LDA font0+0x10,x
XBA
LDA font0+0x30,x
JSR shift_pix
ORA {char_buffer}+0x30,y
STA {char_buffer}+0x30,y
XBA
ORA {char_buffer}+0x10,y
STA {char_buffer}+0x10,y
INY
INX
DEC {buffer1}
BNE -
PLB
REP #$20
LDA #$BFC0
STA $04
LDA #$007E
STA $06
RTS
Như vậy, chỉ cần shift giá trị của ký tự sang trái 6 lần, hay nói cách khác là nhân đôi giá trị đó 6 lần thì sẽ tìm được địa chỉ bắt đầu của phần tileset tương ứng. Tại sao lại như vậy? Qua hình minh họa về cách xếp dữ liệu ở trên, ta thấy tile liền kề bên phải một tile bất kỳ có giá trị = giá trị của tile đó + 2. Trong khi đó, do bộ font có độ sâu màu là 2bpp nên mỗi tile có dung lượng là 0x10 byte. Cụ thể:
Mỗi tile có diện tích 8 x 8 pixel = 0x40 pixel
Ở chế độ 2bpp, mỗi pixel chiếm 2 bit nên mỗi tile có dung lượng 2 x 40 = 0x80 pixel
Trong khi đó, nhóm 8 bit gọp thành 1 byte nên mỗi tile có dung lượng: 80/8 = 0x010 byte
Tuy nhiên, bộ font của ta ở đây là 16 x 16 pixel, tức gồm 4 mảnh tile 8 x 8 ghép lại với nhau, nên ký tự liền sau đó cách 0x40 byte so với ký tự trước đó.
Như vậy, giả dụ bộ font bắt đầu tại địa chỉ $0A8000 trong Rom, và chữ cái khởi đầu là chữ "A" thì chữ "A" có địa chỉ ở $0A8000, còn chữ "B" liền kề sau đó có địa chỉ $0A8080. Nếu coi "A" có ID là 00, thì "B" liền sau đó sẽ có ID là 02 thì địa chỉ của B là: 02 x 2 x 2 x 2 x 2 x 2 x 2 = $80. Dĩ nhiên $80 chỉ mới là giá trị Index/ giá trị tương đối để dẫn đến địa chỉ tuyệt đối của hình ảnh của "B". Để tính được địa chỉ tuyệt đối của "B" thì cần cộng với địa chỉ bắt đầu của bộ font: $80 + $0A8000 = $0A8080.
b. Tiếp theo, sau khi đã tạo được liên kết từ giá trị ID của mỗi ký tự tới tileset tương ứng của nó, việc cần làm là biến hình ảnh tileset trong Rom từ dạng mỗi tile mỗi chữ thành dạng mỗi tile có thể chứa nhiều chữ. Đây là mục đích chính của toàn bộ quá trình, nhằm tạo ra bộ font chữ có độ rộng biến thiên tùy vào từng ký tự.
Bên trái là hình ảnh tileset của bộ chữ được chứa trong Rom, mỗi ký tự là một tile. Nếu không xử lý gì và để nguyên như thế này mà ghi vào Vram thì từ nào có bao nhiêu ký tự thì sẽ tốn bấy nhiêu tile, không đẹp về mặt thẩm mỹ và cũng không hiệu quả về mặt không gian. Còn bên phải là hình ảnh bộ chữ sau khi được xử lý, một tile có thể chứa nhiều ký tự, mỗi ký tự chỉ chiếm một khoảng không gian vừa đủ với độ rộng của nó.
Để làm được việc này thì ta cần đến phép shift bit về bên phải. Nói đại khái thì sau khi đọc dữ liệu hình ảnh của ký tự, ta sẽ đẩy các bit của dữ liệu đó về bên phải với số lần tương ứng với độ rộng của ký tự đứng trước đó. Chẳng hạn, đối với từ "file" ta có 4 ký tự gồm "f", "i", "l" và "e". Giả định "f" có độ rộng là 5 pixel, "i" có độ rộng 1 pixel, "l" có độ rộng 2 pixel và "e" có độ rộng là 4 pixel. Một tile luôn có độ rộng không đổi là 8 pixel. Do vậy, khi ghi ký tự đầu tiên là "f" trong chuỗi này thì ta shift (đẩy) hình ảnh của "f" về phía phải 0 pixel, tức là không cần shift. Do nó chỉ chiếm 5 pixel trong tile, mà tile có 8 pixel nên vẫn còn lại 3 pixel trống về phía phải. Ký tự tiếp theo là "i" chỉ chiếm 1 pixel nên hoàn toàn chứa vừa trong phần còn lại của tile thứ nhất. Lúc này, khi ghi hình ảnh của "i" thì phải đẩy nó về phía phải 5 pixel để nó đứng liền sau hình ảnh của ký tự "f" trong tile. Các ký tự còn lại trong chuỗi cũng được xử lý tương tự.
LDA font0,x
XBA
LDA font0+0x20,x
JSR shift_pix
Điểm lưu ý: đọc 1 byte của tile thứ nhất và 1 byte của tile liền kề sau đó trước khi shift. Mỗi lần shift sang phải, giá trị của byte giảm đi một nửa.
shift_pix:
REP #$20
CMP #$0000
BEQ end_shift_pix
PHX
LDX {shift}
CPX #$0000
BEQ end_shift_pix-1
-
LSR
DEX
BNE -
PLX
end_shift_pix:
SEP #$20
RTS
Routine đẩy bit về bên phải đơn giản như bên trên. Khi giá trị shift là 00 thì thoát khỏi routine ngay lập tức. Hình bên dưới thể hiện mối tương quan giữa vị trí của pixel với giá trị byte của nó.
c. Sau khi đã shift pixel về bên phải, việc cần làm tiếp theo là ghi giá trị đó vào một vùng Ram đệm A có kích thước 0x40 byte, đủ để chứa 4 tile của 1 ký tự. Tuy nhiên, cần thực hiện phép luận lý ORA với vùng Ram đệm đó trước khi ghi vào nó, để chồng lớp hình ảnh của ký tự ta vừa shift vào lớp hình ảnh của ký tự trước đó. Việc lồng ghép này lợi dụng tính chất của phép ORA: 0 ORA 1 = 1, 1 ORA 1 = 1, 0 ORA = 0. Các biến số bên dưới: {char_buffer} là vùng Ram đệm, dùng để chứa hình ảnh của nửa bên trái của ký tự, còn {char_buffer}+0x30 là vùng đệm chứa nửa bên phải của ký tự.
-
LDA font0,x
XBA
LDA font0+0x20,x
JSR shift_pix
ORA {char_buffer}+0x20,y
STA {char_buffer}+0x20,y
XBA
ORA {char_buffer},y
STA {char_buffer},y
LDA font0+0x10,x
XBA
LDA font0+0x30,x
JSR shift_pix
ORA {char_buffer}+0x30,y
STA {char_buffer}+0x30,y
XBA
ORA {char_buffer}+0x10,y
STA {char_buffer}+0x10,y
INY
INX
DEC {buffer1}
BNE -
4) Vẽ phần vừa render vào vùng Ram đệm khác (vùng đệm B), nơi mà ta sẽ DMA toàn bộ hình ảnh vào Vram. Vùng đệm này phải có kích thước đủ lớn để chứa đủ tất cả các tile chữ trong hộp thoại. Phần này chỉ đơn thuần là copy lại toàn bộ 0x40 byte của vùng đệm A vào vùng đệm B mà không phải tính toán hay xử lý gì khác. Ở phần code bên dưới, $04 là địa chỉ vùng đệm A, còn $7F8000 là vùng đệm B.
-
LDA [$04],y
ORA $7F8000,x
STA $7F8000,x
INY #2
INX #2
CPY #$0040
BNE -
5) Tạo liên kết từ giá trị ID của ký tự đến giá trị độ rộng của ký tự đó. Cách thức không khác với cách tạo liên kết đến tileset của bộ font ở phần trên, nhưng thay vì dẫn đến địa chỉ hình ảnh của font trong Rom thì ở đây dẫn đến địa chỉ của table chứa độ rộng của các ký tự. Có thể chứa table này trong Rom hoặc Ram tùy ý.
LDX $1CD9
LDA $7F1200,x
XBA
AND #$00FF
TAX
LDA font0_width,x
AND #$00FF
STA {char_haba}
Biến số {char_haba} là nơi chứa độ rộng của ký tự. Sở dĩ cần phải tính độ rộng của mỗi ký tự vừa ghi vào vùng đệm là để tính số pixel còn thừa lại của tile.
6) Sau khi tính độ rộng của ký tự vừa render, ta cần tính số lần shift sang phải cho ký tự tiếp theo. Ý niệm ở đây rất đơn giản. Nếu ký tự vừa rồi, tính cả số pixel đã shift của nó không dùng hết 8 pixel thì vẫn giữ nguyên tile đó cho lần vẽ tiếp theo, và ở ký tự tiếp theo phải shift sang phải với số lần bằng với số pixel còn thừa lại trong tile. Còn nếu tổng độ rộng của ký tự vừa rồi và số lần shift của nó có độ rộng từ 8 pixel trở lên thì chuyển sang tile tiếp theo.
LDA {shift}
CLC
ADC {char_haba}
CMP #$0008
BCS +
AND #$0007
STA {shift}
RTS
+
CMP #$0010
BCS +
SEC
SBC #$0008
AND #$0007
STA {shift}
INC {tile_no}
RTS
7) Bước cuối cùng của quá trình render một ký tự là chuyển tileset của nó vào Vram. Cách tốt nhất để làm việc này là thực hiện một cách tự động trong thời gian V-blank. Sau khi tính toán xong mọi thứ cần thiết thì chỉ cần ghi địa chỉ Vram cần chuyển dữ liệu, kích thước dữ liệu, địa chỉ dữ liệu và byte kích hoạt chức năng tự động DMA trong kỳ Vram. Đoạn code dưới đây ghi các thông số cần thiết để DMA 0x800 byte hình ảnh các chữ cái ở $7F8000 vào địa chỉ Vram $F800.
REP #$20
LDA #$F800>>1
STA {vram_address}
LDA #$0800
STA {dma_size}
LDA #$8000
STA {dma_adress}
SEP #$20
LDA #$7F
STA {dma_bank}
INC {trigger}
Còn trong routine NMI, chỉ cần đặt một chức năng như dưới đây là có thể tự động DMA vào địa chỉ Vram đã chỉ định.
SEP #$20
LDA {trigger}
BNE +
RTS
+
REP #$20
LDA {vram_address}
STA $2116
LDA {dma_size}
STA $4305
LDA {dma_adress}
STA $4302
LDA #$1801
STA $4300
SEP #$20
LDA #$80
STA $2115
LDA {dma_bank}
STA $4304
LDA #$01
STA $420B
STZ {trigger}
RTS
Như vậy là kết thúc toàn bộ quá trình biến bộ font từ kiểu có độ rộng cố định thành kiểu có độ rộng biến thiên.
Theo engine hiển thị nguyên bản của game thì phần khung thoại trong suốt, không có màu khiến việc đọc chữ đôi khi gặp trở ngại do hình ảnh của cảnh nền chen lấn với phần chữ. Nếu ta đặt nền khung thoại có màu đục thì sẽ khắc phục được tình trạng trên, nhưng nếu nền khung thoại vừa có màu, mà vẫn giữ được độ trong suốt để thấy cảnh nền bên dưới thì sẽ ra sao?
Ý niệm đằng sau kỹ thuật tạo màu nền trong suốt cho khung thoại là sử dụng tính năng cộng trừ màu của máy Super Famicom kết hợp với chức năng IRQ. Ta tạo ra lớp màu cố định bằng cách ghi 3 giá trị màu R, G, B lần lượt vào register $2132 trong khi IRQ đang được kích hoạt. Tuy nhiên cũng có vài điểm cần lưu ý như dưới đây.
Đầu tiên, khi bật khung thoại thì cần thiết lập IRQ sao cho nó chỉ được kích hoạt khi tia laser của Tivi chạm đến scanline bằng với tung độ trên của khung thoại, và IRQ phải được giải trừ sau khi tia laser này chạm đến tung độ dưới của khung thoại. Trong quá trình IRQ được kích hoạt, nó sẽ quét toàn bộ khung thoại với thuật toán cộng trừ màu mà ta sẽ thiết lập để tạo ra vùng màu không vượt quá tung độ trên và tung độ dưới của khung thoại.
Thứ đến là thiết lập giới hạn trái, giới hạn phải của 2 cửa sổ qua các register $2126 ~ $2129, rồi thiết lập giá trị window mask cho register $2125 để vùng màu không bị lan ra khỏi hoành độ trái, hoành độ phải của khung thoại.
LDA {color1}
STA $2132
LDA {color2}
STA $2132
LDA {color3}
STA $2132
LDA #$20
STA $2130
LDA {color_math}
STA $2131
LDA {window1_hidari}
STA $2126
LDA {window1_migi}
STA $2127
LDA {window2_hidari}
STA $2128
LDA {window2_migi}
STA $2129
LDA #$C0
STA $2125
LDA #$04
STA $212B
RTS
Trên đây là toàn bộ phần khẩu quyết hướng dẫn tạo một bản dịch tiếng Việt cho Zelda 3 dành cho người đã có nền tảng về cấu trúc phần cứng của máy Super Famicom, cũng như nền tảng về ngôn ngữ phần mềm của nó.