2016-11-12 17:02:48 +01:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.IO;
|
2017-04-23 18:22:25 +02:00
|
|
|
|
using System.Diagnostics;
|
2016-11-12 17:02:48 +01:00
|
|
|
|
|
|
|
|
|
using SonicAudioLib.IO;
|
2017-06-21 00:19:47 +02:00
|
|
|
|
using SonicAudioLib.FileBases;
|
2016-11-12 17:02:48 +01:00
|
|
|
|
|
2017-06-21 00:19:47 +02:00
|
|
|
|
namespace SonicAudioLib.Archives
|
2016-11-12 17:02:48 +01:00
|
|
|
|
{
|
|
|
|
|
public class CriAfs2Entry : EntryBase
|
|
|
|
|
{
|
2017-04-23 18:22:25 +02:00
|
|
|
|
public uint Id { get; set; }
|
2016-11-12 17:02:48 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class CriAfs2Archive : ArchiveBase<CriAfs2Entry>
|
|
|
|
|
{
|
2021-09-10 11:16:02 +02:00
|
|
|
|
public ushort SubKey { get; set; }
|
|
|
|
|
public ushort Align { get; set; }
|
2017-04-23 18:22:25 +02:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets header of the written AFS2 archive.
|
|
|
|
|
/// Should be gotten after <see cref="Write(Stream)"/> is called.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public byte[] Header { get; private set; }
|
2016-11-12 17:02:48 +01:00
|
|
|
|
|
|
|
|
|
public override void Read(Stream source)
|
|
|
|
|
{
|
2017-04-23 18:22:25 +02:00
|
|
|
|
long ReadByLength(uint length)
|
|
|
|
|
{
|
|
|
|
|
switch (length)
|
|
|
|
|
{
|
|
|
|
|
case 2:
|
2017-06-21 00:19:47 +02:00
|
|
|
|
return DataStream.ReadUInt16(source);
|
2017-04-23 18:22:25 +02:00
|
|
|
|
|
|
|
|
|
case 4:
|
2017-06-21 00:19:47 +02:00
|
|
|
|
return DataStream.ReadUInt32(source);
|
2017-04-23 18:22:25 +02:00
|
|
|
|
|
|
|
|
|
case 8:
|
2017-06-21 00:19:47 +02:00
|
|
|
|
return DataStream.ReadInt64(source);
|
2017-04-23 18:22:25 +02:00
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
throw new ArgumentException($"Unimplemented field length ({length})", nameof(length));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-21 00:19:47 +02:00
|
|
|
|
if (DataStream.ReadCString(source, 4) != "AFS2")
|
2016-11-12 17:02:48 +01:00
|
|
|
|
{
|
2017-03-09 18:48:17 +01:00
|
|
|
|
throw new InvalidDataException("'AFS2' signature could not be found.");
|
2016-11-12 17:02:48 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-06-21 00:19:47 +02:00
|
|
|
|
uint information = DataStream.ReadUInt32(source);
|
2016-11-12 17:02:48 +01:00
|
|
|
|
|
|
|
|
|
uint type = information & 0xFF;
|
2018-09-19 19:23:53 +02:00
|
|
|
|
if (type != 1 && type != 2)
|
2016-11-12 17:02:48 +01:00
|
|
|
|
{
|
2017-03-09 18:48:17 +01:00
|
|
|
|
throw new InvalidDataException($"Unknown AFS2 type ({type}). Please report this error with the file(s).");
|
2016-11-12 17:02:48 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-04-23 18:22:25 +02:00
|
|
|
|
uint idFieldLength = (information >> 16) & 0xFF;
|
|
|
|
|
uint positionFieldLength = (information >> 8) & 0xFF;
|
2016-11-12 17:02:48 +01:00
|
|
|
|
|
2017-06-21 00:19:47 +02:00
|
|
|
|
uint entryCount = DataStream.ReadUInt32(source);
|
2021-09-10 11:16:02 +02:00
|
|
|
|
Align = DataStream.ReadUInt16(source);
|
|
|
|
|
SubKey = DataStream.ReadUInt16(source);
|
2016-11-12 17:02:48 +01:00
|
|
|
|
|
|
|
|
|
CriAfs2Entry previousEntry = null;
|
|
|
|
|
for (uint i = 0; i < entryCount; i++)
|
|
|
|
|
{
|
|
|
|
|
CriAfs2Entry afs2Entry = new CriAfs2Entry();
|
|
|
|
|
|
2017-04-23 18:22:25 +02:00
|
|
|
|
long idPosition = 16 + (i * idFieldLength);
|
2016-12-31 13:56:10 +01:00
|
|
|
|
source.Seek(idPosition, SeekOrigin.Begin);
|
2017-04-23 18:22:25 +02:00
|
|
|
|
afs2Entry.Id = (uint)ReadByLength(idFieldLength);
|
2016-11-12 17:02:48 +01:00
|
|
|
|
|
2017-04-23 18:22:25 +02:00
|
|
|
|
long positionPosition = 16 + (entryCount * idFieldLength) + (i * positionFieldLength);
|
2016-11-12 17:02:48 +01:00
|
|
|
|
source.Seek(positionPosition, SeekOrigin.Begin);
|
2017-04-23 18:22:25 +02:00
|
|
|
|
afs2Entry.Position = ReadByLength(positionFieldLength);
|
2016-11-12 17:02:48 +01:00
|
|
|
|
|
|
|
|
|
if (previousEntry != null)
|
|
|
|
|
{
|
|
|
|
|
previousEntry.Length = afs2Entry.Position - previousEntry.Position;
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-09 18:48:17 +01:00
|
|
|
|
afs2Entry.Position = Helpers.Align(afs2Entry.Position, Align);
|
2016-11-12 17:02:48 +01:00
|
|
|
|
|
|
|
|
|
if (i == entryCount - 1)
|
|
|
|
|
{
|
2017-04-23 18:22:25 +02:00
|
|
|
|
afs2Entry.Length = ReadByLength(positionFieldLength) - afs2Entry.Position;
|
2016-11-12 17:02:48 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
entries.Add(afs2Entry);
|
|
|
|
|
previousEntry = afs2Entry;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void Write(Stream destination)
|
2017-04-23 18:22:25 +02:00
|
|
|
|
{
|
|
|
|
|
long GetHeaderLength(uint idFieldLen, uint positionFieldLen)
|
|
|
|
|
{
|
|
|
|
|
return 16 + (idFieldLen * entries.Count) + (positionFieldLen * entries.Count) + positionFieldLen;
|
|
|
|
|
}
|
2016-11-12 17:02:48 +01:00
|
|
|
|
|
2017-04-23 18:22:25 +02:00
|
|
|
|
long Calculate(out uint idFieldLen, out uint positionFieldLen)
|
2016-11-12 17:02:48 +01:00
|
|
|
|
{
|
2017-04-23 18:22:25 +02:00
|
|
|
|
// It's kind of impossible to have more than 65535 waveforms in one ACB, but just to make sure.
|
|
|
|
|
idFieldLen = entries.Count <= ushort.MaxValue ? 2u : 4u;
|
|
|
|
|
|
|
|
|
|
long dataLength = 0;
|
|
|
|
|
|
|
|
|
|
foreach (CriAfs2Entry entry in entries)
|
2016-11-12 17:02:48 +01:00
|
|
|
|
{
|
2017-04-23 18:22:25 +02:00
|
|
|
|
dataLength = Helpers.Align(dataLength, Align);
|
|
|
|
|
dataLength += entry.Length;
|
|
|
|
|
}
|
2016-11-12 17:02:48 +01:00
|
|
|
|
|
2017-04-23 18:22:25 +02:00
|
|
|
|
positionFieldLen = 2;
|
|
|
|
|
long headerLen;
|
|
|
|
|
|
|
|
|
|
// Check 2, 4 and 8
|
|
|
|
|
if (Helpers.Align((headerLen = GetHeaderLength(idFieldLen, positionFieldLen)), Align) + dataLength > ushort.MaxValue)
|
|
|
|
|
{
|
|
|
|
|
positionFieldLen = 4;
|
|
|
|
|
|
|
|
|
|
if (Helpers.Align((headerLen = GetHeaderLength(idFieldLen, positionFieldLen)), Align) + dataLength > uint.MaxValue)
|
|
|
|
|
{
|
|
|
|
|
positionFieldLen = 8;
|
|
|
|
|
}
|
2016-11-12 17:02:48 +01:00
|
|
|
|
}
|
2017-04-23 18:22:25 +02:00
|
|
|
|
|
|
|
|
|
return headerLen;
|
2016-11-12 17:02:48 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-04-23 18:22:25 +02:00
|
|
|
|
var mDestination = new MemoryStream();
|
2016-11-12 17:02:48 +01:00
|
|
|
|
|
2017-04-23 18:22:25 +02:00
|
|
|
|
void WriteByLength(uint length, long value)
|
|
|
|
|
{
|
|
|
|
|
switch (length)
|
2016-11-12 17:02:48 +01:00
|
|
|
|
{
|
|
|
|
|
case 2:
|
2017-06-21 00:19:47 +02:00
|
|
|
|
DataStream.WriteUInt16(mDestination, (ushort)value);
|
2016-11-12 17:02:48 +01:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 4:
|
2017-06-21 00:19:47 +02:00
|
|
|
|
DataStream.WriteUInt32(mDestination, (uint)value);
|
2017-04-23 18:22:25 +02:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 8:
|
2017-06-21 00:19:47 +02:00
|
|
|
|
DataStream.WriteInt64(mDestination, value);
|
2016-11-12 17:02:48 +01:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
2017-04-23 18:22:25 +02:00
|
|
|
|
throw new ArgumentException($"Unimplemented field length ({length})", nameof(length));
|
2016-11-12 17:02:48 +01:00
|
|
|
|
}
|
2017-04-23 18:22:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
long headerLength = Calculate(out uint idFieldLength, out uint positionFieldLength);
|
|
|
|
|
|
2017-06-21 00:19:47 +02:00
|
|
|
|
DataStream.WriteCString(mDestination, "AFS2", 4);
|
2021-09-10 11:16:02 +02:00
|
|
|
|
DataStream.WriteUInt32(mDestination, (SubKey != 0 ? 2 : 1u) | (idFieldLength << 16) | (positionFieldLength << 8));
|
2017-06-21 00:19:47 +02:00
|
|
|
|
DataStream.WriteUInt32(mDestination, (uint)entries.Count);
|
2021-09-10 11:16:02 +02:00
|
|
|
|
DataStream.WriteUInt16(mDestination, Align);
|
|
|
|
|
DataStream.WriteUInt16(mDestination, SubKey);
|
2017-04-23 18:22:25 +02:00
|
|
|
|
|
2017-06-21 00:19:47 +02:00
|
|
|
|
DataPool vldPool = new DataPool(Align, headerLength);
|
2017-04-23 18:22:25 +02:00
|
|
|
|
vldPool.ProgressChanged += OnProgressChanged;
|
|
|
|
|
|
|
|
|
|
var orderedEntries = entries.OrderBy(entry => entry.Id);
|
|
|
|
|
foreach (CriAfs2Entry afs2Entry in orderedEntries)
|
|
|
|
|
{
|
|
|
|
|
WriteByLength(idFieldLength, afs2Entry.Id);
|
|
|
|
|
}
|
2016-11-12 17:02:48 +01:00
|
|
|
|
|
2017-04-23 18:22:25 +02:00
|
|
|
|
foreach (CriAfs2Entry afs2Entry in orderedEntries)
|
|
|
|
|
{
|
|
|
|
|
long entryPosition = vldPool.Length;
|
|
|
|
|
vldPool.Put(afs2Entry.FilePath);
|
|
|
|
|
|
|
|
|
|
WriteByLength(positionFieldLength, entryPosition);
|
2016-11-12 17:02:48 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-04-23 18:22:25 +02:00
|
|
|
|
WriteByLength(positionFieldLength, vldPool.Length);
|
|
|
|
|
|
|
|
|
|
// Copy the header to Header property and save it to destination
|
|
|
|
|
Header = mDestination.ToArray();
|
|
|
|
|
mDestination.Close();
|
2016-11-12 17:02:48 +01:00
|
|
|
|
|
2017-04-23 18:22:25 +02:00
|
|
|
|
destination.Write(Header, 0, Header.Length);
|
2016-11-12 17:02:48 +01:00
|
|
|
|
vldPool.Write(destination);
|
|
|
|
|
vldPool.Clear();
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-23 18:22:25 +02:00
|
|
|
|
public CriAfs2Entry GetById(uint id)
|
2016-11-12 17:02:48 +01:00
|
|
|
|
{
|
2017-04-23 18:22:25 +02:00
|
|
|
|
return entries.FirstOrDefault(e => (e.Id == id));
|
2016-11-12 17:02:48 +01:00
|
|
|
|
}
|
2016-12-31 13:56:10 +01:00
|
|
|
|
|
2016-11-12 17:02:48 +01:00
|
|
|
|
public CriAfs2Archive()
|
|
|
|
|
{
|
|
|
|
|
Align = 32;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|