Công khai mã nguồn bản dịch Tear Ring Saga

Thảo luận trong 'Turn Based Strategy' bắt đầu bởi SPC700, 7/1/24.

  1. SPC700

    SPC700 Legend of Zelda

    Tham gia ngày:
    1/10/20
    Bài viết:
    1,114


    Tear Ring Saga, tên ban đầu là Emblem Saga, được rất nhiều tay chơi game Việt Nam biết đến với tên gọi "Mộc đế 6" hoặc "Mộc đế PS1".
    Đơn giản là do game này có cùng cốt cách với dòng game "Mộc đế" (Fire Emblem) do hãng Ninh Tiền Đô phát hành.

    Emblem Saga là sản phẩm của Kaga Shōzō sau khi ông này rời khỏi Intelligent Systems, công ty phát triển game cho Ninh Tiền Đô, và nổi tiếng nhất qua series Fire Emblem. Kaga Shōzō cũng là người sáng tạo nên dòng game Fire Emblem, nhưng Ninh Tiền Đô lại là bên chi tiền để phát triển cả dòng game này.
    Vì thế, ngay sau khi Kaga Shōzō thành lập công ty riêng, tạo nên Emblem Saga thì giữa Ninh Tiền Đô và ông này đã diễn ra vụ kiện tụng kéo dài đến năm 2005, xoay quanh vấn đề tác quyền.
    Cũng vì lẽ đó mà Emblem Saga được đổi tên thành Tear Ring Saga.
    Bản dịch tiếng Việt này được hoàn thành (mức độ 100%) vào năm 2014. Sau 10 năm, tác giả bản dịch quyết định công khai code và script của bản dịch này để cộng đồng có thể chỉnh sửa lời thoại, xây dựng nên những câu chuyện mới.

    Link:
    https://www.mediafire.com/file/6f9wafsvd5r91jl/Emblem_Saga.rar

    Cũng giống như các phiên bản Fire Emblem khác, đây là game có khối lượng thoại đồ sộ, không thua kém bất cứ trường thiên tiểu thuyết nào.
    Ngoài ra, tác giả bản dịch này còn công khai mã nguồn + script bản dịch tiếng Việt cho nhiều game khác.
    Xem chi tiết ở link bên dưới.

    https://sites.google.com/view/sfc65816
     
    Sephir0th and namff like this.
  2. SPC700

    SPC700 Legend of Zelda

    Tham gia ngày:
    1/10/20
    Bài viết:
    1,114
    Một số bạn hỏi sao bản dịch Tear Ring Saga không chơi được trên PS Vita.
    Câu trả lời là: PS Vita không thể chơi được game PS1.
    Tuy nhiên, nó có thể chơi được game của PSP, còn máy PSP thì lại chơi được game PS1.
    Do đó, để chơi bản dịch Tear Ring Saga trên máy PS Vita thì cần phải:
    1) Convert Rom Tear Ring Saga thành dạng Eboot để máy PSP có thể đọc được.
    2) Chạy giả lập PSP trên máy PS Vita (giả lập Andreline)
    Một số hệ máy cầm tay của Ninh Tiền Đô cũng có thể chạy được bản dịch này với giả lập PS1, nhưng không khuyến khích.
    Bởi vì CPU của hệ PS nhà Sony là MIPS, trong khi CPU của các hệ máy cầm tay của nhà Ninh Tiền Đô thường là ARM, và thường yếu hơn nên độ tương thích không cao.
    Mà bây giờ nhìn lại thì thấy bản dịch này (năm 2014) chỉ mới dừng ở mức thủ thuật chứ chưa phải kỹ thuật.
    Còn link bên dưới bao gồm Rom tiếng Việt cho cả PS1 lẫn Eboot cho PSP, nhưng có thêm một số chỗ nghịch ngợm của năm 2024.

    Link:
    Super Easy Tear Ring Saga (mediafire.com)

    a) Di chuyển không tốn lượt
    b) Mua đồ không tốn tiền, nhưng bán thì tăng tiền
    c) Level up với 1 EXP
    d) Độ bền của vũ khí chỉ có tăng chứ không có giảm
    Bản vọc này thích hợp cho người chỉ muốn thưởng thức nội dung của game, hoặc người lúc nào cũng muốn tiền vào mà không thích tiền ra....

     
    Sephir0th, namff and T0977999482 like this.
  3. SPC700

    SPC700 Legend of Zelda

    Tham gia ngày:
    1/10/20
    Bài viết:
    1,114
    Hack những điều chưa ai từng làm trong TRS.

    STONE BOAT - Tear Ring Saga (google.com)

    Đánh bại Erunsth ở MAP 10

    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.

    [​IMG]

    [​IMG]
    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.

    [​IMG]

    [​IMG]
    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.

    [​IMG]

    [​IMG]
    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.

    [​IMG]

    [​IMG]
    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.

    [​IMG]

    [​IMG]
    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.

     
    Chỉnh sửa cuối: 29/1/24
    namff thích bài này.
  4. SPC700

    SPC700 Legend of Zelda

    Tham gia ngày:
    1/10/20
    Bài viết:
    1,114
    Thử demake Tear Ring Saga.

     
  5. SPC700

    SPC700 Legend of Zelda

    Tham gia ngày:
    1/10/20
    Bài viết:
    1,114
    STONE BOAT - Tear Ring Saga

    Giải lời thề của Laquel

    Laquel là một nhân vật đặc biệt trong TRS. Nhân vật này đặc biệt không phải vì chỉ số nổi trội, kỹ năng tuyệt đỉnh mà cũng chẳng phải ở avatar trai xinh gái đẹp. Laquel đặc biệt vì sở hữu cây cung khá mạnh ở đầu game, những giữ lời thề bất sát nên khi đụng độ lính bên địch thì không bao giờ kết liễu được quân địch. Vì lời thề không phạm sát giới nên nhân vật này luôn chừa 1 HP cho quân địch, những nhát bắn sau đó luôn trật dù tỷ lệ Hit là 100%. Việc này gần giống với tướng Erunst ở Map 10, luôn tự động né đòn khi lượng HP còn lại không đủ để chịu lượng sát thương do địch gây ra. Chỉ đến giai đoạn giữa game, và chỉ khi người chơi chọn cung thủ Luca vào đội của mình thì mới xảy ra event giúp Laquel phá bỏ lời thề bất sát, có thể kết liễu quân địch trong trận đấu.

    Thử tìm các trang Cheat code khắp chốn Google nhưng tuyệt nhiên không thấy một trang nào đề cập đến Cheat code khiến Laquel bỏ lời thề, bắn chết được quân địch ngay từ những Map đầu tiên. Thế nên tôi đã tìm cách can thiệp vào mã game để sửa lại lời thề này, và đã thành công.



    Ý tưởng ở đây là tìm ra routine xử lý lượng HP còn lại của quân địch trong trận đấu, để rồi từ đó tìm ra phần xử lý đặc biệt khi Laquel là bên tấn công, và rồi vô hiệu hóa phần xử lý đặc biệt đó.

    Để tìm ra routine xử lý lượng HP còn lại của quân địch thì đầu tiên cần phải xác định được địa chỉ Ram quản lý HP của địch. Cách tìm địa chỉ này giống như phần trên đã đề cập, là dùng chức năng Memory Scan của Debugger, hoặc các phần mềm ngoại vi có chức năng dò tìm Memory như Cheat Engine hay Art Money. Chẳng hạn, nếu ban đầu quân địch có 30 HP thì ta dò tìm giá trị 30, sau đó tấn công, địch còn 27 HP thì ta tiếp tục dò tìm phần Memory nào trong số những địa chỉ Ram đã tìm được mà vừa biến đổi thành 27. Tiếp tục một vài lần như thế thì sẽ tìm được địa chỉ Ram quản lý HP của địch. Cần lưu ý là địa chỉ này không cố định, mà biến động ở từng Map, từng đơn vị lính địch nên ở đây không nên địa chỉ cụ thể.

    [​IMG]

    Chức năng Memory Scan của Duck Station. Bên trái là kết quả dò tìm những địa chỉ Ram có giá trị hiện tại là 11 (HP hiện tại của địch), và giá trị trước đó là 21

    Sau đó, đặt Write Break Point ở địa chỉ vừa tìm được thì sẽ thu được kết quả như dưới đây.

    800FB4A8: sw v0, 0x00(s0)

    Trong đó v0 là Register chứa giá trị HP còn lại của địch sau khi bị tấn công, và giá trị này được ghi vào địa chỉ nằm trong Register s2. Truy ngược về trước thì thấy giá trị của v0 là kết quả của phép tính dưới đây.

    8010D664: subu a0, a0, s1

    Trong đó a0 là giá trị HP còn lại của địch, bằng chính nó trừ đi lượng sát thương mà địch nhận được, chứa ở Register s1. Đây là lệnh quan trọng trong khâu tính toán kết quả trận đấu trong game. Nếu ta tìm ngược về trước thì sẽ thấy giá trị sát thương mà bên bị tấn công nhận được chính là giá trị tấn công của bên chủ động tấn công trừ đi giá trị phòng thủ của bên bị tấn công. Từ đây ta có thể thay đổi cơ chế tính toán, chẳng hạn như thay phép trừ bằng phép cộng, hoặc nhân chia, hoặc các phép tính toán kèm điều kiện khác. Chẳng hạn như nếu bên tấn công là một nhân vật xác định nào đó thì sát thương sẽ nhân đôi, hay chỉ còn một nửa,...

    Và nếu so sánh log ở hai trường hợp: trường hợp khi nhân vật khác không phải Laquel tấn công địch, và trường hợp Laquel tấn công địch thì ta sẽ thấy hai log này có sự khác biệt. Ở trường hợp Laquel là bên tấn công, nếu HP của địch còn lại thấp hơn giá trị sát thương mà Laquel gây ra thì CPU sẽ có thêm dòng xử lý như bên dưới.

    8010D2F8: addiu s1, v0, 0xFFFF

    Trong đó s1 chính là lượng sát thương mà bên bị tấn công nhận được, v0 là giá HP hiện tại của địch. Dòng lệnh này cho thấy lượng sát thương mà bên bị tấn công nhận được bằng HP hiện tại của địch trừ 1 HP. Đoạn xử lý này là đặc thù của riêng trường hợp khi Laquel là bên tấn công. Điều kiện để xảy ra đoạn xử lý này nằm ở phần code bên dưới.


    8010D284: addiu v0, r0, 0x4A

    8010D288: lw v1, 0x1C(fp)

    8010D28C: nop

    8010D290: andi v1, v1, 0x03FF

    8010D294: bne v1, v0, 8010D324


    Đầu tiên, giá trị ID của Laquel (0x4A) được chứa trong Register v0, sau đó CPU đọc giá trị ID của nhân vật chủ động tấn công ở địa chỉ cách framepointer một khoảng 0x1C byte. Sau đó hai giá trị này (ID của Laquel và ID của nhân vật chủ động tấn công) được so sánh với nhau, nếu không bằng nhau thì CPU sẽ nhảy đến đoạn xử lý bình thường. Còn nếu hai giá trị này bằng nhau thì CPU sẽ tiếp tục xử lý đoạn 0x8010D2F8 ở trên.

    Từ đây, nếu ta thay con số 0x4A bằng giá trị khác thì lời thề bất sát sẽ được chuyển sang nhân vật mới. Hoặc nếu muốn vô hiệu hóa đoạn xử lý bất sát này thì chỉ cần đổi thành lệnh luôn nhảy đến xử lý ở 0x8010D324, bất kể kết quả so sánh là như thế nào.

    8010D294: J 0x8010D324

    Hoặc cũng có thể tạo lời thề bất sát cho nhiều nhân vật bằng một loạt phép so sánh với từng ID.

    Phần code tính toán này nằm ở file B.bin ở thư mục gốc của CD, nên chỉ cần thay thế file này là có thể thấy kết quả như mong muốn.

    Video dưới đây là toàn bộ quá trình tìm ra routine xử lý lời thề bất sát của Laquel, và cái kết thay đổi nó.


     
  6. badbaby_000

    badbaby_000 C O N T R A Lão Làng GVN

    Tham gia ngày:
    7/5/06
    Bài viết:
    1,584
    Nơi ở:
    Hà nội
    Bác cho hỏi nếu để shigen chết trước map 14 thì không có hội thoại shena nhờ shigen chiến vega đúng không đến khi nào có lại để cộng hưởng cả Vega,và khi nào thì thu được sherra hả bác
     
  7. SPC700

    SPC700 Legend of Zelda

    Tham gia ngày:
    1/10/20
    Bài viết:
    1,114
    Event Shigen vs Vega là ở Map 14 bên đội Holmes nên nếu để Shigen chết trước đó thì không có event này. Nhưng bù lại, nếu để Shigen chết sớm thì sẽ có Sherra sớm hơn bình thường, ngay sau khi chia quân lần 2.
     
  8. SPC700

    SPC700 Legend of Zelda

    Tham gia ngày:
    1/10/20
    Bài viết:
    1,114
    Phá bỏ giới hạn sát thương 99 HP

    Người chơi TRS ai cũng biết, chỉ số HP tối đa của một nhân vật trong game là 60, nên nhà sản xuất để giới hạn 99 sát thương cho mỗi đòn đánh là điều hợp lý. Nhưng khi mà đáng lý ra sát thương lên đến con số vài trăm, nhưng trên màn hình chỉ hiển thị có 99 thì cũng là một loại ức chế. Và để phá bỏ giới hạn trên 99 HP cho đòn đánh thì cần phải làm một số thứ mà không có phần mềm hay công cụ sẵn có nào, hay bất kỳ cheat code nào làm được.

    [​IMG]

    Trong kỳ trước, ta đã biết lượng HP còn lại của bên bị tấn công được tính toán ở địa chỉ như bên dưới.

    8010D664: subu a0, a0, s1

    Trong đó a0 là giá trị HP của bên bị tấn công, còn s1 là Register chứa giá trị của lượng sát thương nhận được. Khi truy tìm nguồn gốc phát sinh của giá trị s1 này, ta có thể tìm tới routine hiển thị lượng sát thương ra màn hình, để rồi từ đó thay đổi nó hiển thị 3 hay 4 hàng số thay vì 2 hàng số mặc định. Nếu truy ngược file log một tí, ta sẽ thấy một đoạn xử lý liên quan tới s1.

    8010D5D4: sll v1, s1, 24

    8010D5E8: sra a1, v1, 24

    8010D608: sb a1, 0x14(v1)


    Các bước xử lý trên cho thấy giá trị sát thương mà địch nhận được (s1) được shift sang trái 24 lần rồi ghi vào v1. Mỗi lần shift sang trái, giá trị của byte được nhân đôi. Sau đó, giá trị của v1 lại được shift sang phải 24 lần trước khi ghi vào Register a1. Như vậy, a1 giữ nguyên được giá trị sát thương ban đầu ở s1. Sau đó, giá trị ở a1 được ghi vào địa chỉ ở (v1 + 0x14). Địa chỉ này là cố định cho mọi nhân vật, cụ thể là 0x8018570B.

    Toàn bộ quá trình xử lý trên đều diễn ra trước cảnh chiến đấu, ngay sau khi ta chọn vũ khí tấn công cho nhân vật. Do con số thể hiện lượng sát thương chỉ xuất hiện trong cảnh chiến đấu Real Anime nên cần phải bật hoạt cảnh này lên trước khi ghi log. Nếu ta đặt Read Break point ở địa chỉ 0x8018570B thì sẽ thấy CPU dừng nhiều lần khi đọc tới địa chỉ này trong cảnh chiến đấu Real Anime. Và sau khi CPU đọc giá trị sát thương mà địch nhận được từ 0x8018570B thì nó sẽ ghi giá trị này vào 0x801910EE sau một loạt các xử lý khác. 0x801910EE là địa chỉ trung gian chứa giá trị sát thương mà địch nhận được trong trận đấu khi bật Real Anime, và là địa chỉ cố định cho mọi nhân vật.

    Nếu tiếp tục đặt Read Break point tại địa chỉ 0x801910EE thì sẽ gặp đoạn xử lý như bên dưới.

    80103D94: lh a3, 0xCE (s3) //đọc giá trị từ 0x801910EE

    8014C57C: sw a3, 0x10(sp) //ghi a3 vào 0x801FFD50

    Thực chất, đây là một bước trung chuyển giá trị sát thương từ 0x801FFD50 sang s0.

    8014B984: lw s0, 0x68(sp) ; s0=0x8018547C, addr=801FFD50



    Nếu tiếp tục theo dõi s0 thì ta sẽ đến được một routine chuyển đổi giá trị sát thương mà địch nhận được từ số thập lục sang số thập phân, trong đó s0 chính là giá trị cần chuyển đổi.

    8014c120: addiu a0, a0, 0x01

    8014c124: lui v0, 0x6666

    8014c128: ori v0, v0, 0x6667

    8014c12c: mult s0, v0

    8014c130: sb zero, 0x00(a1)

    8014c134: mfhi v0

    8014c138: sra v0, v0, 0x02

    8014c13c: subu v1, v0, v1

    8014c140: addu a0, v1, zero

    8014c144: sll v0, a0, 2

    8014c148: addu v0, v0, a0

    8014c14c: sll v0, v0, 1

    8014c150: bne a0, zero, 8014c16c

    8014c154: subu v1, s0, v0

    8014c158: v0, v1, ffa4

    8014c15c: sb v0, 40(sp)

    8014c160: sb zero, 42(sp)

    8014c164: j 0x8014c184

    8014c168: sb zero, 0x29(sp)

    8014c16C: ADDIU v0, a0, 0xFFA4

    8014c170: ADDIU v1, v1, 0xFFA4

    8014c174: SB v0, 0x0028(sp)

    8014c178: SB v1, 0x0029(sp)

    8014c17C: SB r0, 0x002B(sp)

    8014c180: SB r0, 0x002A(sp)



    Đây là đoạn xử lý chuyển đối giá trị số từ hệ thập lục (Hex) sang hệ thập phân (Dec). Cách chuyển đổi ở đây khá lạ khi đối tượng (giá trị sát thương) được nhân với hằng số 0x66666667. Vì thế, nếu đối tượng có giá trị từ 0x03 trở lên thì sẽ xảy ra hiện tượng overflow, kết quả của phép nhân vượt quá một word (4 byte) của một Register. Và ở đây CPU cũng không sử dụng kết quả của phép nhân đó, mà chỉ sử dụng phần overflow được chứa trong Register MFHI để tính toán. Kết quả tính toán này cho ra giá trị của từng hàng số thập phân của giá trị sát thương, và lần lượt 4 giá trị thập phân của 4 chữ số được ghi vào các địa chỉ: 0x28(sp), 0x29(sp), 0x2A(sp), 0x2B(sp). Tuy nhiên, giá trị được ghi vào các địa chỉ 0x2A(sp) và 0x2B(sp) luôn là zero. Chính vì thế mà giá trị sát thương được thể hiện bằng con số thập phân lên màn hình gồm tối đa 2 hàng số (phạm vi thể hiện từ 0 tới 99). Để thể hiện được 3 con số (phạm vi từ 0 tới 999) hoặc 4 con số thì cần phải viết lại đoạn xử lý trên.

    Lưu ý là các giá trị cần được cộng với 0xFFA4 trước khi được ghi vào các địa chỉ hiển thị. 0xFFA4 là mã văn tự thể hiện số 0 lên màn hình. Tương tự, 0xFFA5 thể hiện số 1, 0xFFA6 thể hiện số 2,...

    Dưới đây là routine được viết lại, cho phép trình bày giá trị sát thương lên tới 3 hàng số. Ý niệm cơ bản là lấy giá trị cần chuyển đổi (ở hệ thập lục) chia cho 10, kết quả phép chia ở Register MFLO chính là giá trị của con số thập phân ở hàng cao nhất, còn giá trị dư của phép chia ở Register MFHI là giá trị của con số thập phân ở hàng kế tiếp.


    .org 0x8014C124

    addu a0, r0, r0

    ori a0, a0, 0x0A

    andi s0, s0, 0xFF

    sb r0, 0x29(sp)

    sb r0, 0x2A(sp)

    slti v0, s0, 0x0A

    beq v0, r0, _2keta //nếu giá trị sát thương lớn hơn hoặc bằng 0x0A (10) thì rẽ sang đoạn xử lý 2 hàng số

    sb r0, 0x2B(sp)

    addiu v0, s0, 0xFFA4

    j 0x14C184

    sb v0, 0x28(sp)

    _2keta:

    div s0, a0

    mflo v0

    slti v1, v0, 0x0A

    beq v1, r0, _3keta //nếu giá trị phần dư của phép chia lớn hơn hoặc bằng 0x0A thì rẽ sang đoạn xử lý 3 hàng số

    mfhi v1

    addiu v0, v0, 0xFFA4

    addiu v1, v1, 0xFFA4

    sb v0, 0x28(sp)

    j 0x14C184

    sb v1, 0x29(sp)

    _3keta:

    mfhi v1

    addiu v1, v1, 0xFFA4

    sb v1, 0x2A(sp)

    div v0, a0

    mflo v0

    addiu v0, v0, 0xFFA4

    sb v0, 0x28(sp)

    mfhi v1

    addiu v1, v1, 0xFFA4

    j 0x14C184

    sb v1, 0x29(sp)



    Lưu ý là nếu giá trị các hàng số được cộng với hằng số khác 0xFFA4 thì con số được hiển thị trên màn hình không còn đúng như mong muốn. Vì A4 là vị trí của số 0 trong Vram. Trường hợp cộng với giá trị khác thì cần phải điều chỉnh lại vị trí của bộ số từ 0~9 trong Vram.

    Toàn bộ quá trình tìm đến routine chuyển đổi số thập lục sang số thập phân được trình bày trong video dưới đây.

     
    tryo4vn thích bài này.

Chia sẻ trang này