Repository: Decompollaborate/rabbitizer (language: python)

Issue:
PS1's GTE instruction set support
Support for the "Geometry Transformation Engine" (GTE) instruction set, which is exclusive for the PS1 machines.

Proper documentation for how to decode the instructions is required and has not been found yet


Discussion / hints:
Was talking with Sonic yesterday, and I did find this. You might have already have it, but I figured it would make sense to share here: http://www.raphnet.net/electronique/psx_adaptor/Playstation.txt
I have seen that document before, yes, but unless I'm missing something obvious, it doesn't have any information on how to _decode_ the instructions themselves, only how to use them and what they do (from a development perspective)
Do we have some as-of-yet unhandled byte sequences? Because Sonic's code did contain some `CTC2` and `COP2` instructions, which I assume this was about
Sonic sent me a few days ago a function which had a `0x4A180001` instruction, which is a Coprocessor 2 instruction, but we don't know much besides that
That one is documented in the linked txt.

    Name	Cycles	Command	Description	
    RTPS	15	cop2 0x0180001	Perspective transform	
But it doesn't provide any type of mnemonic or registers it uses, unless it takes all this parameters:
![image](https://user-images.githubusercontent.com/7416381/235234609-cb4b0b68-f6fa-4281-bf75-a621da6f519e.png)

The code I saw from sonic wrote stuff to all those registers using `CTC2` opcodes
References:
- https://github.com/FoxdieTeam/psyq_sdk/blob/master/psyq_4.4/INCLUDE/INLINE_S.H
- https://problemkaputt.de/psxspx-gte-opcode-summary.htm
- https://psx-spx.consoledev.net/geometrytransformationenginegte/#gte-opcode-summary

Fix patch:
diff --git a/Makefile b/Makefile
index 0de78e35..f2eeed78 100644
--- a/Makefile
+++ b/Makefile
@@ -111,6 +111,7 @@ clean:
 distclean: clean
 	$(RM) -rf dist *.egg-info .mypy_cache
 	$(RM) -rf $(TABLE_GENERATED)
+	$(RM) -rf $(DEP_FILES) $(TABLE_DEP_FILES)
 	$(RM) -rf target/
 
 format:
diff --git a/README.md b/README.md
index 79fed5a7..fad214e8 100644
--- a/README.md
+++ b/README.md
@@ -93,6 +93,12 @@ See this crate at <https://crates.io/crates/rabbitizer>.
   - Nintendo Ultra64 RSP Programmer’s Guide: <https://ultra64.ca/files/documentation/silicon-graphics/SGI_Nintendo_64_RSP_Programmers_Guide.pdf>
   - N64brew Reality Signal Processor/CPU Core: <https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core>
 
+- R3000 GTE:
+  - PSYQ SDK headers: <https://github.com/FoxdieTeam/psyq_sdk/blob/master/psyq_4.4/INCLUDE/INLINE_A.H>
+  - no$psx documentation: <https://problemkaputt.de/psxspx-gte-opcode-summary.htm>
+  - no$psx documentation: <http://problemkaputt.de/psx-spx.htm#geometrytransformationenginegte>
+  - <http://www.raphnet.net/electronique/psx_adaptor/Playstation.txt>
+
 - R5900:
   - EmotionEngine instruction decoding: <https://psi-rockin.github.io/ps2tek/#eeinstructiondecoding>
   - Official documentation from Toshiba: <https://wiki.qemu.org/images/2/2a/C790.pdf>
diff --git a/cplusplus/include/instructions/OperandType_enum_class.table.h b/cplusplus/include/instructions/OperandType_enum_class.table.h
index bc71aee8..ed85bbcf 100644
--- a/cplusplus/include/instructions/OperandType_enum_class.table.h
+++ b/cplusplus/include/instructions/OperandType_enum_class.table.h
@@ -45,6 +45,11 @@ rsp_vs_index,
 rsp_offset_rs,
 rsp_immediate_base,
 rsp_maybe_rd_rs,
+    r3000gte_sf,
+    r3000gte_mx,
+    r3000gte_v,
+    r3000gte_cv,
+    r3000gte_lm,
 r5900_I,
 r5900_Q,
 r5900_R,
diff --git a/cplusplus/include/instructions/OperandType_enum_class.table.template b/cplusplus/include/instructions/OperandType_enum_class.table.template
index 983433a4..cb178fec 100644
--- a/cplusplus/include/instructions/OperandType_enum_class.table.template
+++ b/cplusplus/include/instructions/OperandType_enum_class.table.template
@@ -9,6 +9,7 @@ enum class OperandType {
 
 #include "instructions/operands/RabbitizerOperandType_cpu.inc"
 #include "instructions/operands/RabbitizerOperandType_rsp.inc"
+#include "instructions/operands/RabbitizerOperandType_r3000gte.inc"
 #include "instructions/operands/RabbitizerOperandType_r5900.inc"
 
     RAB_DEF_OPERAND(ALL, MAX)
diff --git a/cplusplus/include/instructions/UniqueId_enum_class.table.h b/cplusplus/include/instructions/UniqueId_enum_class.table.h
index 8ac59bef..29acdfd4 100644
--- a/cplusplus/include/instructions/UniqueId_enum_class.table.h
+++ b/cplusplus/include/instructions/UniqueId_enum_class.table.h
@@ -414,6 +414,50 @@ rsp_USERDEF_17,
 rsp_USERDEF_18,
 rsp_USERDEF_19,
 rsp_MAX,
+r3000gte_INVALID,
+r3000gte_RTPS,
+r3000gte_RTPT,
+r3000gte_DPCL,
+r3000gte_DPCS,
+r3000gte_DPCT,
+r3000gte_INTPL,
+r3000gte_NCS,
+r3000gte_NCT,
+r3000gte_NCDS,
+r3000gte_NCDT,
+r3000gte_NCCS,
+r3000gte_NCCT,
+r3000gte_CDP,
+r3000gte_CC,
+r3000gte_NCLIP,
+r3000gte_AVSZ3,
+r3000gte_AVSZ4,
+r3000gte_MVMVA,
+r3000gte_SQR,
+r3000gte_OP,
+r3000gte_GPF,
+r3000gte_GPL,
+r3000gte_USERDEF_00,
+r3000gte_USERDEF_01,
+r3000gte_USERDEF_02,
+r3000gte_USERDEF_03,
+r3000gte_USERDEF_04,
+r3000gte_USERDEF_05,
+r3000gte_USERDEF_06,
+r3000gte_USERDEF_07,
+r3000gte_USERDEF_08,
+r3000gte_USERDEF_09,
+r3000gte_USERDEF_10,
+r3000gte_USERDEF_11,
+r3000gte_USERDEF_12,
+r3000gte_USERDEF_13,
+r3000gte_USERDEF_14,
+r3000gte_USERDEF_15,
+r3000gte_USERDEF_16,
+r3000gte_USERDEF_17,
+r3000gte_USERDEF_18,
+r3000gte_USERDEF_19,
+r3000gte_MAX,
 r5900_INVALID,
 r5900_lq,
 r5900_sq,
diff --git a/cplusplus/include/instructions/UniqueId_enum_class.table.template b/cplusplus/include/instructions/UniqueId_enum_class.table.template
index e5467639..954779ec 100644
--- a/cplusplus/include/instructions/UniqueId_enum_class.table.template
+++ b/cplusplus/include/instructions/UniqueId_enum_class.table.template
@@ -12,6 +12,8 @@ enum class UniqueId {
 
     #include "instructions/instr_id/RabbitizerInstrId_rsp.inc"
 
+    #include "instructions/instr_id/RabbitizerInstrId_r3000gte.inc"
+
     #include "instructions/instr_id/RabbitizerInstrId_r5900.inc"
 
     ALL_MAX = RABBITIZER_DEF_INSTR_ID(r5900, , MAX, )
diff --git a/include/instructions/InstrCategory.inc b/include/instructions/InstrCategory.inc
index 9a6d85c1..48f56895 100644
--- a/include/instructions/InstrCategory.inc
+++ b/include/instructions/InstrCategory.inc
@@ -1,6 +1,7 @@
-/* SPDX-FileCopyrightText: © 2022 Decompollaborate */
+/* SPDX-FileCopyrightText: © 2022-2023 Decompollaborate */
 /* SPDX-License-Identifier: MIT */
 
 RABBITIZER_DEF_INSTR_CATEGORY(CPU),
 RABBITIZER_DEF_INSTR_CATEGORY(RSP), // N64
+RABBITIZER_DEF_INSTR_CATEGORY(R3000GTE), // R3000 CPU with PS1's Geometry Transformation Engine extension
 RABBITIZER_DEF_INSTR_CATEGORY(R5900), // PS2's Emotion Engine
diff --git a/include/instructions/InstrCategory_enum.table.h b/include/instructions/InstrCategory_enum.table.h
index 401be3f2..7afe180a 100644
--- a/include/instructions/InstrCategory_enum.table.h
+++ b/include/instructions/InstrCategory_enum.table.h
@@ -9,6 +9,7 @@
 typedef enum RabbitizerInstrCategory {
 RABBITIZER_INSTRCAT_CPU,
 RABBITIZER_INSTRCAT_RSP,
+RABBITIZER_INSTRCAT_R3000GTE,
 RABBITIZER_INSTRCAT_R5900,
     RABBITIZER_INSTRCAT_MAX,
 } RabbitizerInstrCategory;
diff --git a/include/instructions/InstrId_enum.table.h b/include/instructions/InstrId_enum.table.h
index 78baa7b7..559a7c9d 100644
--- a/include/instructions/InstrId_enum.table.h
+++ b/include/instructions/InstrId_enum.table.h
@@ -414,6 +414,50 @@ RABBITIZER_INSTR_ID_rsp_USERDEF_17,
 RABBITIZER_INSTR_ID_rsp_USERDEF_18,
 RABBITIZER_INSTR_ID_rsp_USERDEF_19,
 RABBITIZER_INSTR_ID_rsp_MAX,
+RABBITIZER_INSTR_ID_r3000gte_INVALID,
+RABBITIZER_INSTR_ID_r3000gte_RTPS,
+RABBITIZER_INSTR_ID_r3000gte_RTPT,
+RABBITIZER_INSTR_ID_r3000gte_DPCL,
+RABBITIZER_INSTR_ID_r3000gte_DPCS,
+RABBITIZER_INSTR_ID_r3000gte_DPCT,
+RABBITIZER_INSTR_ID_r3000gte_INTPL,
+RABBITIZER_INSTR_ID_r3000gte_NCS,
+RABBITIZER_INSTR_ID_r3000gte_NCT,
+RABBITIZER_INSTR_ID_r3000gte_NCDS,
+RABBITIZER_INSTR_ID_r3000gte_NCDT,
+RABBITIZER_INSTR_ID_r3000gte_NCCS,
+RABBITIZER_INSTR_ID_r3000gte_NCCT,
+RABBITIZER_INSTR_ID_r3000gte_CDP,
+RABBITIZER_INSTR_ID_r3000gte_CC,
+RABBITIZER_INSTR_ID_r3000gte_NCLIP,
+RABBITIZER_INSTR_ID_r3000gte_AVSZ3,
+RABBITIZER_INSTR_ID_r3000gte_AVSZ4,
+RABBITIZER_INSTR_ID_r3000gte_MVMVA,
+RABBITIZER_INSTR_ID_r3000gte_SQR,
+RABBITIZER_INSTR_ID_r3000gte_OP,
+RABBITIZER_INSTR_ID_r3000gte_GPF,
+RABBITIZER_INSTR_ID_r3000gte_GPL,
+RABBITIZER_INSTR_ID_r3000gte_USERDEF_00,
+RABBITIZER_INSTR_ID_r3000gte_USERDEF_01,
+RABBITIZER_INSTR_ID_r3000gte_USERDEF_02,
+RABBITIZER_INSTR_ID_r3000gte_USERDEF_03,
+RABBITIZER_INSTR_ID_r3000gte_USERDEF_04,
+RABBITIZER_INSTR_ID_r3000gte_USERDEF_05,
+RABBITIZER_INSTR_ID_r3000gte_USERDEF_06,
+RABBITIZER_INSTR_ID_r3000gte_USERDEF_07,
+RABBITIZER_INSTR_ID_r3000gte_USERDEF_08,
+RABBITIZER_INSTR_ID_r3000gte_USERDEF_09,
+RABBITIZER_INSTR_ID_r3000gte_USERDEF_10,
+RABBITIZER_INSTR_ID_r3000gte_USERDEF_11,
+RABBITIZER_INSTR_ID_r3000gte_USERDEF_12,
+RABBITIZER_INSTR_ID_r3000gte_USERDEF_13,
+RABBITIZER_INSTR_ID_r3000gte_USERDEF_14,
+RABBITIZER_INSTR_ID_r3000gte_USERDEF_15,
+RABBITIZER_INSTR_ID_r3000gte_USERDEF_16,
+RABBITIZER_INSTR_ID_r3000gte_USERDEF_17,
+RABBITIZER_INSTR_ID_r3000gte_USERDEF_18,
+RABBITIZER_INSTR_ID_r3000gte_USERDEF_19,
+RABBITIZER_INSTR_ID_r3000gte_MAX,
 RABBITIZER_INSTR_ID_r5900_INVALID,
 RABBITIZER_INSTR_ID_r5900_lq,
 RABBITIZER_INSTR_ID_r5900_sq,
diff --git a/include/instructions/InstrId_enum.table.template b/include/instructions/InstrId_enum.table.template
index 86a831b0..bb8bbede 100644
--- a/include/instructions/InstrId_enum.table.template
+++ b/include/instructions/InstrId_enum.table.template
@@ -12,6 +12,8 @@ typedef enum RabbitizerInstrId {
 
     #include "instr_id/RabbitizerInstrId_rsp.inc"
 
+    #include "instr_id/RabbitizerInstrId_r3000gte.inc"
+
     #include "instr_id/RabbitizerInstrId_r5900.inc"
 
     RABBITIZER_INSTR_ID_ALL_MAX = RABBITIZER_DEF_INSTR_ID(r5900, , MAX, )
diff --git a/include/instructions/OperandType_enum.table.h b/include/instructions/OperandType_enum.table.h
index 0f01e7b0..8ec3dbbc 100644
--- a/include/instructions/OperandType_enum.table.h
+++ b/include/instructions/OperandType_enum.table.h
@@ -45,6 +45,11 @@ RAB_OPERAND_rsp_vs_index,
 RAB_OPERAND_rsp_offset_rs,
 RAB_OPERAND_rsp_immediate_base,
 RAB_OPERAND_rsp_maybe_rd_rs,
+    RAB_OPERAND_r3000gte_sf,
+    RAB_OPERAND_r3000gte_mx,
+    RAB_OPERAND_r3000gte_v,
+    RAB_OPERAND_r3000gte_cv,
+    RAB_OPERAND_r3000gte_lm,
 RAB_OPERAND_r5900_I,
 RAB_OPERAND_r5900_Q,
 RAB_OPERAND_r5900_R,
diff --git a/include/instructions/OperandType_enum.table.template b/include/instructions/OperandType_enum.table.template
index ba6f522f..41f495b5 100644
--- a/include/instructions/OperandType_enum.table.template
+++ b/include/instructions/OperandType_enum.table.template
@@ -9,6 +9,7 @@ typedef enum RabbitizerOperandType {
 
 #include "operands/RabbitizerOperandType_cpu.inc"
 #include "operands/RabbitizerOperandType_rsp.inc"
+#include "instructions/operands/RabbitizerOperandType_r3000gte.inc"
 #include "operands/RabbitizerOperandType_r5900.inc"
 
     RAB_DEF_OPERAND(ALL, MAX)
diff --git a/include/instructions/OperandType_function_declarations.table.h b/include/instructions/OperandType_function_declarations.table.h
index 96af42a2..ad74e116 100644
--- a/include/instructions/OperandType_function_declarations.table.h
+++ b/include/instructions/OperandType_function_declarations.table.h
@@ -43,6 +43,11 @@ size_t RabbitizerOperandType_process_rsp_vs_index (const struct RabbitizerInstru
 size_t RabbitizerOperandType_process_rsp_offset_rs (const struct RabbitizerInstruction *self, char *dst, const char *immOverride, size_t immOverrideLength);
 size_t RabbitizerOperandType_process_rsp_immediate_base (const struct RabbitizerInstruction *self, char *dst, const char *immOverride, size_t immOverrideLength);
 size_t RabbitizerOperandType_process_rsp_maybe_rd_rs (const struct RabbitizerInstruction *self, char *dst, const char *immOverride, size_t immOverrideLength);
+    size_t RabbitizerOperandType_process_r3000gte_sf (const struct RabbitizerInstruction *self, char *dst, const char *immOverride, size_t immOverrideLength);
+    size_t RabbitizerOperandType_process_r3000gte_mx (const struct RabbitizerInstruction *self, char *dst, const char *immOverride, size_t immOverrideLength);
+    size_t RabbitizerOperandType_process_r3000gte_v (const struct RabbitizerInstruction *self, char *dst, const char *immOverride, size_t immOverrideLength);
+    size_t RabbitizerOperandType_process_r3000gte_cv (const struct RabbitizerInstruction *self, char *dst, const char *immOverride, size_t immOverrideLength);
+    size_t RabbitizerOperandType_process_r3000gte_lm (const struct RabbitizerInstruction *self, char *dst, const char *immOverride, size_t immOverrideLength);
 size_t RabbitizerOperandType_process_r5900_I (const struct RabbitizerInstruction *self, char *dst, const char *immOverride, size_t immOverrideLength);
 size_t RabbitizerOperandType_process_r5900_Q (const struct RabbitizerInstruction *self, char *dst, const char *immOverride, size_t immOverrideLength);
 size_t RabbitizerOperandType_process_r5900_R (const struct RabbitizerInstruction *self, char *dst, const char *immOverride, size_t immOverrideLength);
diff --git a/include/instructions/OperandType_function_declarations.table.template b/include/instructions/OperandType_function_declarations.table.template
index f414eb47..e5a4beb8 100644
--- a/include/instructions/OperandType_function_declarations.table.template
+++ b/include/instructions/OperandType_function_declarations.table.template
@@ -5,6 +5,7 @@
 
 #include "operands/RabbitizerOperandType_cpu.inc"
 #include "operands/RabbitizerOperandType_rsp.inc"
+#include "instructions/operands/RabbitizerOperandType_r3000gte.inc"
 #include "operands/RabbitizerOperandType_r5900.inc"
 
 #undef RAB_DEF_OPERAND
diff --git a/include/instructions/RabbitizerInstrDescriptor.h b/include/instructions/RabbitizerInstrDescriptor.h
index f453b6bf..f59e5232 100644
--- a/include/instructions/RabbitizerInstrDescriptor.h
+++ b/include/instructions/RabbitizerInstrDescriptor.h
@@ -28,10 +28,13 @@ typedef enum RabbitizerInstrType {
     RABBITIZER_INSTR_TYPE_MAX,
 } RabbitizerInstrType;
 
+
+#define OPERAND_COUNT_MAX 5
+
 // Please note the members of this struct may be renamed or removed without further notice.
 // For consistent usage please use the functions instead
 typedef struct RabbitizerInstrDescriptor {
-    RabbitizerOperandType operands[4];
+    RabbitizerOperandType operands[OPERAND_COUNT_MAX+1];
     RabbitizerInstrType instrType;
 
     RabbitizerInstrSuffix instrSuffix;
diff --git a/include/instructions/RabbitizerInstructionR3000GTE.h b/include/instructions/RabbitizerInstructionR3000GTE.h
new file mode 100644
index 00000000..218cdbc9
--- /dev/null
+++ b/include/instructions/RabbitizerInstructionR3000GTE.h
@@ -0,0 +1,61 @@
+/* SPDX-FileCopyrightText: © 2023 Decompollaborate */
+/* SPDX-License-Identifier: MIT */
+
+#ifndef RABBITIZER_INSTRUCTION_R3000GTE_H
+#define RABBITIZER_INSTRUCTION_R3000GTE_H
+#pragma once
+
+#include "RabbitizerInstruction.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#define RAB_INSTR_R3000GTE_GET_FAKE_OPCODE(self)            (SHIFTR((self)->word, 20,  5))
+
+#define RAB_INSTR_R3000GTE_GET_sf(self)                     (SHIFTR((self)->word, 19,  1))
+#define RAB_INSTR_R3000GTE_GET_mx(self)                     (SHIFTR((self)->word, 17,  2))
+#define RAB_INSTR_R3000GTE_GET_v(self)                      (SHIFTR((self)->word, 15,  2))
+#define RAB_INSTR_R3000GTE_GET_cv(self)                     (SHIFTR((self)->word, 13,  2))
+#define RAB_INSTR_R3000GTE_GET_lm(self)                     (SHIFTR((self)->word, 10,  1))
+
+
+#define RAB_INSTR_R3000GTE_PACK_FAKE_OPCODE(word, value)    (BITREPACK((word), (value), 20,  5))
+
+#define RAB_INSTR_R3000GTE_PACK_sf(word, value)             (BITREPACK((word), (value), 19,  1))
+#define RAB_INSTR_R3000GTE_PACK_mx(word, value)             (BITREPACK((word), (value), 17,  2))
+#define RAB_INSTR_R3000GTE_PACK_v(word, value)              (BITREPACK((word), (value), 15,  2))
+#define RAB_INSTR_R3000GTE_PACK_cv(word, value)             (BITREPACK((word), (value), 13,  2))
+#define RAB_INSTR_R3000GTE_PACK_lm(word, value)             (BITREPACK((word), (value), 10,  1))
+
+
+
+NON_NULL(1)
+void RabbitizerInstructionR3000GTE_init(RabbitizerInstruction *self, uint32_t word, uint32_t vram);
+NON_NULL(1)
+void RabbitizerInstructionR3000GTE_destroy(RabbitizerInstruction *self);
+
+NON_NULL(1)
+void RabbitizerInstructionR3000GTE_processUniqueId_Normal(RabbitizerInstruction *self);
+NON_NULL(1)
+void RabbitizerInstructionR3000GTE_processUniqueId_Special(RabbitizerInstruction *self);
+NON_NULL(1)
+void RabbitizerInstructionR3000GTE_processUniqueId_Regimm(RabbitizerInstruction *self);
+NON_NULL(1)
+void RabbitizerInstructionR3000GTE_processUniqueId_Coprocessor0(RabbitizerInstruction *self);
+NON_NULL(1)
+void RabbitizerInstructionR3000GTE_processUniqueId_Coprocessor1(RabbitizerInstruction *self);
+NON_NULL(1)
+void RabbitizerInstructionR3000GTE_processUniqueId_Coprocessor2(RabbitizerInstruction *self);
+
+NON_NULL(1)
+void RabbitizerInstructionR3000GTE_processUniqueId(RabbitizerInstruction *self);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/instructions/instr_id/RabbitizerInstrId_r3000gte.inc b/include/instructions/instr_id/RabbitizerInstrId_r3000gte.inc
new file mode 100644
index 00000000..1dc1b579
--- /dev/null
+++ b/include/instructions/instr_id/RabbitizerInstrId_r3000gte.inc
@@ -0,0 +1,96 @@
+/* SPDX-FileCopyrightText: © 2023 Decompollaborate */
+/* SPDX-License-Identifier: MIT */
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, , INVALID,
+    .operands={RAB_OPERAND_cpu_rs, RAB_OPERAND_cpu_rt, RAB_OPERAND_cpu_immediate},
+    .instrType=RABBITIZER_INSTR_TYPE_UNKNOWN
+)
+
+#include "r3000gte/r3000gte_cop2_gte.inc"
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, , USERDEF_00,
+    .operands={0}
+)
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, , USERDEF_01,
+    .operands={0}
+)
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, , USERDEF_02,
+    .operands={0}
+)
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, , USERDEF_03,
+    .operands={0}
+)
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, , USERDEF_04,
+    .operands={0}
+)
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, , USERDEF_05,
+    .operands={0}
+)
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, , USERDEF_06,
+    .operands={0}
+)
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, , USERDEF_07,
+    .operands={0}
+)
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, , USERDEF_08,
+    .operands={0}
+)
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, , USERDEF_09,
+    .operands={0}
+)
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, , USERDEF_10,
+    .operands={0}
+)
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, , USERDEF_11,
+    .operands={0}
+)
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, , USERDEF_12,
+    .operands={0}
+)
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, , USERDEF_13,
+    .operands={0}
+)
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, , USERDEF_14,
+    .operands={0}
+)
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, , USERDEF_15,
+    .operands={0}
+)
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, , USERDEF_16,
+    .operands={0}
+)
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, , USERDEF_17,
+    .operands={0}
+)
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, , USERDEF_18,
+    .operands={0}
+)
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, , USERDEF_19,
+    .operands={0}
+)
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, , MAX,
+    .operands={0}
+)
diff --git a/include/instructions/instr_id/r3000gte/r3000gte_cop2_gte.inc b/include/instructions/instr_id/r3000gte/r3000gte_cop2_gte.inc
new file mode 100644
index 00000000..538ac2d0
--- /dev/null
+++ b/include/instructions/instr_id/r3000gte/r3000gte_cop2_gte.inc
@@ -0,0 +1,114 @@
+/* SPDX-FileCopyrightText: © 2023 Decompollaborate */
+/* SPDX-License-Identifier: MIT */
+
+/* Indexed by "function" operand */
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, 0x01, RTPS,
+    .operands={0}
+)
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, 0x30, RTPT,
+    .operands={0}
+)
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, 0x29, DPCL,
+    .operands={0}
+)
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, 0x10, DPCS,
+    .operands={0}
+)
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, 0x2A, DPCT,
+    .operands={0}
+)
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, 0x11, INTPL,
+    .operands={0}
+)
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, 0x1E, NCS,
+    .operands={0}
+)
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, 0x20, NCT,
+    .operands={0}
+)
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, 0x13, NCDS,
+    .operands={0}
+)
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, 0x16, NCDT,
+    .operands={0}
+)
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, 0x1B, NCCS,
+    .operands={0}
+)
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, 0x3F, NCCT,
+    .operands={0}
+)
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, 0x14, CDP,
+    .operands={0}
+)
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, 0x1C, CC,
+    .operands={0}
+)
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, 0x06, NCLIP,
+    .operands={0}
+)
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, 0x2D, AVSZ3,
+    .operands={0}
+)
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, 0x2E, AVSZ4,
+    .operands={0}
+)
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, 0x12, MVMVA,
+    .operands={RAB_OPERAND_r3000gte_sf, RAB_OPERAND_r3000gte_mx, RAB_OPERAND_r3000gte_v, RAB_OPERAND_r3000gte_cv, RAB_OPERAND_r3000gte_lm}
+)
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, 0x28, SQR,
+    .operands={RAB_OPERAND_r3000gte_sf}
+)
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, 0x0C, OP,
+    .operands={RAB_OPERAND_r3000gte_sf}
+)
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, 0x3D, GPF,
+    .operands={RAB_OPERAND_r3000gte_sf}
+)
+
+RABBITIZER_DEF_INSTR_ID(
+    r3000gte, 0x3E, GPL,
+    .operands={RAB_OPERAND_r3000gte_sf}
+)
diff --git a/include/instructions/operands/RabbitizerOperandType_r3000gte.inc b/include/instructions/operands/RabbitizerOperandType_r3000gte.inc
new file mode 100644
index 00000000..5b6470b5
--- /dev/null
+++ b/include/instructions/operands/RabbitizerOperandType_r3000gte.inc
@@ -0,0 +1,8 @@
+/* SPDX-FileCopyrightText: © 2023 Decompollaborate */
+/* SPDX-License-Identifier: MIT */
+
+    RAB_DEF_OPERAND(r3000gte, sf)
+    RAB_DEF_OPERAND(r3000gte, mx)
+    RAB_DEF_OPERAND(r3000gte, v)
+    RAB_DEF_OPERAND(r3000gte, cv)
+    RAB_DEF_OPERAND(r3000gte, lm)
diff --git a/include/rabbitizer.h b/include/rabbitizer.h
index 73cdb269..cc767cea 100644
--- a/include/rabbitizer.h
+++ b/include/rabbitizer.h
@@ -17,6 +17,7 @@
 #include "instructions/RabbitizerRegisterDescriptor.h"
 #include "instructions/RabbitizerInstruction.h"
 #include "instructions/RabbitizerInstructionRsp.h"
+#include "instructions/RabbitizerInstructionR3000GTE.h"
 #include "instructions/RabbitizerInstructionR5900.h"
 
 #include "analysis/RabbitizerTrackedRegisterState.h"
diff --git a/rabbitizer/InstrCategory.pyi b/rabbitizer/InstrCategory.pyi
index 366af3f6..346d25bf 100644
--- a/rabbitizer/InstrCategory.pyi
+++ b/rabbitizer/InstrCategory.pyi
@@ -11,6 +11,7 @@ from .Enum import Enum
 class InstrCategory:
     CPU: Enum
     RSP: Enum
+    R3000GTE: Enum
     R5900: Enum
     MAX: Enum
 
diff --git a/rabbitizer/InstrId.pyi b/rabbitizer/InstrId.pyi
index 5dd8eccc..58a3bc0b 100644
--- a/rabbitizer/InstrId.pyi
+++ b/rabbitizer/InstrId.pyi
@@ -377,6 +377,31 @@ class InstrId:
     rsp_negu: Enum
     rsp_MAX: Enum
 
+    r3000gte_INVALID: Enum
+    r3000gte_RTPS: Enum
+    r3000gte_RTPT: Enum
+    r3000gte_DPCL: Enum
+    r3000gte_DPCS: Enum
+    r3000gte_DPCT: Enum
+    r3000gte_INTPL: Enum
+    r3000gte_NCS: Enum
+    r3000gte_NCT: Enum
+    r3000gte_NCDS: Enum
+    r3000gte_NCDT: Enum
+    r3000gte_NCCS: Enum
+    r3000gte_NCCT: Enum
+    r3000gte_CDP: Enum
+    r3000gte_CC: Enum
+    r3000gte_NCLIP: Enum
+    r3000gte_AVSZ3: Enum
+    r3000gte_AVSZ4: Enum
+    r3000gte_MVMVA: Enum
+    r3000gte_SQR: Enum
+    r3000gte_OP: Enum
+    r3000gte_GPF: Enum
+    r3000gte_GPL: Enum
+    r3000gte_MAX: Enum
+
     r5900_INVALID: Enum
     r5900_lq: Enum
     r5900_sq: Enum
diff --git a/rabbitizer/OperandType.pyi b/rabbitizer/OperandType.pyi
index 18011ee3..06bddb6f 100644
--- a/rabbitizer/OperandType.pyi
+++ b/rabbitizer/OperandType.pyi
@@ -49,6 +49,12 @@ class OperandType:
     rsp_immediate_base: Enum
     rsp_maybe_rd_rs: Enum
 
+    r3000gte_sf: Enum
+    r3000gte_mx: Enum
+    r3000gte_v: Enum
+    r3000gte_cv: Enum
+    r3000gte_lm: Enum
+
     r5900_I: Enum
     r5900_Q: Enum
     r5900_R: Enum
diff --git a/rabbitizer/enums/rabbitizer_enum_InstrId.c b/rabbitizer/enums/rabbitizer_enum_InstrId.c
index fabf612d..ca18b658 100644
--- a/rabbitizer/enums/rabbitizer_enum_InstrId.c
+++ b/rabbitizer/enums/rabbitizer_enum_InstrId.c
@@ -13,6 +13,8 @@ RabbitizerEnumMetadata rabbitizer_enum_InstrId_enumvalues[] = {
 
     #include "instructions/instr_id/RabbitizerInstrId_rsp.inc"
 
+    #include "instructions/instr_id/RabbitizerInstrId_r3000gte.inc"
+
     #include "instructions/instr_id/RabbitizerInstrId_r5900.inc"
 
     RABBITIZER_DEF_INSTR_ID(ALL, , MAX, )
diff --git a/rabbitizer/enums/rabbitizer_enum_OperandType.c b/rabbitizer/enums/rabbitizer_enum_OperandType.c
index 8238627e..abf260cf 100644
--- a/rabbitizer/enums/rabbitizer_enum_OperandType.c
+++ b/rabbitizer/enums/rabbitizer_enum_OperandType.c
@@ -12,6 +12,7 @@ RabbitizerEnumMetadata rabbitizer_enum_OperandType_enumvalues[] = {
 
 #include "instructions/operands/RabbitizerOperandType_cpu.inc"
 #include "instructions/operands/RabbitizerOperandType_rsp.inc"
+#include "instructions/operands/RabbitizerOperandType_r3000gte.inc"
 #include "instructions/operands/RabbitizerOperandType_r5900.inc"
 
     RAB_DEF_OPERAND(ALL, MAX)
diff --git a/rabbitizer/rabbitizer_type_Instruction.c b/rabbitizer/rabbitizer_type_Instruction.c
index f768fd48..c7aa24e4 100644
--- a/rabbitizer/rabbitizer_type_Instruction.c
+++ b/rabbitizer/rabbitizer_type_Instruction.c
@@ -4,6 +4,7 @@
 #include "rabbitizer_module.h"
 
 #include "instructions/RabbitizerInstructionRsp.h"
+#include "instructions/RabbitizerInstructionR3000GTE.h"
 #include "instructions/RabbitizerInstructionR5900.h"
 #include "common/RabbitizerConfig.h"
 
@@ -44,6 +45,11 @@ static int rabbitizer_type_Instruction_init(PyRabbitizerInstruction *self, PyObj
             RabbitizerInstructionRsp_processUniqueId(&self->instr);
             break;
 
+        case RABBITIZER_INSTRCAT_R3000GTE:
+            RabbitizerInstructionR3000GTE_init(&self->instr, word, vram);
+            RabbitizerInstructionR3000GTE_processUniqueId(&self->instr);
+            break;
+
         case RABBITIZER_INSTRCAT_R5900:
             RabbitizerInstructionR5900_init(&self->instr, word, vram);
             RabbitizerInstructionR5900_processUniqueId(&self->instr);
diff --git a/rust/src/instr_id_enum.tablers.template b/rust/src/instr_id_enum.tablers.template
index da7a1313..7e37916a 100644
--- a/rust/src/instr_id_enum.tablers.template
+++ b/rust/src/instr_id_enum.tablers.template
@@ -19,6 +19,8 @@ pub enum InstrId {
 
     #include "instructions/instr_id/RabbitizerInstrId_rsp.inc"
 
+    #include "instructions/instr_id/RabbitizerInstrId_r3000gte.inc"
+
     #include "instructions/instr_id/RabbitizerInstrId_r5900.inc"
 }
 
diff --git a/rust/src/operand_type_enum.tablers.template b/rust/src/operand_type_enum.tablers.template
index a4c5be36..69135b1e 100644
--- a/rust/src/operand_type_enum.tablers.template
+++ b/rust/src/operand_type_enum.tablers.template
@@ -16,6 +16,7 @@ pub enum OperandType {
 
 #include "instructions/operands/RabbitizerOperandType_cpu.inc"
 #include "instructions/operands/RabbitizerOperandType_rsp.inc"
+#include "instructions/operands/RabbitizerOperandType_r3000gte.inc"
 #include "instructions/operands/RabbitizerOperandType_r5900.inc"
 
     RAB_DEF_OPERAND(ALL, MAX)
diff --git a/src/instructions/InstrCategory_Names_array.table.h b/src/instructions/InstrCategory_Names_array.table.h
index f7a4d1ca..45165da5 100644
--- a/src/instructions/InstrCategory_Names_array.table.h
+++ b/src/instructions/InstrCategory_Names_array.table.h
@@ -9,6 +9,7 @@
 const char *const RabbitizerInstrCategory_Names[] = {
 [RABBITIZER_INSTRCAT_CPU] = "CPU",
 [RABBITIZER_INSTRCAT_RSP] = "RSP",
+[RABBITIZER_INSTRCAT_R3000GTE] = "R3000GTE",
 [RABBITIZER_INSTRCAT_R5900] = "R5900",
 };
 
diff --git a/src/instructions/InstrDescriptor_Descriptors_array.table.h b/src/instructions/InstrDescriptor_Descriptors_array.table.h
index 0d8a6bad..082a902f 100644
--- a/src/instructions/InstrDescriptor_Descriptors_array.table.h
+++ b/src/instructions/InstrDescriptor_Descriptors_array.table.h
@@ -414,6 +414,50 @@ const RabbitizerInstrDescriptor RabbitizerInstrDescriptor_Descriptors[] = {
 [RABBITIZER_INSTR_ID_rsp_USERDEF_18] = { .operands={0} },
 [RABBITIZER_INSTR_ID_rsp_USERDEF_19] = { .operands={0} },
 [RABBITIZER_INSTR_ID_rsp_MAX] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_INVALID] = { .operands={RAB_OPERAND_cpu_rs, RAB_OPERAND_cpu_rt, RAB_OPERAND_cpu_immediate}, .instrType=RABBITIZER_INSTR_TYPE_UNKNOWN },
+[RABBITIZER_INSTR_ID_r3000gte_RTPS] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_RTPT] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_DPCL] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_DPCS] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_DPCT] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_INTPL] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_NCS] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_NCT] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_NCDS] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_NCDT] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_NCCS] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_NCCT] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_CDP] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_CC] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_NCLIP] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_AVSZ3] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_AVSZ4] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_MVMVA] = { .operands={RAB_OPERAND_r3000gte_sf, RAB_OPERAND_r3000gte_mx, RAB_OPERAND_r3000gte_v, RAB_OPERAND_r3000gte_cv, RAB_OPERAND_r3000gte_lm} },
+[RABBITIZER_INSTR_ID_r3000gte_SQR] = { .operands={RAB_OPERAND_r3000gte_sf} },
+[RABBITIZER_INSTR_ID_r3000gte_OP] = { .operands={RAB_OPERAND_r3000gte_sf} },
+[RABBITIZER_INSTR_ID_r3000gte_GPF] = { .operands={RAB_OPERAND_r3000gte_sf} },
+[RABBITIZER_INSTR_ID_r3000gte_GPL] = { .operands={RAB_OPERAND_r3000gte_sf} },
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_00] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_01] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_02] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_03] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_04] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_05] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_06] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_07] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_08] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_09] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_10] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_11] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_12] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_13] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_14] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_15] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_16] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_17] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_18] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_19] = { .operands={0} },
+[RABBITIZER_INSTR_ID_r3000gte_MAX] = { .operands={0} },
 [RABBITIZER_INSTR_ID_r5900_INVALID] = { .operands={RAB_OPERAND_cpu_rs, RAB_OPERAND_cpu_rt, RAB_OPERAND_cpu_immediate}, .instrType=RABBITIZER_INSTR_TYPE_UNKNOWN },
 [RABBITIZER_INSTR_ID_r5900_lq] = { .operands={RAB_OPERAND_cpu_rt, RAB_OPERAND_cpu_immediate_base}, .instrType=RABBITIZER_INSTR_TYPE_I, .modifiesRt=true, .readsRs=true, .canBeLo=true, .doesDereference=true, .doesLoad=true },
 [RABBITIZER_INSTR_ID_r5900_sq] = { .operands={RAB_OPERAND_cpu_rt, RAB_OPERAND_cpu_immediate_base}, .instrType=RABBITIZER_INSTR_TYPE_I, .readsRs=true, .readsRt=true, .canBeLo=true, .doesDereference=true, .doesStore=true },
diff --git a/src/instructions/InstrDescriptor_Descriptors_array.table.template b/src/instructions/InstrDescriptor_Descriptors_array.table.template
index 0038ad3a..47ed0dc5 100644
--- a/src/instructions/InstrDescriptor_Descriptors_array.table.template
+++ b/src/instructions/InstrDescriptor_Descriptors_array.table.template
@@ -8,6 +8,7 @@
 const RabbitizerInstrDescriptor RabbitizerInstrDescriptor_Descriptors[] = {
 #include "instructions/instr_id/RabbitizerInstrId_cpu.inc"
 #include "instructions/instr_id/RabbitizerInstrId_rsp.inc"
+#include "instructions/instr_id/RabbitizerInstrId_r3000gte.inc"
 #include "instructions/instr_id/RabbitizerInstrId_r5900.inc"
 };
 
diff --git a/src/instructions/InstrId_Names_array.table.h b/src/instructions/InstrId_Names_array.table.h
index 7300db81..7dd956ae 100644
--- a/src/instructions/InstrId_Names_array.table.h
+++ b/src/instructions/InstrId_Names_array.table.h
@@ -414,6 +414,50 @@ const char *RabbitizerInstrId_Names[] = {
 [RABBITIZER_INSTR_ID_rsp_USERDEF_18] = "USERDEF_18",
 [RABBITIZER_INSTR_ID_rsp_USERDEF_19] = "USERDEF_19",
 [RABBITIZER_INSTR_ID_rsp_MAX] = "MAX",
+[RABBITIZER_INSTR_ID_r3000gte_INVALID] = "INVALID",
+[RABBITIZER_INSTR_ID_r3000gte_RTPS] = "RTPS",
+[RABBITIZER_INSTR_ID_r3000gte_RTPT] = "RTPT",
+[RABBITIZER_INSTR_ID_r3000gte_DPCL] = "DPCL",
+[RABBITIZER_INSTR_ID_r3000gte_DPCS] = "DPCS",
+[RABBITIZER_INSTR_ID_r3000gte_DPCT] = "DPCT",
+[RABBITIZER_INSTR_ID_r3000gte_INTPL] = "INTPL",
+[RABBITIZER_INSTR_ID_r3000gte_NCS] = "NCS",
+[RABBITIZER_INSTR_ID_r3000gte_NCT] = "NCT",
+[RABBITIZER_INSTR_ID_r3000gte_NCDS] = "NCDS",
+[RABBITIZER_INSTR_ID_r3000gte_NCDT] = "NCDT",
+[RABBITIZER_INSTR_ID_r3000gte_NCCS] = "NCCS",
+[RABBITIZER_INSTR_ID_r3000gte_NCCT] = "NCCT",
+[RABBITIZER_INSTR_ID_r3000gte_CDP] = "CDP",
+[RABBITIZER_INSTR_ID_r3000gte_CC] = "CC",
+[RABBITIZER_INSTR_ID_r3000gte_NCLIP] = "NCLIP",
+[RABBITIZER_INSTR_ID_r3000gte_AVSZ3] = "AVSZ3",
+[RABBITIZER_INSTR_ID_r3000gte_AVSZ4] = "AVSZ4",
+[RABBITIZER_INSTR_ID_r3000gte_MVMVA] = "MVMVA",
+[RABBITIZER_INSTR_ID_r3000gte_SQR] = "SQR",
+[RABBITIZER_INSTR_ID_r3000gte_OP] = "OP",
+[RABBITIZER_INSTR_ID_r3000gte_GPF] = "GPF",
+[RABBITIZER_INSTR_ID_r3000gte_GPL] = "GPL",
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_00] = "USERDEF_00",
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_01] = "USERDEF_01",
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_02] = "USERDEF_02",
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_03] = "USERDEF_03",
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_04] = "USERDEF_04",
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_05] = "USERDEF_05",
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_06] = "USERDEF_06",
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_07] = "USERDEF_07",
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_08] = "USERDEF_08",
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_09] = "USERDEF_09",
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_10] = "USERDEF_10",
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_11] = "USERDEF_11",
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_12] = "USERDEF_12",
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_13] = "USERDEF_13",
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_14] = "USERDEF_14",
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_15] = "USERDEF_15",
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_16] = "USERDEF_16",
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_17] = "USERDEF_17",
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_18] = "USERDEF_18",
+[RABBITIZER_INSTR_ID_r3000gte_USERDEF_19] = "USERDEF_19",
+[RABBITIZER_INSTR_ID_r3000gte_MAX] = "MAX",
 [RABBITIZER_INSTR_ID_r5900_INVALID] = "INVALID",
 [RABBITIZER_INSTR_ID_r5900_lq] = "lq",
 [RABBITIZER_INSTR_ID_r5900_sq] = "sq",
diff --git a/src/instructions/InstrId_Names_array.table.template b/src/instructions/InstrId_Names_array.table.template
index 7e425355..afc72cbf 100644
--- a/src/instructions/InstrId_Names_array.table.template
+++ b/src/instructions/InstrId_Names_array.table.template
@@ -8,6 +8,7 @@
 const char *RabbitizerInstrId_Names[] = {
 #include "instructions/instr_id/RabbitizerInstrId_cpu.inc"
 #include "instructions/instr_id/RabbitizerInstrId_rsp.inc"
+#include "instructions/instr_id/RabbitizerInstrId_r3000gte.inc"
 #include "instructions/instr_id/RabbitizerInstrId_r5900.inc"
 };
 
diff --git a/src/instructions/RabbitizerInstrDescriptor.c b/src/instructions/RabbitizerInstrDescriptor.c
index fe48ecfe..d32a2ee9 100644
--- a/src/instructions/RabbitizerInstrDescriptor.c
+++ b/src/instructions/RabbitizerInstrDescriptor.c
@@ -251,6 +251,15 @@ bool RabbitizerInstrDescriptor_hasOperandAlias(const RabbitizerInstrDescriptor *
             break;
             /* rsp */
 
+            /* r3000gte */
+        case RAB_OPERAND_r3000gte_sf:
+        case RAB_OPERAND_r3000gte_mx:
+        case RAB_OPERAND_r3000gte_v:
+        case RAB_OPERAND_r3000gte_cv:
+        case RAB_OPERAND_r3000gte_lm:
+            break;
+            /* r3000gte */
+
             /* r5900 */
         case RAB_OPERAND_r5900_I:
         case RAB_OPERAND_r5900_Q:
diff --git a/src/instructions/RabbitizerInstrId.c b/src/instructions/RabbitizerInstrId.c
index 09e810bf..91d84765 100644
--- a/src/instructions/RabbitizerInstrId.c
+++ b/src/instructions/RabbitizerInstrId.c
@@ -11,9 +11,11 @@ bool RabbitizerInstrId_isValid(RabbitizerInstrId uniqueId) {
     switch (uniqueId) {
         case RABBITIZER_INSTR_ID_cpu_INVALID:
         case RABBITIZER_INSTR_ID_rsp_INVALID:
+        case RABBITIZER_INSTR_ID_r3000gte_INVALID:
         case RABBITIZER_INSTR_ID_r5900_INVALID:
         case RABBITIZER_INSTR_ID_cpu_MAX:
         case RABBITIZER_INSTR_ID_rsp_MAX:
+        case RABBITIZER_INSTR_ID_r3000gte_MAX:
         case RABBITIZER_INSTR_ID_r5900_MAX:
             // case RABBITIZER_INSTR_ID_ALL_MAX: Same as last MAX
             return false;
@@ -24,7 +26,12 @@ bool RabbitizerInstrId_isValid(RabbitizerInstrId uniqueId) {
 }
 
 const char *RabbitizerInstrId_getOpcodeName(RabbitizerInstrId uniqueId) {
+    const char *name;
+
     assert(uniqueId >= RABBITIZER_INSTR_ID_cpu_INVALID && uniqueId < RABBITIZER_INSTR_ID_ALL_MAX);
 
-    return RabbitizerInstrId_Names[uniqueId];
+    name = RabbitizerInstrId_Names[uniqueId];
+    assert(name != NULL);
+
+    return name;
 }
diff --git a/src/instructions/RabbitizerInstruction/RabbitizerInstruction.c b/src/instructions/RabbitizerInstruction/RabbitizerInstruction.c
index 484f0631..ecf80452 100644
--- a/src/instructions/RabbitizerInstruction/RabbitizerInstruction.c
+++ b/src/instructions/RabbitizerInstruction/RabbitizerInstruction.c
@@ -8,6 +8,7 @@
 #include "common/RabbitizerConfig.h"
 #include "instructions/RabbitizerRegister.h"
 #include "instructions/RabbitizerInstructionRsp.h"
+#include "instructions/RabbitizerInstructionR3000GTE.h"
 #include "instructions/RabbitizerInstructionR5900.h"
 
 void RabbitizerInstruction_init(RabbitizerInstruction *self, uint32_t word, uint32_t vram) {
@@ -266,6 +267,28 @@ void RabbitizerInstruction_blankOut(RabbitizerInstruction *self) {
                 break;
             /* rsp */
 
+            /* r3000gte */
+            case RAB_OPERAND_r3000gte_sf:
+                self->word = RAB_INSTR_R3000GTE_PACK_sf(self->word, 0);
+                break;
+
+            case RAB_OPERAND_r3000gte_mx:
+                self->word = RAB_INSTR_R3000GTE_PACK_mx(self->word, 0);
+                break;
+
+            case RAB_OPERAND_r3000gte_v:
+                self->word = RAB_INSTR_R3000GTE_PACK_v(self->word, 0);
+                break;
+
+            case RAB_OPERAND_r3000gte_cv:
+                self->word = RAB_INSTR_R3000GTE_PACK_cv(self->word, 0);
+                break;
+
+            case RAB_OPERAND_r3000gte_lm:
+                self->word = RAB_INSTR_R3000GTE_PACK_lm(self->word, 0);
+                break;
+            /* r3000gte */
+
             /* r5900 */
             case RAB_OPERAND_r5900_I:
             case RAB_OPERAND_r5900_Q:
diff --git a/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Examination.c b/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Examination.c
index 5ebab544..9d515a90 100644
--- a/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Examination.c
+++ b/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Examination.c
@@ -7,6 +7,7 @@
 
 #include "common/RabbitizerConfig.h"
 #include "instructions/RabbitizerInstructionRsp.h"
+#include "instructions/RabbitizerInstructionR3000GTE.h"
 #include "instructions/RabbitizerInstructionR5900.h"
 #include "instructions/RabbitizerRegister.h"
 
@@ -316,6 +317,28 @@ uint32_t RabbitizerInstruction_getValidBits(const RabbitizerInstruction *self) {
                 break;
             /* rsp */
 
+            /* r3000gte */
+            case RAB_OPERAND_r3000gte_sf:
+                validbits = RAB_INSTR_R3000GTE_PACK_sf(validbits, ~0);
+                break;
+
+            case RAB_OPERAND_r3000gte_mx:
+                validbits = RAB_INSTR_R3000GTE_PACK_mx(validbits, ~0);
+                break;
+
+            case RAB_OPERAND_r3000gte_v:
+                validbits = RAB_INSTR_R3000GTE_PACK_v(validbits, ~0);
+                break;
+
+            case RAB_OPERAND_r3000gte_cv:
+                validbits = RAB_INSTR_R3000GTE_PACK_cv(validbits, ~0);
+                break;
+
+            case RAB_OPERAND_r3000gte_lm:
+                validbits = RAB_INSTR_R3000GTE_PACK_lm(validbits, ~0);
+                break;
+            /* r3000gte */
+
             /* r5900 */
             case RAB_OPERAND_r5900_I:
             case RAB_OPERAND_r5900_Q:
diff --git a/src/instructions/RabbitizerInstruction/instrOpercandCallbacks_array.table.h b/src/instructions/RabbitizerInstruction/instrOpercandCallbacks_array.table.h
index 626f798e..f9d40e03 100644
--- a/src/instructions/RabbitizerInstruction/instrOpercandCallbacks_array.table.h
+++ b/src/instructions/RabbitizerInstruction/instrOpercandCallbacks_array.table.h
@@ -44,6 +44,11 @@ const OperandCallback instrOpercandCallbacks[] = {
 [RAB_OPERAND_rsp_offset_rs] = RabbitizerOperandType_process_rsp_offset_rs,
 [RAB_OPERAND_rsp_immediate_base] = RabbitizerOperandType_process_rsp_immediate_base,
 [RAB_OPERAND_rsp_maybe_rd_rs] = RabbitizerOperandType_process_rsp_maybe_rd_rs,
+    [RAB_OPERAND_r3000gte_sf] = RabbitizerOperandType_process_r3000gte_sf,
+    [RAB_OPERAND_r3000gte_mx] = RabbitizerOperandType_process_r3000gte_mx,
+    [RAB_OPERAND_r3000gte_v] = RabbitizerOperandType_process_r3000gte_v,
+    [RAB_OPERAND_r3000gte_cv] = RabbitizerOperandType_process_r3000gte_cv,
+    [RAB_OPERAND_r3000gte_lm] = RabbitizerOperandType_process_r3000gte_lm,
 [RAB_OPERAND_r5900_I] = RabbitizerOperandType_process_r5900_I,
 [RAB_OPERAND_r5900_Q] = RabbitizerOperandType_process_r5900_Q,
 [RAB_OPERAND_r5900_R] = RabbitizerOperandType_process_r5900_R,
diff --git a/src/instructions/RabbitizerInstruction/instrOpercandCallbacks_array.table.template b/src/instructions/RabbitizerInstruction/instrOpercandCallbacks_array.table.template
index 5b9b4202..3ac061ba 100644
--- a/src/instructions/RabbitizerInstruction/instrOpercandCallbacks_array.table.template
+++ b/src/instructions/RabbitizerInstruction/instrOpercandCallbacks_array.table.template
@@ -6,6 +6,7 @@
 const OperandCallback instrOpercandCallbacks[] = {
 #include "instructions/operands/RabbitizerOperandType_cpu.inc"
 #include "instructions/operands/RabbitizerOperandType_rsp.inc"
+#include "instructions/operands/RabbitizerOperandType_r3000gte.inc"
 #include "instructions/operands/RabbitizerOperandType_r5900.inc"
 };
 
diff --git a/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE.c b/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE.c
new file mode 100644
index 00000000..c3d7866f
--- /dev/null
+++ b/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE.c
@@ -0,0 +1,17 @@
+/* SPDX-FileCopyrightText: © 2023 Decompollaborate */
+/* SPDX-License-Identifier: MIT */
+
+#include "instructions/RabbitizerInstructionR3000GTE.h"
+
+void RabbitizerInstructionR3000GTE_init(RabbitizerInstruction *self, uint32_t word, uint32_t vram) {
+    RabbitizerInstruction_init(self, word, vram);
+
+    self->uniqueId = RABBITIZER_INSTR_ID_r3000gte_INVALID;
+    self->descriptor = &RabbitizerInstrDescriptor_Descriptors[self->uniqueId];
+
+    self->category = RABBITIZER_INSTRCAT_R3000GTE;
+}
+
+void RabbitizerInstructionR3000GTE_destroy(RabbitizerInstruction *self) {
+    RabbitizerInstruction_destroy(self);
+}
diff --git a/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_OperandType.c b/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_OperandType.c
new file mode 100644
index 00000000..24ee3b8e
--- /dev/null
+++ b/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_OperandType.c
@@ -0,0 +1,50 @@
+/* SPDX-FileCopyrightText: © 2023 Decompollaborate */
+/* SPDX-License-Identifier: MIT */
+
+#include "instructions/RabbitizerInstructionR3000GTE.h"
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "common/Utils.h"
+
+size_t RabbitizerOperandType_process_r3000gte_sf(const struct RabbitizerInstruction *self, char *dst,
+                                                 UNUSED const char *immOverride, UNUSED size_t immOverrideLength) {
+    size_t totalSize = 0;
+
+    RABUTILS_BUFFER_SPRINTF(dst, totalSize, "%i", RAB_INSTR_R3000GTE_GET_sf(self));
+    return totalSize;
+}
+
+size_t RabbitizerOperandType_process_r3000gte_mx(const struct RabbitizerInstruction *self, char *dst,
+                                                 UNUSED const char *immOverride, UNUSED size_t immOverrideLength) {
+    size_t totalSize = 0;
+
+    RABUTILS_BUFFER_SPRINTF(dst, totalSize, "%i", RAB_INSTR_R3000GTE_GET_mx(self));
+    return totalSize;
+}
+
+size_t RabbitizerOperandType_process_r3000gte_v(const struct RabbitizerInstruction *self, char *dst,
+                                                UNUSED const char *immOverride, UNUSED size_t immOverrideLength) {
+    size_t totalSize = 0;
+
+    RABUTILS_BUFFER_SPRINTF(dst, totalSize, "%i", RAB_INSTR_R3000GTE_GET_v(self));
+    return totalSize;
+}
+
+size_t RabbitizerOperandType_process_r3000gte_cv(const struct RabbitizerInstruction *self, char *dst,
+                                                 UNUSED const char *immOverride, UNUSED size_t immOverrideLength) {
+    size_t totalSize = 0;
+
+    RABUTILS_BUFFER_SPRINTF(dst, totalSize, "%i", RAB_INSTR_R3000GTE_GET_cv(self));
+    return totalSize;
+}
+
+size_t RabbitizerOperandType_process_r3000gte_lm(const struct RabbitizerInstruction *self, char *dst,
+                                                 UNUSED const char *immOverride, UNUSED size_t immOverrideLength) {
+    size_t totalSize = 0;
+
+    RABUTILS_BUFFER_SPRINTF(dst, totalSize, "%i", RAB_INSTR_R3000GTE_GET_lm(self));
+    return totalSize;
+}
diff --git a/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_ProcessUniqueId.c b/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_ProcessUniqueId.c
new file mode 100644
index 00000000..3d9cc7f8
--- /dev/null
+++ b/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_ProcessUniqueId.c
@@ -0,0 +1,117 @@
+/* SPDX-FileCopyrightText: © 2023 Decompollaborate */
+/* SPDX-License-Identifier: MIT */
+
+#include "instructions/RabbitizerInstructionR3000GTE.h"
+#include "common/RabbitizerConfig.h"
+#include "stdio.h"
+
+#define RABBITIZER_DEF_INSTR_ID(prefix, caseBits, name, ...)    \
+    case (caseBits):                                            \
+        self->uniqueId = RABBITIZER_INSTR_ID_##prefix##_##name; \
+        break;
+#define RABBITIZER_DEF_INSTR_ID_ALTNAME(prefix, caseBits, name, altname, ...) \
+    RABBITIZER_DEF_INSTR_ID(prefix, caseBits, name, __VA_ARGS__)
+
+void RabbitizerInstructionR3000GTE_processUniqueId_Normal(RabbitizerInstruction *self) {
+    RabbitizerInstruction_processUniqueId_Normal(self);
+}
+
+void RabbitizerInstructionR3000GTE_processUniqueId_Special(RabbitizerInstruction *self) {
+    RabbitizerInstruction_processUniqueId_Special(self);
+}
+
+void RabbitizerInstructionR3000GTE_processUniqueId_Regimm(RabbitizerInstruction *self) {
+    RabbitizerInstruction_processUniqueId_Regimm(self);
+}
+
+void RabbitizerInstructionR3000GTE_processUniqueId_Coprocessor0(RabbitizerInstruction *self) {
+    RabbitizerInstruction_processUniqueId_Coprocessor0(self);
+}
+
+void RabbitizerInstructionR3000GTE_processUniqueId_Coprocessor1(RabbitizerInstruction *self) {
+    RabbitizerInstruction_processUniqueId_Coprocessor1(self);
+}
+
+void RabbitizerInstructionR3000GTE_processUniqueId_Coprocessor2_gte(RabbitizerInstruction *self) {
+    uint32_t function = RAB_INSTR_GET_function(self);
+
+    self->_mandatorybits = RAB_INSTR_PACK_function(self->_mandatorybits, function);
+
+    // GTE instructions are weird
+    self->_mandatorybits =
+        RAB_INSTR_R3000GTE_PACK_FAKE_OPCODE(self->_mandatorybits, RAB_INSTR_R3000GTE_GET_FAKE_OPCODE(self));
+    self->_mandatorybits = RAB_INSTR_R3000GTE_PACK_sf(self->_mandatorybits, RAB_INSTR_R3000GTE_GET_sf(self));
+    self->_mandatorybits = RAB_INSTR_R3000GTE_PACK_mx(self->_mandatorybits, RAB_INSTR_R3000GTE_GET_mx(self));
+    self->_mandatorybits = RAB_INSTR_R3000GTE_PACK_v(self->_mandatorybits, RAB_INSTR_R3000GTE_GET_v(self));
+    self->_mandatorybits = RAB_INSTR_R3000GTE_PACK_cv(self->_mandatorybits, RAB_INSTR_R3000GTE_GET_cv(self));
+    self->_mandatorybits = RAB_INSTR_R3000GTE_PACK_lm(self->_mandatorybits, RAB_INSTR_R3000GTE_GET_lm(self));
+
+    switch (function) {
+#include "instructions/instr_id/r3000gte/r3000gte_cop2_gte.inc"
+    }
+}
+
+void RabbitizerInstructionR3000GTE_processUniqueId_Coprocessor2(RabbitizerInstruction *self) {
+    uint8_t fmt = RAB_INSTR_GET_fmt(self);
+    bool fetchDescriptor = true;
+
+    self->_mandatorybits = RAB_INSTR_PACK_fmt(self->_mandatorybits, fmt);
+
+    switch (fmt) {
+        case 0x10:
+        case 0x11:
+        case 0x12:
+        case 0x13:
+        case 0x14:
+        case 0x15:
+        case 0x16:
+        case 0x17:
+        case 0x18:
+        case 0x19:
+        case 0x1A:
+        case 0x1B:
+        case 0x1C:
+        case 0x1D:
+            RabbitizerInstructionR3000GTE_processUniqueId_Coprocessor2_gte(self);
+            break;
+
+        default:
+            RabbitizerInstruction_processUniqueId_Coprocessor2(self);
+            fetchDescriptor = false;
+            break;
+    }
+
+    if (fetchDescriptor) {
+        self->descriptor = &RabbitizerInstrDescriptor_Descriptors[self->uniqueId];
+    }
+}
+
+#undef RABBITIZER_DEF_INSTR_ID
+#undef RABBITIZER_DEF_INSTR_ID_ALTNAME
+
+void RabbitizerInstructionR3000GTE_processUniqueId(RabbitizerInstruction *self) {
+    uint32_t opcode = RAB_INSTR_GET_opcode(self);
+
+    self->_mandatorybits = RAB_INSTR_PACK_opcode(self->_mandatorybits, opcode);
+
+    switch (opcode) {
+        default:
+            RabbitizerInstructionR3000GTE_processUniqueId_Normal(self);
+            break;
+        case 0x00:
+            RabbitizerInstructionR3000GTE_processUniqueId_Special(self);
+            break;
+        case 0x01:
+            RabbitizerInstructionR3000GTE_processUniqueId_Regimm(self);
+            break;
+        case 0x10:
+            RabbitizerInstructionR3000GTE_processUniqueId_Coprocessor0(self);
+            break;
+        case 0x11:
+            RabbitizerInstructionR3000GTE_processUniqueId_Coprocessor1(self);
+            break;
+        case 0x12:
+            RabbitizerInstructionR3000GTE_processUniqueId_Coprocessor2(self);
+            break;
+    }
+}


Test patch:
diff --git a/tests/asm/r3000gte/gte_instrs.s b/tests/asm/r3000gte/gte_instrs.s
new file mode 100644
index 00000000..94efe19e
--- /dev/null
+++ b/tests/asm/r3000gte/gte_instrs.s
@@ -0,0 +1,8 @@
+.set noreorder
+
+.section .text
+
+.global gte_rtps_b
+    RTPS
+    jr $ra
+    nop
diff --git a/tests/asm/r3000gte/inline_gte_commands.inc.s b/tests/asm/r3000gte/inline_gte_commands.inc.s
new file mode 100644
index 00000000..b3d68e49
--- /dev/null
+++ b/tests/asm/r3000gte/inline_gte_commands.inc.s
@@ -0,0 +1,91 @@
+;
+; GTE commands without nops
+;
+
+RTPS		macro
+		dw	$0000007f
+		endm
+
+RTPT		macro
+		dw	$000000bf
+		endm
+
+DCPL		macro
+		dw	$00000dff
+		endm
+
+DPCS		macro
+		dw	$00000e3f
+		endm
+
+DPCT		macro
+		dw	$00000e7f
+		endm
+
+INTPL		macro
+		dw	$00000ebf
+		endm
+
+NCS		macro
+		dw	$00000f7f
+		endm
+
+NCT		macro
+		dw	$00000fbf
+		endm
+
+NCDS		macro
+		dw	$00000fff
+		endm
+
+NCDT		macro
+		dw	$0000103f
+		endm
+
+NCCS		macro
+		dw	$0000107f
+		endm
+
+NCCT		macro
+		dw	$000010bf
+		endm
+
+CDP		macro
+		dw	$000010ff
+		endm
+
+CC		macro
+		dw	$0000113f
+		endm
+
+NCLIP		macro
+		dw	$0000117f
+		endm
+
+AVSZ3		macro
+		dw	$000011bf
+		endm
+
+AVSZ4		macro
+		dw	$000011ff
+		endm
+
+MVMVA		macro	sf,mx,v,cv,lm
+		dw	$000013bf|sf<<25|mx<<23|v<<21|cv<<19|lm<<18
+		endm
+
+SQR		macro	sf
+		dw	$000013ff|sf<<25
+		endm
+
+OP		macro	sf
+		dw	$0000143f|sf<<25
+		endm
+
+GPF		macro	sf
+		dw	$0000147f|sf<<25
+		endm
+
+GPL		macro	sf
+		dw	$000014bf|sf<<25
+		endm
diff --git a/tests/asm/r3000gte/inline_gte_instr.inc.s b/tests/asm/r3000gte/inline_gte_instr.inc.s
new file mode 100644
index 00000000..bc4c93bf
--- /dev/null
+++ b/tests/asm/r3000gte/inline_gte_instr.inc.s
@@ -0,0 +1,424 @@
+.macro RTPS
+    .word 0x4A180001
+.endm
+
+.macro RTPT
+    .word 0x4A280030
+.endm
+
+.macro DPCL
+    .word 0x4A680029
+.endm
+
+.macro DPCS
+    .word 0x4A780010
+.endm
+
+.macro DPCT
+    .word 0x4AF8002A
+.endm
+
+.macro INTPL
+    .word 0x4A980011
+.endm
+
+.macro NCS
+    .word 0x4AC8041E
+.endm
+
+.macro NCT
+    .word 0x4AD80420
+.endm
+
+.macro NCDS
+    .word 0x4AE80413
+.endm
+
+.macro NCDT
+    .word 0x4AF80416
+.endm
+
+.macro NCCS
+    .word 0x4B08041B
+.endm
+
+.macro NCCT
+    .word 0x4B18043F
+.endm
+
+.macro CDP
+    .word 0x4B280414
+.endm
+
+.macro CC
+    .word 0x4B38041C
+.endm
+
+.macro NCLIP
+    .word 0x4B400006
+.endm
+
+.macro AVSZ3
+    .word 0x4B58002D
+.endm
+
+.macro AVSZ4
+    .word 0x4B68002E
+.endm
+
+.macro MVMVA sf, mx, v, cv, lm
+    .word 0x4A400012
+.endm
+
+.macro SQR sf
+    .word 0x4AA00428
+.endm
+
+.macro OP sf
+    .word 0x4B70000C
+.endm
+
+.macro GPF sf
+    .word 0x4B90003D
+.endm
+
+.macro GPL sf
+    .word 0x4BA0003E
+.endm
+
+
+
+.macro rtv0
+    .word 0x4A486012
+.endm
+
+.macro rtv1
+    .word 0x4A48E012
+.endm
+
+.macro rtv2
+    .word 0x4A496012
+.endm
+
+.macro rtir12
+    .word 0x4A49E012
+.endm
+
+.macro rtir0
+    .word 0x4A41E012
+.endm
+
+.macro rtv0tr
+    .word 0x4A480012
+.endm
+
+.macro rtv1tr
+    .word 0x4A488012
+.endm
+
+.macro rtv2tr
+    .word 0x4A490012
+.endm
+
+.macro rtirtr
+    .word 0x4A498012
+.endm
+
+.macro rtv0bk
+    .word 0x4A482012
+.endm
+
+.macro rtv1bk
+    .word 0x4A48A012
+.endm
+
+.macro rtv2bk
+    .word 0x4A492012
+.endm
+
+.macro rtirbk
+    .word 0x4A49A012
+.endm
+
+.macro ll
+    .word 0x4A4A6412
+.endm
+
+.macro llv0
+    .word 0x4A4A6012
+.endm
+
+.macro llv1
+    .word 0x4A4AE012
+.endm
+
+.macro llv2
+    .word 0x4A4B6012
+.endm
+
+.macro llvir
+    .word 0x4A4BE012
+.endm
+
+.macro llv0tr
+    .word 0x4A4A0012
+.endm
+
+.macro llv1tr
+    .word 0x4A4A8012
+.endm
+
+.macro llv2tr
+    .word 0x4A4B0012
+.endm
+
+.macro llirtr
+    .word 0x4A4B8012
+.endm
+
+.macro llv0bk
+    .word 0x4A4A2012
+.endm
+
+.macro llv1bk
+    .word 0x4A4AA012
+.endm
+
+.macro llv2bk
+    .word 0x4A4B2012
+.endm
+
+.macro llirbk
+    .word 0x4A4BA012
+.endm
+
+.macro lc
+    .word 0x4A4DA412
+.endm
+
+.macro lcv0
+    .word 0x4A4C6012
+.endm
+
+.macro lcv1
+    .word 0x4A4CE012
+.endm
+
+.macro lcv2
+    .word 0x4A4D6012
+.endm
+
+.macro lcvir
+    .word 0x4A4DE012
+.endm
+
+.macro lcv0tr
+    .word 0x4A4C0012
+.endm
+
+.macro lcv1tr
+    .word 0x4A4C8012
+.endm
+
+.macro lcv2tr
+    .word 0x4A4D0012
+.endm
+
+.macro lcirtr
+    .word 0x4A4D8012
+.endm
+
+.macro lev0bk
+    .word 0x4A4C2012
+.endm
+
+.macro lev1bk
+    .word 0x4A4CA012
+.endm
+
+.macro lev2bk
+    .word 0x4A4D2012
+.endm
+
+.macro leirbk
+    .word 0x4A4DA012
+.endm
+
+.macro sqr12
+    .word 0x4AA80428
+.endm
+
+.macro sqr0
+    .word 0x4AA80428
+.endm
+
+.macro op12
+    .word 0x4B78000C
+.endm
+
+.macro op0
+    .word 0x4B70000C
+.endm
+
+.macro gpf12
+    .word 0x4B98003D
+.endm
+
+.macro gpf0
+    .word 0x4B90003D
+.endm
+
+.macro gpl12
+    .word 0x4BA8003E
+.endm
+
+.macro gpl0
+    .word 0x4BA0003E
+.endm
+
+/*  DPCS        macro */
+/*          dw    $00000e3f */
+/*          endm */
+/*   */
+/*  DPCT        macro */
+/*          dw    $00000e7f */
+/*          endm */
+/*   */
+/*  INTPL        macro */
+/*          dw    $00000ebf */
+/*          endm */
+/*   */
+/*  NCS        macro */
+/*          dw    $00000f7f */
+/*          endm */
+/*   */
+/*  NCT        macro */
+/*          dw    $00000fbf */
+/*          endm */
+/*   */
+/*  NCDS        macro */
+/*          dw    $00000fff */
+/*          endm */
+/*   */
+/*  NCDT        macro */
+/*          dw    $0000103f */
+/*          endm */
+/*   */
+/*  NCCS        macro */
+/*          dw    $0000107f */
+/*          endm */
+/*   */
+/*  NCCT        macro */
+/*          dw    $000010bf */
+/*          endm */
+/*   */
+/*  CDP        macro */
+/*          dw    $000010ff */
+/*          endm */
+/*   */
+/*  CC        macro */
+/*          dw    $0000113f */
+/*          endm */
+/*   */
+/*  NCLIP        macro */
+/*          dw    $0000117f */
+/*          endm */
+/*   */
+/*  AVSZ3        macro */
+/*          dw    $000011bf */
+/*          endm */
+/*   */
+/*  AVSZ4        macro */
+/*          dw    $000011ff */
+/*          endm */
+/*   */
+/*  MVMVA        macro    sf,mx,v,cv,lm */
+/*          dw    $000013bf|sf<<25|mx<<23|v<<21|cv<<19|lm<<18 */
+/*          endm */
+/*   */
+/*  SQR        macro    sf */
+/*          dw    $000013ff|sf<<25 */
+/*          endm */
+/*   */
+/*  OP        macro    sf */
+/*          dw    $0000143f|sf<<25 */
+/*          endm */
+/*   */
+/*  GPF        macro    sf */
+/*          dw    $0000147f|sf<<25 */
+/*          endm */
+/*   */
+/*  GPL        macro    sf */
+/*          dw    $000014bf|sf<<25 */
+/*          endm */
+
+
+/*  RTPS    15      0x4A180001  Perspective transform */
+/*  RTPT    23      0x4A280030  Perspective transform on 3 points */
+/*  MVMVA   8       0x4A400012  Multiply vector by matrix and vector addition. */
+/*  DPCL    8       0x4A680029  Depth Cue Color light */
+/*  DPCS    8       0x4A780010  Depth Cueing */
+/*  DPCT    17      0x4AF8002A  Depth cue color RGB0,RGB1,RGB2 */
+/*  INTPL   8       0x4A980011  Interpolation of vector and far color */
+/*  SQR     5       0x4AA00428  Square of vector */
+/*  NCS     14      0x4AC8041E  Normal color v0 */
+/*  NCT     30      0x4AD80420  Normal color v0, v1, v2 */
+/*  NCDS    19      0x4AE80413  Normal color depth cuev0 */
+/*  NCDT    44      0x4AF80416  Normal color depth cue v0, v1, v2 */
+/*  NCCS    17      0x4B08041B  Normal color col. v0 */
+/*  NCCT    39      0x4B18043F  Normal color col.v0, v1, v2 */
+/*  CDP     13      0x4B280414  Color Depth Queue */
+/*  CC      11      0x4B38041C  Color Col. */
+/*  NCLIP   8       0x4B400006  Normal clipping */
+/*  AVSZ3   5       0x4B58002D  Average of three Z values */
+/*  AVSZ4   6       0x4B68002E  Average of four Z values */
+/*  OP      6       0x4B70000C  Outer Product */
+/*  GPF     6       0x4B90003D  General purpose interpolation */
+/*  GPL     5       0x4BA0003E  general purpose interpolation */
+/*  rtv0    -       0x4A486012  v0 * rotmatrix */
+/*  rtv1    -       0x4A48E012  v1 * rotmatrix */
+/*  rtv2    -       0x4A496012  v2 * rotmatrix */
+/*  rtir12  -       0x4A49E012  ir * rotmatrix */
+/*  rtir0   -       0x4A41E012  ir * rotmatrix */
+/*  rtv0tr  -       0x4A480012  v0 * rotmatrix + tr vector */
+/*  rtv1tr  -       0x4A488012  v1 * rotmatrix + tr vector */
+/*  rtv2tr  -       0x4A490012  v2 * rotmatrix + tr vector */
+/*  rtirtr  -       0x4A498012  ir * rotmatrix + tr vector */
+/*  rtv0bk  -       0x4A482012  v0 * rotmatrix + bk vector */
+/*  rtv1bk  -       0x4A48A012  v1 * rotmatrix + bk vector */
+/*  rtv2bk  -       0x4A492012  v2 * rotmatrix + bk vector */
+/*  rtirbk  -       0x4A49A012  ir * rotmatrix + bk vector */
+/*  ll      -       0x4A4A6412  v0 * light matrix. Lower limit result to 0 */
+/*  llv0    -       0x4A4A6012  v0 * light matrix */
+/*  llv1    -       0x4A4AE012  v1 * light matrix */
+/*  llv2    -       0x4A4B6012  v2 * light matrix */
+/*  llvir   -       0x4A4BE012  ir * light matrix */
+/*  llv0tr  -       0x4A4A0012  v0 * light matrix + tr vector */
+/*  llv1tr  -       0x4A4A8012  v1 * light matrix + tr vector */
+/*  llv2tr  -       0x4A4B0012  v2 * light matrix + tr vector */
+/*  llirtr  -       0x4A4B8012  ir * light matrix + tr vector */
+/*  llv0bk  -       0x4A4A2012  v0 * light matrix + bk vector */
+/*  llv1bk  -       0x4A4AA012  v1 * light matrix + bk vector */
+/*  llv2bk  -       0x4A4B2012  v2 * light matrix + bk vector */
+/*  llirbk  -       0x4A4BA012  ir * light matrix + bk vector */
+/*  lc      -       0x4A4DA412  v0 * color matrix, Lower limit clamped to 0 */
+/*  lcv0    -       0x4A4C6012  v0 * color matrix */
+/*  lcv1    -       0x4A4CE012  v1 * color matrix */
+/*  lcv2    -       0x4A4D6012  v2 * color matrix */
+/*  lcvir   -       0x4A4DE012  ir * color matrix */
+/*  lcv0tr  -       0x4A4C0012  v0 * color matrix + tr vector */
+/*  lcv1tr  -       0x4A4C8012  v1 * color matrix + tr vector */
+/*  lcv2tr  -       0x4A4D0012  v2 * color matrix + tr vector */
+/*  lcirtr  -       0x4A4D8012  ir * color matrix + tr vector */
+/*  lev0bk  -       0x4A4C2012  v0 * color matrix + bk vector */
+/*  lev1bk  -       0x4A4CA012  v1 * color matrix + bk vector */
+/*  lev2bk  -       0x4A4D2012  v2 * color matrix + bk vector */
+/*  leirbk  -       0x4A4DA012  ir * color matrix + bk vector */
+/*  sqr12   -       0x4AA80428  square of ir    1,19,12 */
+/*  sqr0    -       0x4AA80428  square of ir    1,31, 0 */
+/*  op12    -       0x4B78000C  outer product   1,19,12 */
+/*  op0     -       0x4B70000C  outer product   1,31, 0 */
+/*  gpf12   -       0x4B98003D  general purpose interpolation   1,19,12 */
+/*  gpf0    -       0x4B90003D  general purpose interpolation   1,31, 0 */
+/*  gpl12   -       0x4BA8003E  general purpose interpolation   1,19,12 */
+/*  gpl0    -       0x4BA0003E  general purpose interpolation   1,31, 0 */
diff --git a/tests/c/instruction_checks/r3000gte_disasm.c b/tests/c/instruction_checks/r3000gte_disasm.c
new file mode 100644
index 00000000..179f2809
--- /dev/null
+++ b/tests/c/instruction_checks/r3000gte_disasm.c
@@ -0,0 +1,130 @@
+/* SPDX-FileCopyrightText: © 2023 Decompollaborate */
+/* SPDX-License-Identifier: MIT */
+
+#include "rabbitizer.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+
+size_t strlen_null(const char *string) {
+    if (string == NULL) {
+        return 0;
+    }
+    return strlen(string);
+}
+
+typedef struct TestEntry {
+    uint32_t word;
+    const char *immOverride;
+    const char *expectedStr;
+} TestEntry;
+
+const TestEntry entries[] = {
+    { 0x4A180001, NULL, "RTPS" },
+    { 0x4A280030, NULL, "RTPT" },
+    { 0x4A680029, NULL, "DPCL" },
+    { 0x4A780010, NULL, "DPCS" },
+    { 0x4AF8002A, NULL, "DPCT" },
+    { 0x4A980011, NULL, "INTPL" },
+    { 0x4AC8041E, NULL, "NCS" },
+    { 0x4AD80420, NULL, "NCT" },
+    { 0x4AE80413, NULL, "NCDS" },
+    { 0x4AF80416, NULL, "NCDT" },
+    { 0x4B08041B, NULL, "NCCS" },
+    { 0x4B18043F, NULL, "NCCT" },
+    { 0x4B280414, NULL, "CDP" },
+    { 0x4B38041C, NULL, "CC" },
+    { 0x4B400006, NULL, "NCLIP" },
+    { 0x4B58002D, NULL, "AVSZ3" },
+    { 0x4B68002E, NULL, "AVSZ4" },
+
+    { 0x4A400012, NULL, "MVMVA       0, 0, 0, 0, 0" },
+    { 0x4AA00428, NULL, "SQR         0" },
+    { 0x4B70000C, NULL, "OP          0" },
+    { 0x4B90003D, NULL, "GPF         0" },
+    { 0x4BA0003E, NULL, "GPL         0" },
+
+    { 0x4A486012, NULL, "MVMVA       1, 0, 0, 3, 0" },
+    { 0x4A48E012, NULL, "MVMVA       1, 0, 1, 3, 0" },
+    { 0x4A496012, NULL, "MVMVA       1, 0, 2, 3, 0" },
+    { 0x4A49E012, NULL, "MVMVA       1, 0, 3, 3, 0" },
+    { 0x4A41E012, NULL, "MVMVA       0, 0, 3, 3, 0" },
+    { 0x4A480012, NULL, "MVMVA       1, 0, 0, 0, 0" },
+    { 0x4A488012, NULL, "MVMVA       1, 0, 1, 0, 0" },
+    { 0x4A490012, NULL, "MVMVA       1, 0, 2, 0, 0" },
+    { 0x4A498012, NULL, "MVMVA       1, 0, 3, 0, 0" },
+    { 0x4A482012, NULL, "MVMVA       1, 0, 0, 1, 0" },
+    { 0x4A48A012, NULL, "MVMVA       1, 0, 1, 1, 0" },
+    { 0x4A492012, NULL, "MVMVA       1, 0, 2, 1, 0" },
+    { 0x4A49A012, NULL, "MVMVA       1, 0, 3, 1, 0" },
+    { 0x4A4A6412, NULL, "MVMVA       1, 1, 0, 3, 1" },
+    { 0x4A4A6012, NULL, "MVMVA       1, 1, 0, 3, 0" },
+    { 0x4A4AE012, NULL, "MVMVA       1, 1, 1, 3, 0" },
+    { 0x4A4B6012, NULL, "MVMVA       1, 1, 2, 3, 0" },
+    { 0x4A4BE012, NULL, "MVMVA       1, 1, 3, 3, 0" },
+    { 0x4A4A0012, NULL, "MVMVA       1, 1, 0, 0, 0" },
+    { 0x4A4A8012, NULL, "MVMVA       1, 1, 1, 0, 0" },
+    { 0x4A4B0012, NULL, "MVMVA       1, 1, 2, 0, 0" },
+    { 0x4A4B8012, NULL, "MVMVA       1, 1, 3, 0, 0" },
+    { 0x4A4A2012, NULL, "MVMVA       1, 1, 0, 1, 0" },
+    { 0x4A4AA012, NULL, "MVMVA       1, 1, 1, 1, 0" },
+    { 0x4A4B2012, NULL, "MVMVA       1, 1, 2, 1, 0" },
+    { 0x4A4BA012, NULL, "MVMVA       1, 1, 3, 1, 0" },
+    { 0x4A4DA412, NULL, "MVMVA       1, 2, 3, 1, 1" },
+    { 0x4A4C6012, NULL, "MVMVA       1, 2, 0, 3, 0" },
+    { 0x4A4CE012, NULL, "MVMVA       1, 2, 1, 3, 0" },
+    { 0x4A4D6012, NULL, "MVMVA       1, 2, 2, 3, 0" },
+    { 0x4A4DE012, NULL, "MVMVA       1, 2, 3, 3, 0" },
+    { 0x4A4C0012, NULL, "MVMVA       1, 2, 0, 0, 0" },
+    { 0x4A4C8012, NULL, "MVMVA       1, 2, 1, 0, 0" },
+    { 0x4A4D0012, NULL, "MVMVA       1, 2, 2, 0, 0" },
+    { 0x4A4D8012, NULL, "MVMVA       1, 2, 3, 0, 0" },
+    { 0x4A4C2012, NULL, "MVMVA       1, 2, 0, 1, 0" },
+    { 0x4A4CA012, NULL, "MVMVA       1, 2, 1, 1, 0" },
+    { 0x4A4D2012, NULL, "MVMVA       1, 2, 2, 1, 0" },
+    { 0x4A4DA012, NULL, "MVMVA       1, 2, 3, 1, 0" },
+    { 0x4AA80428, NULL, "SQR         1" },
+    { 0x4AA80428, NULL, "SQR         1" },
+    { 0x4B78000C, NULL, "OP          1" },
+    { 0x4B70000C, NULL, "OP          0" },
+    { 0x4B98003D, NULL, "GPF         1" },
+    { 0x4B90003D, NULL, "GPF         0" },
+    { 0x4BA8003E, NULL, "GPL         1" },
+    { 0x4BA0003E, NULL, "GPL         0" },
+};
+
+int main() {
+    int errorCount = 0;
+    size_t i;
+
+    for (i = 0; i < ARRAY_COUNT(entries); i++) {
+        const TestEntry *entry = &entries[i];
+        RabbitizerInstruction instr;
+        char *buffer;
+        size_t bufferSize;
+        size_t immOverrideLength = strlen_null(entry->immOverride);
+
+        RabbitizerInstructionR3000GTE_init(&instr, entry->word, 0);
+        RabbitizerInstructionR3000GTE_processUniqueId(&instr);
+
+        bufferSize = RabbitizerInstruction_getSizeForBuffer(&instr, immOverrideLength, 0);
+        buffer = malloc(bufferSize + 1);
+        assert(buffer != NULL);
+
+        RabbitizerInstruction_disassemble(&instr, buffer, entry->immOverride, immOverrideLength, 0);
+
+        if (entry->expectedStr == NULL) {
+            printf("Word '0x%08X' doesn't have a expected str, got '%s'\n", entry->word, buffer);
+            errorCount++;
+        } else if (strcmp(buffer, entry->expectedStr) != 0) {
+            fprintf(stderr, "Error on word '0x%08X'. Expected '%s', got '%s'\n", entry->word, entry->expectedStr, buffer);
+            errorCount++;
+        }
+
+        free(buffer);
+        RabbitizerInstructionR3000GTE_destroy(&instr);
+    }
+
+    return errorCount;
+}
diff --git a/tests/c/r3000gte.c b/tests/c/r3000gte.c
new file mode 100644
index 00000000..2413f31e
--- /dev/null
+++ b/tests/c/r3000gte.c
@@ -0,0 +1,47 @@
+/* SPDX-FileCopyrightText: © 2023 Decompollaborate */
+/* SPDX-License-Identifier: MIT */
+
+#include "instructions/RabbitizerInstructionR3000GTE.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+
+int main() {
+    uint32_t word;
+    RabbitizerInstruction instr;
+    char *buffer;
+    int extraLJust = 5;
+#if 1
+    uint32_t validbits;
+#endif
+
+    word = 0x4A180001; // RTPS
+
+    RabbitizerInstructionR3000GTE_init(&instr, word, 0x00100000);
+
+    RabbitizerInstructionR3000GTE_processUniqueId(&instr);
+
+    buffer = malloc(RabbitizerInstruction_getSizeForBuffer(&instr, 0, extraLJust) + 1);
+    assert(buffer != NULL);
+
+    RabbitizerInstruction_disassemble(&instr, buffer, NULL, 0, extraLJust);
+
+    printf("%08X: %s\n", word, buffer);
+
+#if 1
+    validbits = RabbitizerInstruction_getValidBits(&instr);
+
+    printf("word:           %08X\n", instr.word);
+    printf("mandatory bits: %08X\n", instr._mandatorybits);
+    printf("valid bits:     %08X\n", validbits);
+    printf("invalid bits:   %08X\n", (~validbits) & instr.word);
+#endif
+
+    free(buffer);
+    RabbitizerInstructionR3000GTE_destroy(&instr);
+
+    return 0;
+}
diff --git a/tests/run_instruction_checks.sh b/tests/run_instruction_checks.sh
index a3e55f57..f7203a5b 100755
--- a/tests/run_instruction_checks.sh
+++ b/tests/run_instruction_checks.sh
@@ -7,4 +7,5 @@ set -e
 
 ./build/tests/c/instruction_checks/jalr.elf
 ./build/tests/c/instruction_checks/plain_disassembly.elf
+./build/tests/c/instruction_checks/r3000gte_disasm.elf
 ./build/tests/c/instruction_checks/r5900_trunc_cvt.elf