示例#1
0
        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);
        }
示例#2
0
        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);
            }
        }
示例#3
0
        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)");
                    }
                }
            }
        }
示例#4
0
 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));
 }
示例#5
0
        /// <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);
        }
示例#6
0
        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;
                    }
                }
            }
        }
示例#7
0
        // 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;
            }
        }
示例#8
0
        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);
            }
        }
示例#9
0
        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));
            }
        }