public void Crc32Checksum_6() { if (ZLibInit.ZLibProvided) { byte[] sample = Encoding.UTF8.GetBytes("ABCDEF"); uint checksum = Crc32Checksum.Crc32(sample, 1, 3); Assert.IsTrue(checksum == 0x26BA19F3); // BCD } }
public void Crc32Checksum_5() { if (ZLibInit.ZLibProvided) { uint checksum = Crc32Checksum.Crc32(Encoding.UTF8.GetBytes("ABC")); Assert.IsTrue(checksum == 0xA3830348); // ABC checksum = Crc32Checksum.Crc32(checksum, Encoding.UTF8.GetBytes("DEF")); Assert.IsTrue(checksum == 0xBB76FE69); // ABCDEF } }
public void Crc32Checksum_7() { if (ZLibInit.ZLibProvided) { string filePath = Path.Combine(TestSetup.SampleDir, "ex3.jpg"); using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) { uint checksum = Crc32Checksum.Crc32(fs); Assert.IsTrue(checksum == 0x63D4D64B); } } }
public void Crc32Checksum_8() { if (ZLibInit.ZLibProvided) { byte[] sample1 = Encoding.UTF8.GetBytes("ABC"); byte[] sample2 = Encoding.UTF8.GetBytes("DEF"); using (MemoryStream ms1 = new MemoryStream(sample1)) using (MemoryStream ms2 = new MemoryStream(sample2)) { uint checksum = Crc32Checksum.Crc32(ms1); Assert.IsTrue(checksum == 0xA3830348); // ABC checksum = Crc32Checksum.Crc32(checksum, ms2); Assert.IsTrue(checksum == 0xBB76FE69); // ABCDEF } } }
public EncodedFileInfo(Plugin p, string dirName, string fileName) { string section = $"EncodedFile-{dirName}-{fileName}"; if (p.Sections.ContainsKey(section) == false) { throw new FileDecodeFailException($"[{dirName}\\{fileName}] does not exists in [{p.FullPath}]"); } List <string> encodedList = p.Sections[$"EncodedFile-{dirName}-{fileName}"].GetLinesOnce(); if (Ini.GetKeyValueFromLine(encodedList[0], out string key, out string value)) { throw new FileDecodeFailException("Encoded lines are malformed"); } // [Stage 1] Concat sliced base64-encoded lines into one string byte[] decoded; { int.TryParse(value, out int blockCount); encodedList.RemoveAt(0); // Remove "lines=n" // Each line is 64KB block if (Ini.GetKeyValueFromLines(encodedList, out List <string> keys, out List <string> base64Blocks)) { throw new FileDecodeFailException("Encoded lines are malformed"); } StringBuilder b = new StringBuilder(); foreach (string block in base64Blocks) { b.Append(block); } switch (b.Length % 4) { case 0: break; case 1: throw new FileDecodeFailException("Encoded lines are malformed"); case 2: b.Append("=="); break; case 3: b.Append("="); break; } decoded = Convert.FromBase64String(b.ToString()); } // [Stage 2] Read final footer const int finalFooterLen = 0x24; int finalFooterIdx = decoded.Length - finalFooterLen; // 0x00 - 0x04 : 4B -> CRC32 uint full_crc32 = BitConverter.ToUInt32(decoded, finalFooterIdx + 0x00); // 0x0C - 0x0F : 4B -> Zlib Compressed Footer Length int compressedFooterLen = (int)BitConverter.ToUInt32(decoded, finalFooterIdx + 0x0C); int compressedFooterIdx = decoded.Length - (finalFooterLen + compressedFooterLen); // 0x10 - 0x17 : 8B -> Zlib Compressed File Length int compressedBodyLen = (int)BitConverter.ToUInt64(decoded, finalFooterIdx + 0x10); // [Stage 3] Validate final footer this.FinalFooterValid = true; if (compressedBodyLen != compressedFooterIdx) { this.FinalFooterValid = false; } uint calcFull_crc32 = Crc32Checksum.Crc32(decoded, 0, finalFooterIdx); if (full_crc32 != calcFull_crc32) { this.FinalFooterValid = false; } if (this.FinalFooterValid == false) { return; } // [Stage 4] Decompress first footer byte[] rawFooter; using (MemoryStream rawFooterStream = new MemoryStream()) { using (MemoryStream ms = new MemoryStream(decoded, compressedFooterIdx, compressedFooterLen)) using (ZLibStream zs = new ZLibStream(ms, CompressionMode.Decompress, CompressionLevel.Default)) { zs.CopyTo(rawFooterStream); } rawFooter = rawFooterStream.ToArray(); } // [Stage 5] Read first footer this.FirstFooterValid = true; // 0x200 - 0x207 : 8B -> Length of raw file, in little endian int rawBodyLen = (int)BitConverter.ToUInt32(rawFooter, 0x200); // 0x208 - 0x20F : 8B -> Length of zlib-compressed file, in little endian // Note: In Type 2, 0x208 entry is null - padded int compressedBodyLen2 = (int)BitConverter.ToUInt32(rawFooter, 0x208); // 0x220 - 0x223 : 4B -> CRC32C Checksum of zlib-compressed file uint compressedBody_crc32 = BitConverter.ToUInt32(rawFooter, 0x220); // 0x224 : 1B -> Compress Mode (Type 1 : 00, Type 2 : 01) byte compMode = rawFooter[0x224]; // 0x225 : 1B -> ZLib Compress Level (Type 1 : 01~09, Type 2 : 00) byte compLevel = rawFooter[0x225]; // [Stage 6] Validate first footer if (compMode == 0) { this.Mode = EncodedFile.EncodeMode.Compress; if (compLevel < 1 || 9 < compLevel) { this.FirstFooterValid = false; } if (compressedBodyLen2 == 0 || (compressedBodyLen2 != compressedBodyLen)) { this.FirstFooterValid = false; } } else if (compMode == 1) { this.Mode = EncodedFile.EncodeMode.Raw; if (compLevel != 0) { this.FirstFooterValid = false; } if (compressedBodyLen2 != 0) { this.FirstFooterValid = false; } } else // Wrong compMode { this.FirstFooterValid = false; } if (this.FirstFooterValid == false) { return; } // [Stage 7] Decompress body if (compMode == (ushort)EncodedFile.EncodeMode.Compress) { this.RawBodyStream = new MemoryStream(); using (MemoryStream ms = new MemoryStream(decoded, 0, compressedBodyLen)) using (ZLibStream zs = new ZLibStream(ms, CompressionMode.Decompress, CompressionLevel.Default)) { zs.CopyTo(this.RawBodyStream); } this.RawBodyStream.Position = 0; this.CompressedBodyValid = true; } else if (compMode == (ushort)EncodedFile.EncodeMode.Raw) { this.CompressedBodyValid = true; this.RawBodyStream = new MemoryStream(decoded, 0, rawBodyLen); } else { throw new InternalException($"Wrong EncodeMode [{compMode}]"); } // [Stage 8] Validate decompressed body this.RawBodyValid = true; uint calcCompBody_crc32 = Crc32Checksum.Crc32(RawBodyStream.ToArray()); if (compressedBody_crc32 != calcCompBody_crc32) { this.RawBodyValid = false; } // [Stage 9] Return decompressed body stream this.RawBodyStream.Position = 0; }
private static MemoryStream Decode(List <string> encodedList) { if (Ini.GetKeyValueFromLine(encodedList[0], out string key, out string value)) { throw new FileDecodeFailException("Encoded lines are malformed"); } // [Stage 1] Concat sliced base64-encoded lines into one string byte[] decoded; { int.TryParse(value, out int blockCount); encodedList.RemoveAt(0); // Remove "lines=n" // Each line is 64KB block if (Ini.GetKeyValueFromLines(encodedList, out List <string> keys, out List <string> base64Blocks)) { throw new FileDecodeFailException("Encoded lines are malformed"); } StringBuilder b = new StringBuilder(); foreach (string block in base64Blocks) { b.Append(block); } switch (b.Length % 4) { case 0: break; case 1: throw new FileDecodeFailException("Encoded lines are malformed"); case 2: b.Append("=="); break; case 3: b.Append("="); break; } decoded = Convert.FromBase64String(b.ToString()); } // [Stage 2] Read final footer const int finalFooterLen = 0x24; int finalFooterIdx = decoded.Length - finalFooterLen; // 0x00 - 0x04 : 4B -> CRC32 uint full_crc32 = BitConverter.ToUInt32(decoded, finalFooterIdx + 0x00); // 0x0C - 0x0F : 4B -> Zlib Compressed Footer Length int compressedFooterLen = (int)BitConverter.ToUInt32(decoded, finalFooterIdx + 0x0C); int compressedFooterIdx = decoded.Length - (finalFooterLen + compressedFooterLen); // 0x10 - 0x17 : 8B -> Zlib Compressed File Length int compressedBodyLen = (int)BitConverter.ToUInt64(decoded, finalFooterIdx + 0x10); // [Stage 3] Validate final footer if (compressedBodyLen != compressedFooterIdx) { throw new FileDecodeFailException($"Encoded file is corrupted"); } uint calcFull_crc32 = Crc32Checksum.Crc32(decoded, 0, finalFooterIdx); if (full_crc32 != calcFull_crc32) { throw new FileDecodeFailException($"Encoded file is corrupted"); } // [Stage 4] Decompress first footer byte[] rawFooter; using (MemoryStream rawFooterStream = new MemoryStream()) { using (MemoryStream ms = new MemoryStream(decoded, compressedFooterIdx, compressedFooterLen)) using (ZLibStream zs = new ZLibStream(ms, CompressionMode.Decompress, CompressionLevel.Default)) { zs.CopyTo(rawFooterStream); } rawFooter = rawFooterStream.ToArray(); } // [Stage 5] Read first footer // 0x200 - 0x207 : 8B -> Length of raw file, in little endian int rawBodyLen = (int)BitConverter.ToUInt32(rawFooter, 0x200); // 0x208 - 0x20F : 8B -> Length of zlib-compressed file, in little endian // Note: In Type 2, 0x208 entry is null - padded int compressedBodyLen2 = (int)BitConverter.ToUInt32(rawFooter, 0x208); // 0x220 - 0x223 : 4B -> CRC32C Checksum of zlib-compressed file uint compressedBody_crc32 = BitConverter.ToUInt32(rawFooter, 0x220); // 0x224 : 1B -> Compress Mode (Type 1 : 00, Type 2 : 01) byte compMode = rawFooter[0x224]; // 0x225 : 1B -> ZLib Compress Level (Type 1 : 01~09, Type 2 : 00) byte compLevel = rawFooter[0x225]; // [Stage 6] Validate first footer if (compMode == 0) // Type 1, zlib { if (compressedBodyLen2 == 0 || (compressedBodyLen2 != compressedBodyLen)) { throw new FileDecodeFailException($"Encoded file is corrupted: compMode"); } if (compLevel < 1 || 9 < compLevel) { throw new FileDecodeFailException($"Encoded file is corrupted: compLevel"); } } else if (compMode == 1) // Type 2, Raw { if (compressedBodyLen2 != 0) { throw new FileDecodeFailException($"Encoded file is corrupted: compMode"); } if (compLevel != 0) { throw new FileDecodeFailException($"Encoded file is corrupted: compLevel"); } } else // Wrong compMode { throw new FileDecodeFailException($"Encoded file is corrupted: compMode"); } // [Stage 7] Decompress body MemoryStream rawBodyStream; // This stream should be alive even after this method returns if (compMode == 0) // Type 1, zlib { rawBodyStream = new MemoryStream(); using (MemoryStream ms = new MemoryStream(decoded, 0, compressedBodyLen)) using (ZLibStream zs = new ZLibStream(ms, CompressionMode.Decompress, false)) { zs.CopyTo(rawBodyStream); } rawBodyStream.Position = 0; } else if (compMode == 1) // Type 2, raw { rawBodyStream = new MemoryStream(decoded, 0, rawBodyLen); } else { throw new FileDecodeFailException($"Encoded file is corrupted"); } // [Stage 8] Validate decompressed body uint calcCompBody_crc32 = Crc32Checksum.Crc32(rawBodyStream.ToArray()); if (compressedBody_crc32 != calcCompBody_crc32) { throw new FileDecodeFailException($"Encoded file is corrupted"); } // [Stage 9] Return decompressed body stream rawBodyStream.Position = 0; return(rawBodyStream); }
private static Plugin Encode(Plugin p, string dirName, string fileName, byte[] input, EncodeMode mode) { byte[] fileNameUTF8 = Encoding.UTF8.GetBytes(fileName); if (fileName.Length == 0 || 512 <= fileNameUTF8.Length) { throw new FileDecodeFailException($"Filename's UTF8 encoded length should be shorter than 512B"); } // Check Overwrite bool fileOverwrite = false; if (p.Sections.ContainsKey(dirName)) { // [{dirName}] section exists, check if there is already same file encoded List <string> lines = p.Sections[dirName].GetLines(); if (lines.FirstOrDefault(x => x.Equals(fileName, StringComparison.OrdinalIgnoreCase)) != null) { fileOverwrite = true; } } string encodedStr; using (MemoryStream bodyStream = new MemoryStream()) using (MemoryStream footerStream = new MemoryStream()) using (MemoryStream concatStream = new MemoryStream()) { // [Stage 1] Compress file with zlib switch (mode) { case EncodeMode.Compress: { using (ZLibStream zs = new ZLibStream(bodyStream, CompressionMode.Compress, CompressionLevel.Level6, true)) { zs.Write(input, 0, input.Length); } bodyStream.Position = 0; } break; case EncodeMode.Raw: { bodyStream.Write(input, 0, input.Length); bodyStream.Position = 0; } break; default: throw new InternalException($"Wrong EncodeMode [{mode}]"); } // [Stage 2] Generate first footer byte[] rawFooter = new byte[0x226]; // 0x550 { // 0x000 - 0x1FF : Filename and its length rawFooter[0] = (byte)fileNameUTF8.Length; fileNameUTF8.CopyTo(rawFooter, 1); for (int i = 1 + fileNameUTF8.Length; i < 0x200; i++) { rawFooter[i] = 0; // Null Pad } // 0x200 - 0x207 : 8B -> Length of raw file, in little endian BitConverter.GetBytes(input.Length).CopyTo(rawFooter, 0x200); switch (mode) { case EncodeMode.Compress: // Type 1 // 0x208 - 0x20F : 8B -> Length of zlibed body, in little endian BitConverter.GetBytes(bodyStream.Length).CopyTo(rawFooter, 0x208); // 0x210 - 0x21F : 16B -> Null padding for (int i = 0x210; i < 0x220; i++) { rawFooter[i] = 0; } break; case EncodeMode.Raw: // Type 2 // 0x208 - 0x21F : 16B -> Null padding for (int i = 0x208; i < 0x220; i++) { rawFooter[i] = 0; } break; default: throw new InternalException($"Wrong EncodeMode [{mode}]"); } // 0x220 - 0x223 : CRC32 of raw file uint crc32 = Crc32Checksum.Crc32(input); BitConverter.GetBytes(crc32).CopyTo(rawFooter, 0x220); // 0x224 : 1B -> Compress Mode (Type 1 : 00, Type 2 : 01) rawFooter[0x224] = (byte)mode; // 0x225 : 1B -> ZLib Compress Level (Type 1 : 01 ~ 09, Type 2 : 00) switch (mode) { case EncodeMode.Compress: // Type 1 rawFooter[0x225] = (byte)CompressionLevel.Level6; break; case EncodeMode.Raw: // Type 2 rawFooter[0x225] = 0; break; default: throw new InternalException($"Wrong EncodeMode [{mode}]"); } } // [Stage 3] Compress first footer using (ZLibStream zs = new ZLibStream(footerStream, CompressionMode.Compress, CompressionLevel.Default, true)) { zs.Write(rawFooter, 0, rawFooter.Length); } footerStream.Position = 0; // [Stage 4] Concat body and footer bodyStream.CopyTo(concatStream); footerStream.CopyTo(concatStream); bodyStream.Position = 0; footerStream.Position = 0; // [Stage 5] Generate final footer { byte[] finalFooter = new byte[0x24]; // 0x00 - 0x04 : 4B -> CRC32 of compressed body and compressed footer uint crc32 = Crc32Checksum.Crc32(concatStream.ToArray()); BitConverter.GetBytes(crc32).CopyTo(finalFooter, 0x00); // 0x04 - 0x08 : 4B -> Unknown - Always 1 BitConverter.GetBytes((uint)1).CopyTo(finalFooter, 0x04); // 0x08 - 0x0B : 4B -> ZLBArchive version (Always 2) BitConverter.GetBytes((uint)2).CopyTo(finalFooter, 0x08); // 0x0C - 0x0F : 4B -> Zlib Compressed Footer Length BitConverter.GetBytes((int)footerStream.Length).CopyTo(finalFooter, 0x0C); // 0x10 - 0x17 : 8B -> Zlib Compressed File Length BitConverter.GetBytes(bodyStream.Length).CopyTo(finalFooter, 0x10); // 0x18 - 0x1B : 4B -> Unknown - Always 1 BitConverter.GetBytes((uint)1).CopyTo(finalFooter, 0x18); // 0x1C - 0x23 : 8B -> Unknown - Always 0 for (int i = 0x1C; i < 0x24; i++) { finalFooter[i] = 0; } concatStream.Write(finalFooter, 0, finalFooter.Length); } // [Stage 6] Encode body, footer and finalFooter with Base64 encodedStr = Convert.ToBase64String(concatStream.ToArray()); // Remove Base64 Padding (==, =) if (encodedStr.EndsWith("==", StringComparison.Ordinal)) { encodedStr = encodedStr.Substring(0, encodedStr.Length - 2); } else if (encodedStr.EndsWith("=", StringComparison.Ordinal)) { encodedStr = encodedStr.Substring(0, encodedStr.Length - 1); } } // [Stage 7] Tokenize encoded string into 4090B. string section = $"EncodedFile-{dirName}-{fileName}"; List <IniKey> keys = new List <IniKey>(); for (int i = 0; i <= (encodedStr.Length / 4090); i++) { if (i < (encodedStr.Length / 4090)) // 1 Line is 4090 characters { keys.Add(new IniKey(section, i.ToString(), encodedStr.Substring(i * 4090, 4090))); // X=eJyFk0Fr20AQhe8G... } else // Last Iteration { keys.Add(new IniKey(section, i.ToString(), encodedStr.Substring(i * 4090, encodedStr.Length - (i * 4090)))); // X=N3q8ryccAAQWuBjqA5QvAAAAAA (end) keys.Insert(0, new IniKey(section, "lines", i.ToString())); // lines=X } } // [Stage 8] Before writing to file, backup original plugin string tempFile = Path.GetTempFileName(); File.Copy(p.FullPath, tempFile, true); // [Stage 9] Write to file try { // Write folder info to [EncodedFolders] bool writeFolderSection = true; if (p.Sections.ContainsKey("EncodedFolders")) { List <string> folders = p.Sections["EncodedFolders"].GetLines(); if (0 < folders.Count(x => x.Equals(dirName, StringComparison.OrdinalIgnoreCase))) { writeFolderSection = false; } } if (writeFolderSection) { Ini.WriteRawLine(p.FullPath, "EncodedFolders", dirName, false); } // Write file info into [{dirName}] Ini.SetKey(p.FullPath, dirName, fileName, $"{input.Length},{encodedStr.Length}"); // UncompressedSize,EncodedSize // Write encoded file into [EncodedFile-{dirName}-{fileName}] if (fileOverwrite) { Ini.DeleteSection(p.FullPath, section); // Delete existing encoded file } Ini.SetKeys(p.FullPath, keys); // Write into } catch { // Error -> Rollback! File.Copy(tempFile, p.FullPath, true); throw new FileDecodeFailException($"Error while writing encoded file into [{p.FullPath}]"); } finally { // Delete temp script File.Delete(tempFile); } // [Stage 10] Refresh Plugin return(p.Project.RefreshPlugin(p)); }