From a30a7ac3e36ba595e5d52f9c85ec4ccfb991ea6e Mon Sep 17 00:00:00 2001 From: barubary Date: Mon, 21 Nov 2011 23:46:48 +0000 Subject: [PATCH] C#: implemented a compression algorithm for the LZE/Le compression scheme. (although it appears to perform worse than the original compression algorithm, it should still work) --- CSharp/DSDecmp.sln | 34 ++++ CSharp/DSDecmp/NewestProgram.cs | 38 ++-- CSharp/DSDecmp/Utils/LZUtil.cs | 12 +- CSharp/LuminousArc/LuminousArc.cs | 223 +++++++++++++++++++++-- CSharp/LuminousArc/LuminousArc.csproj | 2 + CSharp/PluginDistro/DSDecmp.xml | 6 +- CSharp/PluginDistro/GoldenSunDD.dll | Bin 6656 -> 6656 bytes CSharp/PluginDistro/LuminousArc.dll | Bin 6656 -> 8704 bytes CSharp/Tester/Program.cs | 27 +++ CSharp/Tester/Properties/AssemblyInfo.cs | 36 ++++ CSharp/Tester/Tester.csproj | 71 ++++++++ 11 files changed, 412 insertions(+), 37 deletions(-) create mode 100644 CSharp/Tester/Program.cs create mode 100644 CSharp/Tester/Properties/AssemblyInfo.cs create mode 100644 CSharp/Tester/Tester.csproj diff --git a/CSharp/DSDecmp.sln b/CSharp/DSDecmp.sln index 7bdafa6..899f987 100644 --- a/CSharp/DSDecmp.sln +++ b/CSharp/DSDecmp.sln @@ -7,24 +7,58 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GoldenSunDD", "GoldenSunDD\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuminousArc", "LuminousArc\LuminousArc.csproj", "{4BD8DF5C-E971-45D1-B170-340D22DDB351}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tester", "Tester\Tester.csproj", "{A4FABF4B-59F2-4D4B-9012-FF177980EAB7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {E6F419F9-D6B5-4BE7-99BB-97C48C927FF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E6F419F9-D6B5-4BE7-99BB-97C48C927FF3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E6F419F9-D6B5-4BE7-99BB-97C48C927FF3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {E6F419F9-D6B5-4BE7-99BB-97C48C927FF3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {E6F419F9-D6B5-4BE7-99BB-97C48C927FF3}.Debug|x86.ActiveCfg = Debug|Any CPU {E6F419F9-D6B5-4BE7-99BB-97C48C927FF3}.Release|Any CPU.ActiveCfg = Release|Any CPU {E6F419F9-D6B5-4BE7-99BB-97C48C927FF3}.Release|Any CPU.Build.0 = Release|Any CPU + {E6F419F9-D6B5-4BE7-99BB-97C48C927FF3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {E6F419F9-D6B5-4BE7-99BB-97C48C927FF3}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {E6F419F9-D6B5-4BE7-99BB-97C48C927FF3}.Release|x86.ActiveCfg = Release|Any CPU {8CE72663-0036-4A94-BD70-99AFE7CEEC0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8CE72663-0036-4A94-BD70-99AFE7CEEC0C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8CE72663-0036-4A94-BD70-99AFE7CEEC0C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {8CE72663-0036-4A94-BD70-99AFE7CEEC0C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {8CE72663-0036-4A94-BD70-99AFE7CEEC0C}.Debug|x86.ActiveCfg = Debug|Any CPU {8CE72663-0036-4A94-BD70-99AFE7CEEC0C}.Release|Any CPU.ActiveCfg = Release|Any CPU {8CE72663-0036-4A94-BD70-99AFE7CEEC0C}.Release|Any CPU.Build.0 = Release|Any CPU + {8CE72663-0036-4A94-BD70-99AFE7CEEC0C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {8CE72663-0036-4A94-BD70-99AFE7CEEC0C}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {8CE72663-0036-4A94-BD70-99AFE7CEEC0C}.Release|x86.ActiveCfg = Release|Any CPU {4BD8DF5C-E971-45D1-B170-340D22DDB351}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4BD8DF5C-E971-45D1-B170-340D22DDB351}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4BD8DF5C-E971-45D1-B170-340D22DDB351}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {4BD8DF5C-E971-45D1-B170-340D22DDB351}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {4BD8DF5C-E971-45D1-B170-340D22DDB351}.Debug|x86.ActiveCfg = Debug|Any CPU {4BD8DF5C-E971-45D1-B170-340D22DDB351}.Release|Any CPU.ActiveCfg = Release|Any CPU {4BD8DF5C-E971-45D1-B170-340D22DDB351}.Release|Any CPU.Build.0 = Release|Any CPU + {4BD8DF5C-E971-45D1-B170-340D22DDB351}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {4BD8DF5C-E971-45D1-B170-340D22DDB351}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {4BD8DF5C-E971-45D1-B170-340D22DDB351}.Release|x86.ActiveCfg = Release|Any CPU + {A4FABF4B-59F2-4D4B-9012-FF177980EAB7}.Debug|Any CPU.ActiveCfg = Debug|x86 + {A4FABF4B-59F2-4D4B-9012-FF177980EAB7}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {A4FABF4B-59F2-4D4B-9012-FF177980EAB7}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {A4FABF4B-59F2-4D4B-9012-FF177980EAB7}.Debug|x86.ActiveCfg = Debug|x86 + {A4FABF4B-59F2-4D4B-9012-FF177980EAB7}.Debug|x86.Build.0 = Debug|x86 + {A4FABF4B-59F2-4D4B-9012-FF177980EAB7}.Release|Any CPU.ActiveCfg = Release|x86 + {A4FABF4B-59F2-4D4B-9012-FF177980EAB7}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {A4FABF4B-59F2-4D4B-9012-FF177980EAB7}.Release|Mixed Platforms.Build.0 = Release|x86 + {A4FABF4B-59F2-4D4B-9012-FF177980EAB7}.Release|x86.ActiveCfg = Release|x86 + {A4FABF4B-59F2-4D4B-9012-FF177980EAB7}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/CSharp/DSDecmp/NewestProgram.cs b/CSharp/DSDecmp/NewestProgram.cs index bce536c..ffe20eb 100644 --- a/CSharp/DSDecmp/NewestProgram.cs +++ b/CSharp/DSDecmp/NewestProgram.cs @@ -11,7 +11,7 @@ namespace DSDecmp class NewestProgram { #if DEBUG - public static string PluginFolder = "../../../PluginDistro/Debug"; + public static string PluginFolder = "./Plugins/Debug"; #else public static string PluginFolder = "./Plugins"; #endif @@ -299,7 +299,6 @@ namespace DSDecmp inputData = new byte[inStream.Length]; inStream.Read(inputData, 0, inputData.Length); } - bool compressed = false; #region try to compress @@ -315,7 +314,6 @@ namespace DSDecmp { outStr.WriteTo(output); } - compressed = true; if (format is CompositeFormat) Console.Write((format as CompositeFormat).LastUsedCompressFormatString); else @@ -323,24 +321,28 @@ namespace DSDecmp Console.WriteLine("-compressed " + input + " to " + outputFile); } } - catch (Exception) { } + catch (Exception ex) + { + #region copy or print and continue + + if (copyErrors) + { + Copy(input, outputFile); + } + else + { + Console.WriteLine("Could not " + format.ShortFormatString + "-compress " + input + ";"); + Console.WriteLine(ex.Message); +#if DEBUG + Console.WriteLine(ex.StackTrace); +#endif + } + + #endregion + } } #endregion - - if (!compressed) - { - #region copy or print and continue - - if (copyErrors) - { - Copy(input, outputFile); - } - else - Console.WriteLine("Could not " + format.ShortFormatString + "-compress " + input + "."); - - #endregion - } } catch (FileNotFoundException) { diff --git a/CSharp/DSDecmp/Utils/LZUtil.cs b/CSharp/DSDecmp/Utils/LZUtil.cs index 7e33712..05303c3 100644 --- a/CSharp/DSDecmp/Utils/LZUtil.cs +++ b/CSharp/DSDecmp/Utils/LZUtil.cs @@ -14,19 +14,21 @@ namespace DSDecmp /// starting at oldPtr. Takes O(inLength * oldLength) = O(n^2) time. /// /// The start of the data that needs to be compressed. - /// The number of bytes that still need to be compressed. + /// The number of bytes that still need to be compressed. + /// (or: the maximum number of bytes that _may_ be compressed into one block) /// The start of the raw file. /// The number of bytes already compressed. /// The offset of the start of the longest block to refer to. + /// The minimum allowed value for 'disp'. /// The length of the longest sequence of bytes that can be copied from the already decompressed data. - public static unsafe int GetOccurrenceLength(byte* newPtr, int newLength, byte* oldPtr, int oldLength, out int disp) + public static unsafe int GetOccurrenceLength(byte* newPtr, int newLength, byte* oldPtr, int oldLength, out int disp, int minDisp = 1) { disp = 0; if (newLength == 0) return 0; int maxLength = 0; // try every possible 'disp' value (disp = oldLength - i) - for (int i = 0; i < oldLength - 1; i++) + for (int i = 0; i < oldLength - minDisp; i++) { // work from the start of the old data to the end, to mimic the original implementation's behaviour // (and going from start to end or from end to start does not influence the compression ratio anyway) @@ -48,6 +50,10 @@ namespace DSDecmp { maxLength = currentLength; disp = oldLength - i; + + // if we cannot do better anyway, stop trying. + if (maxLength == newLength) + break; } } return maxLength; diff --git a/CSharp/LuminousArc/LuminousArc.cs b/CSharp/LuminousArc/LuminousArc.cs index bb8908d..78b8cca 100644 --- a/CSharp/LuminousArc/LuminousArc.cs +++ b/CSharp/LuminousArc/LuminousArc.cs @@ -29,9 +29,15 @@ namespace GameFormats public override bool SupportsCompression { - get { return false; } + get { return true; } } + private static bool lookAhead = false; + /// + /// Gets or sets if, when compressing using this format, the optimal compression scheme should be used. + /// + public static bool LookAhead { get { return lookAhead; } set { lookAhead = value; } } + /* * An LZE / Le file consists of the following: - A six byte header @@ -97,6 +103,10 @@ namespace GameFormats } #endregion + #region Method: Decompress(instream, inLength, outstream) + /// + /// Decompresses the given stream using the LZE/Le compression format. + /// public override long Decompress(System.IO.Stream instream, long inLength, System.IO.Stream outstream) { long readBytes = 0; @@ -191,7 +201,7 @@ namespace GameFormats } #endregion case 1: - #region 1 -> compact LZ10-like format + #region 1 -> compact LZ10/RLE-like format { #region Get length and displacement('disp') values from next byte // there are < 2 bytes available when the end is at most 1 byte away @@ -268,6 +278,7 @@ namespace GameFormats default: throw new Exception("BUG: Mask is not 2 bits long!"); } + outstream.Flush(); } @@ -281,22 +292,206 @@ namespace GameFormats return decompressedSize; } + #endregion - private int MaskRsh(int value, int mask) + /// + /// Checks if the given aguments have the '-opt' option, which makes this format + /// compress using (near-)optimal compression instead of the original compression algorithm. + /// + public override int ParseCompressionOptions(string[] args) { - int maskCopy = mask; - int masked = value & mask; - if (maskCopy == 0) - return masked; - while ((maskCopy & 1) == 0) - { - masked >>= 1; - maskCopy >>= 1; - } - return masked; + LookAhead = false; + if (args.Length > 0) + if (args[0] == "-opt") + { + LookAhead = true; + return 1; + } + return 0; } - public override int Compress(System.IO.Stream instream, long inLength, System.IO.Stream outstream) + public unsafe override int Compress(System.IO.Stream instream, long inLength, System.IO.Stream outstream) + { + // block type 0: stores at most 3+0xF = 0x12 = 18 bytes (in 2 bytes) + // block type 1: stores at most 2+0x3F = 0x41 = 65 bytes (in 1 byte) + // block type 2: 1 raw byte + // block type 3: 3 raw bytes + + if (LookAhead) + return CompressWithLA(instream, inLength, outstream); + + + // save the input data in an array to prevent having to go back and forth in a file + byte[] indata = new byte[inLength]; + int numReadBytes = instream.Read(indata, 0, (int)inLength); + if (numReadBytes != inLength) + throw new StreamTooShortException(); + + // write the compression head first + outstream.WriteByte((byte)'L'); + outstream.WriteByte((byte)'e'); + outstream.WriteByte((byte)(inLength & 0xFF)); + outstream.WriteByte((byte)((inLength >> 8) & 0xFF)); + outstream.WriteByte((byte)((inLength >> 16) & 0xFF)); + outstream.WriteByte((byte)((inLength >> 24) & 0xFF)); + + int compressedLength = 6; + + fixed (byte* instart = &indata[0]) + { + // we do need to buffer the output, as the first byte indicates which blocks are compressed. + // this version does not use a look-ahead, so we do not need to buffer more than 4 blocks at a time. + // (a block is at most 3 bytes long) + byte[] outbuffer = new byte[4 * 3 + 1]; + outbuffer[0] = 0; + int bufferlength = 1, bufferedBlocks = 0; + int readBytes = 0; + + int cacheByte = -1; + + while (readBytes < inLength) + { + #region If 4 blocks are bufferd, write them and reset the buffer + // we can only buffer 4 blocks at a time. + if (bufferedBlocks == 4) + { + outstream.Write(outbuffer, 0, bufferlength); + compressedLength += bufferlength; + // reset the buffer + outbuffer[0] = 0; + bufferlength = 1; + bufferedBlocks = 0; + } + #endregion + + // type 0: 3 <= len <= 18; 5 <= disp <= 0x1004 + // type 1: 2 <= len <= 65; 1 <= disp <= 4 + // type 2: 1 raw byte + // type 3: 3 raw bytes + + // check if we can compress it using type 1 first (only 1 byte-long block) + int disp; + int oldLength = Math.Min(readBytes, 0x1004); + int length = LZUtil.GetOccurrenceLength(instart + readBytes, (int)Math.Min(inLength - readBytes, 65), + instart + readBytes - oldLength, oldLength, out disp, 1); + if (disp >= 1 && ((disp <= 4 && length >= 2) || (disp >= 5 && length >= 3))) + { + if (cacheByte >= 0) + { + // write a single raw byte block (the previous byte could not be the start of any compressed block) + outbuffer[bufferlength++] = (byte)(cacheByte & 0xFF); + outbuffer[0] |= (byte)(2 << (bufferedBlocks * 2)); + cacheByte = -1; + bufferedBlocks++; + // the block set may be full; just retry this iteration. + continue; + } + if (disp >= 5) + { + #region compress using type 0 + + // type 0: store len/disp in 2 bytes: + // AB CD, with len = C + 3, disp = DAB + 5 + + // make sure we do not try to compress more than fits into the block + length = Math.Min(length, 0xF + 3); + + readBytes += length; + + outbuffer[bufferlength++] = (byte)((disp - 5) & 0xFF); + outbuffer[bufferlength] = (byte)(((disp - 5) >> 8) & 0xF); + outbuffer[bufferlength++] |= (byte)(((length - 3) & 0xF) << 4); + + #endregion + } + else // 1 <= disp <= 4 + { + #region compress using type 1 + + // type 1: store len/disp in 1 byte: + // ABCDEFGH, wih len = ABCDEF + 2, disp = GH + 1 + + readBytes += length; + + outbuffer[bufferlength] = (byte)(((length - 2) << 2) & 0xFC); + outbuffer[bufferlength] |= (byte)((disp - 1) & 0x3); + bufferlength++; + + outbuffer[0] |= (byte)(1 << (bufferedBlocks * 2)); + + #endregion + } + } + else + { + if (cacheByte < 0) + { + // first fail? remember byte, try to compress starting at next byte + cacheByte = *(instart + (readBytes++)); + continue; + } + else + { + // 2 consecutive fails -> store 3 raw bytes (type 3) if possible. + if (inLength - readBytes >= 2) + { + outbuffer[bufferlength++] = (byte)(cacheByte & 0xFF); + outbuffer[bufferlength++] = *(instart + (readBytes++)); + outbuffer[bufferlength++] = *(instart + (readBytes++)); + outbuffer[0] |= (byte)(3 << (bufferedBlocks * 2)); + cacheByte = -1; + } + else + { + // there are only two bytes remaining (incl. the cched byte) + // so write the cached byte first as single raw byte. + // keep the next/last byte as new cache, since the block buffer may be full. + outbuffer[bufferlength++] = (byte)(cacheByte & 0xFF); + outbuffer[0] |= (byte)(2 << (bufferedBlocks * 2)); + cacheByte = *(instart + (readBytes++)); + } + } + } + + bufferedBlocks++; + } + + // there may be one cache-byte left. + if (cacheByte >= 0) + { + // if the current set of blocks is full, empty it first + if (bufferedBlocks == 4) + { + #region empty block buffer + + outstream.Write(outbuffer, 0, bufferlength); + compressedLength += bufferlength; + // reset the buffer + outbuffer[0] = 0; + bufferlength = 1; + bufferedBlocks = 0; + + #endregion + } + + outbuffer[bufferlength++] = (byte)(cacheByte & 0xFF); + cacheByte = -1; + outbuffer[0] |= (byte)(2 << (bufferedBlocks * 2)); + bufferedBlocks++; + } + + // copy any remaining blocks to the output + if (bufferedBlocks > 0) + { + outstream.Write(outbuffer, 0, bufferlength); + compressedLength += bufferlength; + } + } + + return compressedLength; + } + + private unsafe int CompressWithLA(Stream instream, long inLength, Stream outstream) { throw new NotImplementedException(); } diff --git a/CSharp/LuminousArc/LuminousArc.csproj b/CSharp/LuminousArc/LuminousArc.csproj index da7b6a5..2d5c466 100644 --- a/CSharp/LuminousArc/LuminousArc.csproj +++ b/CSharp/LuminousArc/LuminousArc.csproj @@ -22,6 +22,7 @@ DEBUG;TRACE prompt 4 + true pdbonly @@ -30,6 +31,7 @@ TRACE prompt 4 + true diff --git a/CSharp/PluginDistro/DSDecmp.xml b/CSharp/PluginDistro/DSDecmp.xml index acc65e7..8a02d3f 100644 --- a/CSharp/PluginDistro/DSDecmp.xml +++ b/CSharp/PluginDistro/DSDecmp.xml @@ -211,16 +211,18 @@ Utility class for compression using LZ-like compression schemes. - + Determine the maximum size of a LZ-compressed block starting at newPtr, using the already compressed data starting at oldPtr. Takes O(inLength * oldLength) = O(n^2) time. The start of the data that needs to be compressed. - The number of bytes that still need to be compressed. + The number of bytes that still need to be compressed. + (or: the maximum number of bytes that _may_ be compressed into one block) The start of the raw file. The number of bytes already compressed. The offset of the start of the longest block to refer to. + The minimum allowed value for 'disp'. The length of the longest sequence of bytes that can be copied from the already decompressed data. diff --git a/CSharp/PluginDistro/GoldenSunDD.dll b/CSharp/PluginDistro/GoldenSunDD.dll index 070f46c24a31a005817d6ce6afbd3040cc769f2c..66dac93e82a537f72787f8162482092e9c5662a6 100644 GIT binary patch delta 51 zcmZoLX)u}4!F>JJsg2zy`2{va?>q3w$=iGSY}Jb_Yb^^lrwI5kf@CLe7Sv?a+I&|~ GnF9cE5f)JZ delta 51 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 + + + Debug + x86 + 8.0.30703 + 2.0 + {A4FABF4B-59F2-4D4B-9012-FF177980EAB7} + Exe + Properties + Tester + Tester + v4.0 + Client + 512 + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + {E6F419F9-D6B5-4BE7-99BB-97C48C927FF3} + DSDecmp + + + {8CE72663-0036-4A94-BD70-99AFE7CEEC0C} + GoldenSunDD + + + {4BD8DF5C-E971-45D1-B170-340D22DDB351} + LuminousArc + + + + + \ No newline at end of file