public int Move(int start, ulong moveNum) { int current = start; foreach (var b in moveNum.Bits()) { if ((uint)current >= (uint)paths[b].Length) { return(current); } current = paths[b][current]; } return(current); }
public void BitEnumerateUInt64Test(ulong num, int[] expected) { num.Bits().Should().Equal(expected); }
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); }