Tear Ring Saga

Trang phụ này viết về những điều chưa ai khác viết về Tear Ring Saga, game chiến thuật theo lượt được phát hành vào năm 2001 cho máy Sony PlayStation.

Một cách chính thức thì game này không thuộc dòng Fire Emblem của Nintendō (vốn không bao giờ có mặt trên các máy chơi game của Sony), nhưng nó lại là sản phẩm của Kaga Shōzō, người được gọi là cha đẻ của Fire Emblem. Nói vắn tắt thì ông này là người của hãng Intelligent Systems, hãng phát triển các phiên bản Fire Emblem cho Nintendō. Ông là người sáng tạo ra dòng Fire Emblem, từ bản đầu tiên trên máy Famicom tới bản cuối trên máy Super Famicom là Thracia 776. Sau đó Kaga nghỉ việc ở Intelligent Systems, thành lập công ty trách nhiệm hữu hạn Tirnanog vào tháng 8 năm 1999. Trong thời gian này, Tirnanog bắt đầu phát triển một game có lối chơi và thế giới quan giống loạt game Fire Emblem. Đến tháng 5 năm 2000, hãng phát hành là Ascii công bố tựa game này với tên là Emblem Saga

Trong các bài phỏng vấn trên tạp chí, Kaga ngầm ám chỉ rằng Emblem Saga chung vũ trụ quan với dòng Fire Emblem, và việc này bị coi là lợi dụng thương hiệu Fire Emblem để quảng cáo cho sản phẩm của mình. Vì thế mà dẫn đến vụ kiện tụng bản quyền giữa Tirnanog với Nintendō. Vụ kiện này được đưa lên tòa án tối cao, phía Tirnanog bị xử bồi thường cho Nintendō một khoản tiền. Ngoài ra thì tựa đề của game cũng như nội dung cũng bị buộc phải thay đổi. Vì thế mà vào tháng 5 năm 2001, Tirnanog đã thay đổi bên phát hành thành Enterbrain để tung ra thị trường bản game có tên Tear Ring Saga - Yuthna anh hùng chiến ký mà hầu hết thế hệ 8x, 9x ở Việt Nam đều từng chơi qua nếu là người ham thích thể loại game chiến thuật trên console.

1. Đánh bại Erunsth ở MAP 10 (click)

Hầu hết người chơi kỹ Tear Ring Saga (TRS) đều có ấn tượng với kỵ sĩ Hoàng kim Erunsth ở MAP 10. Đây là một trong những nhân vật mạnh nhất game, các chỉ số cao ngất ngưởng cùng với bộ skill khủng và mấy cái khiên giảm sát thương đáng kể. May thay là trong lần đụng độ ở MAP 10, nhân vật này không chủ động tấn công người chơi, và cũng chủ động rút lui sau một số lượt đi. 

Trang bị của Erunsth: khiên giảm 20 sát thương vật lý và 2 khiên giảm sát thương ma thuật

Tuy nhiên, nếu muốn thì người chơi cũng có thể giao chiến với Erunsth nếu đủ tự tin vào sức mạnh của mình. Chỉ có điều nhân vật này được thiết kế để không thể bị đánh bại ở MAP 10 nếu giao đấu trực tiếp. Cụ thể là trước mỗi trận đấu, CPU luôn kiểm tra xem lượng sát thương mà Erunsth sẽ nhận được trong trận ngay sau đó có vượt quá số HP còn lại của nhân vật này hay không. Nếu lượng sát thương vượt quá số lượng HP còn lại thì CPU sẽ rẻ nhánh sang hướng xử lý khác thông thường, là khiến nhân vật luôn tránh được mọi đòn đánh của người chơi. Trận đánh trong TRS và nhiều phiên bản Fire Emblem khác đều diễn ra theo kịch bản đã được tính toán sẵn từ trước. Trước mỗi trận đấu, CPU luôn tính toán tỷ lệ trúng đòn, lượng sát thương hay tỷ lệ đánh tất sát của mỗi bên, có nhân vật nào chết hay không. Mọi hình ảnh chiến đấu diễn ra sau đó chỉ là phần diễn kịch lại những kịch bản đã được định đoạn ở bước tính toán trước đó.

Ta có thể kiểm chứng điều này bằng cách dùng phần mềm giả lập kiêm Debugger cho máy PlayStation, từ đó chỉnh sửa code để game bỏ qua bước kiểm tra này. Khi đó thì Erunsth sẽ không còn tự động né đòn khi HP xuống thấp nữa.

Ý tưởng ở đây là đặt break point tại địa chỉ Ram quản lý giá trị HP hiện tại của nhân vật này để xem CPU làm gì với giá trị này. Đầu tiên là cần phải tìm được địa chỉ Ram quản lý HP hiện tại của Erunsth. Các Debugger thường có chức năng scan memory để tìm ra những địa chỉ nào có giá trị biến đổi theo thời gian. Chẳng hạn, nếu HP hiện tại của Erunsth đang là 49, và ta scan giá trị 49 trong memory; rồi sau một trận đánh, HP của nhân vật này giảm còn 40  thì ta tiếp tục dò tìm giá trị nào từng là 49 nhưng ngay thời điểm này là 40; và cứ tiếp tục như thế sau vài lần lọc thì sẽ tìm được địa chỉ Ram quản lý giá trị HP hiện tại của nhân vật này.

Không chỉ các Debugger mới có chức năng này, mà một số phần mềm độc lập như Art Money hay Cheat Engine cũng có chức năng tương tự. Bằng cách này thì ta dễ dàng tìm được  địa chỉ Ram này là $801925E8.

Lưu ý là địa chỉ này chỉ quản lý HP hiện tại của nhân vật trong trận đấu, không phải là địa chỉ thể hiện giá trị HP hiện tại "thật" của nhân vật. Trước mỗi trận đấu, giá trị HP hiện tại "thật" từ địa chỉ Ram khác được copy vào $801925E8, để rồi khi trận đấu diễn ra thì CPU luôn đọc địa chỉ $801925E8 ở mỗi frame để vẽ các vạch tương ứng với số HP còn lại. 

Bằng cách thay đổi giá trị tại $801925E8 thì lượng HP được hiển thị cũng thay đổi theo.

Nếu đặt write break point tại địa chỉ $801925E8 thì sẽ thấy giá trị HP được copy sang địa chỉ này từ Register $v0 trước khi trận đấu diễn ra.

800f9c2c ae420020: sw     $v0(00000031), 0x0020(s2)([801925e8] = 00000031) 

Đoạn log trên thể hiện giá trị 0x31 (tương đương số thập phân 49), tức giá trị HP hiện tại của Erunsth, được copy từ Register $v0 sang địa chỉ Ram $801925E8. Nếu truy ngược về trước một chút của đoạn log thì sẽ thấy có đoạn xử lý như bên dưới.

8010d278 3042003f: andi   $v0(007bc031), 0x003f

8010d27c 0262102a: slt    $v0(00000031), $s3(00000009), $v0(00000031)

8010d280 144000d0: bne    $v0(00000001), $r0(00000000), 0x8010d5c4

8010d284 2402004a: li     $v0(00000001), 0x004a

8010d5c4 8fa30020: lw     $v1(00000065), 0x0020(sp)([801ffd60] = 00000001)

8010d5c8 00000000: nop    

Điều này có nghĩa là tại Register $v0, giá trị HP là 1 byte (0x31) nằm chung với 3 byte khác (0x00, 0x7B, 0xC0), sau đó được AND với 0x3F để giữ lại byte cuối là 0x31. Sau đó, CPU sẽ kiểm tra xem giá trị tại $s3 (đang là 0x09) có nhỏ hơn giá trị tại Register $v0 (đang là 0x31) hay không. Giá trị tại $s3 lúc  này chính là lượng sát thương mà nhân vật sẽ nhận được trong trận đấu sắp tới. Nếu lượng sát thương sẽ nhận được (0x09) nhỏ hơn lượng HP hiện tại (0x31) thì $v0 sẽ được set giá trị thành 0x01.

Lệnh SLT trong ngôn ngữ MIPS R3000A mà máy PlayStation sử dụng mang nghĩa là set giá trị 1 cho Register đích nếu giá trị của Register được so sánh nhỏ hơn giá trị của Register là đối tượng so sánh

Sau đó, giá trị của Register $v0 được so với 0x0000 (tại Register $r0), nếu giá trị này khác 0x00 thì CPU sẽ rẻ nhánh sang địa chỉ thực thi ở $8010D5C4. Điều này có nghĩa là nếu kết quả của phép so sánh trước đó là 1 (trường hợp lượng sát thương nhỏ hơn lượng HP còn lại) thì CPU sẽ xử lý ở phần cho phép nhân vật nhận sát thương bình thường. Còn nếu kết quả của phép so sánh trước đó là 0 (trường hợp lượng sát thương lớn hơn lượng HP còn lại) thì CPU sẽ không phân nhánh mà xử lý theo hướng khiến nhân vật tự động né đòn.

Do vậy, nếu tại địa chỉ thực hiện phép so sánh mà ta sửa lại lệnh thi hành, khiến CPU luôn phân nhánh (jump) tới địa chỉ $8010D5C4 thì nhân vật không còn né đòn được khi lượng HP xuống thấp.

8010d278: andi   $v0, 0x003f

8010d27c: j  0x043571

Hằng số theo sau lệnh J (jump) trong ngôn ngữ MIPS không phải là địa chỉ tuyệt đối mà CPU sẽ nhảy tới, mà là số lượng câu lệnh mà nó sẽ bỏ qua để tới địa chỉ thực thi tiếp theo. Do mỗi câu lệnh của MIPS là 32 bit (tức 4 byte) nên chỉ cần lấy địa chỉ cần nhảy tới chia cho 4 là được giá trị tham số cho lệnh J. Cụ thể ở đây là: 10D5C4/4 = 43571.

Và kết quả sau khi chỉnh sửa câu lệnh trên thì Erunsth không còn né đòn khi HP xuống thấp nữa, cho nên nhân vật này vẫn trúng đòn, và HP xuống còn zero. Tuy nhiên, game không có phần xử lý cho nhân vật này biến mất khi HP là zero nên dẫn tới hiện tượng như bên dưới.

Mặc dù HP là zero nhưng CPU vẫn coi nhân vật này đang tồn tại, vẫn xảy ra hội thoại của nhân vật này sau đó. Bởi mặc định CPU luôn coi nhân vật này không thể chết ở map này. Nếu muốn CPU coi nhân vật này đã chết, xóa hội thoại sau đó thì cần phải can thiệp thêm nhiều vị trí khác. Và dĩ nhiên là việc này sẽ tác động tới các flag liên quan tới event của nhân vật này ở những map sau.

Và phần can thiệp bên trên chỉ là can thiệp vào memory mang tính nhất thời. Nếu muốn phần code này có hiệu lực vĩnh viễn (hard code) thì cần phải tìm đoạn code trên trong Rom rồi sửa như trên. Phần xử lý HP của nhân vật này nằm ở file B.bin trong CD-ROM, có địa chỉ như bên dưới.

0001C270  ANDI v0, v0, 0x003F

 0001C274 SLT v0, v0, 0x00

Đầu tiên là trích xuất file B.bin từ CD-ROM thông qua những phần mềm xử lý CD như CDmage, rồi chỉnh sửa đoạn code trên, compile thành mã máy rồi chèn lại B.bin đã chỉnh sửa vào CD cũng bằng CDmage.