diff --git a/Ultima/FileIndex.cs b/Ultima/FileIndex.cs index 6b2e2af4..d6e8a2e7 100644 --- a/Ultima/FileIndex.cs +++ b/Ultima/FileIndex.cs @@ -391,6 +391,12 @@ public bool Valid(int index, out int length, out int extra, out bool patched) } } + public enum CompressionFlag + { + None = 0, + Zlib = 1, + Mythic = 3 + } [StructLayout(LayoutKind.Sequential, Pack = 1)] @@ -423,7 +429,7 @@ public int Extra2 set => Extra = (int)((Extra & 0xFFFF0000) | (uint)value); } - public int Flag { get => 0; set { } } // No compression, means that we have only three first fields + public CompressionFlag Flag { get => CompressionFlag.None; set { } } // No compression, means that we have only three first fields } [StructLayout(LayoutKind.Sequential, Pack = 1)] @@ -454,7 +460,7 @@ public int Extra public int Extra2 { get; set; } - public int Flag { get; set; } + public CompressionFlag Flag { get; set; } } // Dumb access to all possible fields of entries @@ -466,8 +472,7 @@ public interface IEntry public int DecompressedLength { get; set; } public int Extra1 { get; set; } public int Extra2 { get; set; } - public int Flag { get; set; } - //public IEntry Invalid { get; } + public CompressionFlag Flag { get; set; } } public interface IFileAccessor @@ -658,7 +663,7 @@ public UopFileAccessor(string path, string uopEntryExtension, int length, int id Index[idx].Lookup = (int)(offset + 8); Index[idx].Length = compressedLength - 8; Index[idx].DecompressedLength = decompressedLength; - Index[idx].Flag = flag; + Index[idx].Flag = (CompressionFlag)flag; Index[idx].Extra = extra1 << 16 | extra2; Index[idx].Extra1 = extra1; Index[idx].Extra2 = extra2; @@ -670,7 +675,7 @@ public UopFileAccessor(string path, string uopEntryExtension, int length, int id Index[idx].Lookup = (int)(offset); Index[idx].Length = compressedLength; Index[idx].DecompressedLength = decompressedLength; - Index[idx].Flag = flag; + Index[idx].Flag = (CompressionFlag)flag; Index[idx].Extra = 0x0FFFFFFF; // we cant read it right now, but -1 and 0 makes this entry invalid } } diff --git a/Ultima/Gumps.cs b/Ultima/Gumps.cs index 997154dd..cd6e9a23 100644 --- a/Ultima/Gumps.cs +++ b/Ultima/Gumps.cs @@ -155,7 +155,7 @@ public static byte[] GetRawGump(int index, out int width, out int height) stream.Read(buffer, 0, length); stream.Close(); - buffer = BwtDecompress.Decompress(buffer); + buffer = MythicDecompress.Decompress(buffer); return buffer; } @@ -393,19 +393,17 @@ public static unsafe Bitmap GetGump(int index, out bool patched) uint height = (uint)entry.Extra2; // Compressed UOPs - if (entry.Flag >= 1) + if (entry.Flag >= CompressionFlag.Zlib) { var result = UopUtils.Decompress(_streamBuffer); if (result.success is false) { return null; } - - if (entry.Flag == 3) + if (entry.Flag == CompressionFlag.Mythic) { - _streamBuffer = BwtDecompress.Decompress(result.data); + _streamBuffer = MythicDecompress.Decompress(result.data); } - using (BinaryReader reader = new BinaryReader(new MemoryStream(_streamBuffer))) { byte[] extra = reader.ReadBytes(8); diff --git a/Ultima/Helpers/BwtDecompress.cs b/Ultima/Helpers/BwtDecompress.cs deleted file mode 100644 index 1f2b65df..00000000 --- a/Ultima/Helpers/BwtDecompress.cs +++ /dev/null @@ -1,208 +0,0 @@ -using System; -using System.IO; -using System.Runtime.InteropServices; - -namespace Ultima.Helpers -{ - public static class BwtDecompress - { - public static byte[] Decompress(byte[] buffer) - { - byte[] output; - - using (var reader = new BinaryReader(new MemoryStream(buffer))) - { - var header = reader.ReadUInt32(); - - var len = 0u; - - var firstChar = reader.ReadByte(); - - Span table = new ushort[256 * 256]; - BuildTable(table, firstChar); - - var list = new byte[reader.BaseStream.Length - 4]; - var i = 0; - while (reader.BaseStream.Position < reader.BaseStream.Length) - { - var currentValue = firstChar; - var value = table[currentValue]; - if (currentValue > 0) - { - do - { - table[currentValue] = table[currentValue - 1]; - } while (--currentValue > 0); - } - - table[0] = value; - - list[i++] = (byte)value; - firstChar = reader.ReadByte(); - } - - output = InternalDecompress(list, len); - } - - return output; - } - - static void BuildTable(Span table, byte startValue) - { - int index = 0; - byte firstByte = startValue; - byte secondByte = 0; - for (int i = 0; i < 256 * 256; i++) - { - table[index++] = (ushort)(firstByte + (secondByte << 8)); - - firstByte++; - if (firstByte == 0) - { - secondByte++; - } - } - - table.Sort(); - } - - static byte[] InternalDecompress(Span input, uint len) - { - try - { - Span symbolTable = stackalloc char[256]; - Span frequency = stackalloc char[256]; - Span partialInput = stackalloc int[256 * 3]; - - partialInput.Clear(); - - for (var i = 0; i < 256; i++) - { - symbolTable[i] = (char)i; - } - - input.Slice(0, 1024).CopyTo(MemoryMarshal.AsBytes(partialInput)); - - var sum = 0; - for (var i = 0; i < 256; i++) - { - sum += partialInput[i]; - } - - if (len == 0) - { - len = (uint)sum; - } - - if (sum != len) - { - return Array.Empty(); - } - - var output = new byte[len]; - - var count = 0; - var nonZeroCount = 0; - - for (var i = 0; i < 256; i++) - { - if (partialInput[i] != 0) - { - nonZeroCount++; - } - } - - Frequency(partialInput, frequency); - - for (int i = 0, m = 0; i < nonZeroCount; ++i) - { - var freq = (byte)frequency[i]; - symbolTable[input[m + 1024]] = (char)freq; - partialInput[freq + 256] = m + 1; - m += partialInput[freq]; - partialInput[freq + 512] = m; - } - - var val = (byte)symbolTable[0]; - - if (len == 0) - { - return output; - } - - do - { - ref var firstValRef = ref partialInput[val + 256]; - output[count] = val; - - if (firstValRef >= partialInput[val + 512]) - { - if (nonZeroCount-- > 0) - { - ShiftLeft(symbolTable, nonZeroCount); - val = (byte)symbolTable[0]; - } - } - else - { - var idx = (char)input[firstValRef + 1024]; - firstValRef++; - - if (idx != 0) - { - ShiftLeft(symbolTable, idx); - symbolTable[(byte)idx] = (char)val; - val = (byte)symbolTable[0]; - } - } - - count++; - } while (count < len); - - return output; - } - catch (Exception ex) - { - Console.WriteLine($"Error during decompression: {ex.Message}"); - throw; - } - } - - static void Frequency(Span input, Span output) - { - Span tmp = stackalloc int[256]; - input.Slice(0, tmp.Length).CopyTo(tmp); - - for (var i = 0; i < 256; i++) - { - uint value = 0; - byte index = 0; - - for (var j = 0; j < 256; j++) - { - if (tmp[j] > value) - { - index = (byte)j; - value = (uint)tmp[j]; - } - } - - if (value == 0) - { - break; - } - - output[i] = (char)index; - tmp[index] = 0; - } - } - - static void ShiftLeft(Span input, int max) - { - for (var i = 0; i < max; ++i) - { - input[i] = input[i + 1]; - } - } - } -} \ No newline at end of file diff --git a/Ultima/Helpers/MoveToFront.cs b/Ultima/Helpers/MoveToFront.cs new file mode 100644 index 00000000..5ad7d5ba --- /dev/null +++ b/Ultima/Helpers/MoveToFront.cs @@ -0,0 +1,73 @@ +using System; +using System.Windows.Documents; + +namespace Ultima.Helpers +{ + public static class MoveToFrontCoding + { + + // complexity : O(256*N) -> O(N) + public static byte[] Encode(byte[] input) + { + Span symbols = stackalloc byte[256]; + byte[] output = new byte[input.Length]; + + for (int i = 0; i < 256; i++) + symbols[i] = (byte)i; + + for (int i = 0; i < input.Length; i++) + { + int ind = MoveToFront(symbols, input[i]); + output[i] = (byte)ind; + } + return output; + } + + // complexity : O(256*N) -> O(N) + public static byte[] Decode(byte[] input) + { + Span symbols = stackalloc byte[256]; + byte[] output = new byte[input.Length]; + + for (int i = 0; i < 256; i++) + symbols[i] = (byte)i; + + for (int i = 0; i < input.Length; i++) + { + int ind = (int)input[i]; + output[i] = (byte)symbols[ind]; + MoveToFront(symbols, ind); + } + return output; + } + + // params : array , element to move . + //get the index of the element and move it to the front . + // best case : O(1) , average and worst Case : O(N) + private static int MoveToFront(Span array, byte element) + { + if (array[0] == element) return 0; + int elementInd = -1; + for (int i = array.Length - 1; i > 0; i--) + { + if (array[i] == element) elementInd = i; + if (elementInd != -1) array[i] = array[i - 1]; + } + array[0] = element; + return elementInd; + } + + // params : array ,index of the element . + // move element to the front . + // complexity : O(elementInd) . + // best case : O(1) , worst case : O(N) + private static void MoveToFront(Span array, int elementInd) + { + byte element = (byte)array[elementInd]; + for (int i = elementInd; i > 0; i--) + array[i] = array[i - 1]; + array[0] = element; + } + + } +} diff --git a/Ultima/Helpers/MythicDecompress.cs b/Ultima/Helpers/MythicDecompress.cs new file mode 100644 index 00000000..26acdbc7 --- /dev/null +++ b/Ultima/Helpers/MythicDecompress.cs @@ -0,0 +1,274 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Ultima.Helpers +{ + public static class MythicDecompress + { + public static byte[] Transform(byte[] buffer) + { + return MoveToFrontCoding.Encode(InternalCompress(buffer)); + } + + public static byte[] Detransform(byte[] buffer) + { + return InternalDecompress(MoveToFrontCoding.Decode(buffer)); + } + + public static byte[] Decompress(byte[] buffer) + { + byte[] output; + + using (var reader = new BinaryReader(new MemoryStream(buffer))) + { + var header = reader.ReadUInt32(); + uint dataLength = header ^ 0x8E2C9A3D; // Must be equal to output length, error otherwise + + // MoveToFront decoding + var list = reader.ReadBytes((int)(reader.BaseStream.Length - 4)); + output = InternalDecompress(MoveToFrontCoding.Decode(list)); + } + + return output; + } + + public static byte[] InternalDecompress(Span input) + { + try + { + Span symbolTable = stackalloc byte[256]; + Span frequency = stackalloc byte[256]; + Span partialInput = stackalloc int[256 * 3]; + + partialInput.Clear(); + + for (var i = 0; i < 256; i++) + { + symbolTable[i] = (byte)i; + } + + input.Slice(0, 1024).CopyTo(MemoryMarshal.AsBytes(partialInput)); + + var sum = 0; + for (var i = 0; i < 256; i++) + { + sum += partialInput[i]; + } + + if (sum == 0) + { + return Array.Empty(); + } + var output = new byte[sum]; + var count = 0; + var nonZeroCount = 0; + for (var i = 0; i < 256; i++) + { + if (partialInput[i] != 0) + { + nonZeroCount++; + } + } + + Frequency(partialInput, frequency); + + for (int i = 0, m = 0; i < nonZeroCount; ++i) + { + var freq = frequency[i]; + symbolTable[input[m + 1024]] = freq; + partialInput[freq + 256] = m + 1; + m += partialInput[freq]; + partialInput[freq + 512] = m; + } + + var val = (byte)symbolTable[0]; + + if (sum == 0) + { + return output; + } + + do + { + ref var firstValRef = ref partialInput[val + 256]; + output[count] = val; + + if (firstValRef < partialInput[val + 512]) + { + var idx = input[firstValRef + 1024]; + firstValRef++; + + if (idx != 0) + { + ShiftLeft(symbolTable, idx); + symbolTable[idx] = val; + val = symbolTable[0]; + } + } + else + { + if (nonZeroCount-- > 0) + { + ShiftLeft(symbolTable, nonZeroCount); + val = symbolTable[0]; + } + } + + count++; + } while (count < sum); + + return output; + } + catch (Exception ex) + { + Console.WriteLine($"Error during decompression: {ex.Message}"); + throw; + } + } + + // + // Looking for max value in freq table, adding it index to output and erasing to find next max value except last one + // + static void Frequency(Span input, Span output) + { + Span tmp = stackalloc int[256]; + input.Slice(0, tmp.Length).CopyTo(tmp); + + for (var i = 0; i < 256; i++) + { + uint value = 0; + byte index = 0; + + for (var j = 0; j < 256; j++) + { + if (tmp[j] > value) + { + index = (byte)j; + value = (uint)tmp[j]; + } + } + + if (value == 0) + { + break; + } + + output[i] = (byte)index; + tmp[index] = 0; + } + } + + // element - this is index of element until which we re shifting our array to the left, destroying zero element + static void ShiftLeft(Span input, int element) + { + for (var i = 0; i < element; ++i) + { + input[i] = input[i + 1]; + } + } + + public static byte[] InternalCompress(Span input) + { + Span symbolTable = stackalloc byte[256]; + Span frequency = stackalloc byte[256]; + Span partialInput = stackalloc int[256 * 3]; + + // counting frequencies + for (int i = 0; i < input.Length; ++i) + { + partialInput[input[i]]++; + } + + Frequency(partialInput, frequency); + + var count = 0; + var nonZeroCount = 0; + for (var i = 0; i < 256; i++) + { + if (partialInput[i] != 0) + { + nonZeroCount++; + } + } + + byte[] output = new byte[input.Length + nonZeroCount + 1024]; + + for (int i = 0, m = 0; i < nonZeroCount; ++i) + { + var freqIndex = frequency[i]; + partialInput[freqIndex + 256] = m + 1; + m += partialInput[freqIndex]; + partialInput[freqIndex + 512] = m; + } + + for(int i = 0; i < 256; ++i) + { + byte[] bytes = BitConverter.GetBytes(partialInput[i]); + output[i * 4] = bytes[0]; + output[i * 4 + 1] = bytes[1]; + output[i * 4 + 2] = bytes[2]; + output[i * 4 + 3] = bytes[3]; + } + + count = input.Length - 1; + List addedSymbols = new List(256); // keeping track for added symbols + do + { + var val = (byte)input[count]; + + ref var firstValRef = ref partialInput[val + 512]; + var outputAddress = firstValRef + 1024; + + if (!addedSymbols.Contains(val)) // first add, just put it in symbolTable from the left and assign 0 idx + { + ShiftRight(symbolTable, addedSymbols.Count); + symbolTable[0] = val; + addedSymbols.Add(val); + output[outputAddress] = (byte)0; + } + else if (firstValRef >= partialInput[val + 256]) // we're already have symbol in table, so getting it idx + // and putting it in output stream + { + var idx = GetIdx(symbolTable, val, addedSymbols.Count); + ShiftRight(symbolTable, idx); + symbolTable[0] = val; + output[outputAddress] = idx; + } + firstValRef--; + + count--; + } while (count >= 0); + + for (int i = 0, m = 0; i < nonZeroCount; ++i) + { + var freqIndex = frequency[i]; + output[m + 1024] = GetIdx(symbolTable, freqIndex, nonZeroCount); + m += partialInput[freqIndex]; + } + + return output; + + } + + private static byte GetIdx(Span input, byte val, int nonZeroCount) + { + for (byte i = 0; i < input.Length && i < nonZeroCount; ++i) + { + if (input[i] == val) + return i; + } + return (byte)0; + } + + static void ShiftRight(Span input, int element) + { + for (var i = element; i >= 1; --i) + { + input[i] = input[i - 1]; + } + } + } +} \ No newline at end of file diff --git a/Ultima/Helpers/UopUtils.cs b/Ultima/Helpers/UopUtils.cs index 9b555709..9555fe49 100644 --- a/Ultima/Helpers/UopUtils.cs +++ b/Ultima/Helpers/UopUtils.cs @@ -4,7 +4,7 @@ namespace Ultima.Helpers { - static internal class UopUtils + static public class UopUtils { /// /// Method for calculating entry hash by it's name. @@ -112,10 +112,42 @@ public static (bool success, byte[] data) Decompress(byte[] compressedData) try { using (var compressedStream = new MemoryStream(compressedData)) - using (var zlibStream = new ZLibStream(compressedStream, CompressionMode.Decompress)) + using (var zlibStream = new ZLibStream(compressedStream, CompressionMode.Decompress, false)) using (var resultStream = new MemoryStream()) { zlibStream.CopyTo(resultStream); + resultStream.Flush(); + zlibStream.Close(); + return (true, resultStream.ToArray()); + } + } + catch (Exception) + { + return (false, Array.Empty()); + } + } + + /// + /// Method for compressing zlib byte arrays inside .uop + /// + /// Input compressed array of bytes + /// compressed byte[] data + public static (bool success, byte[] compressedData) Compress(byte[] rawData) + { + if (rawData == null || rawData.Length == 0) + { + return (false, Array.Empty()); + } + + try + { + using (var dataStream = new MemoryStream(rawData)) + using (var resultStream = new MemoryStream()) + using (var zlibStream = new ZLibStream(resultStream, CompressionLevel.Optimal)) + { + dataStream.CopyTo(zlibStream); + zlibStream.Flush(); + zlibStream.Close(); return (true, resultStream.ToArray()); } } diff --git a/Ultima/StringList.cs b/Ultima/StringList.cs index 1d322a6e..b620dccf 100644 --- a/Ultima/StringList.cs +++ b/Ultima/StringList.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.IO; +using System.IO.Pipes; using System.Text; using Ultima.Helpers; @@ -61,7 +62,7 @@ private void LoadEntry(string path) _ = fileStream.Read(buffer, 0, buffer.Length); byte[] clilocData = _decompress - ? BwtDecompress.Decompress(buffer) + ? MythicDecompress.Decompress(buffer) : buffer; using (var reader = new BinaryReader(new MemoryStream(clilocData))) @@ -119,16 +120,47 @@ public void SaveStringList(string fileName) } byte[] data = memoryStream.ToArray(); + if (_decompress) + { + byte[] data2 = new byte[data.Length + 6]; + using (MemoryStream ms = new MemoryStream(data2)) + { + using (var bin = new BinaryWriter(ms)) + { + bin.Write(_header1); + bin.Write(_header2); - // Write the final output to the file - using (var fileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None)) - using (var bin = new BinaryWriter(fileStream)) + bin.Write(data); + } + } + uint length = (uint)data2.Length; + data2 = MythicDecompress.Transform(data2); + byte[] data3 = new byte[data2.Length + 4]; + + using (MemoryStream ms = new MemoryStream(data3)) + using (var bin = new BinaryWriter(ms)) + { + bin.Write((uint)length ^ 0x8E2C9A3D); // xored decrypted data length + bin.Write(data2); + } + using (var fileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None)) + using (var bin = new BinaryWriter(fileStream)) + { + bin.Write(data3); + } + } + else { - // Write the headers at the beginning - bin.Write(_header1); - bin.Write(_header2); + // Write the final output to the file + using (var fileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None)) + using (var bin = new BinaryWriter(fileStream)) + { + // Write the headers at the beginning + bin.Write(_header1); + bin.Write(_header2); - bin.Write(data); + bin.Write(data); + } } } } diff --git a/UoFiddler.Plugin.UopPacker/Classes/LegacyMulFileConverter.cs b/UoFiddler.Plugin.UopPacker/Classes/LegacyMulFileConverter.cs index bb754059..925f23de 100644 --- a/UoFiddler.Plugin.UopPacker/Classes/LegacyMulFileConverter.cs +++ b/UoFiddler.Plugin.UopPacker/Classes/LegacyMulFileConverter.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.IO; using System.IO.Compression; +using Ultima; +using Ultima.Helpers; namespace UoFiddler.Plugin.UopPacker.Classes { @@ -24,6 +26,7 @@ private struct TableEntry public int DecompressedSize; public ulong Identifier; public uint Hash; + public short CompressionFlag; public bool Compressed; } @@ -47,7 +50,7 @@ private static BinaryWriter OpenOutput(string path) // // MUL -> UOP // - public static void ToUop(string inFile, string inFileIdx, string outFile, FileType type, int typeIndex) + public static void ToUop(string inFile, string inFileIdx, string outFile, FileType type, int typeIndex, CompressionFlag compressionFlag = CompressionFlag.None) { // Same for all UOP files const long firstTable = 0x200; @@ -120,19 +123,22 @@ public static void ToUop(string inFile, string inFileIdx, string outFile, FileTy // File header writer.Write(0x50594D); // MYP - writer.Write(5); // version + writer.Write(type == FileType.GumpartLegacyMul ? 4 : 5); // version writer.Write(0xFD23EC43); // format timestamp? - writer.Write(firstTable); // first table + writer.Write(type == FileType.GumpartLegacyMul ? (long)0x28 : firstTable); // first table writer.Write(tableSize); // table size writer.Write(idxEntries.Count); // file count - writer.Write(1); // modified count? - writer.Write(1); // ? + writer.Write(0); // modified count? + writer.Write(0); // ? writer.Write(0); // ? // Padding - for (int i = 0x28; i < firstTable; ++i) + if (type != FileType.GumpartLegacyMul) { - writer.Write((byte)0); + for (int i = 0x28; i < firstTable; ++i) + { + writer.Write((byte)0); + } } int tableCount = (int)Math.Ceiling((double)idxEntries.Count / tableSize); @@ -161,7 +167,8 @@ public static void ToUop(string inFile, string inFileIdx, string outFile, FileTy byte[] data = reader.ReadBytes(idxEntries[j].Size); tableEntries[tableIdx].Offset = writer.BaseStream.Position; - tableEntries[tableIdx].Size = data.Length; + tableEntries[tableIdx].DecompressedSize = data.Length; + tableEntries[tableIdx].CompressionFlag = (short)compressionFlag; // hash 906142efe9fdb38a, which is file 0009834.tga (and no others, as 7.0.59.5) use a different name format (7 digits instead of 8); // if in newer versions more of these files will have adopted that format, someone should update this list of exceptions // (even if this seems so much like a typo from someone from the UO development team :P) @@ -174,21 +181,62 @@ public static void ToUop(string inFile, string inFileIdx, string outFile, FileTy tableEntries[tableIdx].Identifier = HashLittle2(string.Format(hashFormat[0], idxEntries[j].Id)); } - tableEntries[tableIdx].Hash = HashAdler32(data); if (type == FileType.GumpartLegacyMul) { - // Prepend width/height from IDX's extra - int width = idxEntries[j].Extra >> 16 & 0xFFFF; - int height = idxEntries[j].Extra & 0xFFFF; - writer.Write(width); - writer.Write(height); + byte[] gumpArtData = new byte[data.Length + 8]; + using (MemoryStream ms = new MemoryStream(gumpArtData)) + using (BinaryWriter gumpArtWriter = new BinaryWriter(ms)) + { + int width = idxEntries[j].Extra >> 16 & 0xFFFF; + int height = idxEntries[j].Extra & 0xFFFF; - tableEntries[tableIdx].Size += 8; - } + gumpArtWriter.Write(width); + gumpArtWriter.Write(height); + gumpArtWriter.Write(data); + + tableEntries[tableIdx].DecompressedSize += 8; + tableEntries[tableIdx].Size = tableEntries[tableIdx].DecompressedSize; + } + + if (compressionFlag == CompressionFlag.Mythic) + { + uint length = (uint)gumpArtData.Length; + gumpArtData = MythicDecompress.Transform(gumpArtData); + byte[] gumpArtData2 = new byte[gumpArtData.Length + 4]; + using (MemoryStream ms2 = new MemoryStream(gumpArtData2)) + { + using (BinaryWriter writer2 = new BinaryWriter(ms2)) + { + writer2.Write((uint)length ^ 0x8E2C9A3D); + writer2.Write(gumpArtData); + } + } + gumpArtData = gumpArtData2; + tableEntries[tableIdx].DecompressedSize = (int)gumpArtData.Length; + tableEntries[tableIdx].Size = tableEntries[tableIdx].DecompressedSize; + } + if (compressionFlag >= CompressionFlag.Zlib) + { + var result = UopUtils.Compress(gumpArtData); + if (!result.success) + { + // Handle error + return; + } - writer.Write(data); + tableEntries[tableIdx].Size = result.compressedData.Length; + gumpArtData = result.compressedData; + } + tableEntries[tableIdx].Hash = HashAdler32(gumpArtData); + writer.Write(gumpArtData); + } + else + { + tableEntries[tableIdx].Hash = HashAdler32(data); + writer.Write(data); + } } long nextTable = writer.BaseStream.Position; @@ -213,10 +261,10 @@ public static void ToUop(string inFile, string inFileIdx, string outFile, FileTy writer.Write(tableEntries[tableIdx].Offset); writer.Write(0); // header length writer.Write(tableEntries[tableIdx].Size); // compressed size - writer.Write(tableEntries[tableIdx].Size); // decompressed size + writer.Write(tableEntries[tableIdx].DecompressedSize); // decompressed size writer.Write(tableEntries[tableIdx].Identifier); writer.Write(tableEntries[tableIdx].Hash); - writer.Write((short)0); // compression method, none + writer.Write(tableEntries[tableIdx].CompressionFlag); // compression method } // Fill remainder with empty entries @@ -336,7 +384,7 @@ public void FromUop(string inFile, string outFile, string outFileIdx, FileType t continue; } - if (!chunkIds.TryGetValue( offsets[i].Identifier, out var chunkId)) + if (!chunkIds.TryGetValue(offsets[i].Identifier, out var chunkId)) { if (!chunkIds2.TryGetValue(offsets[i].Identifier, out int chunkId2)) { diff --git a/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.Designer.cs b/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.Designer.cs index 8e1b7674..ab82e360 100644 --- a/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.Designer.cs +++ b/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.Designer.cs @@ -82,6 +82,7 @@ private void InitializeComponent() inputfolder = new System.Windows.Forms.TextBox(); SelectFolderButton = new System.Windows.Forms.Button(); ExtractSingleFileTabPage = new System.Windows.Forms.TabPage(); + compressionBox = new System.Windows.Forms.ComboBox(); MainStatusStrip = new System.Windows.Forms.StatusStrip(); toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel(); guilabel = new System.Windows.Forms.ToolStripStatusLabel(); @@ -466,7 +467,7 @@ private void InitializeComponent() // statustext.ForeColor = System.Drawing.Color.DarkRed; statustext.Name = "statustext"; - statustext.Size = new System.Drawing.Size(299, 17); + statustext.Size = new System.Drawing.Size(330, 17); statustext.Spring = true; statustext.Text = "Status"; statustext.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; @@ -533,6 +534,7 @@ private void InitializeComponent() // // ExtractSingleFileTabPage // + ExtractSingleFileTabPage.Controls.Add(compressionBox); ExtractSingleFileTabPage.Controls.Add(label1); ExtractSingleFileTabPage.Controls.Add(label2); ExtractSingleFileTabPage.Controls.Add(inmul); @@ -572,6 +574,17 @@ private void InitializeComponent() ExtractSingleFileTabPage.Text = "One file"; ExtractSingleFileTabPage.UseVisualStyleBackColor = true; // + // compressionBox + // + compressionBox.BackColor = System.Drawing.Color.White; + compressionBox.Items.AddRange(new object[] { "None", "Zlib", "Mythic" }); + compressionBox.Location = new System.Drawing.Point(168, 134); + compressionBox.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + compressionBox.Name = "compressionBox"; + compressionBox.Size = new System.Drawing.Size(191, 23); + compressionBox.TabIndex = 40; + compressionBox.Text = "None"; + // // MainStatusStrip // MainStatusStrip.Enabled = false; @@ -704,5 +717,6 @@ private void InitializeComponent() private System.Windows.Forms.Button uoptomul; private System.Windows.Forms.ComboBox uoptype; private System.Windows.Forms.ToolStripStatusLabel VersionLabel; + private System.Windows.Forms.ComboBox compressionBox; } } diff --git a/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.cs b/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.cs index 5478b664..9b52c913 100644 --- a/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.cs +++ b/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.cs @@ -13,6 +13,7 @@ using System.IO; using System.Linq; using System.Windows.Forms; +using Ultima; using UoFiddler.Plugin.UopPacker.Classes; namespace UoFiddler.Plugin.UopPacker.UserControls @@ -116,13 +117,13 @@ private void ToUop(object sender, EventArgs e) MessageBox.Show("Output file already exists"); return; } - + CompressionFlag selectedCompressionMethod = Enum.Parse(compressionBox.SelectedItem.ToString()); try { multouop.Text = "Converting..."; multouop.Enabled = false; - LegacyMulFileConverter.ToUop(inmul.Text, inidx.Text, outuop.Text, fileType, (int)mulMapIndex.Value); + LegacyMulFileConverter.ToUop(inmul.Text, inidx.Text, outuop.Text, fileType, (int)mulMapIndex.Value, selectedCompressionMethod); } catch { @@ -308,8 +309,9 @@ private void Pack(string inFile, string inIdx, string outFile, FileType type, in inIdx = FixPath(inIdx); ++_total; + CompressionFlag selectedCompressionMethod = Enum.Parse(compressionBox.SelectedItem.ToString()); - LegacyMulFileConverter.ToUop(inFile, inIdx, outFile, type, typeIndex); + LegacyMulFileConverter.ToUop(inFile, inIdx, outFile, type, typeIndex, selectedCompressionMethod); ++_success; } diff --git a/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.resx b/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.resx index 4d52b268..4cbd8996 100644 --- a/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.resx +++ b/UoFiddler.Plugin.UopPacker/UserControls/UopPackerControl.resx @@ -1,7 +1,7 @@