using System; using System.Collections.Generic; using System.IO; using VGMToolbox.util; namespace VGMToolbox.format { public class CriUsmStream : MpegStream { public const string DefaultAudioExtension = ".adx"; public const string DefaultVideoExtension = ".m2v"; public const string HcaAudioExtension = ".hca"; static readonly byte[] HCA_SIG_BYTES = new byte[] { 0x48, 0x43, 0x41, 0x00 }; protected static readonly byte[] ALP_BYTES = new byte[] { 0x40, 0x41, 0x4C, 0x50 }; protected static readonly byte[] CRID_BYTES = new byte[] { 0x43, 0x52, 0x49, 0x44 }; protected static readonly byte[] SFV_BYTES = new byte[] { 0x40, 0x53, 0x46, 0x56 }; protected static readonly byte[] SFA_BYTES = new byte[] { 0x40, 0x53, 0x46, 0x41 }; protected static readonly byte[] SBT_BYTES = new byte[] { 0x40, 0x53, 0x42, 0x54 }; protected static readonly byte[] CUE_BYTES = new byte[] { 0x40, 0x43, 0x55, 0x45 }; protected static readonly byte[] UTF_BYTES = new byte[] { 0x40, 0x55, 0x54, 0x46 }; protected static readonly byte[] HEADER_END_BYTES = new byte[] { 0x23, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x20, 0x45, 0x4E, 0x44, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x00 }; protected static readonly byte[] METADATA_END_BYTES = new byte[] { 0x23, 0x4D, 0x45, 0x54, 0x41, 0x44, 0x41, 0x54, 0x41, 0x20, 0x45, 0x4E, 0x44, 0x20, 0x20, 0x20, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x00 }; protected static readonly byte[] CONTENTS_END_BYTES = new byte[] { 0x23, 0x43, 0x4F, 0x4E, 0x54, 0x45, 0x4E, 0x54, 0x53, 0x20, 0x45, 0x4E, 0x44, 0x20, 0x20, 0x20, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x3D, 0x00 }; public CriUsmStream(string path) : base(path) { this.UsesSameIdForMultipleAudioTracks = true; this.FileExtensionAudio = DefaultAudioExtension; this.FileExtensionVideo = DefaultVideoExtension; base.BlockIdDictionary.Clear(); base.BlockIdDictionary[BitConverter.ToUInt32(ALP_BYTES, 0)] = new BlockSizeStruct(PacketSizeType.SizeBytes, 4); // @ALP base.BlockIdDictionary[BitConverter.ToUInt32(CRID_BYTES, 0)] = new BlockSizeStruct(PacketSizeType.SizeBytes, 4); // CRID base.BlockIdDictionary[BitConverter.ToUInt32(SFV_BYTES, 0)] = new BlockSizeStruct(PacketSizeType.SizeBytes, 4); // @SFV base.BlockIdDictionary[BitConverter.ToUInt32(SFA_BYTES, 0)] = new BlockSizeStruct(PacketSizeType.SizeBytes, 4); // @SFA base.BlockIdDictionary[BitConverter.ToUInt32(SBT_BYTES, 0)] = new BlockSizeStruct(PacketSizeType.SizeBytes, 4); // @SBT base.BlockIdDictionary[BitConverter.ToUInt32(CUE_BYTES, 0)] = new BlockSizeStruct(PacketSizeType.SizeBytes, 4); // @CUE } protected override byte[] GetPacketStartBytes() { return CRID_BYTES; } protected override int GetAudioPacketHeaderSize(Stream readStream, long currentOffset) { UInt16 checkBytes; OffsetDescription od = new OffsetDescription(); od.OffsetByteOrder = Constants.BigEndianByteOrder; od.OffsetSize = "2"; od.OffsetValue = "8"; checkBytes = (UInt16)ParseFile.GetVaryingByteValueAtRelativeOffset(readStream, od, currentOffset); return checkBytes; } protected override int GetVideoPacketHeaderSize(Stream readStream, long currentOffset) { UInt16 checkBytes; OffsetDescription od = new OffsetDescription(); od.OffsetByteOrder = Constants.BigEndianByteOrder; od.OffsetSize = "2"; od.OffsetValue = "8"; checkBytes = (UInt16)ParseFile.GetVaryingByteValueAtRelativeOffset(readStream, od, currentOffset); return checkBytes; } protected override bool IsThisAnAudioBlock(byte[] blockToCheck) { return ParseFile.CompareSegment(blockToCheck, 0, SFA_BYTES); } protected override bool IsThisAVideoBlock(byte[] blockToCheck) { return ParseFile.CompareSegment(blockToCheck, 0, SFV_BYTES); } protected override byte GetStreamId(Stream readStream, long currentOffset) { byte streamId; streamId = ParseFile.ParseSimpleOffset(readStream, currentOffset + 0xC, 1)[0]; return streamId; } protected override int GetAudioPacketFooterSize(Stream readStream, long currentOffset) { UInt16 checkBytes; OffsetDescription od = new OffsetDescription(); od.OffsetByteOrder = Constants.BigEndianByteOrder; od.OffsetSize = "2"; od.OffsetValue = "0xA"; checkBytes = (UInt16)ParseFile.GetVaryingByteValueAtRelativeOffset(readStream, od, currentOffset); return checkBytes; } protected override int GetVideoPacketFooterSize(Stream readStream, long currentOffset) { UInt16 checkBytes; OffsetDescription od = new OffsetDescription(); od.OffsetByteOrder = Constants.BigEndianByteOrder; od.OffsetSize = "2"; od.OffsetValue = "0xA"; checkBytes = (UInt16)ParseFile.GetVaryingByteValueAtRelativeOffset(readStream, od, currentOffset); return checkBytes; } protected override void DoFinalTasks(FileStream sourceFileStream, Dictionary outputFiles, bool addHeader) { long headerEndOffset; long metadataEndOffset; long headerSize; long footerOffset; long footerSize; string sourceFileName; string workingFile; string fileExtension; string destinationFileName; foreach (uint streamId in outputFiles.Keys) { sourceFileName = outputFiles[streamId].Name; //-------------------------- // get header size //-------------------------- headerEndOffset = ParseFile.GetNextOffset(outputFiles[streamId], 0, HEADER_END_BYTES); metadataEndOffset = ParseFile.GetNextOffset(outputFiles[streamId], 0, METADATA_END_BYTES); if (metadataEndOffset > headerEndOffset) { headerSize = metadataEndOffset + METADATA_END_BYTES.Length; } else { headerSize = headerEndOffset + METADATA_END_BYTES.Length; } //----------------- // get footer size //----------------- footerOffset = ParseFile.GetNextOffset(outputFiles[streamId], 0, CONTENTS_END_BYTES) - headerSize; footerSize = outputFiles[streamId].Length - footerOffset; //------------------------------------------ // check data to adjust extension if needed //------------------------------------------ if (this.IsThisAnAudioBlock(BitConverter.GetBytes(streamId & 0xFFFFFFF0))) // may need to change mask if more than 0xF streams { byte[] checkBytes = ParseFile.ParseSimpleOffset(outputFiles[streamId], headerSize, 4); if (ParseFile.CompareSegment(checkBytes, 0, SofdecStream.AixSignatureBytes)) { fileExtension = SofdecStream.AixAudioExtension; } else if (checkBytes[0] == 0x80) { fileExtension = SofdecStream.AdxAudioExtension; } else if (ParseFile.CompareSegment(checkBytes, 0, HCA_SIG_BYTES)) { fileExtension = HcaAudioExtension; } else { fileExtension = ".bin"; } this.FinalAudioExtension = fileExtension; this.HasAudio = true; } else { fileExtension = Path.GetExtension(sourceFileName); } outputFiles[streamId].Close(); outputFiles[streamId].Dispose(); workingFile = FileUtil.RemoveChunkFromFile(sourceFileName, 0, headerSize); File.Copy(workingFile, sourceFileName, true); File.Delete(workingFile); workingFile = FileUtil.RemoveChunkFromFile(sourceFileName, footerOffset, footerSize); destinationFileName = Path.ChangeExtension(sourceFileName, fileExtension); destinationFileName = destinationFileName.Substring(0, destinationFileName.LastIndexOf("_"))+fileExtension; File.Copy(workingFile, destinationFileName, true); File.Delete(workingFile); if ((sourceFileName != destinationFileName) && (File.Exists(sourceFileName))) { File.Delete(sourceFileName); } } } } }