Beispiel #1
0
 public static void ParseTri(F3DEX2Command cmd, bool firstTri,
                             out int a, out int b, out int c)
 {
     a = (int)((cmd.Words >> ((firstTri ? 32 : 0) + 16)) & 0xFF) / 2;
     b = (int)((cmd.Words >> ((firstTri ? 32 : 0) + 8)) & 0xFF) / 2;
     c = (int)((cmd.Words >> ((firstTri ? 32 : 0) + 0)) & 0xFF) / 2;
 }
Beispiel #2
0
        public static void ParseRDPSetOtherMode(F3DEX2Command cmd,
                                                out PipelineMode pm, out CycleType cyc, out TexturePersp tp, out TextureDetail td, out TextureLOD tl,
                                                out TextureLUT tt, out TextureFilter tf, out TextureConvert tc, out CombineKey ck, out ColorDither cd,
                                                out AlphaDither ad, out AlphaCompare ac, out DepthSource zs, out RenderMode rm)
        {
            rm = new RenderMode(cmd.Words & (0xFFFFFFFF & ~((ulong)AlphaCompare.Mask | (ulong)DepthSource.Mask)));

            if (!rm.Known) // Handle TCL modes by checking again with alpha compare and dither included
            {
                rm = new RenderMode(cmd.Words & (0xFFFFFFFF & ~(ulong)DepthSource.Mask));
            }

            ulong wordH = cmd.Words >> 32;

            ad  = (AlphaDither)(wordH & (ulong)AlphaDither.Mask);
            cd  = (ColorDither)(wordH & (ulong)ColorDither.Mask);
            ck  = (CombineKey)(wordH & (ulong)CombineKey.Mask);
            pm  = (PipelineMode)(wordH & (ulong)PipelineMode.Mask);
            cyc = (CycleType)(wordH & (ulong)CycleType.Mask);
            tp  = (TexturePersp)(wordH & (ulong)TexturePersp.Mask);
            td  = (TextureDetail)(wordH & (ulong)TextureDetail.Mask);
            tl  = (TextureLOD)(wordH & (ulong)TextureLOD.Mask);
            tt  = (TextureLUT)(wordH & (ulong)TextureLUT.Mask);
            tf  = (TextureFilter)(wordH & (ulong)TextureFilter.Mask);
            tc  = (TextureConvert)(wordH & (ulong)TextureConvert.Mask);

            ac = (AlphaCompare)(cmd.Words & (ulong)AlphaCompare.Mask);
            zs = (DepthSource)(cmd.Words & (ulong)DepthSource.Mask);
        }
Beispiel #3
0
 public static void ParseOtherMode(F3DEX2Command cmd,
                                   out int sft, out int len, out ulong data)
 {
     len  = (int)((cmd.Words >> (32 + 0)) & 0xFF) + 1;
     sft  = 32 - ((int)((cmd.Words >> (32 + 8)) & 0xFF) + len);
     data = cmd.Words & 0xFFFFFFFF;
 }
Beispiel #4
0
 public static void ParseSetImage(F3DEX2Command cmd,
                                  out int fmt, out int siz, out int width, out ulong i)
 {
     fmt   = (int)((cmd.Words >> (32 + 21)) & 0b111);
     siz   = (int)((cmd.Words >> (32 + 19)) & 0b11);
     width = (int)((cmd.Words >> (32 + 0)) & 0xFFF) + 1;
     i     = cmd.Words & 0xFFFFFFFF;
 }
Beispiel #5
0
 public static void ParseScissor(F3DEX2Command cmd,
                                 out int mode, out int ulx, out int uly, out int lrx, out int lry)
 {
     ulx  = (int)((cmd.Words >> (32 + 12)) & 0xFFF);
     uly  = (int)((cmd.Words >> (32 + 0)) & 0xFFF);
     mode = (int)((cmd.Words >> (24)) & 0b11);
     lrx  = (int)((cmd.Words >> (12)) & 0xFFF);
     lry  = (int)((cmd.Words >> (0)) & 0xFFF);
 }
Beispiel #6
0
 public static void ParseTexture(F3DEX2Command cmd,
                                 out int s, out int t, out int level, out int tile, out int on)
 {
     level = (int)((cmd.Words >> (32 + 11))) & 0b111;
     tile  = (int)((cmd.Words >> (32 + 8))) & 0b111;
     on    = (int)((cmd.Words >> (32 + 1))) & 0b1111111;
     s     = (int)((cmd.Words >> (0 + 16))) & 0xFFFF;
     t     = (int)((cmd.Words >> (0 + 0))) & 0xFFFF;
 }
Beispiel #7
0
 public static void ParseRect(F3DEX2Command cmd,
                              out int xl, out int yl, out int xh, out int yh, out int tile)
 {
     xh   = (int)((cmd.Words >> (32 + 12)) & 0xFFF);
     yh   = (int)((cmd.Words >> (32 + 0)) & 0xFFF);
     tile = (int)((cmd.Words >> (24)) & 0b111);
     xl   = (int)((cmd.Words >> (12)) & 0xFFF);
     yl   = (int)((cmd.Words >> (0)) & 0xFFF);
 }
Beispiel #8
0
 public static void ParseLoadTileGeneric(F3DEX2Command cmd,
                                         out int tile, out int uls, out int ult, out int lrs, out int lrt)
 {
     uls  = (int)((cmd.Words >> (32 + 12)) & 0xFFF);
     ult  = (int)((cmd.Words >> (32 + 0)) & 0xFFF);
     tile = (int)((cmd.Words >> (24)) & 0b111);
     lrs  = (int)((cmd.Words >> (12)) & 0xFFF);
     lrt  = (int)((cmd.Words >> (0)) & 0xFFF);
 }
Beispiel #9
0
        public static void ParseVtx(F3DEX2Command cmd,
                                    out int vtxCount, out int vtxOffset, out ulong vtxAddr)
        {
            vtxCount = (int)((cmd.Words >> (32 + 12)) & 0xFF);
            int lastVtx = (int)((cmd.Words >> (32 + 1)) & 0x7F);

            vtxOffset = lastVtx - vtxCount;
            vtxAddr   = cmd.Words & 0xFFFFFFFF;
        }
Beispiel #10
0
        public static void ParseMatrix(F3DEX2Command cmd,
                                       out MatrixType mType, out MatrixLoadType loadType, out MatrixPushType pushType, out ulong mAddr,
                                       out int size, out int offset)
        {
            int matrixParams = (int)((cmd.Words >> (32 + 0)) & 0xFF) ^ (int)MatrixPushType.G_MTX_PUSH;

            mType    = (MatrixType)(matrixParams & (int)MatrixType.MASK);
            loadType = (MatrixLoadType)(matrixParams & (int)MatrixLoadType.MASK);
            pushType = (MatrixPushType)(matrixParams & (int)MatrixPushType.MASK);
            size     = (int)(((cmd.Words >> (32 + 19)) & 0b11111) + 1) * 8;
            offset   = (int)((cmd.Words >> (32 + 8)) & 0xFF) * 8;

            mAddr = cmd.Words & 0xFFFFFFFF;
        }
Beispiel #11
0
        public static List <F3DEX2Command> GetDLCommands(byte[] rdram, ulong address, ulong[] segTable)
        {
            List <F3DEX2Command> commands = new List <F3DEX2Command>();

            if (address >= 0x80000000)
            {
                address -= 0x80000000;
            }
            if ((address & 0xFF000000) != 0)
            {
                int   segment     = (int)(address >> 24) & 0xFF;
                ulong segmentAddr = segTable[segment] & 0x1FFFFFF;
                address &= 0xFFFFFF;
                address += segmentAddr;
            }

            do
            {
                ArraySegment <byte> curWordsArray = new ArraySegment <byte>(rdram, (int)address, 8);
                ulong         curWords            = BitConverter.ToUInt64(curWordsArray.Reverse().ToArray());
                F3DEX2Command curCommand          = new F3DEX2Command(curWords);
                commands.Add(curCommand);
                address += 8;
                if (curCommand.CommandId == F3DEX2CommandId.G_DL && (curWords >> (32 + 16) & 0xFF) == 0x01)
                {
                    address = curWords & 0xFFFFFFFF;
                    if (address >= 0x80000000)
                    {
                        address -= 0x80000000;
                    }
                    else if ((address & 0xFF000000) != 0)
                    {
                        int   segment     = (int)(address >> 24) & 0xFF;
                        ulong segmentAddr = segTable[segment] & 0x1FFFFFFF;
                        address &= 0xFFFFFF;
                        address += segmentAddr;
                    }
                    else
                    {
                        throw new Exception(string.Format("Invalid address: {0}", address));
                    }
                }
                //else if (curCommand.CommandByte == G_)
            } while (commands.Last().CommandId != F3DEX2CommandId.G_ENDDL);

            return(commands);
        }
Beispiel #12
0
        public static void ParseSetTile(F3DEX2Command cmd,
                                        out int fmt, out int siz, out int line, out int tmem, out int tile, out int palette, out int cmt,
                                        out int maskt, out int shiftt, out int cms, out int masks, out int shifts)
        {
            fmt  = (int)((cmd.Words >> (32 + 21)) & 0b111);
            siz  = (int)((cmd.Words >> (32 + 19)) & 0b11);
            line = (int)((cmd.Words >> (32 + 9)) & 0b111111111);
            tmem = (int)((cmd.Words >> (32 + 0)) & 0b111111111);

            tile    = (int)((cmd.Words >> (24)) & 0b111);
            palette = (int)((cmd.Words >> (20)) & 0b1111);
            cmt     = (int)((cmd.Words >> (18)) & 0b11);
            maskt   = (int)((cmd.Words >> (14)) & 0b1111);
            shiftt  = (int)((cmd.Words >> (10)) & 0b1111);
            cms     = (int)((cmd.Words >> (8)) & 0b11);
            masks   = (int)((cmd.Words >> (4)) & 0b1111);
            shifts  = (int)((cmd.Words >> (0)) & 0b1111);
        }
Beispiel #13
0
        public static void ParseConvert(F3DEX2Command cmd,
                                        out int[] coeffs)
        {
            ulong words = cmd.Words;

            coeffs = new int[6];

            for (int i = 5; i >= 0; i--)
            {
                coeffs[i] = (int)(words & 0b111111111);

                if ((coeffs[i] & 0b100000000) != 0)
                {
                    coeffs[i] = unchecked ((int)((uint)coeffs[i] | 0xFFFFFE00));
                }

                words >>= 9;
            }
        }
Beispiel #14
0
        public static void PrintDisplaylist(byte[] rdram, ulong address, ulong[] segTable, ref int totalTris, ref int totalVertices, string indentation = "")
        {
            List <F3DEX2Command> cmds = GetDLCommands(rdram, address, segTable);

            for (int i = 0; i < cmds.Count; i++)
            {
                F3DEX2Command cmd = cmds[i];

                string cmdStr = cmd.ParseString(cmds.GetRange(i + 1, Math.Min((cmds.Count() - i - 1), 10)), out int cmdsSkipped, ref totalTris, ref totalVertices, indentation + "                    "); // TODO replace getrange with something of constant time

                if (cmdStr.StartsWith("gs"))
                {
                    Console.Write(string.Format("{0:X16}: ", cmd.Words));
                    Console.WriteLine(indentation + cmdStr + ",");
                }
                else
                {
                    Console.WriteLine(indentation + cmd.CommandId);
                    Console.WriteLine(" " + indentation + cmd + ",");
                }
                if (cmd.CommandId == F3DEX2CommandId.G_DL && (cmd.Words >> (32 + 16) & 0xFF) != 0x01)
                {
                    PrintDisplaylist(rdram, cmd.Words & 0xFFFFFFFF, segTable, ref totalTris, ref totalVertices, "  " + indentation);
                }
                else if (cmd.CommandId == F3DEX2CommandId.G_MOVEWORD)
                {
                    MoveWord mw = new MoveWord(cmd.Words);
                    if (mw.Index == MoveWordIndex.G_MW_SEGMENT)
                    {
                        segTable[mw.Offset / 4] = mw.Data;
                    }
                }

                i += cmdsSkipped;
            }
        }
Beispiel #15
0
 public static void ParseCullDL(F3DEX2Command cmd,
                                out int vstart, out int vend)
 {
     vstart = (int)((cmd.Words >> (32 + 0)) & 0xFFFF) / 2;
     vend   = (int)((cmd.Words >> (0 + 0)) & 0xFFFF) / 2;
 }
Beispiel #16
0
 public static void ParseLoadTLUT(F3DEX2Command cmd,
                                  out int tile, out int count)
 {
     tile  = (int)((cmd.Words >> (24)) & 0b111);
     count = (int)((cmd.Words >> (14)) & 0b1111111111);
 }
Beispiel #17
0
 public static void ParseRDPHalf_W(F3DEX2Command cmd,
                                   out ulong a)
 {
     a = cmd.Words & 0xFFFFFFFF;
 }
Beispiel #18
0
 public static void ParsePopMtx(F3DEX2Command cmd,
                                out int bytes, out int idx)
 {
     bytes = (int)(((cmd.Words >> (32 + 19)) & 0b11111) + 1) * 8;
     idx   = (int)((cmd.Words >> (32 + 0)) & 0xFF);
 }
Beispiel #19
0
 public static void ParseRDPHalf_HH(F3DEX2Command cmd,
                                    out int a, out int b)
 {
     a = (int)(cmd.Words >> (16)) & 0xFFFF;
     b = (int)(cmd.Words >> (0)) & 0xFFFF;
 }
Beispiel #20
0
 public static void ParseSetCombine(F3DEX2Command cmd,
                                    out ulong mode)
 {
     mode = cmd.Words & 0xFFFFFFFFFFFFFF;
 }
Beispiel #21
0
 public static void ParseLoadUcode(F3DEX2Command cmd,
                                   out ulong uc_start, out int uc_dsize)
 {
     uc_dsize = (int)((cmd.Words >> (32 + 0)) & 0xFFFF) + 1;
     uc_start = cmd.Words & 0xFFFFFFFF;
 }
Beispiel #22
0
 public static void ParseSetColor(F3DEX2Command cmd,
                                  out ulong col)
 {
     col = (cmd.Words & 0xFFFFFFFF);
 }
Beispiel #23
0
 public static void ParseBranchLessZ(F3DEX2Command cmd,
                                     out int vtx, out int zval)
 {
     vtx  = (int)((cmd.Words >> 32) & 0xFFF) / 2;
     zval = (int)(cmd.Words & 0xFFFFFFFF);
 }
Beispiel #24
0
        public string ParseString(List <F3DEX2Command> nextCmds, out int cmdsSkipped, ref int totalTris, ref int totalVertices, string indentation)
        {
            cmdsSkipped = 0;
            switch (CommandId)
            {
            case F3DEX2CommandId.G_NOOP:
            {
                if ((Words & 0xFFFFFF) == 0)
                {
                    return("gsDPNoOp()");
                }
                else
                {
                    return(string.Format("gsDPNoOpTag({0})", Words & 0xFFFFFF));
                }
            }

            case F3DEX2CommandId.G_RDPHALF_2: break;

            case F3DEX2CommandId.G_SETOTHERMODE_H:
            {
                F3DEX2Decoder.ParseOtherMode(this,
                                             out int sft, out int len, out ulong data);

                OthermodeHShift sftType = (OthermodeHShift)sft;

                switch (sftType)
                {
                case OthermodeHShift.G_MDSFT_ALPHADITHER:
                    if (len == 2)
                    {
                        return(string.Format("gsDPSetAlphaDither({0})", (AlphaDither)data));
                    }
                    break;

                case OthermodeHShift.G_MDSFT_RGBDITHER:
                    if (len == 2)
                    {
                        return(string.Format("gsDPSetColorDither({0})", (ColorDither)data));
                    }
                    break;

                case OthermodeHShift.G_MDSFT_COMBKEY:
                    if (len == 1)
                    {
                        return(string.Format("gsDPSetCombineKey({0})", (CombineKey)data));
                    }
                    break;

                case OthermodeHShift.G_MDSFT_TEXTCONV:
                    if (len == 3)
                    {
                        return(string.Format("gsDPSetTextureConvert({0})", (TextureConvert)data));
                    }
                    break;

                case OthermodeHShift.G_MDSFT_TEXTFILT:
                    if (len == 2)
                    {
                        return(string.Format("gsDPSetTextureFilter({0})", (TextureFilter)data));
                    }
                    break;

                case OthermodeHShift.G_MDSFT_TEXTLUT:
                    if (len == 2)
                    {
                        return(string.Format("gsDPSetTextureLUT({0})", (TextureLUT)data));
                    }
                    break;

                case OthermodeHShift.G_MDSFT_TEXTLOD:
                    if (len == 1)
                    {
                        return(string.Format("gsDPSetTextureLOD({0})", (TextureLOD)data));
                    }
                    break;

                case OthermodeHShift.G_MDSFT_TEXTDETAIL:
                    if (len == 2)
                    {
                        return(string.Format("gsDPSetTextureDetail({0})", (TextureDetail)data));
                    }
                    break;

                case OthermodeHShift.G_MDSFT_TEXTPERSP:
                    if (len == 1)
                    {
                        return(string.Format("gsDPSetTexturePersp({0})", (TexturePersp)data));
                    }
                    break;

                case OthermodeHShift.G_MDSFT_CYCLETYPE:
                    if (len == 2)
                    {
                        return(string.Format("gsDPSetCycleType({0})", (CycleType)data));
                    }
                    break;

                case OthermodeHShift.G_MDSFT_PIPELINE:
                    if (len == 1)
                    {
                        return(string.Format("gsDPPipelineMode({0})", (PipelineMode)data));
                    }
                    break;
                }
                break;
            }

            case F3DEX2CommandId.G_SETOTHERMODE_L:
            {
                F3DEX2Decoder.ParseOtherMode(this,
                                             out int sft, out int len, out ulong data);

                OthermodeLShift sftType = (OthermodeLShift)sft;

                switch (sftType)
                {
                case OthermodeLShift.G_MDSFT_ALPHACOMPARE:
                    if (len == 2)
                    {
                        return(string.Format("gsDPSetAlphaCompare({0})", (AlphaCompare)data));
                    }
                    break;

                case OthermodeLShift.G_MDSFT_ZSRCSEL:
                    if (len == 1)
                    {
                        return(string.Format("gsDPSetDepthSource({0})", (DepthSource)data));
                    }
                    break;

                case OthermodeLShift.G_MDSFT_RENDERMODE:
                    if (len == 29)
                    {
                        RenderMode rm = new RenderMode(Words);
                        return(string.Format("gsDPSetRenderMode({0})", rm));
                    }
                    break;

                case OthermodeLShift.G_MDSFT_BLENDER:
                    break;
                }

                break;
            }

            case F3DEX2CommandId.G_RDPHALF_1:
            {
                ulong rdpHalfData = Words & 0xFFFFFFFF;

                if (nextCmds.Count >= 1)
                {
                    // gsSPBranchLessZrg/raw
                    if (nextCmds[0].CommandId == F3DEX2CommandId.G_BRANCH_Z)
                    {
                        F3DEX2Decoder.ParseBranchLessZ(nextCmds[0],
                                                       out int vtx, out int zval);

                        cmdsSkipped = 1;

                        return(string.Format("gsSPBranchLessZraw(0x{0:X8}, {1}, {2})", rdpHalfData, vtx, zval));
                    }
                    // gsSPLoadUCodeEx
                    else if (nextCmds[0].CommandId == F3DEX2CommandId.G_LOAD_UCODE)
                    {
                        F3DEX2Decoder.ParseLoadUcode(nextCmds[0],
                                                     out ulong uc_start, out int uc_dsize);

                        cmdsSkipped = 1;

                        return(string.Format("gsSPLoadUcodeEx(0x{0:X8}, 0x{1:X8}, 0x{2:X8})", uc_start, rdpHalfData, uc_dsize));
                    }
                }

                return(string.Format("gImmp1(pkt, G_RDPHALF_1, {0})", rdpHalfData));
            }

            case F3DEX2CommandId.G_SPNOOP:
                return("gsSPNoOp()");

            case F3DEX2CommandId.G_ENDDL:
                return("gsSPEndDisplayList()");

            case F3DEX2CommandId.G_DL:
                if ((Words >> (32 + 16) & 0xFF) == 0x01)
                {
                    return(string.Format("gsSPBranchList(0x{0:X8})", Words & 0xFFFFFFFF));
                }
                else
                {
                    return(string.Format("gsSPDisplayList(0x{0:X8})", Words & 0xFFFFFFFF));
                }

            case F3DEX2CommandId.G_LOAD_UCODE: break;

            case F3DEX2CommandId.G_MOVEMEM:
            {
                MoveMem mm = new MoveMem(Words);
                return(mm.ToString());
            }

            case F3DEX2CommandId.G_MOVEWORD:
            {
                MoveWord mw = new MoveWord(Words);
                return(mw.ToString(nextCmds, out cmdsSkipped));
            }

            case F3DEX2CommandId.G_MTX:
            {
                F3DEX2Decoder.ParseMatrix(this,
                                          out MatrixType mType, out MatrixLoadType loadType, out MatrixPushType pushType, out ulong mAddr,
                                          out int size, out int offset);

                if (size == 64 && offset == 0)         // TODO remove magic number
                {
                    return(string.Format("gsSPMatrix(0x{0:X8}, {1} | {2} | {3})", mAddr, mType, loadType, pushType));
                }
                else
                {
                    return(string.Format("gsDma2p(G_MTX, {0:X8}, {1}, {2} | {3} | {4}, {5}", mAddr, size, mType, loadType, pushType, offset));
                }
            }

            case F3DEX2CommandId.G_GEOMETRYMODE:
            {
                GeometryMode gm = new GeometryMode(Words);
                return(gm.ToString());
            }

            case F3DEX2CommandId.G_POPMTX:
            {
                F3DEX2Decoder.ParsePopMtx(this,
                                          out int bytes, out int idx);

                if (idx == 2)
                {
                    if (bytes == 64)
                    {
                        return("gsSPPopMatrix(G_MTX_MODELVIEW)");
                    }
                    else if (bytes % 64 == 0)
                    {
                        return(string.Format("gsSPPopMatrixN(G_MTX_MODELVIEW, {0})", bytes / 64));
                    }
                }

                break;
            }

            case F3DEX2CommandId.G_TEXTURE:
            {
                F3DEX2Decoder.ParseTexture(this,
                                           out int s, out int t, out int level, out int tile, out int on);

                string tileString;

                if (tile == 7)
                {
                    tileString = "G_TX_LOADTILE";
                }
                else if (tile == 0)
                {
                    tileString = "G_TX_RENDERTILE";
                }
                else
                {
                    tileString = tile.ToString();
                }

                string onString;

                if (on == 1)
                {
                    onString = "G_ON";
                }
                else if (on == 0)
                {
                    onString = "G_OFF";
                }
                else
                {
                    onString = on.ToString();
                }

                return(string.Format("gsSPTexture(0x{0:X4}, 0x{1:X4}, {2}, {3}, {4})", s, t, level, tileString, onString));
            }

            case F3DEX2CommandId.G_DMA_IO: break;

            case F3DEX2CommandId.G_SPECIAL_1:
            case F3DEX2CommandId.G_SPECIAL_2:
            case F3DEX2CommandId.G_SPECIAL_3:
            {
                // TODO This will not work in the general case, and is tailored to F3DEX2 2.04H, which is the only ucode I've seen
                // that uses any G_SPECIAL commands (G_SPECIAL_1 in that case).
                return(string.Format("gsImmp1({0}, {1})", CommandId, Words & 0xFFFFFFFF));
            }

            case F3DEX2CommandId.G_VTX:
            {
                F3DEX2Decoder.ParseVtx(this,
                                       out int vtxCount, out int vtxOffset, out ulong vtxAddr);

                totalVertices += vtxCount;

                return(string.Format("gsSPVertex(0x{0:X8}, {1}, {2})", vtxAddr, vtxCount, vtxOffset));
            }

            case F3DEX2CommandId.G_MODIFYVTX: break;

            case F3DEX2CommandId.G_CULLDL:
            {
                F3DEX2Decoder.ParseCullDL(this,
                                          out int vstart, out int vend);
                return(string.Format("gsSPCullDisplayList({0}, {1})", vstart, vend));
            }

            case F3DEX2CommandId.G_BRANCH_Z: break;

            case F3DEX2CommandId.G_TRI1:
            {
                F3DEX2Decoder.ParseTri(this, true,
                                       out int a, out int b, out int c);

                totalTris++;

                return(string.Format("gsSP1Triangle({0}, {1}, {2}, 0)", a, b, c));
            }

            case F3DEX2CommandId.G_TRI2:
            {
                F3DEX2Decoder.ParseTri(this, true,
                                       out int a1, out int b1, out int c1);
                F3DEX2Decoder.ParseTri(this, false,
                                       out int a2, out int b2, out int c2);

                totalTris += 2;

                return(string.Format("gsSP2Triangles({0}, {1}, {2}, 0, {3}, {4}, {5}, 0)", a1, b1, c1, a2, b2, c2));
            }

            case F3DEX2CommandId.G_QUAD:     // TODO test this
            {
                F3DEX2Decoder.ParseTri(this, true,
                                       out int a1, out int b1, out int c1);
                F3DEX2Decoder.ParseTri(this, false,
                                       out int _, out int _, out int c2);

                return(string.Format("gsSP1Quadrangle({0}, {1}, {2}, {3}, 0)", a1, b1, c1, c2));
            }

            case F3DEX2CommandId.G_LINE3D: break;

            case F3DEX2CommandId.G_SETCIMG:
            {
                F3DEX2Decoder.ParseSetImage(this,
                                            out int fmt, out int siz, out int width, out ulong i);

                return(string.Format("gsDPSetColorImage({0}, {1}, {2}, 0x{3:X8})",
                                     (RDPImgFormat)fmt, (RDPImgSize)siz,
                                     width, i));
            }

            case F3DEX2CommandId.G_SETZIMG:
            {
                F3DEX2Decoder.ParseSetImage(this,
                                            out int fmt, out int siz, out int width, out ulong i);

                if (fmt == 0 && siz == 0 && width == 1)
                {
                    return(string.Format("gsDPSetDepthImage(0x{0:X8})", i));
                }
                else
                {
                    return(string.Format("gsSetImage(G_SETZIMG, {0}, {1}, {2}, 0x{3:X8})", fmt, siz, width, i));
                }
            }

            case F3DEX2CommandId.G_SETTIMG:
            {
                F3DEX2Decoder.ParseSetImage(this,
                                            out int fmt, out int siz, out int width, out ulong i);

                return(string.Format("gsDPSetTextureImage({0}, {1}, {2}, 0x{3:X8})",
                                     (RDPImgFormat)fmt, (RDPImgSize)siz,
                                     width, i));
            }

            case F3DEX2CommandId.G_SETCOMBINE:
            {
                F3DEX2Decoder.ParseSetCombine(this,
                                              out ulong ulongMode);

                CombineMode mode = new CombineMode(ulongMode);

                string modeStr = mode.ToString();

                if (modeStr.StartsWith("G_CC"))
                {
                    return(string.Format("gsDPSetCombine({0})", modeStr));
                }
                else
                {
                    return(string.Format("gsDPSetCombineLerp({0})", modeStr));
                }
            }

            case F3DEX2CommandId.G_SETENVCOLOR:
            {
                F3DEX2Decoder.ParseSetColor(this,
                                            out ulong col);
                F3DEX2Decoder.GetComponents(col,
                                            out int r, out int g, out int b, out int a);

                return(string.Format("gsDPSetEnvColor(0x{0:X2}, 0x{1:X2}, 0x{2:X2}, 0x{3:X2})", r, g, b, a));
            }

            case F3DEX2CommandId.G_SETPRIMCOLOR:
            {
                F3DEX2Decoder.ParseSetColor(this,
                                            out ulong col);
                F3DEX2Decoder.GetComponents(col,
                                            out int r, out int g, out int b, out int a);

                int m = (int)(Words >> (32 + 8)) & 0xFF;
                int l = (int)(Words >> (32 + 0)) & 0xFF;

                return(string.Format("gsDPSetPrimColor(0x{0:X2}, 0x{1:X2}, 0x{2:X2}, 0x{3:X2}, 0x{4:X2}, 0x{5:X2})", m, l, r, g, b, a));
            }

            case F3DEX2CommandId.G_SETBLENDCOLOR:
            {
                F3DEX2Decoder.ParseSetColor(this,
                                            out ulong col);
                F3DEX2Decoder.GetComponents(col,
                                            out int r, out int g, out int b, out int a);

                return(string.Format("gsDPSetBlendColor(0x{0:X2}, 0x{1:X2}, 0x{2:X2}, 0x{3:X2})", r, g, b, a));
            }

            case F3DEX2CommandId.G_SETFOGCOLOR:
            {
                F3DEX2Decoder.ParseSetColor(this,
                                            out ulong col);
                F3DEX2Decoder.GetComponents(col,
                                            out int r, out int g, out int b, out int a);

                return(string.Format("gsDPSetFogColor(0x{0:X2}, 0x{1:X2}, 0x{2:X2}, 0x{3:X2})", r, g, b, a));
            }

            case F3DEX2CommandId.G_SETFILLCOLOR:
            {
                F3DEX2Decoder.ParseSetColor(this,
                                            out ulong col);

                return(string.Format("gsDPSetFillColor({0})", F3DEX2Decoder.PackedColorWordToStr(col)));
            }

            case F3DEX2CommandId.G_FILLRECT:
            {
                F3DEX2Decoder.ParseRect(this,
                                        out int xl, out int yl, out int xh, out int yh, out int _);

                return(string.Format("gsDPFillRectangle({0}, {1}, {2}, {3})",
                                     xl >> 2, yl >> 2, (xh >> 2), (yh >> 2)));
            }

            case F3DEX2CommandId.G_SETTILE:
            {
                F3DEX2Decoder.ParseSetTile(this,
                                           out int fmt, out int siz, out int line, out int tmem, out int tile, out int palette, out int cmt,
                                           out int maskt, out int shiftt, out int cms, out int masks, out int shifts);
                RDPMirror    mirrorT = (RDPMirror)(cmt & 0x1);
                RDPWrapClamp clampT  = (RDPWrapClamp)(cmt & 0x2);
                RDPMirror    mirrorS = (RDPMirror)(cms & 0x1);
                RDPWrapClamp clampS  = (RDPWrapClamp)(cms & 0x2);

                return(string.Format("gsDPSetTile({0}, {1}, {2}, {3}, {4}, {5}, {6}|{7}, {8}, {9}, {10}|{11}, {12}, {13})",
                                     (RDPImgFormat)fmt, (RDPImgSize)siz,
                                     line, tmem, tile, palette,
                                     mirrorT, clampT,
                                     maskt, shiftt,
                                     mirrorS, clampS,
                                     masks, shifts));
            }

            case F3DEX2CommandId.G_LOADTILE:
            {
                F3DEX2Decoder.ParseLoadTileGeneric(this,
                                                   out int tile, out int uls, out int ult, out int lrs, out int lrt);

                string ulsStr = F3DEX2Decoder.TexCoord102ToStr(uls);
                string ultStr = F3DEX2Decoder.TexCoord102ToStr(ult);
                string lrsStr = F3DEX2Decoder.TexCoord102ToStr(lrs, true);
                string lrtStr = F3DEX2Decoder.TexCoord102ToStr(lrt, true);

                return(string.Format("gsDPLoadTile({0}, {1}, {2}, {3}, {4})",
                                     tile, ulsStr, ultStr, lrsStr, lrtStr)); // TODO CI4 textures are loaded as 8b, so this may not produce a clean output
            }

            case F3DEX2CommandId.G_LOADBLOCK:
            {
                F3DEX2Decoder.ParseLoadTileGeneric(this,
                                                   out int tile, out int uls, out int ult, out int lrs, out int dxt);

                return(string.Format("gsDPLoadBlock({0}, 0x{1:X3}, 0x{2:X3}, 0x{3:X3}, 0x{4:X3})",
                                     tile, uls, ult, lrs, dxt));
            }

            case F3DEX2CommandId.G_SETTILESIZE:
            {
                F3DEX2Decoder.ParseLoadTileGeneric(this,
                                                   out int tile, out int uls, out int ult, out int lrs, out int lrt);

                string ulsStr = F3DEX2Decoder.TexCoord102ToStr(uls);
                string ultStr = F3DEX2Decoder.TexCoord102ToStr(ult);
                string lrsStr = F3DEX2Decoder.TexCoord102ToStr(lrs, true);
                string lrtStr = F3DEX2Decoder.TexCoord102ToStr(lrt, true);

                return(string.Format("gsDPSetTileSize({0}, {1}, {2}, {3}, {4})",
                                     tile, ulsStr, ultStr, lrsStr, lrtStr));
            }

            case F3DEX2CommandId.G_LOADTLUT:
            {
                F3DEX2Decoder.ParseLoadTLUT(this,
                                            out int tile, out int count);
                return(string.Format("gsDPLoadTLUTCmd({0}, {1})", tile, count));
            }

            case F3DEX2CommandId.G_RDPSETOTHERMODE:
            {
                F3DEX2Decoder.ParseRDPSetOtherMode(this,
                                                   out PipelineMode pm, out CycleType cyc, out TexturePersp tp, out TextureDetail td, out TextureLOD tl,
                                                   out TextureLUT tt, out TextureFilter tf, out TextureConvert tc, out CombineKey ck, out ColorDither cd,
                                                   out AlphaDither ad, out AlphaCompare ac, out DepthSource zs, out RenderMode rm);

                return(string.Format("gsDPSetOtherMode(\n" +
                                     indentation + "{0} | {1} | {2} | {3} | {4} | {5}\n" +
                                     indentation + "{6} | {7} | {8} | {9} | {10},\n" +
                                     indentation + "{11} | {12} | {13})",
                                     pm, cyc, tp, td, tl, tt, tf, tc, ck, cd, ad,
                                     ac, zs, rm.ToString().Replace(",", " |")));
            }

            case F3DEX2CommandId.G_SETPRIMDEPTH: break;

            case F3DEX2CommandId.G_SETSCISSOR:
            {
                F3DEX2Decoder.ParseScissor(this,
                                           out int mode, out int ulx, out int uly, out int lrx, out int lry);

                return(string.Format("gsDPSetScissor({0}, {1:F2}f, {2:F2}f, {3:F2}f, {4:F2}f)",
                                     (ScissorMode)mode, ulx / 4.0f, uly / 4.0f, lrx / 4.0f, lry / 4.0f));
            }

            case F3DEX2CommandId.G_SETCONVERT:
            {
                F3DEX2Decoder.ParseConvert(this,
                                           out int[] coeffs);

                string k0Str = (coeffs[0] == (short)ConvertCoeff.G_CV_K0) ? "G_CV_K0" : coeffs[0].ToString();
                string k1Str = (coeffs[1] == (short)ConvertCoeff.G_CV_K1) ? "G_CV_K1" : coeffs[1].ToString();
                string k2Str = (coeffs[2] == (short)ConvertCoeff.G_CV_K2) ? "G_CV_K2" : coeffs[2].ToString();
                string k3Str = (coeffs[3] == (short)ConvertCoeff.G_CV_K3) ? "G_CV_K3" : coeffs[3].ToString();
                string k4Str = (coeffs[4] == (short)ConvertCoeff.G_CV_K4) ? "G_CV_K4" : coeffs[4].ToString();
                string k5Str = (coeffs[5] == (short)ConvertCoeff.G_CV_K5) ? "G_CV_K5" : coeffs[5].ToString();

                return(string.Format("gsDPSetConvert({0}, {1}, {2}, {3}, {4}, {5})",
                                     k0Str, k1Str, k2Str, k3Str, k4Str, k5Str));
            }

            case F3DEX2CommandId.G_SETKEYR: break;

            case F3DEX2CommandId.G_SETKEYGB: break;

            case F3DEX2CommandId.G_RDPFULLSYNC:
                return("gsDPFullSync()");

            case F3DEX2CommandId.G_RDPTILESYNC:
                return("gsDPTileSync()");

            case F3DEX2CommandId.G_RDPPIPESYNC:
                return("gsDPPipeSync()");

            case F3DEX2CommandId.G_RDPLOADSYNC:
                return("gsDPLoadSync()");

            case F3DEX2CommandId.G_TEXRECTFLIP:     // Fall through
            case F3DEX2CommandId.G_TEXRECT:
            {
                F3DEX2Decoder.ParseRect(this,
                                        out int xl, out int yl, out int xh, out int yh, out int tile);

                string xlStr = F3DEX2Decoder.TexCoord102ToStr(xl);
                string ylStr = F3DEX2Decoder.TexCoord102ToStr(yl);

                string xhStr = F3DEX2Decoder.TexCoord102OffsetToStr(xl, xh);
                string yhStr = F3DEX2Decoder.TexCoord102OffsetToStr(yl, yh);

                F3DEX2Command nextCmd0 = nextCmds[0];
                F3DEX2Command nextCmd1 = nextCmds[1];

                if (nextCmd0.CommandId == F3DEX2CommandId.G_RDPHALF_1 && nextCmd1.CommandId == F3DEX2CommandId.G_RDPHALF_2)
                {
                    F3DEX2Decoder.ParseRDPHalf_HH(nextCmd0, out int s, out int t);
                    F3DEX2Decoder.ParseRDPHalf_HH(nextCmd1, out int dsdx, out int dtdy);

                    cmdsSkipped = 2;

                    if (CommandId == F3DEX2CommandId.G_TEXRECT)
                    {
                        return(string.Format("gsSPTextureRectangle({0}, {1}, {2}, {3}, {4}, 0x{5:X4}, 0x{6:X4}, 0x{7:X4}, 0x{8:X4})",
                                             xlStr, ylStr, xhStr, yhStr, tile, s, t, dsdx, dtdy));
                    }
                    else
                    {
                        return(string.Format("gsSPTextureRectangleFlip({0}, {1}, {2}, {3}, {4}, 0x{5:X4}, 0x{6:X4}, 0x{7:X4}, 0x{8:X4})",
                                             xlStr, ylStr, xhStr, yhStr, tile, s, t, dsdx, dtdy));
                    }
                }
                break;
            }
            }
            return(string.Format("{0:X8},{1:X8}", Words >> 32, Words & 0xFFFFFFFF));
        }