Beispiel #1
0
 /// <summary>
 /// Update encryption keys
 /// </summary>
 protected void UpdateKeys(byte ch)
 {
     keys[0] = Crc32Checksum.ComputeCrc32(keys[0], ch);
     keys[1] = keys[1] + (byte)keys[0];
     keys[1] = keys[1] * 134775813 + 1;
     keys[2] = Crc32Checksum.ComputeCrc32(keys[2], (byte)(keys[1] >> 24));
 }
Beispiel #2
0
 /// <summary>
 /// Calculates the CRC checksum of the file.
 /// </summary>
 public static uint CalculateCrc(this FileInfo file)
 {
     using (var stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None))
     {
         return(Crc32Checksum.Generate(stream));
     }
 }
Beispiel #3
0
        /// <summary>
        /// Closes the zip input stream
        /// </summary>
        public override void Close()
        {
            internalReader = ReadingNotAvailable;
            crc            = null;
            entry          = null;

            base.Close();
        }
Beispiel #4
0
        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
            }
        }
Beispiel #5
0
 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
     }
 }
Beispiel #6
0
 public void Crc32Checksum_4()
 {
     if (ZLibInit.ZLibProvided)
     {
         Crc32Checksum crc = new Crc32Checksum();
         crc.Append(Encoding.UTF8.GetBytes("ABC"));
         Assert.IsTrue(crc.Checksum == 0xA3830348); // ABC
         crc.Append(Encoding.UTF8.GetBytes("DEF"));
         Assert.IsTrue(crc.Checksum == 0xBB76FE69); // ABCDEF
     }
 }
Beispiel #7
0
 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);
         }
     }
 }
Beispiel #8
0
        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
                    }
            }
        }
Beispiel #9
0
        /// <summary>
        /// Generates new encryption keys based on given seed
        /// </summary>
        /// <param name="seed">The seed value to initialise keys with.</param>
        /// <returns>A new key value.</returns>
        static public byte[] GenerateKeys(byte[] seed)
        {
            if (seed == null)
            {
                throw new ArgumentNullException("seed");
            }

            if (seed.Length == 0)
            {
                throw new ArgumentException("Length is zero", "seed");
            }

            uint[] newKeys = new uint[] {
                0x12345678,
                0x23456789,
                0x34567890
            };

            foreach (byte t in seed)
            {
                newKeys[0] = Crc32Checksum.ComputeCrc32(newKeys[0], t);
                newKeys[1] = newKeys[1] + (byte)newKeys[0];
                newKeys[1] = newKeys[1] * 134775813 + 1;
                newKeys[2] = Crc32Checksum.ComputeCrc32(newKeys[2], (byte)(newKeys[1] >> 24));
            }

            byte[] result = new byte[12];
            result[0]  = (byte)(newKeys[0] & 0xff);
            result[1]  = (byte)((newKeys[0] >> 8) & 0xff);
            result[2]  = (byte)((newKeys[0] >> 16) & 0xff);
            result[3]  = (byte)((newKeys[0] >> 24) & 0xff);
            result[4]  = (byte)(newKeys[1] & 0xff);
            result[5]  = (byte)((newKeys[1] >> 8) & 0xff);
            result[6]  = (byte)((newKeys[1] >> 16) & 0xff);
            result[7]  = (byte)((newKeys[1] >> 24) & 0xff);
            result[8]  = (byte)(newKeys[2] & 0xff);
            result[9]  = (byte)((newKeys[2] >> 8) & 0xff);
            result[10] = (byte)((newKeys[2] >> 16) & 0xff);
            result[11] = (byte)((newKeys[2] >> 24) & 0xff);
            return(result);
        }
Beispiel #10
0
        public void Crc32Checksum_1()
        {
            void Template(string path, uint checksum)
            {
                string filePath = Path.Combine(TestSetup.SampleDir, path);

                using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    Crc32Checksum crc = new Crc32Checksum();
                    crc.Append(fs);
                    Assert.IsTrue(crc.Checksum == checksum);
                }
            }

            if (!ZLibInit.ZLibProvided)
            {
                return;
            }

            Template("ex1.jpg", 0x1961D0C6);
            Template("ex2.jpg", 0x7641A243);
            Template("ex3.jpg", 0x63D4D64B);
        }
Beispiel #11
0
        void ReadHeader()
        {
            // 1. Check the two magic bytes
            Crc32Checksum headCRC = new Crc32Checksum();
            int           magic   = baseInputStream.ReadByte();

            if (magic < 0)
            {
                throw new EndOfStreamException("EOS reading GZIP header");
            }

            headCRC.Update(magic);
            if (magic != (GZipConstants.GzipMagic >> 8))
            {
                throw new GZipException("Error GZIP header, first magic byte doesn't match");
            }

            magic = baseInputStream.ReadByte();

            if (magic < 0)
            {
                throw new EndOfStreamException("EOS reading GZIP header");
            }

            if (magic != (GZipConstants.GzipMagic & 0xFF))
            {
                throw new GZipException("Error GZIP header,  second magic byte doesn't match");
            }

            headCRC.Update(magic);

            // 2. Check the compression type (must be 8)
            int compressionType = baseInputStream.ReadByte();

            if (compressionType < 0)
            {
                throw new EndOfStreamException("EOS reading GZIP header");
            }

            if (compressionType != 8)
            {
                throw new GZipException("Error GZIP header, data not in deflate format");
            }
            headCRC.Update(compressionType);

            // 3. Check the flags
            int flags = baseInputStream.ReadByte();

            if (flags < 0)
            {
                throw new EndOfStreamException("EOS reading GZIP header");
            }
            headCRC.Update(flags);

            /*    This flag byte is divided into individual bits as follows:
             *
             *      bit 0   FTEXT
             *      bit 1   FHCRC
             *      bit 2   FEXTRA
             *      bit 3   FNAME
             *      bit 4   FCOMMENT
             *      bit 5   reserved
             *      bit 6   reserved
             *      bit 7   reserved
             */

            // 3.1 Check the reserved bits are zero

            if ((flags & 0xE0) != 0)
            {
                throw new GZipException("Reserved flag bits in GZIP header != 0");
            }

            // 4.-6. Skip the modification time, extra flags, and OS type
            for (int i = 0; i < 6; i++)
            {
                int readByte = baseInputStream.ReadByte();
                if (readByte < 0)
                {
                    throw new EndOfStreamException("EOS reading GZIP header");
                }
                headCRC.Update(readByte);
            }

            // 7. Read extra field
            if ((flags & GZipConstants.FEXTRA) != 0)
            {
                // Skip subfield id
                for (int i = 0; i < 2; i++)
                {
                    int readByte = baseInputStream.ReadByte();
                    if (readByte < 0)
                    {
                        throw new EndOfStreamException("EOS reading GZIP header");
                    }
                    headCRC.Update(readByte);
                }

                if (baseInputStream.ReadByte() < 0 || baseInputStream.ReadByte() < 0)
                {
                    throw new EndOfStreamException("EOS reading GZIP header");
                }

                int len1, len2;
                len1 = baseInputStream.ReadByte();
                len2 = baseInputStream.ReadByte();
                if ((len1 < 0) || (len2 < 0))
                {
                    throw new EndOfStreamException("EOS reading GZIP header");
                }
                headCRC.Update(len1);
                headCRC.Update(len2);

                int extraLen = (len1 << 8) | len2;
                for (int i = 0; i < extraLen; i++)
                {
                    int readByte = baseInputStream.ReadByte();
                    if (readByte < 0)
                    {
                        throw new EndOfStreamException("EOS reading GZIP header");
                    }
                    headCRC.Update(readByte);
                }
            }

            // 8. Read file name
            if ((flags & GZipConstants.FNAME) != 0)
            {
                int readByte;
                while ((readByte = baseInputStream.ReadByte()) > 0)
                {
                    headCRC.Update(readByte);
                }

                if (readByte < 0)
                {
                    throw new EndOfStreamException("EOS reading GZIP header");
                }
                headCRC.Update(readByte);
            }

            // 9. Read comment
            if ((flags & GZipConstants.FCOMMENT) != 0)
            {
                int readByte;
                while ((readByte = baseInputStream.ReadByte()) > 0)
                {
                    headCRC.Update(readByte);
                }

                if (readByte < 0)
                {
                    throw new EndOfStreamException("EOS reading GZIP header");
                }

                headCRC.Update(readByte);
            }

            // 10. Read header CRC
            if ((flags & GZipConstants.FHCRC) != 0)
            {
                int tempByte;
                int crcval = baseInputStream.ReadByte();
                if (crcval < 0)
                {
                    throw new EndOfStreamException("EOS reading GZIP header");
                }

                tempByte = baseInputStream.ReadByte();
                if (tempByte < 0)
                {
                    throw new EndOfStreamException("EOS reading GZIP header");
                }

                crcval = (crcval << 8) | tempByte;
                if (crcval != ((int)headCRC.Value & 0xffff))
                {
                    throw new GZipException("Header CRC value mismatch");
                }
            }

            readGZIPHeader = true;
        }
Beispiel #12
0
        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;
        }
Beispiel #13
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);
        }
Beispiel #14
0
        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));
        }