Example #1
0
        static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                Console.WriteLine("usage:");
                Console.WriteLine("Extraction:");
                Console.WriteLine("\t{0} x input.l7z", AppDomain.CurrentDomain.FriendlyName);
                Console.WriteLine();
                Console.WriteLine("Creation:");
                Console.WriteLine("\t{0} c input_folderName output.l7z", AppDomain.CurrentDomain.FriendlyName);
                Console.WriteLine();
                Console.WriteLine("Decompress individual file with mode 0x80:");
                Console.WriteLine("\t{0} d input.bin output.bin", AppDomain.CurrentDomain.FriendlyName);
                Console.WriteLine();
                Console.WriteLine("Compress individual file with mode 0x80:");
                Console.WriteLine("\t{0} e input.bin output.bin", AppDomain.CurrentDomain.FriendlyName);
                Environment.Exit(1);
            }

            if (args[0] == "c")
            {
                PackArchive(args[1], args[2]);
            }
            else if (args[0] == "x")
            {
                UnpackArchive(args[1]);
            }
            else if (args[0] == "d")
            {
                var input  = args[1];
                var output = input + ".out";

                if (args.Length >= 2)
                {
                    output = args[2];
                }

                byte[] data;
                using (BinaryReader reader = new BinaryReader(File.OpenRead(input)))
                {
                    var expectedFileSize = reader.ReadUInt32();

                    if (expectedFileSize == 0x19) // Blank
                    {
                        expectedFileSize = reader.ReadUInt32();
                    }
                    else
                    {
                        expectedFileSize = (expectedFileSize & 0xffffff00) >> 8;
                    }

                    data = TaikoCompression.Decompress(reader.ReadBytes((int)reader.BaseStream.Length - 4));
                    if (data.Length != expectedFileSize)
                    {
                        Console.WriteLine("File size didn't match expected output file size. Maybe bad decompression? ({0:x8} != {1:x8})", data.Length, expectedFileSize);
                    }
                }

                File.WriteAllBytes(output, data);
            }
            else if (args[0] == "e")
            {
                var input  = args[1];
                var output = input + ".out";

                if (args.Length >= 2)
                {
                    output = args[2];
                }

                var data = File.ReadAllBytes(input);
                using (var writer = new BinaryWriter(File.OpenWrite(output)))
                {
                    var expectedFileSize = data.Length;

                    if (expectedFileSize > 0xffffff)
                    {
                        writer.Write(0x00000019);
                        writer.Write(expectedFileSize);
                    }
                    else
                    {
                        writer.Write((expectedFileSize << 8) | 0x19);
                    }

                    writer.Write(TaikoCompression.Compress(data));
                }
            }
        }
Example #2
0
        static void UnpackArchive(string inputFilename)
        {
            using (BinaryReader reader = new BinaryReader(File.OpenRead(inputFilename)))
            {
                if (reader.ReadUInt32() != 0x4143374c)
                {
                    Console.WriteLine("Not a L7CA archive");
                    Environment.Exit(1);
                }

                var header = new L7CAHeader
                {
                    unk               = reader.ReadUInt32(),
                    archiveSize       = reader.ReadInt32(),
                    metadataOffset    = reader.ReadInt32(),
                    metadataSize      = reader.ReadInt32(),
                    unk2              = reader.ReadUInt32(),
                    filesystemEntries = reader.ReadInt32(),
                    folders           = reader.ReadInt32(),
                    files             = reader.ReadInt32(),
                    chunks            = reader.ReadInt32(),
                    stringTableSize   = reader.ReadInt32(),
                    unk4              = reader.ReadInt32()
                };

                if (header.unk4 != 0x05)
                {
                    Console.WriteLine("This archive type is unsupported and most likely won't unpack properly.");
                }

                // Read strings
                var baseOffset = reader.BaseStream.Length - header.stringTableSize;
                reader.BaseStream.Seek(baseOffset, SeekOrigin.Begin);

                var strings = new Dictionary <int, string>();
                while (reader.PeekChar() != -1)
                {
                    var offset = (int)(reader.BaseStream.Position - baseOffset);
                    int len    = reader.ReadByte();
                    var str    = Encoding.UTF8.GetString(reader.ReadBytes(len));

                    strings.Add(offset, str);
                }


                // Read filesystem entries
                var entries = new Dictionary <int, L7CAFilesystemEntry>();
                reader.BaseStream.Seek(header.metadataOffset, SeekOrigin.Begin);
                for (var i = 0; i < header.filesystemEntries; i++)
                {
                    var entry = new L7CAFilesystemEntry
                    {
                        id             = reader.ReadInt32(),
                        hash           = reader.ReadUInt32(),
                        folderOffset   = reader.ReadInt32(),
                        filenameOffset = reader.ReadInt32(),
                        timestamp      = reader.ReadInt64()
                    };

                    entry.filename = entry.id == -1 ?
                                     $"{strings[entry.folderOffset]}" :
                                     $"{strings[entry.folderOffset]}/{strings[entry.filenameOffset]}";

                    //Console.WriteLine("{0:x8} {1:x8} {2:x8} {3:x8} {4:x16}", entry.id, entry.hash, entry.folderOffset, entry.filenameOffset, entry.timestamp);

                    if (Crc32.CalculateNamco(entry.filename) != entry.hash)
                    {
                        Console.WriteLine("{0} did not match expected hash", entry.filename);
                    }

                    if (entry.id != -1)
                    {
                        entries.Add(entry.id, entry);
                    }
                    else
                    {
                        // -1 is a folder.
                        // Only create a folder and move on to next entry.
                        // This step probably isn't needed,  but just for the sake of completeness I added it.
                        // There might be some game out there that has blank folders but no actual data in it, so those will be accounted for as well.

                        if (!Directory.Exists(entry.filename))
                        {
                            Directory.CreateDirectory(entry.filename);
                        }
                    }
                }

                // Read file information
                var files = new List <L7CAFileEntry>();
                for (var i = 0; i < header.files; i++)
                {
                    var entry = new L7CAFileEntry
                    {
                        compressedFilesize = reader.ReadInt32(),
                        rawFilesize        = reader.ReadInt32(),
                        chunkIdx           = reader.ReadInt32(),
                        chunkCount         = reader.ReadInt32(),
                        offset             = reader.ReadInt32(),
                        crc32 = reader.ReadUInt32()
                    };

                    //var filename = entries[(uint)i].filename;
                    //Console.WriteLine("{2}\noffset[{0:x8}] fileSize[{1:x8}]\nreal_crc32[{3:x8}] crc32[{4:x8}] crc32[{5:x8}]\n", entry.offset, entry.compressedFileSize, filename, entries[(uint)i].hash, crc32.Value, entry.crc32);

                    files.Add(entry);
                }

                // Read chunk information
                var chunks = new List <L7CAChunkEntry>();
                for (var i = 0; i < header.chunks; i++)
                {
                    var entry = new L7CAChunkEntry
                    {
                        chunkSize = reader.ReadInt32(),
                        unk       = reader.ReadUInt16(),
                        chunkId   = reader.ReadUInt16()
                    };

                    //Console.WriteLine("{3:x8} {0:x8} {1:x4} {2:x4}", entry.chunkSize, entry.unk, entry.chunkNum, i);

                    chunks.Add(entry);
                }

                for (var i = 0; i < header.files; i++)
                {
                    var file  = files[i];
                    var entry = entries[i];

                    Console.WriteLine("Extracting {0}...", entry.filename);
                    //Console.WriteLine("{0:x1} {1:x8} {2:x8} {3:x8} {4:x8} {5:x8}", file.chunkIdx, file.chunkCount, file.offset, file.compressedFileSize, file.rawFileSize, file.crc32);

                    //var output = Path.Combine("output", entry.filename);
                    var output = entry.filename;
                    Directory.CreateDirectory(Path.GetDirectoryName(output));

                    reader.BaseStream.Seek(file.offset, SeekOrigin.Begin);
                    var origData = reader.ReadBytes(file.compressedFilesize);

                    var data = new List <byte>();

                    using (var dataStream = new BinaryReader(new MemoryStream(origData)))
                    {
                        if (_debugDecompressionCode)
                        {
                            foreach (var f in Directory.EnumerateFiles(".", "output-chunk-*.bin"))
                            {
                                File.Delete(f);
                            }
                        }

                        for (int x = 0; x < file.chunkCount; x++)
                        {
                            var compMode     = (chunks[file.chunkIdx + x].chunkSize >> 24) & 0xff;
                            var len          = chunks[file.chunkIdx + x].chunkSize & 0xffffff;
                            var isCompressed = (chunks[file.chunkIdx + x].chunkSize & 0x80000000) != 0;
                            if (isCompressed)
                            {
                                Console.WriteLine("Chunk is compressed.");
                            }

                            if (_debugDecompressionCode)
                            {
                                Console.WriteLine("{0:x8} {1}", len, isCompressed);
                            }

                            var d = dataStream.ReadBytes(len);

                            if (_debugDecompressionCode)
                            {
                                File.WriteAllBytes($"output-chunk-{x}.bin", d);
                            }

                            if (isCompressed)
                            {
                                try
                                {
                                    // Decompress chunk
                                    switch (compMode)
                                    {
                                    case 0x80:
                                        d    = TaikoCompression.Decompress(d, data.ToArray());
                                        data = new List <byte>(d);
                                        break;

                                    case 0x81:
                                        d    = TaikoCompression2.Decompress(d, data.ToArray());
                                        data = new List <byte>(d);
                                        break;
                                    }
                                }
                                catch
                                {
                                    // Save compressed data
                                    Console.WriteLine("Could not decompress file.");
                                    data.AddRange(d);
                                }
                            }
                            else
                            {
                                data.AddRange(d);
                            }

                            //Console.WriteLine(" {0:x8}", d.Length);

                            if (_debugDecompressionCode)
                            {
                                File.WriteAllBytes($"output-chunk-{x}-decomp.bin", d);
                            }
                        }
                    }

                    var crc32 = Crc32.Calculate(data.ToArray());
                    if (crc32 != file.crc32)
                    {
                        Console.WriteLine("Invalid CRC32: {0:x8} vs {1:x8}", crc32, file.crc32);

                        //File.WriteAllBytes("invalid.bin", data.ToArray());
                        //Environment.Exit(1);
                    }

                    File.WriteAllBytes(output, data.ToArray());

                    if (data.Count != file.rawFilesize)
                    {
                        Console.WriteLine("Invalid file size: {0:x8} vs {1:x8}", data.Count, file.rawFilesize);
                        //Environment.Exit(1);
                    }

                    Console.WriteLine();
                }
            }
        }