private uint GetExtraDataSize(UtfHeader header, out UtfFieldImage[] orderedDataFieldImages) { var baseOffset = header.ExtraDataOffset; uint totalSize = 0; var fieldImagesInNewOrder = new List <UtfFieldImage>(); var row0 = Rows[0]; var firstTableDataFieldIndex = -1; foreach (var fieldImage in row0) { if (fieldImage.Type == ColumnType.Data && (fieldImage.Storage == ColumnStorage.Constant || fieldImage.Storage == ColumnStorage.Constant2)) { AddToNewOrdererdDataFieldList(fieldImagesInNewOrder, fieldImage, ref firstTableDataFieldIndex); } } foreach (var row in Rows) { foreach (var fieldImage in row) { if (fieldImage.Type == ColumnType.Data && fieldImage.Storage == ColumnStorage.PerRow) { AddToNewOrdererdDataFieldList(fieldImagesInNewOrder, fieldImage, ref firstTableDataFieldIndex); } } } // If the first field is a table, rebase the whole data field. if (firstTableDataFieldIndex == 0) { baseOffset = SerializationHelper.RoundUpAsTable(baseOffset, Alignment); header.ExtraDataOffset = baseOffset; } orderedDataFieldImages = fieldImagesInNewOrder.ToArray(); foreach (var fieldImage in orderedDataFieldImages) { var rawOffset = baseOffset; // Tables' starting locations are rounded by Alignment (usually 0x20). if (fieldImage.IsTable && fieldImage.DataValue.Length > 0) { baseOffset = SerializationHelper.RoundUpAsTable(baseOffset, Alignment); } baseOffset += (uint)fieldImage.DataValue.Length; // Tables' ending locations are rounded by 4. if (fieldImage.IsTable && fieldImage.DataValue.Length > 0) { baseOffset = AcbHelper.RoundUpToAlignment(baseOffset, 4); } totalSize += (baseOffset - rawOffset); } return(totalSize); }
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); } }
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; } } } }
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)); } }