duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

cpu_disasm.cpp (21957B)


      1 // SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
      3 
      4 #include "cpu_disasm.h"
      5 #include "cpu_core.h"
      6 #include "cpu_types.h"
      7 
      8 #include "common/assert.h"
      9 #include "common/small_string.h"
     10 
     11 #include <array>
     12 
     13 namespace CPU {
     14 namespace {
     15 
     16 enum Operand : u8
     17 {
     18   Operand_None,
     19   i_rs,
     20   i_rt,
     21   i_imm,
     22   j_target,
     23   r_rs,
     24   r_rt,
     25   r_rd,
     26   r_shamt,
     27   r_funct
     28 };
     29 
     30 struct TableEntry
     31 {
     32   const char* format;
     33 };
     34 
     35 struct GTEInstructionTable
     36 {
     37   const char* name;
     38   bool sf;
     39   bool lm;
     40   bool mvmva;
     41 };
     42 } // namespace
     43 
     44 static void FormatInstruction(SmallStringBase* dest, const Instruction inst, u32 pc, const char* format);
     45 static void FormatComment(SmallStringBase* dest, const Instruction inst, u32 pc, const char* format);
     46 
     47 template<typename T>
     48 static void FormatCopInstruction(SmallStringBase* dest, u32 pc, const Instruction inst,
     49                                  const std::pair<T, const char*>* table, size_t table_size, T table_key);
     50 
     51 template<typename T>
     52 static void FormatCopComment(SmallStringBase* dest, u32 pc, const Instruction inst,
     53                              const std::pair<T, const char*>* table, size_t table_size, T table_key);
     54 
     55 static void FormatGTEInstruction(SmallStringBase* dest, u32 pc, const Instruction inst);
     56 
     57 static const std::array<const char*, 64> s_base_table = {{
     58   "",                       // 0
     59   "UNKNOWN",                // 1
     60   "j $jt",                  // 2
     61   "jal $jt",                // 3
     62   "beq $rs, $rt, $rel",     // 4
     63   "bne $rs, $rt, $rel",     // 5
     64   "blez $rs, $rel",         // 6
     65   "bgtz $rs, $rel",         // 7
     66   "addi $rt, $rs, $imm",    // 8
     67   "addiu $rt, $rs, $imm",   // 9
     68   "slti $rt, $rs, $imm",    // 10
     69   "sltiu $rt, $rs, $immu",  // 11
     70   "andi $rt, $rs, $immu",   // 12
     71   "ori $rt, $rs, $immu",    // 13
     72   "xori $rt, $rs, $immu",   // 14
     73   "lui $rt, $imm",          // 15
     74   "UNKNOWN",                // 16
     75   "UNKNOWN",                // 17
     76   "UNKNOWN",                // 18
     77   "UNKNOWN",                // 19
     78   "UNKNOWN",                // 20
     79   "UNKNOWN",                // 21
     80   "UNKNOWN",                // 22
     81   "UNKNOWN",                // 23
     82   "UNKNOWN",                // 24
     83   "UNKNOWN",                // 25
     84   "UNKNOWN",                // 26
     85   "UNKNOWN",                // 27
     86   "UNKNOWN",                // 28
     87   "UNKNOWN",                // 29
     88   "UNKNOWN",                // 30
     89   "UNKNOWN",                // 31
     90   "lb $rt, $offsetrs",      // 32
     91   "lh $rt, $offsetrs",      // 33
     92   "lwl $rt, $offsetrs",     // 34
     93   "lw $rt, $offsetrs",      // 35
     94   "lbu $rt, $offsetrs",     // 36
     95   "lhu $rt, $offsetrs",     // 37
     96   "lwr $rt, $offsetrs",     // 38
     97   "UNKNOWN",                // 39
     98   "sb $rt, $offsetrs",      // 40
     99   "sh $rt, $offsetrs",      // 41
    100   "swl $rt, $offsetrs",     // 42
    101   "sw $rt, $offsetrs",      // 43
    102   "UNKNOWN",                // 44
    103   "UNKNOWN",                // 45
    104   "swr $rt, $offsetrs",     // 46
    105   "UNKNOWN",                // 47
    106   "lwc0 $coprt, $offsetrs", // 48
    107   "lwc1 $coprt, $offsetrs", // 49
    108   "lwc2 $coprt, $offsetrs", // 50
    109   "lwc3 $coprt, $offsetrs", // 51
    110   "UNKNOWN",                // 52
    111   "UNKNOWN",                // 53
    112   "UNKNOWN",                // 54
    113   "UNKNOWN",                // 55
    114   "swc0 $coprt, $offsetrs", // 56
    115   "swc1 $coprt, $offsetrs", // 57
    116   "swc2 $coprt, $offsetrs", // 58
    117   "swc3 $coprt, $offsetrs", // 59
    118   "UNKNOWN",                // 60
    119   "UNKNOWN",                // 61
    120   "UNKNOWN",                // 62
    121   "UNKNOWN"                 // 63
    122 }};
    123 
    124 static const std::array<const char*, 64> s_special_table = {{
    125   "sll $rd, $rt, $shamt", // 0
    126   "UNKNOWN",              // 1
    127   "srl $rd, $rt, $shamt", // 2
    128   "sra $rd, $rt, $shamt", // 3
    129   "sllv $rd, $rt, $rs",   // 4
    130   "UNKNOWN",              // 5
    131   "srlv $rd, $rt, $rs",   // 6
    132   "srav $rd, $rt, $rs",   // 7
    133   "jr $rs",               // 8
    134   "jalr $rd, $rs",        // 9
    135   "UNKNOWN",              // 10
    136   "UNKNOWN",              // 11
    137   "syscall",              // 12
    138   "break",                // 13
    139   "UNKNOWN",              // 14
    140   "UNKNOWN",              // 15
    141   "mfhi $rd",             // 16
    142   "mthi $rs",             // 17
    143   "mflo $rd",             // 18
    144   "mtlo $rs",             // 19
    145   "UNKNOWN",              // 20
    146   "UNKNOWN",              // 21
    147   "UNKNOWN",              // 22
    148   "UNKNOWN",              // 23
    149   "mult $rs, $rt",        // 24
    150   "multu $rs, $rt",       // 25
    151   "div $rs, $rt",         // 26
    152   "divu $rs, $rt",        // 27
    153   "UNKNOWN",              // 28
    154   "UNKNOWN",              // 29
    155   "UNKNOWN",              // 30
    156   "UNKNOWN",              // 31
    157   "add $rd, $rs, $rt",    // 32
    158   "addu $rd, $rs, $rt",   // 33
    159   "sub $rd, $rs, $rt",    // 34
    160   "subu $rd, $rs, $rt",   // 35
    161   "and $rd, $rs, $rt",    // 36
    162   "or $rd, $rs, $rt",     // 37
    163   "xor $rd, $rs, $rt",    // 38
    164   "nor $rd, $rs, $rt",    // 39
    165   "UNKNOWN",              // 40
    166   "UNKNOWN",              // 41
    167   "slt $rd, $rs, $rt",    // 42
    168   "sltu $rd, $rs, $rt",   // 43
    169   "UNKNOWN",              // 44
    170   "UNKNOWN",              // 45
    171   "UNKNOWN",              // 46
    172   "UNKNOWN",              // 47
    173   "UNKNOWN",              // 48
    174   "UNKNOWN",              // 49
    175   "UNKNOWN",              // 50
    176   "UNKNOWN",              // 51
    177   "UNKNOWN",              // 52
    178   "UNKNOWN",              // 53
    179   "UNKNOWN",              // 54
    180   "UNKNOWN",              // 55
    181   "UNKNOWN",              // 56
    182   "UNKNOWN",              // 57
    183   "UNKNOWN",              // 58
    184   "UNKNOWN",              // 59
    185   "UNKNOWN",              // 60
    186   "UNKNOWN",              // 61
    187   "UNKNOWN",              // 62
    188   "UNKNOWN"               // 63
    189 }};
    190 
    191 static const std::array<std::pair<CopCommonInstruction, const char*>, 4> s_cop_common_table = {
    192   {{CopCommonInstruction::mfcn, "mfc$cop $rt_, $coprd"},
    193    {CopCommonInstruction::cfcn, "cfc$cop $rt_, $coprdc"},
    194    {CopCommonInstruction::mtcn, "mtc$cop $rt, $coprd"},
    195    {CopCommonInstruction::ctcn, "ctc$cop $rt, $coprdc"}}};
    196 
    197 static const std::array<std::pair<Cop0Instruction, const char*>, 1> s_cop0_table = {{{Cop0Instruction::rfe, "rfe"}}};
    198 
    199 static constexpr const std::array<const char*, 64> s_gte_register_names = {
    200   {"v0_xy", "v0_z",  "v1_xy", "v1_z",  "v2_xy", "v2_z",  "rgbc", "otz",  "ir0",  "ir1",   "ir2",   "ir3",   "sxy0",
    201    "sxy1",  "sxy2",  "sxyp",  "sz0",   "sz1",   "sz2",   "sz3",  "rgb0", "rgb1", "rgb2",  "res1",  "mac0",  "mac1",
    202    "mac2",  "mac3",  "irgb",  "orgb",  "lzcs",  "lzcr",  "rt_0", "rt_1", "rt_2", "rt_3",  "rt_4",  "trx",   "try",
    203    "trz",   "llm_0", "llm_1", "llm_2", "llm_3", "llm_4", "rbk",  "gbk",  "bbk",  "lcm_0", "lcm_1", "lcm_2", "lcm_3",
    204    "lcm_4", "rfc",   "gfc",   "bfc",   "ofx",   "ofy",   "h",    "dqa",  "dqb",  "zsf3",  "zsf4",  "flag"}};
    205 
    206 static constexpr const std::array<GTEInstructionTable, 64> s_gte_instructions = {{
    207   {"UNKNOWN", false, false, false}, // 0x00
    208   {"rtps", true, true, false},      // 0x01
    209   {"UNKNOWN", false, false, false}, // 0x02
    210   {"UNKNOWN", false, false, false}, // 0x03
    211   {"UNKNOWN", false, false, false}, // 0x04
    212   {"UNKNOWN", false, false, false}, // 0x05
    213   {"nclip", false, false, false},   // 0x06
    214   {"UNKNOWN", false, false, false}, // 0x07
    215   {"UNKNOWN", false, false, false}, // 0x08
    216   {"UNKNOWN", false, false, false}, // 0x09
    217   {"UNKNOWN", false, false, false}, // 0x0A
    218   {"UNKNOWN", false, false, false}, // 0x0B
    219   {"op", true, true, false},        // 0x0C
    220   {"UNKNOWN", false, false, false}, // 0x0D
    221   {"UNKNOWN", false, false, false}, // 0x0E
    222   {"UNKNOWN", false, false, false}, // 0x0F
    223   {"dpcs", true, true, false},      // 0x10
    224   {"intpl", true, true, false},     // 0x11
    225   {"mvmva", true, true, true},      // 0x12
    226   {"ncds", true, true, false},      // 0x13
    227   {"cdp", true, true, false},       // 0x14
    228   {"UNKNOWN", false, false, false}, // 0x15
    229   {"ncdt", true, true, false},      // 0x16
    230   {"UNKNOWN", false, false, false}, // 0x17
    231   {"UNKNOWN", false, false, false}, // 0x18
    232   {"UNKNOWN", false, false, false}, // 0x19
    233   {"UNKNOWN", false, false, false}, // 0x1A
    234   {"nccs", true, true, false},      // 0x1B
    235   {"cc", true, true, false},        // 0x1C
    236   {"UNKNOWN", false, false, false}, // 0x1D
    237   {"ncs", true, true, false},       // 0x1E
    238   {"UNKNOWN", false, false, false}, // 0x1F
    239   {"nct", true, true, false},       // 0x20
    240   {"UNKNOWN", false, false, false}, // 0x21
    241   {"UNKNOWN", false, false, false}, // 0x22
    242   {"UNKNOWN", false, false, false}, // 0x23
    243   {"UNKNOWN", false, false, false}, // 0x24
    244   {"UNKNOWN", false, false, false}, // 0x25
    245   {"UNKNOWN", false, false, false}, // 0x26
    246   {"UNKNOWN", false, false, false}, // 0x27
    247   {"sqr", true, true, false},       // 0x28
    248   {"dcpl", true, true, false},      // 0x29
    249   {"dpct", true, true, false},      // 0x2A
    250   {"UNKNOWN", false, false, false}, // 0x2B
    251   {"UNKNOWN", false, false, false}, // 0x2C
    252   {"avsz3", false, false, false},   // 0x2D
    253   {"avsz4", false, false, false},   // 0x2E
    254   {"UNKNOWN", false, false, false}, // 0x2F
    255   {"rtpt", true, true, false},      // 0x30
    256   {"UNKNOWN", false, false, false}, // 0x31
    257   {"UNKNOWN", false, false, false}, // 0x32
    258   {"UNKNOWN", false, false, false}, // 0x33
    259   {"UNKNOWN", false, false, false}, // 0x34
    260   {"UNKNOWN", false, false, false}, // 0x35
    261   {"UNKNOWN", false, false, false}, // 0x36
    262   {"UNKNOWN", false, false, false}, // 0x37
    263   {"UNKNOWN", false, false, false}, // 0x38
    264   {"UNKNOWN", false, false, false}, // 0x39
    265   {"UNKNOWN", false, false, false}, // 0x3A
    266   {"UNKNOWN", false, false, false}, // 0x3B
    267   {"UNKNOWN", false, false, false}, // 0x3C
    268   {"gpf", true, true, false},       // 0x3D
    269   {"gpl", true, true, false},       // 0x3E
    270   {"ncct", true, true, false},      // 0x3F
    271 }};
    272 
    273 } // namespace CPU
    274 
    275 void CPU::FormatInstruction(SmallStringBase* dest, const Instruction inst, u32 pc, const char* format)
    276 {
    277   dest->clear();
    278 
    279   const char* str = format;
    280   while (*str != '\0')
    281   {
    282     const char ch = *(str++);
    283     if (ch != '$')
    284     {
    285       dest->append(ch);
    286       continue;
    287     }
    288 
    289     if (std::strncmp(str, "rs", 2) == 0)
    290     {
    291       dest->append(GetRegName(inst.r.rs));
    292       str += 2;
    293     }
    294     else if (std::strncmp(str, "rt_", 3) == 0)
    295     {
    296       dest->append(GetRegName(inst.r.rt));
    297       str += 3;
    298     }
    299     else if (std::strncmp(str, "rt", 2) == 0)
    300     {
    301       dest->append(GetRegName(inst.r.rt));
    302       str += 2;
    303     }
    304     else if (std::strncmp(str, "rd", 2) == 0)
    305     {
    306       dest->append(GetRegName(inst.r.rd));
    307       str += 2;
    308     }
    309     else if (std::strncmp(str, "shamt", 5) == 0)
    310     {
    311       dest->append_format("{}", ZeroExtend32(inst.r.shamt.GetValue()));
    312       str += 5;
    313     }
    314     else if (std::strncmp(str, "immu", 4) == 0)
    315     {
    316       dest->append_format("{}", inst.i.imm_zext32());
    317       str += 4;
    318     }
    319     else if (std::strncmp(str, "imm", 3) == 0)
    320     {
    321       // dest->AppendFormattedString("%d", static_cast<int>(inst.i.imm_sext32()));
    322       dest->append_format("{:04x}", inst.i.imm_zext32());
    323       str += 3;
    324     }
    325     else if (std::strncmp(str, "rel", 3) == 0)
    326     {
    327       const u32 target = (pc + UINT32_C(4)) + (inst.i.imm_sext32() << 2);
    328       dest->append_format("{:08x}", target);
    329       str += 3;
    330     }
    331     else if (std::strncmp(str, "offsetrs", 8) == 0)
    332     {
    333       const s32 offset = static_cast<s32>(inst.i.imm_sext32());
    334       dest->append_format("{}({})", offset, GetRegName(inst.i.rs));
    335       str += 8;
    336     }
    337     else if (std::strncmp(str, "jt", 2) == 0)
    338     {
    339       const u32 target = ((pc + UINT32_C(4)) & UINT32_C(0xF0000000)) | (inst.j.target << 2);
    340       dest->append_format("{:08x}", target);
    341       str += 2;
    342     }
    343     else if (std::strncmp(str, "copcc", 5) == 0)
    344     {
    345       dest->append(((inst.bits & (UINT32_C(1) << 24)) != 0) ? 't' : 'f');
    346       str += 5;
    347     }
    348     else if (std::strncmp(str, "coprdc", 6) == 0)
    349     {
    350       if (inst.IsCop2Instruction())
    351         dest->append(GetGTERegisterName(static_cast<u8>(inst.r.rd.GetValue()) + 32));
    352       else
    353         dest->append_format("{}", ZeroExtend32(static_cast<u8>(inst.r.rd.GetValue())));
    354       str += 6;
    355     }
    356     else if (std::strncmp(str, "coprd", 5) == 0)
    357     {
    358       if (inst.IsCop2Instruction())
    359         dest->append(GetGTERegisterName(static_cast<u8>(inst.r.rd.GetValue())));
    360       else
    361         dest->append_format("{}", ZeroExtend32(static_cast<u8>(inst.r.rd.GetValue())));
    362       str += 5;
    363     }
    364     else if (std::strncmp(str, "coprt", 5) == 0)
    365     {
    366       if (inst.IsCop2Instruction())
    367         dest->append(GetGTERegisterName(static_cast<u8>(inst.r.rt.GetValue())));
    368       else
    369         dest->append_format("{}", ZeroExtend32(static_cast<u8>(inst.r.rt.GetValue())));
    370       str += 5;
    371     }
    372     else if (std::strncmp(str, "cop", 3) == 0)
    373     {
    374       dest->append_format("{}", static_cast<u8>(inst.op.GetValue()) & INSTRUCTION_COP_N_MASK);
    375       str += 3;
    376     }
    377     else
    378     {
    379       Panic("Unknown operand");
    380     }
    381   }
    382 }
    383 
    384 void CPU::FormatComment(SmallStringBase* dest, const Instruction inst, u32 pc, const char* format)
    385 {
    386   const CPU::Registers* regs = &CPU::g_state.regs;
    387 
    388   const char* str = format;
    389   while (*str != '\0')
    390   {
    391     const char ch = *(str++);
    392     if (ch != '$')
    393       continue;
    394 
    395     if (std::strncmp(str, "rs", 2) == 0)
    396     {
    397       dest->append_format("{}{}=0x{:08X}", dest->empty() ? "" : ", ", GetRegName(inst.r.rs),
    398                           regs->r[static_cast<u8>(inst.r.rs.GetValue())]);
    399 
    400       str += 2;
    401     }
    402     else if (std::strncmp(str, "rt_", 3) == 0)
    403     {
    404       str += 3;
    405     }
    406     else if (std::strncmp(str, "rt", 2) == 0)
    407     {
    408       dest->append_format("{}{}=0x{:08X}", dest->empty() ? "" : ", ", GetRegName(inst.r.rt),
    409                           regs->r[static_cast<u8>(inst.r.rt.GetValue())]);
    410       str += 2;
    411     }
    412     else if (std::strncmp(str, "rd", 2) == 0)
    413     {
    414       dest->append_format("{}{}=0x{:08X}", dest->empty() ? "" : ", ", GetRegName(inst.r.rd),
    415                           regs->r[static_cast<u8>(inst.r.rd.GetValue())]);
    416       str += 2;
    417     }
    418     else if (std::strncmp(str, "shamt", 5) == 0)
    419     {
    420       str += 5;
    421     }
    422     else if (std::strncmp(str, "immu", 4) == 0)
    423     {
    424       str += 4;
    425     }
    426     else if (std::strncmp(str, "imm", 3) == 0)
    427     {
    428       str += 3;
    429     }
    430     else if (std::strncmp(str, "rel", 3) == 0)
    431     {
    432       str += 3;
    433     }
    434     else if (std::strncmp(str, "offsetrs", 8) == 0)
    435     {
    436       const s32 offset = static_cast<s32>(inst.i.imm_sext32());
    437       const VirtualMemoryAddress address = (regs->r[static_cast<u8>(inst.i.rs.GetValue())] + offset);
    438 
    439       if (!dest->empty())
    440         dest->append_format(", ");
    441 
    442       if (inst.op == InstructionOp::lb || inst.op == InstructionOp::lbu)
    443       {
    444         u8 data = 0;
    445         CPU::SafeReadMemoryByte(address, &data);
    446         dest->append_format("addr={:08X}[{:02X}]", address, data);
    447       }
    448       else if (inst.op == InstructionOp::lh || inst.op == InstructionOp::lhu)
    449       {
    450         u16 data = 0;
    451         CPU::SafeReadMemoryHalfWord(address, &data);
    452         dest->append_format("addr={:08X}[{:04X}]", address, data);
    453       }
    454       else if (inst.op == InstructionOp::lw || (inst.op >= InstructionOp::lwc0 && inst.op <= InstructionOp::lwc3) ||
    455                inst.op == InstructionOp::lwl || inst.op == InstructionOp::lwr)
    456       {
    457         u32 data = 0;
    458         CPU::SafeReadMemoryWord(address, &data);
    459         dest->append_format("addr={:08X}[{:08X}]", address, data);
    460       }
    461       else
    462       {
    463         dest->append_format("addr={:08X}", address);
    464       }
    465 
    466       str += 8;
    467     }
    468     else if (std::strncmp(str, "jt", 2) == 0)
    469     {
    470       str += 2;
    471     }
    472     else if (std::strncmp(str, "copcc", 5) == 0)
    473     {
    474       str += 5;
    475     }
    476     else if (std::strncmp(str, "coprdc", 6) == 0)
    477     {
    478       if (inst.IsCop2Instruction())
    479       {
    480         dest->append_format("{}{}=0x{:08X}", dest->empty() ? "" : ", ",
    481                             GetGTERegisterName(static_cast<u8>(inst.r.rd.GetValue()) + 32),
    482                             g_state.gte_regs.cr32[static_cast<u8>(inst.r.rd.GetValue())]);
    483       }
    484       str += 6;
    485     }
    486     else if (std::strncmp(str, "coprd", 5) == 0)
    487     {
    488       if (inst.IsCop2Instruction())
    489       {
    490         dest->append_format("{}{}=0x{:08X}", dest->empty() ? "" : ", ",
    491                             GetGTERegisterName(static_cast<u8>(inst.r.rd.GetValue())),
    492                             g_state.gte_regs.dr32[static_cast<u8>(inst.r.rd.GetValue())]);
    493       }
    494 
    495       str += 5;
    496     }
    497     else if (std::strncmp(str, "coprt", 5) == 0)
    498     {
    499       if (inst.IsCop2Instruction())
    500       {
    501         dest->append_format("{}{}=0x{:08X}", dest->empty() ? "" : ", ",
    502                             GetGTERegisterName(static_cast<u8>(inst.r.rt.GetValue())),
    503                             g_state.gte_regs.dr32[static_cast<u8>(inst.r.rt.GetValue())]);
    504       }
    505 
    506       str += 5;
    507     }
    508     else if (std::strncmp(str, "cop", 3) == 0)
    509     {
    510       str += 3;
    511     }
    512     else
    513     {
    514       Panic("Unknown operand");
    515     }
    516   }
    517 }
    518 
    519 template<typename T>
    520 void CPU::FormatCopInstruction(SmallStringBase* dest, u32 pc, const Instruction inst,
    521                                const std::pair<T, const char*>* table, size_t table_size, T table_key)
    522 {
    523   for (size_t i = 0; i < table_size; i++)
    524   {
    525     if (table[i].first == table_key)
    526     {
    527       FormatInstruction(dest, inst, pc, table[i].second);
    528       return;
    529     }
    530   }
    531 
    532   dest->format("<cop{} 0x{:08X}>", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue());
    533 }
    534 
    535 template<typename T>
    536 void CPU::FormatCopComment(SmallStringBase* dest, u32 pc, const Instruction inst,
    537                            const std::pair<T, const char*>* table, size_t table_size, T table_key)
    538 {
    539   for (size_t i = 0; i < table_size; i++)
    540   {
    541     if (table[i].first == table_key)
    542     {
    543       FormatComment(dest, inst, pc, table[i].second);
    544       return;
    545     }
    546   }
    547 }
    548 
    549 void CPU::FormatGTEInstruction(SmallStringBase* dest, u32 pc, const Instruction inst)
    550 {
    551   const GTE::Instruction gi{inst.bits};
    552   const GTEInstructionTable& t = s_gte_instructions[gi.command];
    553   dest->append(t.name);
    554 
    555   if (t.sf && gi.sf)
    556     dest->append(" sf");
    557 
    558   if (t.lm && gi.lm)
    559     dest->append(" lm");
    560 
    561   if (t.mvmva)
    562   {
    563     dest->append_format(" m={} v={} t={}", static_cast<u8>(gi.mvmva_multiply_matrix),
    564                         static_cast<u8>(gi.mvmva_multiply_vector), static_cast<u8>(gi.mvmva_translation_vector));
    565   }
    566 }
    567 
    568 void CPU::DisassembleInstruction(SmallStringBase* dest, u32 pc, u32 bits)
    569 {
    570   const Instruction inst{bits};
    571   switch (inst.op)
    572   {
    573     case InstructionOp::funct:
    574       FormatInstruction(dest, inst, pc, s_special_table[static_cast<u8>(inst.r.funct.GetValue())]);
    575       return;
    576 
    577     case InstructionOp::cop0:
    578     case InstructionOp::cop1:
    579     case InstructionOp::cop2:
    580     case InstructionOp::cop3:
    581     {
    582       if (inst.cop.IsCommonInstruction())
    583       {
    584         FormatCopInstruction(dest, pc, inst, s_cop_common_table.data(), s_cop_common_table.size(), inst.cop.CommonOp());
    585       }
    586       else
    587       {
    588         switch (inst.op)
    589         {
    590           case InstructionOp::cop0:
    591           {
    592             FormatCopInstruction(dest, pc, inst, s_cop0_table.data(), s_cop0_table.size(), inst.cop.Cop0Op());
    593           }
    594           break;
    595 
    596           case InstructionOp::cop2:
    597           {
    598             FormatGTEInstruction(dest, pc, inst);
    599           }
    600           break;
    601 
    602           case InstructionOp::cop1:
    603           case InstructionOp::cop3:
    604           default:
    605           {
    606             dest->format("<cop{} 0x{:08X}>", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue());
    607           }
    608           break;
    609         }
    610       }
    611     }
    612     break;
    613 
    614     // special case for bltz/bgez{al}
    615     case InstructionOp::b:
    616     {
    617       const u8 rt = static_cast<u8>(inst.i.rt.GetValue());
    618       const bool bgez = ConvertToBoolUnchecked(rt & u8(1));
    619       const bool link = ConvertToBoolUnchecked((rt >> 4) & u8(1));
    620       if (link)
    621         FormatInstruction(dest, inst, pc, bgez ? "bgezal $rs, $rel" : "bltzal $rs, $rel");
    622       else
    623         FormatInstruction(dest, inst, pc, bgez ? "bgez $rs, $rel" : "bltz $rs, $rel");
    624     }
    625     break;
    626 
    627     default:
    628       FormatInstruction(dest, inst, pc, s_base_table[static_cast<u8>(inst.op.GetValue())]);
    629       break;
    630   }
    631 }
    632 
    633 void CPU::DisassembleInstructionComment(SmallStringBase* dest, u32 pc, u32 bits)
    634 {
    635   const Instruction inst{bits};
    636   switch (inst.op)
    637   {
    638     case InstructionOp::funct:
    639       FormatComment(dest, inst, pc, s_special_table[static_cast<u8>(inst.r.funct.GetValue())]);
    640       return;
    641 
    642     case InstructionOp::cop0:
    643     case InstructionOp::cop1:
    644     case InstructionOp::cop2:
    645     case InstructionOp::cop3:
    646     {
    647       if (inst.cop.IsCommonInstruction())
    648       {
    649         FormatCopComment(dest, pc, inst, s_cop_common_table.data(), s_cop_common_table.size(), inst.cop.CommonOp());
    650       }
    651       else
    652       {
    653         switch (inst.op)
    654         {
    655           case InstructionOp::cop0:
    656           {
    657             FormatCopComment(dest, pc, inst, s_cop0_table.data(), s_cop0_table.size(), inst.cop.Cop0Op());
    658           }
    659           break;
    660 
    661           case InstructionOp::cop2:
    662             // TODO: Show GTE regs?
    663             break;
    664 
    665           case InstructionOp::cop1:
    666           case InstructionOp::cop3:
    667           default:
    668           {
    669             dest->format("<cop{} 0x{:08X}>", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue());
    670           }
    671           break;
    672         }
    673       }
    674     }
    675     break;
    676 
    677       // special case for bltz/bgez{al}
    678     case InstructionOp::b:
    679     {
    680       const u8 rt = static_cast<u8>(inst.i.rt.GetValue());
    681       const bool bgez = ConvertToBoolUnchecked(rt & u8(1));
    682       const bool link = ConvertToBoolUnchecked((rt >> 4) & u8(1));
    683       if (link)
    684         FormatComment(dest, inst, pc, bgez ? "bgezal $rs, $rel" : "bltzal $rs, $rel");
    685       else
    686         FormatComment(dest, inst, pc, bgez ? "bgez $rs, $rel" : "bltz $rs, $rel");
    687     }
    688     break;
    689 
    690     default:
    691       FormatComment(dest, inst, pc, s_base_table[static_cast<u8>(inst.op.GetValue())]);
    692       break;
    693   }
    694 }
    695 
    696 const char* CPU::GetGTERegisterName(u32 index)
    697 {
    698   return (index < s_gte_register_names.size()) ? s_gte_register_names[index] : "";
    699 }