From 07c209bf17120c6627f59d39623f21e2c0d69bec Mon Sep 17 00:00:00 2001 From: OlieGamerTV <48225845+OlieGamerTV@users.noreply.github.com> Date: Sun, 5 Nov 2023 19:16:20 +0000 Subject: [PATCH] Add import functionality for the Wii's U8 Compressed Archive and Archived Font files. (#675) * U8: Implemented LZ77 Type 11 & Type 10 Decompression. BXFNT: Implemented Wii's BRFNA files. * Removed accidental "using" inclusions in LZ77_WII --- BrawlboxHelper/BrawlboxHelper.dll | Bin 22016 -> 22016 bytes .../FileFormats/Archives/U8.cs | 43 ++- .../FileFormats/Font/BXFNT/BXFNT.cs | 76 +++-- .../FileFormats/Font/BXFNT/FINF.cs | 1 + .../FileFormats/Font/BXFNT/GLGR.cs | 37 +++ .../FileFormats/Font/BXFNT/TGLP.cs | 28 +- .../File_Format_Library.csproj | 1 + .../Compression/Huffman_WII.cs | 260 ++++++++++++++++++ .../Compression/LZ77_WII.cs | 58 ++++ Switch_Toolbox_Library/Toolbox_Library.csproj | 1 + 10 files changed, 466 insertions(+), 39 deletions(-) create mode 100644 File_Format_Library/FileFormats/Font/BXFNT/GLGR.cs create mode 100644 Switch_Toolbox_Library/Compression/Huffman_WII.cs diff --git a/BrawlboxHelper/BrawlboxHelper.dll b/BrawlboxHelper/BrawlboxHelper.dll index 8564fa74a8b215af555f48e0beaa99744d8aea43..b781a8afaaac82f9035c41ad58f4eb0c098aee50 100644 GIT binary patch delta 197 zcmZoz!`QHfaY6^n#kET>P3)0jY?`<-oKbf&BV#>d$K*yJnYfveiP=>kayd(ZMMb>V zq$1CkMT(PNY!-1ZVCR*nI<|hV0r?=d m!bC+e#>B}f{#uiJ{F^2k@NDJ__{GfBV!K%|(a(nbEXhdBi*dXjU*vuF3i + diff --git a/Switch_Toolbox_Library/Compression/Huffman_WII.cs b/Switch_Toolbox_Library/Compression/Huffman_WII.cs new file mode 100644 index 00000000..b7cfbe32 --- /dev/null +++ b/Switch_Toolbox_Library/Compression/Huffman_WII.cs @@ -0,0 +1,260 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Toolbox.Library.IO; + +namespace Toolbox.Library +{ + public class Huffman_WII + { + //Ported from + //https://github.com/Barubary/dsdecmp/blob/4ddd87206bacf4ce7d803b40ff3bd2663327b083/CSharp/DSDecmp/Program.cs#L417 + //(Modified to use byte arrays instead of file paths) + //Copyright (c) 2010 Nick Kraayenbrink + // + //Permission is hereby granted, free of charge, to any person obtaining a copy + //of this software and associated documentation files (the "Software"), to deal + //in the Software without restriction, including without limitation the rights + //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + //copies of the Software, and to permit persons to whom the Software is + //furnished to do so, subject to the following conditions: + // + //The above copyright notice and this permission notice shall be included in + //all copies or substantial portions of the Software. + // + //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + //THE SOFTWARE. + public static byte[] DecompressHuffman(byte[] input, int decompSize) + { + + FileReader br = new FileReader(new MemoryStream(input), true); + + byte firstByte = br.ReadByte(); + + int dataSize = firstByte & 0x0F; + + if ((firstByte & 0xF0) != 0x20) + throw new InvalidDataException(String.Format("Invalid huffman comressed file; invalid tag {0:x}", firstByte)); + + //Console.WriteLine("Data size: {0:x}", dataSize); + if (dataSize != 8 && dataSize != 4) + throw new InvalidDataException(String.Format("Unhandled dataSize {0:x}", dataSize)); + + int decomp_size = 0; + for (int i = 0; i < 3; i++) + { + decomp_size |= br.ReadByte() << (i * 8); + } + + byte treeSize = br.ReadByte(); + HuffTreeNode.maxInpos = 4 + (treeSize + 1) * 2; + + Console.WriteLine("Tree Size: {0:x}", treeSize); + Console.WriteLine("Tee end: 0x{0:X}", HuffTreeNode.maxInpos); + + HuffTreeNode rootNode = new HuffTreeNode(); + rootNode.parseData(br); + + br.BaseStream.Position = 4 + (treeSize + 1) * 2; // go to start of coded bitstream. + // read all data + uint[] indata = new uint[(br.BaseStream.Length - br.BaseStream.Position) / 4]; + for (int i = 0; i < indata.Length; i++) + indata[i] = br.ReadUInt32(); + + long curr_size = 0; + decomp_size *= dataSize == 8 ? 1 : 2; + byte[] outdata = new byte[decomp_size]; + + int idx = -1; + string codestr = ""; + LinkedList code = new LinkedList(); + int value; + + // Overwriting the likely nonsense read with the proper intended texture size. + decomp_size = decompSize; + + while (curr_size < decomp_size) + { + try + { + string newstr = uint_to_bits(indata[++idx]); + codestr += newstr; + Console.WriteLine("next uint: " + newstr); + } + catch (IndexOutOfRangeException e) + { + throw new IndexOutOfRangeException("not enough data.", e); + } + while (codestr.Length > 0 && curr_size < decompSize) // Need to force stop at the max decompSize, otherwise we get IndexOutOfRange Exceptions. + { + code.AddFirst(byte.Parse(codestr[0] + "")); + //Console.Write(code.First.Value); + codestr = codestr.Remove(0, 1); + if (rootNode.getValue(code.Last, out value)) + { + //Console.WriteLine(" -> "+value.ToString("X")); + try + { + outdata[curr_size++] = (byte)value; + } + catch (IndexOutOfRangeException ex) + { + if (code.First.Value != 0) + throw ex; + } + code.Clear(); + } + } + } + + br.Close(); + + byte[] realout; + if (dataSize == 4) + { + realout = new byte[decomp_size / 2]; + for (int i = 0; i < decomp_size / 2; i++) + { + if ((outdata[i * 2] & 0xF0) > 0 + || (outdata[i * 2 + 1] & 0xF0) > 0) + throw new Exception("first 4 bits of data should be 0 if dataSize = 4"); + realout[i] = (byte)((outdata[i * 2] << 4) | outdata[i * 2 + 1]); + } + } + else + { + realout = outdata; + } + Console.WriteLine("Huffman decompressed."); + return realout; + } + + private static string byte_to_bits(byte b) + { + string o = ""; + for (int i = 0; i < 8; i++) + o = (((b & (1 << i)) >> i) & 1) + o; + return o; + } + private static string uint_to_bits(uint u) + { + string o = ""; + for (int i = 3; i > -1; i--) + o += byte_to_bits((byte)((u & (0xFF << (i * 8))) >> (i * 8))); + return o; + } + } + + //Ported from + //https://github.com/Barubary/dsdecmp/blob/4ddd87206bacf4ce7d803b40ff3bd2663327b083/CSharp/DSDecmp/Program.cs#L1367 + class HuffTreeNode + { + internal static int maxInpos = 0; + internal HuffTreeNode node0, node1; + internal int data = -1; // [-1,0xFF] + /// + /// To get a value, provide the last node of a list of bytes < 2. + /// the list will be read from back to front. + /// + internal bool getValue(LinkedListNode code, out int value) + { + value = data; + if (code == null) + return node0 == null && node1 == null && data >= 0; + + if (code.Value > 1) + throw new Exception(String.Format("the list should be a list of bytes < 2. got:{0:g}", code.Value)); + + byte c = code.Value; + bool retVal; + HuffTreeNode n = c == 0 ? node0 : node1; + retVal = n != null && n.getValue(code.Previous, out value); + return retVal; + } + + internal int this[string code] + { + get + { + LinkedList c = new LinkedList(); + foreach (char ch in code) + c.AddFirst((byte)ch); + int val = 1; + return this.getValue(c.Last, out val) ? val : -1; + } + } + + internal void parseData(BinaryReader br) + { + /* + * Tree Table (list of 8bit nodes, starting with the root node) + Root Node and Non-Data-Child Nodes are: + Bit0-5 Offset to next child node, + Next child node0 is at (CurrentAddr AND NOT 1)+Offset*2+2 + Next child node1 is at (CurrentAddr AND NOT 1)+Offset*2+2+1 + Bit6 Node1 End Flag (1=Next child node is data) + Bit7 Node0 End Flag (1=Next child node is data) + Data nodes are (when End Flag was set in parent node): + Bit0-7 Data (upper bits should be zero if Data Size is less than 8) + */ + this.node0 = new HuffTreeNode(); + this.node1 = new HuffTreeNode(); + long currPos = br.BaseStream.Position; + byte b = br.ReadByte(); + long offset = b & 0x3F; + bool end0 = (b & 0x80) > 0, end1 = (b & 0x40) > 0; + + // parse data for node0 + br.BaseStream.Position = (currPos - (currPos & 1)) + offset * 2 + 2; + if (br.BaseStream.Position < maxInpos) + { + if (end0) + node0.data = br.ReadByte(); + else + node0.parseData(br); + } + + // parse data for node1 + br.BaseStream.Position = (currPos - (currPos & 1)) + offset * 2 + 2 + 1; + if (br.BaseStream.Position < maxInpos) + { + if (end1) + node1.data = br.ReadByte(); + else + node1.parseData(br); + } + + // reset position + br.BaseStream.Position = currPos; + } + + public override string ToString() + { + if (data < 0 && node0 != null && node1 != null) + return "<" + node0.ToString() + ", " + node1.ToString() + ">"; + else + return String.Format("[{0:x}]", data); + } + + internal int Depth + { + get + { + if (data < 0) + return 0; + else + return 1 + Math.Max(node0.Depth, node1.Depth); + } + } + } +} diff --git a/Switch_Toolbox_Library/Compression/LZ77_WII.cs b/Switch_Toolbox_Library/Compression/LZ77_WII.cs index 803bb2c1..8588bb9c 100644 --- a/Switch_Toolbox_Library/Compression/LZ77_WII.cs +++ b/Switch_Toolbox_Library/Compression/LZ77_WII.cs @@ -140,6 +140,64 @@ namespace Toolbox.Library return outdata; } + //Ported from + //https://github.com/Barubary/dsdecmp/blob/master/Java/JavaDSDecmp.java#L27 + //Rewrote to C# + public static byte[] Decompress10LZ(byte[] in_data, int decomp_size) + { + byte[] out_data = new byte[decomp_size]; + int curr_size = 0, flags, disp, n, b, cdest; + bool flag; + + var reader = new FileReader(new MemoryStream(in_data), true); + + while (curr_size < decomp_size) + { + try { flags = reader.ReadByte(); } + catch (EndOfStreamException ex) { throw ex; } + for (int i = 0; i < 8; i++) + { + flag = (flags & (0x80 >> i)) > 0; + if (flag) + { + disp = 0; + try { b = reader.ReadByte(); } + catch (EndOfStreamException ex) { throw new InvalidDataException("Incomplete data", ex); } + n = b >> 4; + disp = (b & 0x0F) << 8; + try { disp |= reader.ReadByte(); } + catch (EndOfStreamException ex) { throw new InvalidDataException("Incomplete data", ex); } + n += 3; + cdest = curr_size; + Console.WriteLine(string.Format("disp: 0x{0:x}", disp)); + if (disp > curr_size) { throw new InvalidDataException("Cannot go back more than already written"); } + for (int j = 0; j < n; j++) + { + out_data[curr_size++] = out_data[cdest - disp - 1 + j]; + } + if (curr_size > decomp_size) break; + } + else + { + try { b = reader.ReadByte(); } + catch(EndOfStreamException ex) + { + Console.Error.WriteLine("Incomplete data, " + ex); + break; + } + try { out_data[curr_size++] = (byte)b; } + catch(IndexOutOfRangeException ex) + { if (b == 0) + { + break; + } + } + } + } + } + return out_data; + } + public static byte[] Decompress(byte[] input, bool useMagic = true) { diff --git a/Switch_Toolbox_Library/Toolbox_Library.csproj b/Switch_Toolbox_Library/Toolbox_Library.csproj index 11825169..2a6501d5 100644 --- a/Switch_Toolbox_Library/Toolbox_Library.csproj +++ b/Switch_Toolbox_Library/Toolbox_Library.csproj @@ -231,6 +231,7 @@ +