/// <summary> /// Instantiates a new <see cref="CpkTable"/> from an input <see cref="Stream"/>. /// </summary> /// <param name="input"></param> public CpkTable(Stream input) { using (var br = new BinaryReaderX(input, true)) { // Read in the table header. Header = br.ReadType <CpkTableHeader>(); br.ByteOrder = ByteOrder.BigEndian; // Handle obfuscated UTF table if (br.PeekString() != "@UTF") { UtfObfuscation = true; // Decrypt the UTF table _tableStream = new BinaryReaderX(new MemoryStream(UtfTools.XorUtf(br.ReadBytes(Header.PacketSize))), true, ByteOrder.BigEndian); // Read the table info. TableInfo = _tableStream.ReadType <CpkTableInfo>(); // Replace the stream with a new SubStream into the decrypted data. _tableStream = new BinaryReaderX(new SubStream(_tableStream.BaseStream, 0x8, TableInfo.TableSize), true, ByteOrder.BigEndian); } else { // Read the table info. TableInfo = br.ReadType <CpkTableInfo>(); // Create a sub stream of the entire table. _tableStream = new BinaryReaderX(new SubStream(br.BaseStream, 0x18, TableInfo.TableSize), true, ByteOrder.BigEndian); } // Move forward to right after TableInfo _tableStream.BaseStream.Position += 0x18; // Make sure the data isn't bogus. if (Header.PacketSize - 0x8 != TableInfo.TableSize) { throw new FormatException("The packet size doesn't match the table size. The file might be corrupt, encrypted or it is not a CPK."); } if (Header.PacketSize > 100 * 1024 * 1024) { throw new FormatException("The packet size is too big. The file might be corrupt, encrypted or it is not a CPK."); } // Create sub streams for the string data and binary data. _stringStream = new BinaryReaderX(new SubStream(_tableStream.BaseStream, TableInfo.StringsOffset, TableInfo.TableSize - TableInfo.StringsOffset), true, ByteOrder.BigEndian); _binaryStream = new BinaryReaderX(new SubStream(_tableStream.BaseStream, TableInfo.BinaryOffset, TableInfo.TableSize - TableInfo.BinaryOffset), true, ByteOrder.BigEndian); // Read in the table name. Name = ReadString(TableInfo.NameOffset); // Read in the columns. var columns = new List <CpkColumnInfo>(); for (var i = 0; i < TableInfo.ColumnCount; i++) { var flags = _tableStream.ReadByte(); var column = new CpkColumnInfo { Name = ReadString(_tableStream.ReadInt32()), Storage = (CpkColumnStorage)(flags & 0xF0), Type = (CpkDataType)(flags & 0x0F) }; columns.Add(column); if (column.Storage == CpkColumnStorage.Const) { column.Value = ReadValue(column.Type); } if (column.Storage == CpkColumnStorage.Zero) { column.Value = ZeroValue(column.Type); } } Columns = columns; // Read in the values. _tableStream.BaseStream.Position = TableInfo.ValuesOffset; Rows = new List <CpkRow>(); for (var i = 0; i < TableInfo.RowCount; i++) { var row = new CpkRow(Columns); foreach (var column in Columns) { if (column.Storage == CpkColumnStorage.Const || column.Storage == CpkColumnStorage.Zero) { row[column.Name].Value = column.Value.Value; } else if (column.Storage == CpkColumnStorage.Row) { row[column.Name].Value = ReadValue(column.Type).Value; } } Rows.Add(row); } } }
/// <summary> /// Instantiates a new <see cref="CPK"/> from an input <see cref="Stream"/>. /// </summary> /// <param name="input"></param> public CPK(Stream input) { using (var br = new BinaryReaderX(input, true)) { // Read in the CPK table. HeaderTable = new CpkTable(br.BaseStream); // Check the Magic if (HeaderTable.Header.Magic != "CPK ") { throw new FormatException("The loaded file is not a CPK archive."); } Alignment = (short)(ushort)HeaderTable.Rows.First().Values["Align"].Value; // Retrieve the offsets for the other tables. var tocOffset = (long)(ulong)HeaderTable.Rows.First().Values["TocOffset"].Value; var tocSize = (long)(ulong)HeaderTable.Rows.First().Values["TocSize"].Value; var etocOffset = (long)(ulong)HeaderTable.Rows.First().Values["EtocOffset"].Value; var etocSize = (long)(ulong)HeaderTable.Rows.First().Values["EtocSize"].Value; var itocOffset = (long)(ulong)HeaderTable.Rows.First().Values["ItocOffset"].Value; var itocSize = (long)(ulong)HeaderTable.Rows.First().Values["ItocSize"].Value; // Read in the TOC table. if (tocOffset > 0) { // Fluff br.BaseStream.Position = HeaderTable.Header.PacketSize + 0x10; _unknownPostHeaderData = HeaderTable.UtfObfuscation ? UtfTools.XorUtf(br.ReadBytes((int)tocOffset - (int)br.BaseStream.Position)) : br.ReadBytes((int)tocOffset - (int)br.BaseStream.Position); // Set the file offset base value FileOffsetBase = tocOffset; br.BaseStream.Position = tocOffset; TocTable = new CpkTable(new SubStream(br.BaseStream, tocOffset, tocSize)); // Read in the ETOC table. if (etocOffset > 0) { br.BaseStream.Position = etocOffset; ETocTable = new CpkTable(new SubStream(br.BaseStream, etocOffset, etocSize)); } // Populate files foreach (var row in TocTable.Rows) { var dir = ((string)row["DirName"].Value).Replace("/", "\\"); var name = (string)row["FileName"].Value; var offset = FileOffsetBase + Convert.ToInt64(row["FileOffset"].Value); var compressedSize = Convert.ToInt32(row["FileSize"].Value); var fileSize = Convert.ToInt32(row["ExtractSize"].Value); Files.Add(new CpkFileInfo(new SubStream(br.BaseStream, offset, compressedSize), row, TocTable.UtfObfuscation, fileSize, compressedSize) { FileName = Path.Combine(dir, name), State = ArchiveFileState.Archived }); } // Sort files by offset Files.Sort((a, b) => Convert.ToInt32(((CpkFileInfo)a).Row["FileOffset"].Value).CompareTo(Convert.ToInt32(((CpkFileInfo)b).Row["FileOffset"].Value))); } // Read in the ITOC table. else if (itocOffset > 0) { // Fluff br.BaseStream.Position = HeaderTable.Header.PacketSize + 0x10; _unknownPostHeaderData = HeaderTable.UtfObfuscation ? UtfTools.XorUtf(br.ReadBytes((int)itocOffset - (int)br.BaseStream.Position)) : br.ReadBytes((int)itocOffset - (int)br.BaseStream.Position); // Set the file offset base value FileOffsetBase = itocOffset; br.BaseStream.Position = itocOffset; ITocTable = new CpkTable(new SubStream(br.BaseStream, itocOffset, itocSize)); } } }