Exemplo n.º 1
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();
                }
            }
        }
Exemplo n.º 2
0
        static void PackArchive(string inputFolderName, string outputFileName)
        {
            var filesystemSection  = new MemoryStream();
            var fileEntriesSection = new MemoryStream();
            var chunksSection      = new MemoryStream();
            var stringSection      = new MemoryStream();

            // Build folders and paths
            var paths     = new List <string>();
            var fullpaths = new List <string>();

            foreach (var file in Directory.EnumerateFiles(inputFolderName, "*.*", SearchOption.AllDirectories))
            {
                var curPaths = new List <string>();

                var path = file.Replace('\\', '/');
                while (!string.IsNullOrWhiteSpace((path = Path.GetDirectoryName(path))))
                {
                    path = path.Replace('\\', '/');

                    if (!paths.Contains(path))
                    {
                        curPaths.Add(path);
                    }
                }

                curPaths.Reverse();

                fullpaths.AddRange(curPaths);
                fullpaths.Add(file);

                var filename = Path.GetFileName(file);
                if (!paths.Contains(filename))
                {
                    curPaths.Add(filename);
                }

                paths.AddRange(curPaths);
            }

            // Build string table
            var stringTableMapping = new Dictionary <string, int>();

            using (var writer = new BinaryWriter(stringSection, Encoding.ASCII, true))
            {
                foreach (var path in paths)
                {
                    var temp   = Encoding.ASCII.GetBytes(path);
                    var offset = (int)writer.BaseStream.Position;

                    writer.Write((byte)temp.Length);
                    writer.Write(temp);

                    stringTableMapping.Add(path, offset);
                }
            }

            // Build filesystem table entries
            var filesystemEntries = new List <L7CAFilesystemEntry>();
            var folders           = new List <string>();
            var files             = new List <string>();

            var filesystemId = 0;

            foreach (var path in fullpaths)
            {
                var entry = new L7CAFilesystemEntry();

                if (File.GetAttributes(path).HasFlag(FileAttributes.Directory))
                {
                    entry.id           = -1;
                    entry.folderOffset = stringTableMapping[path.Replace("\\", "/")];
                    folders.Add(path);
                }
                else
                {
                    entry.id             = filesystemId++;
                    entry.folderOffset   = stringTableMapping[Path.GetDirectoryName(path).Replace("\\", "/")];
                    entry.filenameOffset = stringTableMapping[Path.GetFileName(path)];
                    files.Add(path);
                }

                entry.hash      = Crc32.CalculateNamco(path.Replace("\\", "/"));
                entry.timestamp = new FileInfo(path).LastWriteTime.ToFileTime();
                entry.filename  = path;

                filesystemEntries.Add(entry);
            }

            using (var writer = new BinaryWriter(filesystemSection, Encoding.ASCII, true))
            {
                foreach (var entry in filesystemEntries)
                {
                    writer.Write(entry.id);
                    writer.Write(entry.hash);
                    writer.Write(entry.folderOffset);
                    writer.Write(entry.filenameOffset);
                    writer.Write(entry.timestamp);
                }
            }

            using (var archiveWriter = new BinaryWriter(File.OpenWrite(outputFileName)))
            {
                // Write heading padding
                archiveWriter.Write(new byte[PaddingBoundary]);

                // Build file entries
                var fileEntries  = new List <L7CAFileEntry>();
                var chunkEntries = new List <L7CAChunkEntry>();
                foreach (var fs in filesystemEntries)
                {
                    if (fs.id == -1)
                    {
                        continue;
                    }

                    Console.WriteLine("Reading {0}...", fs.filename);

                    var entry = new L7CAFileEntry
                    {
                        chunkIdx = chunkEntries.Count
                    };

                    var    data   = File.ReadAllBytes(fs.filename);
                    ushort chunks = 0;
                    for (var size = data.Length; size > 0; size -= MaxChunkSize)
                    {
                        var chunkEntry = new L7CAChunkEntry
                        {
                            chunkId   = chunks++,
                            chunkSize = size > MaxChunkSize ? MaxChunkSize : size
                        };
                        chunkEntries.Add(chunkEntry);
                    }

                    entry.chunkCount         = chunks;
                    entry.compressedFilesize = data.Length;
                    entry.rawFilesize        = data.Length;
                    entry.crc32  = Crc32.Calculate(data);
                    entry.offset = (int)archiveWriter.BaseStream.Position;

                    // Write data to data table
                    archiveWriter.Write(data);

                    // Write padding
                    archiveWriter.Write(new byte[(int)(PaddingBoundary - (archiveWriter.BaseStream.Length - PaddingBoundary) % PaddingBoundary)]);

                    fileEntries.Add(entry);
                }

                // Write file entries section data
                using (var writer = new BinaryWriter(fileEntriesSection, Encoding.ASCII, true))
                {
                    foreach (var entry in fileEntries)
                    {
                        writer.Write(entry.compressedFilesize);
                        writer.Write(entry.rawFilesize);
                        writer.Write(entry.chunkIdx);
                        writer.Write(entry.chunkCount);
                        writer.Write(entry.offset);
                        writer.Write(entry.crc32);
                    }
                }

                // Write chunk section data
                using (var writer = new BinaryWriter(chunksSection, Encoding.ASCII, true))
                {
                    foreach (var entry in chunkEntries)
                    {
                        writer.Write(entry.chunkSize);
                        writer.Write(entry.unk);
                        writer.Write(entry.chunkId);
                    }
                }

                Console.WriteLine("Writing {0}...", outputFileName);

                var header = new L7CAHeader
                {
                    metadataOffset = (int)archiveWriter.BaseStream.Position
                };
                header.archiveSize       = (int)(header.metadataOffset + filesystemSection.Length + fileEntriesSection.Length + chunksSection.Length + stringSection.Length);
                header.metadataSize      = (int)(filesystemSection.Length + fileEntriesSection.Length + chunksSection.Length + stringSection.Length);
                header.filesystemEntries = filesystemEntries.Count;
                header.folders           = folders.Count;
                header.files             = files.Count;
                header.chunks            = chunkEntries.Count;
                header.stringTableSize   = (int)stringSection.Length;

                archiveWriter.BaseStream.Seek(0, SeekOrigin.Begin);
                archiveWriter.Write(header.magic);
                archiveWriter.Write(header.unk);
                archiveWriter.Write(header.archiveSize);
                archiveWriter.Write(header.metadataOffset);
                archiveWriter.Write(header.metadataSize);
                archiveWriter.Write(header.unk2);
                archiveWriter.Write(header.filesystemEntries);
                archiveWriter.Write(header.folders);
                archiveWriter.Write(header.files);
                archiveWriter.Write(header.chunks);
                archiveWriter.Write(header.stringTableSize);
                archiveWriter.Write(header.unk4);

                archiveWriter.BaseStream.Seek(0, SeekOrigin.End);

                // Write file info sections
                archiveWriter.Write(filesystemSection.GetBuffer(), 0, (int)filesystemSection.Length);
                archiveWriter.Write(fileEntriesSection.GetBuffer(), 0, (int)fileEntriesSection.Length);
                archiveWriter.Write(chunksSection.GetBuffer(), 0, (int)chunksSection.Length);
                archiveWriter.Write(stringSection.GetBuffer(), 0, (int)stringSection.Length);
            }
        }