Пример #1
0
 public Fletcher(BitSize bitSize)
 {
     this.bitSize  = bitSize;
     bytesPerCycle = (int)bitSize / 16;
     modValue      = (ulong)(Math.Pow(2, 8 * bytesPerCycle) - 1);
     Initialize();
 }
Пример #2
0
        void pack_roundtrip_check <T>(BitSize bitcount)
            where T : unmanaged
        {
            var src = Random.BitString(bitcount);

            Claim.eq(bitcount, src.Length);

            var x = src.ToBits();

            Claim.eq(bitcount, x.Length);

            var y     = Bits.pack(x);
            var sizeT = size <T>();

            var q     = Math.DivRem(bitcount, 8, out int r);
            var bytes = q + (r == 0 ? 0 : 1);

            Claim.eq(bytes, y.Length);

            var bulk = ByteSpan.ReadValues <T>(y, out Span <byte> rem);

            var merged = rem.Length != 0 ? bulk.Extend(bulk.Length + 1) : bulk;

            if (merged.Length != bulk.Length)
            {
                merged[merged.Length - 1] = rem.TakeScalar <T>();
            }

            var bsOutput = merged.ToBitString().Truncate(bitcount);

            Claim.eq(src, bsOutput);
            Claim.eq((src & ~bsOutput).PopCount(), 0);
        }
Пример #3
0
        public static BitVector <T> Alloc(BitSize len, T?fill = null)
        {
            Span <T> cells = new T[CellCount(len)];

            if (fill.HasValue)
            {
                cells.Fill(fill.Value);
            }
            return(Load(cells));
        }
Пример #4
0
        public static int GetNumBytes(this int numTexels, BitSize bitSize)
        {
            float numBytesFloat = numTexels * BitSizeToNumBytesFloat(bitSize);
            int   numBytes      = (int)numBytesFloat;

            if ((float)numBytes != numBytesFloat)
            {
                throw new Exception("Non-integer number of bytes for the given number of texels");
            }
            return(numBytes);
        }
Пример #5
0
 public void bitsizes()
 {
     Claim.eq(8, BitSize.Size <byte>());
     Claim.eq(8, BitSize.Size <sbyte>());
     Claim.eq(16, BitSize.Size <short>());
     Claim.eq(16, BitSize.Size <ushort>());
     Claim.eq(32, BitSize.Size <int>());
     Claim.eq(32, BitSize.Size <uint>());
     Claim.eq(64, BitSize.Size <long>());
     Claim.eq(64, BitSize.Size <ulong>());
     Claim.eq(32, BitSize.Size <float>());
     Claim.eq(64, BitSize.Size <double>());
 }
Пример #6
0
        /// <summary>
        /// BitItems 태그에 값을 가진 BitItem Element를 추가한다.
        /// </summary>
        /// <param name="xDoc"></param>
        /// <param name="bitItemsNode"></param>
        /// <returns></returns>
        public XmlNode GetXml(XmlDocument xDoc, XmlNode bitItemsNode)
        {
            XmlNode root = XmlAdder.Element(xDoc, "BitItem", bitItemsNode);

            XmlAdder.Attribute(xDoc, "Name", BitName, root);
            XmlAdder.Attribute(xDoc, "StartOffset", StartOffset.ToString(), root);
            XmlAdder.Attribute(xDoc, "BitSize", BitSize.ToString(), root);
            XmlAdder.Attribute(xDoc, "PassCondition", PassCondition.Replace("<", "&lt;").Replace(">", "&gt;"), root);
            XmlAdder.Attribute(xDoc, "Description", Description, root);
            //XmlAdder.Attribute(xDoc, "Visible", Visible?"True":"False", root);
            XmlAdder.Attribute(xDoc, "ShowOnReport", ShowOnReport ? "True" : "False", root);
            return(root);
        }
Пример #7
0
        public static float BitSizeToNumBytesFloat(BitSize bitSize)
        {
            switch (bitSize)
            {
            case BitSize._4: return(0.5f);

            case BitSize._8: return(1);

            case BitSize._16: return(2);

            case BitSize._32: return(4);

            default: throw new Exception();
            }
        }
Пример #8
0
        void dotg_check <T>(BitSize bitcount, T rep = default, int cycles = DefaltCycleCount)
            where T : unmanaged
        {
            TypeCaseStart <T>();

            for (var i = 0; i < cycles; i++)
            {
                var x = Random.BitVector <T>(bitcount);
                var y = Random.BitVector <T>(bitcount);
                var a = x % y;
                var b = ModProd(x, y);
                Claim.yea(a == b);
            }

            TypeCaseEnd <T>();
        }
Пример #9
0
        void bv_disable_check <T>(BitSize n)
            where T : unmanaged
        {
            for (var k = 0; k < SampleSize; k++)
            {
                var bv = Random.BitVector <T>(n);
                var bs = bv.ToBitString();
                Claim.eq(bv.Length, n);
                Claim.eq(bv.Length, bs.Length);
                for (var i = 0; i < bv.Length; i += 2)
                {
                    bv.Disable(i);
                    bs[i] = Bit.Off;
                }

                Claim.eq(bv.ToBitString(), bs);
            }
        }
Пример #10
0
        public void pop_generic()
        {
            BitSize  bitlen  = 128 + 8;
            ByteSize bytelen = (ByteSize)bitlen;

            Claim.eq((int)bytelen, (int)bitlen / 8);
            for (var i = 0; i < CycleCount; i++)
            {
                var bv     = Random.BitVector <ulong>(bitlen);
                var actual = bv.Pop();
                var expect = 0ul;
                var bytes  = bv.Bytes;
                for (var j = 0; j < bytes.Length; j++)
                {
                    expect += Bits.pop(bytes[j]);
                }
                Claim.eq(expect, actual);
            }
        }
Пример #11
0
 public override int GetHashCode()
 {
     return((int)Domain * 17 ^ BitAddress.GetHashCode() ^ BitSize.GetHashCode());
 }
Пример #12
0
        public static Bitmap ConvertToBitmap(byte[] data, ColorFormat format, BitSize bitSize, int width, int height, int bytesPerLine, bool flipVertically, bool deinterleave, ushort[]?palette)
        {
            if (bytesPerLine % 8 != 0)
            {
                throw new Exception();
            }

            byte[][] lines = new byte[height][];

            for (int l = 0; l < height; l++)
            {
                lines[l] = data.Subsection(l * bytesPerLine, bytesPerLine);
            }

            // Deinterleave odd lines
            if (deinterleave)
            {
                for (int l = 1; l < height; l += 2)
                {
                    byte[] line = lines[l];

                    for (int word = 0; word < bytesPerLine / 8; word++)
                    {
                        int  wordPos = word * 8;
                        byte b1      = line[wordPos];
                        byte b2      = line[wordPos + 1];
                        byte b3      = line[wordPos + 2];
                        byte b4      = line[wordPos + 3];
                        byte b5      = line[wordPos + 4];
                        byte b6      = line[wordPos + 5];
                        byte b7      = line[wordPos + 6];
                        byte b8      = line[wordPos + 7];

                        line[wordPos]     = b5;
                        line[wordPos + 1] = b6;
                        line[wordPos + 2] = b7;
                        line[wordPos + 3] = b8;
                        line[wordPos + 4] = b1;
                        line[wordPos + 5] = b2;
                        line[wordPos + 6] = b3;
                        line[wordPos + 7] = b4;
                    }

                    // might not be necessary but w/e
                    lines[l] = line;
                }
            }

            Bitmap bmp = new Bitmap(width, height);

            for (int l = 0; l < height; l++)
            {
                byte[] line = lines[l];
                for (int x = 0; x < width; x++)
                {
                    Color texelColor;
                    if (format == ColorFormat.CI)
                    {
                        if (bitSize != BitSize._4)
                        {
                            throw new Exception();
                        }

                        if (palette == null)
                        {
                            throw new Exception();
                        }

                        int index = Get4BitTexel(line, x);

                        // We know it's always in RGBA16
                        texelColor = RGBA16ToColor(palette[index]);
                    }
                    else
                    {
                        texelColor = GetTexel(line, x, format, bitSize);
                    }

                    bmp.SetPixel(x, flipVertically ? (height - 1) - l : l, texelColor);
                }
            }
            return(bmp);
        }
Пример #13
0
        public static Color GetTexel(byte[] line, int texelX, ColorFormat format, BitSize bitSize)
        {
            ulong texel;

            switch (bitSize)
            {
            case BitSize._4:
                texel = Get4BitTexel(line, texelX);
                break;

            case BitSize._8:
                texel = line[texelX];
                break;

            case BitSize._16:
                texel = line.ReadUInt16(texelX * 2);
                break;

            case BitSize._32:
                texel = line.ReadUInt32(texelX * 4);
                break;

            default:
                throw new Exception();
            }

            if (format == ColorFormat.I)
            {
                byte i;
                if (bitSize == BitSize._4)
                {
                    i = (byte)(texel | (texel << 4));
                }
                else if (bitSize == BitSize._8)
                {
                    i = (byte)texel;
                }
                else
                {
                    throw new Exception();
                }
                return(Color.FromArgb(i, i, i, i));
            }
            else if (format == ColorFormat.IA)
            {
                byte i;
                byte alpha;
                if (bitSize == BitSize._4)
                {
                    ulong i3 = texel >> 1;
                    i     = (byte)(((i3 << 5) | (i3 << 2) | (i3 >> 1)) & 0xFF);
                    alpha = (byte)(0xFF * (texel & 1));
                }
                else if (bitSize == BitSize._8)
                {
                    ulong i4 = texel >> 4;
                    ulong a4 = texel & 0x0F;
                    i     = (byte)(i4 | (i4 << 4));
                    alpha = (byte)(a4 | (a4 << 4));
                }
                else if (bitSize == BitSize._16)
                {
                    i     = (byte)(texel >> 8);
                    alpha = (byte)(texel & 0xFF);
                }
                else
                {
                    throw new Exception();
                }
                return(Color.FromArgb(alpha, i, i, i));
            }
            else if (format == ColorFormat.RGBA)
            {
                if (bitSize == BitSize._16)
                {
                    return(RGBA16ToColor((ushort)texel));
                }
                else if (bitSize == BitSize._32)
                {
                    int argb = (int)((texel >> 8) | ((texel & 0xFF) << 24));
                    return(Color.FromArgb(argb));
                }
                else
                {
                    throw new Exception();
                }
            }
            else
            {
                throw new Exception();
            }
        }
Пример #14
0
        public static int FetchBits(this int word, BitNumber high, BitSize length)
        {
            var mask = ~(-1 << length.Value);

            return(word >> (high.Value - length.Value + 1) & mask);
        }
Пример #15
0
        public static int CellCount(BitSize len)
        {
            var q = Math.DivRem(len, SegmentCapacity, out int r);

            return(r == 0 ? q : q + 1);
        }
        public static void DumpBlits(byte[] romBytes, string outputDir)
        {
            Directory.CreateDirectory(outputDir + "Converted");
            string fullOutputPath = outputDir + "Converted/blit/";

            Directory.CreateDirectory(fullOutputPath);

            // ok this is dumb i need to fix this
            Filesystem.Filesystem filesystem = new Filesystem.Filesystem(romBytes);

            foreach (Filesystem.Filesystem.File file in filesystem.AllFiles.Where(file => file.fileTypeFromFileHeader == "UVBT"))
            {
                string outputFileName = $"[0x{file.formLocationInROM:x6}]";
                byte[] bytes          = file.Sections.Single().Item2;

                ColorFormat colorFormat   = (ColorFormat)bytes.ReadUInt16(0);
                BitSize     bitSize       = Texels.NumBitsToBitSize(bytes.ReadUInt16(2));
                ushort      width         = bytes.ReadUInt16(4);
                ushort      texelsPerLine = bytes.ReadUInt16(6);
                ushort      height        = bytes.ReadUInt16(8);
                ushort      tileWidth     = bytes.ReadUInt16(10);
                ushort      tileHeight    = bytes.ReadUInt16(12);

                byte[] colorData = bytes.Subsection(14, bytes.Length - 14);



                Console.WriteLine(bytes.Subsection(0, 14).PrettyPrint());
                Console.WriteLine($"{outputFileName} {bitSize}-bit {colorFormat}\t <{width} ({texelsPerLine}), {height}> [{tileWidth} {tileHeight}]");
                int bytesPerTile = (int)(tileWidth * tileHeight * Texels.BitSizeToNumBytesFloat(bitSize));

                Bitmap outBitmap   = new Bitmap(width, height);
                int    p           = 0;
                int    curTopLeftX = 0;
                int    curTopLeftY = 0;
                while (p < colorData.Length)
                {
                    if (curTopLeftY + tileHeight > height)
                    {
                        tileHeight   = (ushort)(height - curTopLeftY);
                        bytesPerTile = (int)(tileWidth * tileHeight * Texels.BitSizeToNumBytesFloat(bitSize));
                    }

                    byte[] tileColorData;
                    if (p + bytesPerTile >= colorData.Length)
                    {
                        tileColorData = new byte[bytesPerTile];
                        Array.Copy(colorData, p, tileColorData, 0, colorData.Length - p);
                    }
                    else
                    {
                        tileColorData = colorData.Subsection(p, bytesPerTile);
                    }
                    // TODO: why arent 32-bit textures deinterleaved?
                    Bitmap tile = Texels.ConvertToBitmap(tileColorData, colorFormat, bitSize, tileWidth, tileHeight, (int)(tileWidth * Texels.BitSizeToNumBytesFloat(bitSize)), false, bitSize != BitSize._32, null);
                    CopyToMainBitmap(tile, curTopLeftX, curTopLeftY, outBitmap);


                    p += bytesPerTile;

                    curTopLeftX += tileWidth;

                    if (curTopLeftX >= width)
                    {
                        curTopLeftX  = 0;
                        curTopLeftY += tileHeight;

                        if (curTopLeftY >= height)
                        {
                            break;
                        }
                    }
                }

                outBitmap.Save(fullOutputPath + outputFileName + ".png");
            }
        }
Пример #17
0
        public static ITextureConverter GetConverter(
            ColorFormat colorFormat,
            BitSize bitSize)
        {
            switch (colorFormat)
            {
            case ColorFormat.RGBA:
                switch (bitSize)
                {
                case BitSize.S_32B:
                    // TODO: Implement OGLTexImg = N64TexImg
                    throw new NotImplementedException();

                case BitSize.S_16B:
                    return(new Rgba16());

                default:
                    throw new NotSupportedException();
                }

            case ColorFormat.CI:
                switch (bitSize)
                {
                case BitSize.S_8B:
                    return(new Ci8());

                case BitSize.S_4B:
                    return(new Ci4());

                default:
                    throw new NotSupportedException();
                }

            case ColorFormat.IA:
                switch (bitSize)
                {
                case BitSize.S_16B:
                    return(new Ia16());

                case BitSize.S_8B:
                    return(new Ia8());

                case BitSize.S_4B:
                    return(new Ia4());

                default:
                    throw new NotSupportedException();
                }

            case ColorFormat.I:
                switch (bitSize)
                {
                case BitSize.S_8B:
                    return(new I8());

                case BitSize.S_4B:
                    return(new I4());

                default:
                    throw new NotSupportedException();
                }

            default:
                throw new NotSupportedException();
            }
        }
Пример #18
0
        public static RDPState ExecuteCommands(UVTXFile uvtx, out StringBuilder cmdsString)
        {
            RDPState rdpState = new RDPState();

            cmdsString = new StringBuilder();
            foreach (byte[] commandBytes in uvtx.displayListCommands)
            {
                byte[] bytes = commandBytes;

                string operationDesc;
                switch ((Fast3DEX2Opcode)bytes[0])
                {
                case Fast3DEX2Opcode.G_TEXTURE:
                {
                    ulong word          = bytes.ReadUInt64(0);
                    byte  mipMap        = (byte)(word.Bits(43, 3) + 1);
                    byte  tileDescIndex = (byte)word.Bits(40, 3);

                    bool  on           = word.Bit(33);
                    float scaleFactorS = bytes.ReadUInt16(4) / (float)0x10000;
                    float scaleFactorT = bytes.ReadUInt16(6) / (float)0x10000;

                    rdpState.tileToUseWhenTexturing = tileDescIndex;
                    rdpState.maxMipMapLevels        = mipMap;
                    rdpState.texturingEnabled       = on;
                    rdpState.textureScaleS          = scaleFactorS;
                    rdpState.textureScaleT          = scaleFactorT;

                    operationDesc  = "G_TEXTURE        (Set RSP texture state)";
                    operationDesc += $": tile {tileDescIndex} scale=<{scaleFactorS}, {scaleFactorT}>; mm={mipMap} on={on}";
                    break;
                }

                case Fast3DEX2Opcode.G_SetOtherMode_H:
                {
                    // TODO: actually implement this?
                    operationDesc = "G_SetOtherMode_H (Set Other Modes Hi)";

                    int length = bytes[3] + 1;
                    int shift  = 32 - length - bytes[2];

                    string str = otherModeHShiftToStr[shift];
                    byte   val = (byte)bytes.ReadUInt64(0).Bits(shift, length);

                    // TODO?: https://wiki.cloudmodding.com/oot/F3DZEX#RDP_Other_Modes.2C_Higher_Half
                    string valStr = Convert.ToString(val, 2).PadLeft(length, '0');

                    operationDesc += $": {str} = {valStr}";
                    break;
                }

                case Fast3DEX2Opcode.G_SETTILESIZE:
                {
                    ulong word = bytes.ReadUInt64(0);


                    ushort sLoRaw        = (ushort)word.Bits(44, 12);
                    ushort tLoRaw        = (ushort)word.Bits(32, 12);
                    byte   tileDescIndex = (byte)word.Bits(24, 3);
                    ushort sHiRaw        = (ushort)word.Bits(12, 12);
                    ushort tHiRaw        = (ushort)word.Bits(0, 12);

                    TileDescriptor t = rdpState.tileDescriptors[tileDescIndex];
                    t.sLo = sLoRaw / 4.0f;
                    t.tLo = tLoRaw / 4.0f;
                    t.sHi = sHiRaw / 4.0f;
                    t.tHi = tHiRaw / 4.0f;

                    float visWidth  = (t.sHi - t.sLo) + 1;
                    float visHeight = (t.tHi - t.tLo) + 1;

                    operationDesc  = "G_SETTILESIZE    (Set texture coords and size)";
                    operationDesc += $": tile {tileDescIndex} lo=({t.sLo}, {t.tLo}) hi=({t.sHi}, {t.tHi}) [[{visWidth}, {visHeight}]]";
                    break;
                }

                case Fast3DEX2Opcode.G_LOADBLOCK:
                {
                    ulong word = bytes.ReadUInt64(0);

                    ushort sLo           = (ushort)word.Bits(44, 12);
                    ushort tLo           = (ushort)word.Bits(32, 12);
                    byte   tileDescIndex = (byte)word.Bits(24, 3);
                    ushort sHi           = (ushort)word.Bits(12, 12);
                    ushort dxt           = (ushort)word.Bits(0, 12);

                    if (dxt != 0)
                    {
                        throw new Exception();
                    }

                    if (sLo != 0 || tLo != 0)
                    {
                        throw new Exception();
                    }

                    if (rdpState.nextDRAMAddrForLoad == null || rdpState.bitSizeOfNextDataForLoad == null)
                    {
                        throw new Exception();
                    }

                    rdpState.tileDescriptors[tileDescIndex].sLo = sLo;
                    rdpState.tileDescriptors[tileDescIndex].tLo = tLo;
                    rdpState.tileDescriptors[tileDescIndex].sHi = sHi;
                    rdpState.tileDescriptors[tileDescIndex].tHi = dxt;         // Not 100% sure this is the correct behavior


                    int dataStart       = (int)rdpState.nextDRAMAddrForLoad;
                    int dataLengthBytes = (sHi + 1) * Texels.BitSizeToNumBytes((BitSize)rdpState.bitSizeOfNextDataForLoad);
                    int destPtr         = rdpState.tileDescriptors[tileDescIndex].tmemAddressInWords * 8;

                    // I'm assuming this is the correct behavior because if I don't do this a lot of textures have a notch at the top right
                    // (Also it would make sense given that interleaving and addresses are all done on 64-bit words
                    dataLengthBytes = (int)Math.Ceiling(dataLengthBytes / 8f) * 8;

                    // Note: technically this inaccurate, we shouldn't clamp. But the instructions read beyond the file and IDK why,
                    // it doesn't seem to serve any purpose so I assume it's a bug (or I don't understand something about how the RSP works)
                    Array.Copy(uvtx.texelData, dataStart, rdpState.tmem, destPtr, Math.Min(uvtx.texelData.Length - dataStart, dataLengthBytes));

                    operationDesc  = "G_LOADBLOCK      (Load data into TMEM (uses params set in SETTIMG))";
                    operationDesc += $": tile {tileDescIndex} sLo={sLo} tLo={tLo} sHi={sHi} dxt={dxt}";
                    break;
                }

                case Fast3DEX2Opcode.G_SETTILE:
                {
                    ulong          word = bytes.ReadUInt64(0);
                    TileDescriptor t    = new TileDescriptor
                    {
                        format             = (ColorFormat)word.Bits(53, 3),
                        bitSize            = (BitSize)word.Bits(51, 2),
                        wordsPerLine       = (ushort)word.Bits(41, 9),
                        tmemAddressInWords = (ushort)word.Bits(32, 9),
                        palette            = (byte)word.Bits(20, 4),
                        clampEnableT       = word.Bit(19),
                        mirrorEnableT      = word.Bit(18),
                        maskT         = (byte)word.Bits(14, 4),
                        shiftT        = (byte)word.Bits(10, 4),
                        clampEnableS  = word.Bit(9),
                        mirrorEnableS = word.Bit(8),
                        maskS         = (byte)word.Bits(4, 4),
                        shiftS        = (byte)word.Bits(0, 4),
                    };
                    byte tileDescIndex = (byte)word.Bits(24, 3);
                    rdpState.tileDescriptors[tileDescIndex] = t;

                    operationDesc  = "G_SETTILE        (Set texture properties)";
                    operationDesc += $": tile {tileDescIndex} fmt={t.bitSize}-bit {t.format} wordsPerLine={t.wordsPerLine} addrWords={t.tmemAddressInWords} palette={t.palette}"
                                     + $" s(clmp={t.clampEnableS} mirr={t.mirrorEnableS} mask={t.maskS} shift={t.shiftS}) t(clmp={t.clampEnableT} mirr={t.mirrorEnableT} mask={t.maskT} shift={t.shiftT})";

                    break;
                }

                case Fast3DEX2Opcode.G_SETPRIMCOLOR:
                {
                    float minLODLevel = bytes[2] / 0x100f;
                    float LODfrac     = bytes[3] / 0x100f;
                    byte  r           = bytes[4];
                    byte  g           = bytes[5];
                    byte  b           = bytes[6];
                    byte  a           = bytes[7];

                    rdpState.colorCombinerSettings.primR       = r;
                    rdpState.colorCombinerSettings.primG       = g;
                    rdpState.colorCombinerSettings.primB       = b;
                    rdpState.colorCombinerSettings.primA       = a;
                    rdpState.colorCombinerSettings.minLODLevel = minLODLevel;
                    rdpState.colorCombinerSettings.LODfrac     = LODfrac;

                    operationDesc  = "G_SETPRIMCOLOR   (Set color combiner primitive color + LOD)";
                    operationDesc += $": rgba({r}, {g}, {b}, {a}) minLOD={minLODLevel} LODfrac={LODfrac}";
                    break;
                }

                case Fast3DEX2Opcode.G_SETENVCOLOR:
                {
                    byte r = bytes[4];
                    byte g = bytes[5];
                    byte b = bytes[6];
                    byte a = bytes[7];

                    rdpState.colorCombinerSettings.envR = r;
                    rdpState.colorCombinerSettings.envG = g;
                    rdpState.colorCombinerSettings.envB = b;
                    rdpState.colorCombinerSettings.envA = a;

                    operationDesc  = "G_SETENVCOLOR    (Set color combiner environment color)";
                    operationDesc += $": rgba({r}, {g}, {b}, {a})";
                    break;
                }

                case Fast3DEX2Opcode.G_SETCOMBINE:
                    operationDesc = "G_SETCOMBINE     (Set color combiner algorithm)";
                    break;

                case Fast3DEX2Opcode.G_SETTIMG:
                {
                    ulong word = bytes.ReadUInt64(0);

                    ColorFormat format      = (ColorFormat)word.Bits(53, 3);
                    BitSize     bitSize     = (BitSize)word.Bits(51, 2);
                    uint        dramAddress = (uint)word.Bits(0, 25);

                    rdpState.nextDRAMAddrForLoad      = dramAddress;
                    rdpState.bitSizeOfNextDataForLoad = bitSize;

                    operationDesc  = "G_SETTIMG        (Set pointer to data to load + size of data)";
                    operationDesc += $": DRAM 0x{dramAddress:X8}; fmt={bitSize}-bit {format}";

                    break;
                }

                case Fast3DEX2Opcode.G_RDPLOADSYNC:
                    operationDesc = "G_RDPLOADSYNC    (Wait for texture load)";
                    break;

                case Fast3DEX2Opcode.G_RDPTILESYNC:
                    operationDesc = "G_RDPTILESYNC    (Wait for rendering + update tile descriptor attributes)";
                    break;

                case Fast3DEX2Opcode.G_ENDDL:
                    operationDesc = "G_ENDDL          (End display list)";
                    break;

                default:
                    throw new InvalidOperationException();
                }

                string bytesStr = String.Join(" ", bytes.Select(b => b.ToString("X2")));
                cmdsString.AppendLine(bytesStr + " | " + operationDesc);
            }

            return(rdpState);
        }