public static byte[] RoundUpTo(this byte[] data, int alignment) { var newLength = AcbHelper.RoundUpToAlignment(data.Length, alignment); var buffer = new byte[newLength]; data.CopyTo(buffer, 0); return(buffer); }
private static void WriteAfs2ArchiveToStream(ReadOnlyCollection <byte[]> files, Stream stream, uint alignment) { var fileCount = (uint)files.Count; if (files.Count >= ushort.MaxValue) { throw new IndexOutOfRangeException($"File count {fileCount} exceeds maximum possible value (65535)."); } if (files.Count != 1) { throw new NotSupportedException("Currently DereTore does not support more than one file."); } stream.WriteBytes(Afs2Archive.Afs2Signature); const uint version = 0x00020401; stream.WriteUInt32LE(version); stream.WriteUInt32LE(fileCount); stream.WriteUInt32LE(alignment); const uint offsetFieldSize = (version >> 8) & 0xff; // version[1], always 4? See Afs2Archive.Initialize(). // Prepare the fields. var afs2HeaderSegmentSize = 0x10 + // General data 2 * fileCount + // Cue IDs offsetFieldSize * fileCount + // File offsets sizeof(uint); // Size of last file (U32) // Assuming the music file always has ID 0 in Waveform table and Cue table. var records = new List <Afs2FileRecord>(); var currentFileRawOffset = afs2HeaderSegmentSize; for (ushort i = 0; i < fileCount; ++i) { var record = new Afs2FileRecord { // TODO: Use the Cue table. CueId = i, FileOffsetRaw = currentFileRawOffset, FileOffsetAligned = AcbHelper.RoundUpToAlignment(currentFileRawOffset, alignment) }; records.Add(record); currentFileRawOffset = (uint)(record.FileOffsetAligned + files[i].Length); } var lastFileEndOffset = currentFileRawOffset; for (var i = 0; i < files.Count; ++i) { stream.WriteUInt16LE(records[i].CueId); } for (var i = 0; i < files.Count; ++i) { stream.WriteUInt32LE((uint)records[i].FileOffsetRaw); } // TODO: Dynamically judge it. See Afs2Archive.Initialize(). stream.WriteUInt32LE(lastFileEndOffset); for (var i = 0; i < files.Count; ++i) { stream.SeekAndWriteBytes(files[i], records[i].FileOffsetAligned); } }
private static void ProcessAllBinaries(uint acbFormatVersion, DecodeParams baseDecodeParams, string extractDir, Afs2Archive archive, Stream dataStream, bool isInternal) { if (!Directory.Exists(extractDir)) { Directory.CreateDirectory(extractDir); } var afsSource = isInternal ? "internal" : "external"; var decodeParams = baseDecodeParams; if (acbFormatVersion >= NewEncryptionVersion) { decodeParams.KeyModifier = archive.HcaKeyModifier; } else { decodeParams.KeyModifier = 0; } foreach (var entry in archive.Files) { var record = entry.Value; var extractFileName = AcbFile.GetSymbolicFileNameFromCueId(record.CueId); extractFileName = extractFileName.ReplaceExtension(".bin", ".wav"); var extractFilePath = Path.Combine(extractDir, extractFileName); using (var fileData = AcbHelper.ExtractToNewStream(dataStream, record.FileOffsetAligned, (int)record.FileLength)) { var isHcaStream = HcaReader.IsHcaStream(fileData); Console.Write("Processing {0} AFS: #{1} (offset={2} size={3})... ", afsSource, record.CueId, record.FileOffsetAligned, record.FileLength); if (isHcaStream) { try { using (var fs = File.Open(extractFilePath, FileMode.Create, FileAccess.Write, FileShare.Write)) { DecodeHca(fileData, fs, decodeParams); } Console.WriteLine("decoded"); } catch (Exception ex) { if (File.Exists(extractFilePath)) { File.Delete(extractFilePath); } Console.WriteLine(ex.Message); } } else { Console.WriteLine("skipped (not HCA)"); } } } }
public static uint RoundUpAsTable(uint value, uint alignment) { // This action seems weird. But it does exist (see Cue table in CGSS song_1001[oneshin]), I don't know why. value = AcbHelper.RoundUpToAlignment(value, 4); if (value % alignment == 0) { value += alignment; } return(AcbHelper.RoundUpToAlignment(value, alignment)); }
/// <summary> /// Some fields are not filled. /// </summary> /// <returns></returns> public UtfHeader GetHeader(out UtfFieldImage[] orderedDataFieldImages) { // Basic information if (Rows.Count < 1) { throw new InvalidOperationException("Rows should not be empty."); } var fieldCount = Rows[0].Count; for (var i = 1; i < Rows.Count; ++i) { if (Rows[i].Count != fieldCount) { throw new InvalidOperationException("Number of fields in each row do not match."); } } var singleRowDataSize = GetSingleRowDataSize(); var rowDescriptorSize = GetRowDescriptorSize(); var header = new UtfHeader { TableName = TableName, RowCount = (uint)Rows.Count, FieldCount = (ushort)fieldCount, Unknown1 = 1, RowSize = singleRowDataSize, PerRowDataOffset = rowDescriptorSize + SchemaOffset // "per row" data offset, actually }; var perRowDataSize = header.RowCount * header.RowSize; header.StringTableOffset = header.PerRowDataOffset + perRowDataSize; header.TableNameOffset = 0; var stringTableSize = GetStringTableSize(); header.ExtraDataOffset = header.StringTableOffset + stringTableSize; var extraDataSize = GetExtraDataSize(header, out orderedDataFieldImages); header.TableSize = header.ExtraDataOffset + extraDataSize; // ??? header.TableSize = AcbHelper.RoundUpToAlignment(header.TableSize, 4); return(header); }
private void SetExtraDataFieldRelocations(UtfHeader header, UtfFieldImage[] orderedFieldImages) { // This value is already calibrated in GetExtraDataSize(). It will move to a mod16 position if the first item is a table. var baseOffset = header.ExtraDataOffset; var currentOffset = baseOffset; foreach (var fieldImage in orderedFieldImages) { if (fieldImage.DataValue.Length > 0) { // For priority, see AddToNewOrderedDataFieldList(). if (fieldImage.IsTable) { currentOffset = SerializationHelper.RoundUpAsTable(currentOffset, Alignment); } fieldImage.DataOffset = currentOffset - baseOffset; currentOffset += (uint)fieldImage.DataValue.Length; // Extra 4-byte alignment at the end of tables. if (fieldImage.IsTable) { currentOffset = AcbHelper.RoundUpToAlignment(currentOffset, Alignment); } } else { fieldImage.DataOffset = 0; } } var row0 = Rows[0]; for (var i = 1; i < Rows.Count; ++i) { for (var j = 0; j < Rows[i].Count; ++j) { var fieldImage = Rows[i][j]; if (fieldImage.Type == ColumnType.Data && (fieldImage.Storage == ColumnStorage.Constant || fieldImage.Storage == ColumnStorage.Constant2)) { fieldImage.DataOffset = row0[j].DataOffset; } } } }
// ReSharper disable once IdentifierTypo private static void AfsToWav(Afs2FileRecord afsRecord, Stream awbStream, DecodeParams decodeParams, string output) { using var fileData = AcbHelper.ExtractToNewStream(awbStream, afsRecord.FileOffsetAligned, (int)afsRecord.FileLength); var isHcaStream = DereTore.Exchange.Audio.HCA.HcaReader.IsHcaStream(fileData); if (!isHcaStream) { return; } using var fs = File.OpenWrite(output); try { HcaToWav(fileData, fs, decodeParams); } catch (Exception) { Console.Error.WriteLine($"Failed to convert {output}:"); throw; } }
private static void ExtractAllBinaries(string extractDir, Afs2Archive archive, Stream dataStream, bool isInternal) { if (!Directory.Exists(extractDir)) { Directory.CreateDirectory(extractDir); } var afsSource = isInternal ? "internal" : "external"; foreach (var entry in archive.Files) { var record = entry.Value; var extractFileName = AcbFile.GetSymbolicFileNameFromCueId(record.CueId); var extractFilePath = Path.Combine(extractDir, extractFileName); using (var fs = File.Open(extractFilePath, FileMode.Create, FileAccess.Write, FileShare.Write)) { using (var fileData = AcbHelper.ExtractToNewStream(dataStream, record.FileOffsetAligned, (int)record.FileLength)) { WriteFile(fileData, fs); } } Console.WriteLine("Extracted from {0} AFS: #{1} (offset={2} size={3})", afsSource, record.CueId, record.FileOffsetAligned, record.FileLength); } }
public void WriteValueTo(Stream stream) { switch (Type) { case ColumnType.Byte: stream.WriteByte(NumericValue.U8); break; case ColumnType.SByte: stream.WriteSByte(NumericValue.S8); break; case ColumnType.UInt16: stream.WriteUInt16BE(NumericValue.U16); break; case ColumnType.Int16: stream.WriteInt16BE(NumericValue.S16); break; case ColumnType.UInt32: stream.WriteUInt32BE(NumericValue.U32); break; case ColumnType.Int32: stream.WriteInt32BE(NumericValue.S32); break; case ColumnType.UInt64: stream.WriteUInt64BE(NumericValue.U64); break; case ColumnType.Int64: stream.WriteInt64BE(NumericValue.S64); break; case ColumnType.Single: stream.WriteSingleBE(NumericValue.R32); break; case ColumnType.Double: stream.WriteDoubleBE(NumericValue.R64); break; case ColumnType.String: stream.WriteUInt32BE(StringOffset); break; case ColumnType.Data: stream.WriteUInt32BE(DataOffset); // Tables ending locations, 4-byte alignment. if (IsTable) { stream.WriteUInt32BE(AcbHelper.RoundUpToAlignment((uint)DataValue.Length, 4)); } else { stream.WriteUInt32BE((uint)DataValue.Length); } break; default: throw new ArgumentOutOfRangeException(nameof(Type)); } }