Advertisement
Guest User

TKMK00

a guest
May 10th, 2016
101
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 24.63 KB | None | 0 0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using Cereal64.Common.Utils;
  6.  
  7. //Written by mib_f8sm9c on May 2016, blah blah blah
  8.  
  9. namespace MarioKartTestingTool
  10. {
  11.     public static class TKMK00Encoder
  12.     {
  13.         /// <summary>
  14.         /// Huffman trees have a left/right node and a value. In TKMK00, if the left or right node exists, then it's just a traversal
  15.         ///  node, and will not be an output. Traversal nodes will have a value >= 0x20, while output nodes will have a value between
  16.         ///  0x00 - 0x1F (all values for a 5-bit number).
  17.         ///  
  18.         /// While it isn't necessary in decoding to store the values of traversal nodes, we DO need to have it work for the encoding
  19.         ///  process, since the assembly code uses two arrays to simulate a binary tree, and incorrect traversal values will make it
  20.         ///  non-functional.
  21.         /// </summary>
  22.         public class TKMK00HuffmanTreeNode
  23.         {
  24.             public TKMK00HuffmanTreeNode Left;
  25.             public TKMK00HuffmanTreeNode Right;
  26.             public int Value;
  27.  
  28.             public bool IsEndNode
  29.             { get { return Left == null && Right == null; } }
  30.         }
  31.  
  32.         /// <summary>
  33.         /// The 0x2C size header for the TKMK00 data. Starts with "TKMK00".
  34.         /// </summary>
  35.         public class TKMK00Header
  36.         {
  37.             public byte RepeatModeMask;
  38.             public byte Unknown;
  39.             public ushort Width;
  40.             public ushort Height;
  41.             public int[] ChannelPointers = new int[8];
  42.  
  43.             public TKMK00Header()
  44.             {
  45.                 //Empty, let the user define the values themself
  46.             }
  47.  
  48.             public TKMK00Header(byte[] headerData)
  49.             {
  50.                 RepeatModeMask = ByteHelper.ReadByte(headerData, 0x6);
  51.                 Unknown = ByteHelper.ReadByte(headerData, 0x7);
  52.                 Width = ByteHelper.ReadUShort(headerData, 0x8);
  53.                 Height = ByteHelper.ReadUShort(headerData, 0xA);
  54.                 for (int i = 0; i < 8; i++)
  55.                     ChannelPointers[i] = ByteHelper.ReadInt(headerData, 0xC + 4 * i);
  56.             }
  57.  
  58.             public byte[] GetAsBytes() //TEST THIS
  59.             {
  60.                 return ByteHelper.CombineIntoBytes('T', 'K', 'M', 'K', '0', '0',
  61.                     RepeatModeMask, Unknown, Width, Height, ChannelPointers);
  62.             }
  63.  
  64.             /// <summary>
  65.             /// Reads the RepeatModeMask for the specific channel's bit, and returns if it's flipped on or off.
  66.             /// </summary>
  67.             public bool RepeatEnabledFor(int channelNum)
  68.             {
  69.                 int maskCompare = 0x1 << channelNum;
  70.                 return ((RepeatModeMask & maskCompare) != 0);
  71.             }
  72.  
  73.             public static int DataSize { get { return 0x2C; } }
  74.         }
  75.  
  76.         /// <summary>
  77.         /// This class is combined from two different bit readers specified in the decoding assembly. The first form of it
  78.         ///  (no repeat mode) reads in 4 bytes at a time, and reads 1 bit at a time. It can also return multi-bit sized
  79.         ///  outputs (the decoding reads up to 6 at a time).
  80.         ///  
  81.         /// The second form is repeat mode. In this form it loads 1 byte at a time and always returns 1 bit, but there's
  82.         ///  an extra ignoreRepeat flag. If the flag is off (repeat mode is on), then when it hits the end of the byte it
  83.         ///  re-uses the current byte. When RemainingBits reaches zero, it reads in a byte that tells it if the IgnoreRepeat
  84.         ///  flag is on or off, and then the number of RemainingBits (will almost always be bigger than the size of a byte).
  85.         /// </summary>
  86.         public class TKMK00CommandReader
  87.         {
  88.             private byte[] _data; //Rom data, TKMK00 data, whatever you're reading from
  89.             private int _dataPointer; //Reading offset in the data
  90.             private int _buffer; //Last byte data loaded from _data
  91.             private int _remainingBits; //Remaining bits before loading the next byte[s]
  92.             private bool _repeatEnabled; //Repeat mode
  93.             private bool _ignoreRepeatFlag; //Flag used in repeat mode
  94.  
  95.             public TKMK00CommandReader(byte[] data, int dataPointer, bool repeatEnabled)
  96.             {
  97.                 _data = data;
  98.                 _buffer = 0;
  99.                 _dataPointer = dataPointer;
  100.                 _remainingBits = 0;
  101.                 _repeatEnabled = repeatEnabled;
  102.                 _ignoreRepeatFlag = false;
  103.  
  104.                 //Here, we set back the pointer 1 load's worth so when it reads the first value it moves up and reads the correct location
  105.                 if (!repeatEnabled)
  106.                     _dataPointer -= 4;
  107.                 else
  108.                     _dataPointer--;
  109.             }
  110.  
  111.             public int ReadBits()
  112.             {
  113.                 return ReadBits(1);
  114.             }
  115.  
  116.             public int ReadBits(int bitCount) //Bitcount only counts for non-repeating readers
  117.             {
  118.                 int commandValue = 0;//clear the command value
  119.  
  120.                 if (!_repeatEnabled) //Not repeat mode
  121.                 {
  122.                     while (bitCount > 0) //Read out the # of bits in the parameter
  123.                     {
  124.                         if (_remainingBits == 0) //If the end of the current buffer has been reached
  125.                         {
  126.                             //Load the next 4 bytes and progress the pointer/reset the remaining bits
  127.                             _dataPointer += 4;
  128.                             _buffer = ByteHelper.ReadInt(_data, _dataPointer);
  129.                             _remainingBits = 0x20;
  130.                         }
  131.  
  132.                         //Append the next bit in the buffer to the end of the commandValue
  133.                         commandValue = ((_buffer >> (_remainingBits - 1)) & 0x1) | (commandValue << 1);
  134.                         _remainingBits--;
  135.  
  136.                         bitCount--;
  137.                     }
  138.                 }
  139.                 else //Repeat mode
  140.                 {
  141.                     if (_remainingBits == 0) //out of data
  142.                     {
  143.                         _dataPointer++;
  144.                         byte repeatInfo = ByteHelper.ReadByte(_data, _dataPointer);
  145.  
  146.                         _ignoreRepeatFlag = ((repeatInfo & 0x80) != 0); //The new ignoreRepeatFlag is stored in the top bit
  147.  
  148.                         if (_ignoreRepeatFlag)
  149.                         {
  150.                             _remainingBits = (ushort)(((repeatInfo & 0x7F) + 1) * 8); //Read new byte (repeatInfo + 1) times
  151.                         }
  152.                         else
  153.                         {
  154.                             _remainingBits = (ushort)((repeatInfo + 3) * 8); //Repeat byte (repeatInfo + 3) times
  155.                         }
  156.  
  157.                         _dataPointer++;
  158.                         _buffer = ByteHelper.ReadByte(_data, _dataPointer); //Read in next byte
  159.                     }
  160.  
  161.                     int byteWrappedRemainingBits = (_remainingBits & 0x7);
  162.                     if (byteWrappedRemainingBits == 0)
  163.                         byteWrappedRemainingBits = 8; //Remaining bits on CURRENT byte
  164.  
  165.                     //Set the commandValue to the bit in the buffer
  166.                     commandValue = ((_buffer >> (byteWrappedRemainingBits - 1)) & 0x1);
  167.                     _remainingBits--;
  168.  
  169.                     byteWrappedRemainingBits--;
  170.  
  171.                     //Check if we've reached the end of the bit but not the remaining bits
  172.                     if (byteWrappedRemainingBits == 0 && _remainingBits > 0)
  173.                     {
  174.                         if (_ignoreRepeatFlag) //Load in next byte if ignoreRepeatMode
  175.                         {
  176.                             _dataPointer++;
  177.                             _buffer = ByteHelper.ReadByte(_data, _dataPointer);
  178.                         }
  179.                     }
  180.                 }
  181.                 return commandValue;
  182.             }
  183.         }
  184.  
  185.         public static void Encode()
  186.         {
  187.             //Finish me!
  188.         }
  189.  
  190.         #region Decode
  191.  
  192.         /// <summary>
  193.         /// Decodes the TKMK00 format into RGBA5551 formatted data. Uses a Huffman tree to store
  194.         ///  color values that are added/subtracted/sometimes overwrite a predicted color for the
  195.         ///  current pixel. Look up DPCM for a conceptual idea of what it's doing (the Huffman tree
  196.         ///  stands in place for entropy coding.
  197.         /// </summary>
  198.         public static byte[] Decode(byte[] data, int tkmk00Offset, ushort alphaColor)
  199.         {
  200.             //Initialize the header & readers
  201.             byte[] headerBytes = new byte[TKMK00Header.DataSize];
  202.             Array.Copy(data, tkmk00Offset, headerBytes, 0, TKMK00Header.DataSize);
  203.             TKMK00Header header = new TKMK00Header(headerBytes);
  204.             TKMK00CommandReader masterReader = new TKMK00CommandReader(data, tkmk00Offset + TKMK00Header.DataSize, false);
  205.             TKMK00CommandReader[] channelReaders = new TKMK00CommandReader[8];
  206.             for(int i = 0; i < 8; i++)
  207.                 channelReaders[i] = new TKMK00CommandReader(data, tkmk00Offset + header.ChannelPointers[i], header.RepeatEnabledFor(i));
  208.  
  209.             //Set up the image data/buffers
  210.             ushort[] rgbaBuffer = new ushort[0x40];
  211.             for (int i = 0; i < 0x40; i++)
  212.                 rgbaBuffer[i] = 0xFF;
  213.             byte[] colorChangeMap = new byte[header.Width * header.Height];
  214.             byte[] imageData = new byte[header.Width * header.Height * 2];
  215.             int pixelIndex = 0;
  216.  
  217.             //Set up the Huffman binary tree
  218.             TKMK00HuffmanTreeNode headTreeNode = SetUpHuffmanTree(0x20, data, channelReaders[0]);
  219.            
  220.             ushort lastPixelColor = 0;
  221.  
  222.             //Iterate through each pixel in order left to right, top to bottom
  223.             for (int row = 0; row < header.Height; row++)
  224.             {
  225.                 for (int col = 0; col < header.Width; col++)
  226.                 {
  227.                     //Look at the current pixel's color. If it's not empty, then it's already been
  228.                     // set to its correct value, and we can skip to the next pixel
  229.                     ushort currentPixelColor = ByteHelper.ReadUShort(imageData, pixelIndex * 2);
  230.  
  231.                     if (currentPixelColor != 0) //Color already exists
  232.                     {
  233.                         lastPixelColor = currentPixelColor;
  234.  
  235.                         //Test to make sure that the curent color is not the alpha value with the incorrect alpha channel value
  236.                         ushort currentPixelWithoutAlpha = (ushort)(currentPixelColor & 0xFFFE);
  237.                         if (currentPixelWithoutAlpha == alphaColor)
  238.                         {
  239.                             ByteHelper.WriteUShort(alphaColor, imageData, pixelIndex * 2);
  240.                             lastPixelColor = alphaColor;
  241.                         }
  242.  
  243.                         //Done, go to end of the loop
  244.                     }
  245.                     else
  246.                     {
  247.                         //Load up the channel reader that is associated with the given color change value in the
  248.                         // colorChangeMap (low values = not much change around that pixel, high values = lots of change)
  249.                         byte channelIndex = (byte)(colorChangeMap[pixelIndex] + 1);
  250.  
  251.                         int command = channelReaders[channelIndex].ReadBits(1); // 0 - Use the previous color, 1 - Use new color
  252.  
  253.                         if (command == 0)
  254.                         {
  255.                             ByteHelper.WriteUShort(lastPixelColor, imageData, pixelIndex * 2);
  256.                             //End of this line
  257.                         }
  258.                         else
  259.                         {
  260.                             command = masterReader.ReadBits(1); // 0 - Create new RGBA, 1 - Use existing RGBA
  261.                             if (command != 0)
  262.                             {
  263.                                 //Load in the huffman values for the new green, red and blue. These are combined
  264.                                 // with a predicted pixel value for them later on.
  265.                                 int newGreen = RetrieveHuffmanTreeValue(data, headTreeNode, channelReaders[0]);
  266.                                 int newRed = RetrieveHuffmanTreeValue(data, headTreeNode, channelReaders[0]);
  267.                                 int newBlue = RetrieveHuffmanTreeValue(data, headTreeNode, channelReaders[0]);
  268.  
  269.                                 //Retreive the pixel colors from the pixel above and the pixel to the left
  270.                                 ushort rgbaTop, rgbaLeft;
  271.  
  272.                                 if (row != 0)
  273.                                 {
  274.                                     rgbaTop = ByteHelper.ReadUShort(imageData, (pixelIndex - header.Width) * 2);
  275.                                     rgbaLeft = ByteHelper.ReadUShort(imageData, (pixelIndex - 1) * 2);
  276.                                 }
  277.                                 else
  278.                                 {
  279.                                     rgbaTop = 0;
  280.                                     if (col != 0)
  281.                                         rgbaLeft = ByteHelper.ReadUShort(imageData, (pixelIndex - 1) * 2);
  282.                                     else
  283.                                         rgbaLeft = 0;
  284.                                 }
  285.  
  286.                                 //Combine green values of the pixels to make our predicted pixel color
  287.                                 ushort greenTop = (byte)((rgbaTop & 0x7C0) >> 6);
  288.                                 ushort greenLeft = (byte)((rgbaLeft & 0x7C0) >> 6);
  289.                                 int greenPrediction = (greenTop + greenLeft) / 2;
  290.  
  291.                                 //Combine the prediction & huffman value to make the output color
  292.                                 ColorCombine(greenPrediction, ref newGreen);
  293.  
  294.                                 //Use the change between the old & new green values to project expected
  295.                                 // values for the red & blue colors
  296.                                 int greenChange = newGreen - greenPrediction;
  297.  
  298.                                 //Combine red values of the pixels to make our predicted pixel color
  299.                                 ushort redTop = (byte)((rgbaTop & 0xF800) >> 11);
  300.                                 ushort redLeft = (byte)((rgbaLeft & 0xF800) >> 11);
  301.                                 int redPrediction = greenChange + (redTop + redLeft) / 2;
  302.                                 redPrediction = Math.Max(0, Math.Min(0x1F, redPrediction)); //Keep between 0 and 0x1F
  303.  
  304.                                 //Combine the prediction & huffman value to make the output color
  305.                                 ColorCombine(redPrediction, ref newRed);
  306.  
  307.                                 //Combine blue values of the pixels to make our predicted pixel color
  308.                                 ushort blueTop = (byte)((rgbaTop & 0x3E) >> 1);
  309.                                 ushort blueLeft = (byte)((rgbaLeft & 0x3E) >> 1);
  310.                                 int bluePrediction = greenChange + (blueTop + blueLeft) / 2;
  311.                                 bluePrediction = Math.Max(0, Math.Min(0x1F, bluePrediction)); //Keep between 0 and 0x1F
  312.  
  313.                                 //Combine the prediction & huffman value to make the output color
  314.                                 ColorCombine(bluePrediction, ref newBlue);
  315.  
  316.                                 //Make the newpixel color
  317.                                 currentPixelColor = (ushort)((newRed << 11) | (newGreen << 6) | (newBlue << 1));
  318.                                 if (currentPixelColor != alphaColor) //Only transparent if it matches the transparency pixel
  319.                                     currentPixelColor |= 0x1;
  320.  
  321.                                 //Add to the front of the color buffer
  322.                                 for (int i = rgbaBuffer.Length - 1; i > 0; i--)
  323.                                     rgbaBuffer[i] = rgbaBuffer[i - 1];
  324.                                 rgbaBuffer[0] = currentPixelColor;
  325.                             }
  326.                             else //Use existing RGBA
  327.                             {
  328.                                 command = masterReader.ReadBits(6); // Returns index of color in color buffer to use
  329.                                 currentPixelColor = rgbaBuffer[command];
  330.                                 if (command != 0)
  331.                                 {
  332.                                     //Bump the selected color to the front of the buffer
  333.                                     for (int i = command; i > 0; i--)
  334.                                         rgbaBuffer[i] = rgbaBuffer[i - 1];
  335.                                     rgbaBuffer[0] = currentPixelColor;
  336.                                 }
  337.                             }
  338.  
  339.                             //Write the RGBA to the imageData
  340.                             ByteHelper.WriteUShort(currentPixelColor, imageData, pixelIndex * 2);
  341.                             lastPixelColor = currentPixelColor;
  342.  
  343.                             //Add nearby pixels to the colorChangeMap
  344.                             bool hasLeftCol = (col != 0);
  345.                             bool hasRightCol = (col < (header.Width - 1));
  346.                             bool has2RightCols = (col < (header.Width - 2));
  347.                             bool hasDownRow = (row < (header.Height - 1));
  348.                             bool has2DownRows = (row < (header.Height - 2));
  349.  
  350.                             //Right 1
  351.                             if (hasRightCol)
  352.                                 colorChangeMap[pixelIndex + 1]++;
  353.                             //Right 2
  354.                             if (has2RightCols)
  355.                                 colorChangeMap[pixelIndex + 2]++;
  356.                             //Down 1 Left 1
  357.                             if (hasDownRow && hasLeftCol)
  358.                                 colorChangeMap[pixelIndex + header.Width - 1]++;
  359.                             //Down 1
  360.                             if (hasDownRow)
  361.                                 colorChangeMap[pixelIndex + header.Width]++;
  362.                             //Down 1 Right 1
  363.                             if (hasDownRow && hasRightCol)
  364.                                 colorChangeMap[pixelIndex + header.Width + 1]++;
  365.                             //Down 2
  366.                             if (has2DownRows)
  367.                                 colorChangeMap[pixelIndex + header.Width * 2]++;
  368.  
  369.                             //Now test to see if we need to continue writing this color down the column
  370.                             command = masterReader.ReadBits(1);//1 - repeat color, 0 - continue
  371.  
  372.                             if (command == 1) //Repeat color
  373.                             {
  374.                                 //Basically move down one row each repeat, possibly moving to the side, and write the color again
  375.                                 int pixelOffset = 0;
  376.                                 ushort currentPixelColorOpaque = (ushort)(currentPixelColor | 0x1); //Not sure why this is the case, is it to catch it in the first if statement?
  377.                                
  378.                                 while(true) //I hate while(true)
  379.                                 {
  380.                                     command = masterReader.ReadBits(2);//0 - advanced move, 1 - back one, 2 - no lateral move, 3 - forward one
  381.                                     if (command == 0)
  382.                                     {
  383.                                         //Advanced move
  384.                                         command = masterReader.ReadBits(1);//0 - stop, 1 - advanced move
  385.  
  386.                                         if (command == 0)
  387.                                             break;
  388.  
  389.                                         command = masterReader.ReadBits(1); //0 - move back 2, 1 - move forward 2
  390.                                         if (command == 0)
  391.                                             pixelOffset -= 2;
  392.                                         else
  393.                                             pixelOffset += 2;
  394.                                     }
  395.                                     else if (command == 1)
  396.                                         pixelOffset--;
  397.                                     else if (command == 3)
  398.                                         pixelOffset++;
  399.  
  400.                                     pixelOffset += header.Width; //move down a row
  401.                                     ByteHelper.WriteUShort(currentPixelColorOpaque, imageData, (pixelIndex + pixelOffset) * 2);
  402.                                 }
  403.                             }
  404.                         }
  405.                     }
  406.  
  407.                     //Next pixel
  408.                     pixelIndex++;
  409.                 }
  410.             }
  411.  
  412.             return imageData;
  413.         }
  414.  
  415.         /// <summary>
  416.         /// Creates a Huffman-style binary tree that holds the 5-bit color data referenced in the
  417.         /// TKMK00 decoding process.
  418.         /// </summary>
  419.         private static TKMK00HuffmanTreeNode SetUpHuffmanTree(uint val, byte[] data, TKMK00CommandReader commandReader)
  420.         {
  421.             TKMK00HuffmanTreeNode newTree = new TKMK00HuffmanTreeNode();
  422.  
  423.             int command = commandReader.ReadBits(1);//0 - Make new branches, 1 - End current branch
  424.  
  425.             if (command != 0)
  426.             {
  427.                 newTree.Value = (int)val; //Not used, but can't hurt to number it
  428.                 val++;
  429.                 newTree.Left = SetUpHuffmanTree(val, data,  commandReader);
  430.                 newTree.Right = SetUpHuffmanTree(val, data,  commandReader);
  431.                 return newTree;
  432.             }
  433.  
  434.             //Else, return a node with a value
  435.             int value = 0;
  436.             int bitCount = 5;
  437.             do
  438.             {
  439.                 command = commandReader.ReadBits(1);
  440.                 value = value * 2 + command; //basically bitshifts s0 to the left and adds v0, aka it's loading 5 bytes straight from the comandReader
  441.                 bitCount -= 1;
  442.             } while (bitCount > 0);
  443.  
  444.             newTree.Value = value;
  445.             return newTree;
  446.         }
  447.  
  448.         //Only called when creating new rgba value. Traverses the Huffman tree to return the correct value
  449.         private static int RetrieveHuffmanTreeValue(byte[] data, TKMK00HuffmanTreeNode currentNode, TKMK00CommandReader commandReader)
  450.         {
  451.             while (!currentNode.IsEndNode)/*currentNode.Value >= 0x20*/
  452.             {
  453.                 int command = commandReader.ReadBits(1); // 0 - left, 1 - right
  454.                 if (command == 0)
  455.                     currentNode = currentNode.Left;
  456.                 else
  457.                     currentNode = currentNode.Right;
  458.             }
  459.            
  460.             return currentNode.Value;
  461.         }
  462.  
  463.         /// <summary>
  464.         /// Combines a predicted color with a new color. Sometimes the new color is just something added to/subtracted
  465.         ///  from the predicted color, others it's just used as the new color. Complex behavior, but it seems to work.
  466.         /// </summary>
  467.         private static void ColorCombine(int predictedColor, ref int newColor)
  468.         {
  469.             //See if our numbers could possibly go over 0x1F or below 0x00 if we do the differential method. If they could,
  470.             // then just use the new color.
  471.  
  472.             if (predictedColor >= 0x10) //Check if it'll go above 0x1F
  473.             {
  474.                 int remainingSpace = (0x1F - predictedColor);
  475.                 int incrementDecrementValue = newColor >> 1;
  476.                 bool isAdding = (newColor & 0x1) != 0;
  477.                 if (remainingSpace < incrementDecrementValue || (remainingSpace == incrementDecrementValue && isAdding)) //Since an extra 1 is used when adding
  478.                 {
  479.                     newColor = 0x1F - newColor;
  480.                     return;
  481.                 }
  482.             }
  483.             else //Check if it'll go below 0
  484.             {
  485.                 int incrementDecrementValue = newColor >> 1;
  486.                 bool isAdding = (newColor & 0x1) != 0;
  487.                 if (predictedColor < incrementDecrementValue || (predictedColor == incrementDecrementValue && isAdding))
  488.                 {
  489.                     return;
  490.                 }
  491.             }
  492.  
  493.             bool addToOld = (newColor & 0x1) != 0; //Last bit is used to determine if adding or subtracting the value
  494.             newColor = newColor >> 1;
  495.             if (addToOld)
  496.                 newColor += predictedColor + 1;
  497.             else
  498.                 newColor = predictedColor - newColor;
  499.  
  500.             return;
  501.         }
  502.  
  503.         #endregion
  504.     }
  505. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement