mirror of
https://github.com/blueskythlikesclouds/SonicAudioTools.git
synced 2024-11-15 02:47:39 +01:00
449 lines
17 KiB
C#
449 lines
17 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Windows.Forms;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
using AcbEditor.Properties;
|
|
|
|
using SonicAudioLib;
|
|
using SonicAudioLib.CriMw;
|
|
using SonicAudioLib.IO;
|
|
using SonicAudioLib.Archives;
|
|
|
|
namespace AcbEditor
|
|
{
|
|
class Program
|
|
{
|
|
static void Main(string[] args)
|
|
{
|
|
if (!File.Exists(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile))
|
|
{
|
|
Settings.Default.Reset();
|
|
Settings.Default.Save();
|
|
}
|
|
|
|
if (args.Length < 1)
|
|
{
|
|
Console.WriteLine(Resources.Description);
|
|
Console.ReadLine();
|
|
return;
|
|
}
|
|
#if !DEBUG
|
|
try
|
|
{
|
|
#endif
|
|
if (args[0].EndsWith(".acb", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var extractor = new DataExtractor();
|
|
extractor.ProgressChanged += OnProgressChanged;
|
|
|
|
extractor.BufferSize = Settings.Default.BufferSize;
|
|
extractor.EnableThreading = Settings.Default.EnableThreading;
|
|
extractor.MaxThreads = Settings.Default.MaxThreads;
|
|
|
|
string baseDirectory = Path.GetDirectoryName(args[0]);
|
|
string outputDirectoryPath = Path.ChangeExtension(args[0], null);
|
|
string extAfs2ArchivePath = string.Empty;
|
|
|
|
Directory.CreateDirectory(outputDirectoryPath);
|
|
|
|
using (CriTableReader acbReader = CriTableReader.Create(args[0]))
|
|
{
|
|
acbReader.Read();
|
|
|
|
CriAfs2Archive afs2Archive = new CriAfs2Archive();
|
|
CriAfs2Archive extAfs2Archive = new CriAfs2Archive();
|
|
|
|
CriCpkArchive cpkArchive = new CriCpkArchive();
|
|
CriCpkArchive extCpkArchive = null;
|
|
|
|
extAfs2ArchivePath = outputDirectoryPath + ".awb";
|
|
bool found = File.Exists(extAfs2ArchivePath);
|
|
|
|
if (!found)
|
|
{
|
|
extAfs2ArchivePath = outputDirectoryPath + "_streamfiles.awb";
|
|
found = File.Exists(extAfs2ArchivePath);
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
extAfs2ArchivePath = outputDirectoryPath + "_STR.awb";
|
|
found = File.Exists(extAfs2ArchivePath);
|
|
}
|
|
|
|
bool cpkMode = true;
|
|
|
|
long awbPosition = acbReader.GetPosition("AwbFile");
|
|
if (acbReader.GetLength("AwbFile") > 0)
|
|
{
|
|
using (SubStream afs2Stream = acbReader.GetSubStream("AwbFile"))
|
|
{
|
|
cpkMode = !CheckIfAfs2(afs2Stream);
|
|
|
|
if (cpkMode)
|
|
{
|
|
cpkArchive.Read(afs2Stream);
|
|
}
|
|
|
|
else
|
|
{
|
|
afs2Archive.Read(afs2Stream);
|
|
}
|
|
}
|
|
|
|
if (afs2Archive.SubKey != 0)
|
|
{
|
|
using (var stream = File.Create(Path.Combine(outputDirectoryPath, ".subkey")))
|
|
DataStream.WriteUInt16(stream, afs2Archive.SubKey);
|
|
}
|
|
}
|
|
|
|
if (acbReader.GetLength("StreamAwbAfs2Header") > 0)
|
|
{
|
|
cpkMode = false;
|
|
|
|
using (SubStream extAfs2Stream = acbReader.GetSubStream("StreamAwbAfs2Header"))
|
|
{
|
|
bool utfMode = DataStream.ReadCString(extAfs2Stream, 4) == "@UTF";
|
|
extAfs2Stream.Seek(0, SeekOrigin.Begin);
|
|
|
|
if (utfMode)
|
|
{
|
|
using (CriTableReader utfAfs2HeaderReader = CriTableReader.Create(extAfs2Stream))
|
|
{
|
|
utfAfs2HeaderReader.Read();
|
|
|
|
using (SubStream extAfs2HeaderStream = utfAfs2HeaderReader.GetSubStream("Header"))
|
|
{
|
|
extAfs2Archive.Read(extAfs2HeaderStream);
|
|
}
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
extAfs2Archive.Read(extAfs2Stream);
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
throw new FileNotFoundException("Cannot find the external .AWB file for this .ACB file. Please ensure that the external .AWB file is stored in the directory where the .ACB file is.");
|
|
}
|
|
|
|
if (extAfs2Archive.SubKey != 0)
|
|
{
|
|
using (var stream = File.Create(Path.Combine(outputDirectoryPath, ".subkey_streaming")))
|
|
DataStream.WriteUInt16(stream, extAfs2Archive.SubKey);
|
|
}
|
|
}
|
|
|
|
using (SubStream waveformTableStream = acbReader.GetSubStream("WaveformTable"))
|
|
using (CriTableReader waveformReader = CriTableReader.Create(waveformTableStream))
|
|
{
|
|
while (waveformReader.Read())
|
|
{
|
|
byte encodeType = waveformReader.GetByte("EncodeType");
|
|
bool streaming = waveformReader.GetBoolean("Streaming");
|
|
|
|
ushort id =
|
|
waveformReader.ContainsField("MemoryAwbId") ?
|
|
streaming ? waveformReader.GetUInt16("StreamAwbId") : waveformReader.GetUInt16("MemoryAwbId") :
|
|
waveformReader.GetUInt16("Id");
|
|
|
|
string outputName = id.ToString("D5");
|
|
if (streaming)
|
|
{
|
|
outputName += "_streaming";
|
|
}
|
|
|
|
outputName += GetExtension(encodeType);
|
|
outputName = Path.Combine(outputDirectoryPath, outputName);
|
|
|
|
if (streaming)
|
|
{
|
|
if (!found)
|
|
{
|
|
throw new Exception("Cannot find the external .AWB file for this .ACB file. Please ensure that the external .AWB file is stored in the directory where the .ACB file is.");
|
|
}
|
|
|
|
else if (extCpkArchive == null && cpkMode)
|
|
{
|
|
extCpkArchive = new CriCpkArchive();
|
|
extCpkArchive.Load(extAfs2ArchivePath, Settings.Default.BufferSize);
|
|
}
|
|
|
|
EntryBase afs2Entry = null;
|
|
|
|
if (cpkMode)
|
|
{
|
|
afs2Entry = extCpkArchive.GetById(id);
|
|
}
|
|
|
|
else
|
|
{
|
|
afs2Entry = extAfs2Archive.GetById(id);
|
|
}
|
|
|
|
extractor.Add(extAfs2ArchivePath, outputName, afs2Entry.Position, afs2Entry.Length);
|
|
}
|
|
|
|
else
|
|
{
|
|
EntryBase afs2Entry = null;
|
|
|
|
if (cpkMode)
|
|
{
|
|
afs2Entry = cpkArchive.GetById(id);
|
|
}
|
|
|
|
else
|
|
{
|
|
afs2Entry = afs2Archive.GetById(id);
|
|
}
|
|
|
|
extractor.Add(args[0], outputName, awbPosition + afs2Entry.Position, afs2Entry.Length);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extractor.Run();
|
|
}
|
|
|
|
else if (File.GetAttributes(args[0]).HasFlag(FileAttributes.Directory))
|
|
{
|
|
string baseDirectory = Path.GetDirectoryName(args[0]);
|
|
string acbPath = args[0] + ".acb";
|
|
|
|
string awbPath = args[0] + "_streamfiles.awb";
|
|
bool found = File.Exists(awbPath);
|
|
|
|
if (!found)
|
|
{
|
|
awbPath = args[0] + "_STR.awb";
|
|
found = File.Exists(awbPath);
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
awbPath = args[0] + ".awb";
|
|
}
|
|
|
|
if (!File.Exists(acbPath))
|
|
{
|
|
throw new FileNotFoundException("Cannot find the .ACB file for this directory. Please ensure that the .ACB file is stored in the directory where this directory is.");
|
|
}
|
|
|
|
CriTable acbFile = new CriTable();
|
|
acbFile.Load(acbPath, Settings.Default.BufferSize);
|
|
|
|
CriAfs2Archive afs2Archive = new CriAfs2Archive();
|
|
CriAfs2Archive extAfs2Archive = new CriAfs2Archive();
|
|
|
|
CriCpkArchive cpkArchive = new CriCpkArchive();
|
|
CriCpkArchive extCpkArchive = new CriCpkArchive();
|
|
cpkArchive.Mode = extCpkArchive.Mode = CriCpkMode.Id;
|
|
|
|
afs2Archive.ProgressChanged += OnProgressChanged;
|
|
extAfs2Archive.ProgressChanged += OnProgressChanged;
|
|
cpkArchive.ProgressChanged += OnProgressChanged;
|
|
extCpkArchive.ProgressChanged += OnProgressChanged;
|
|
|
|
bool cpkMode = true;
|
|
|
|
byte[] awbFile = (byte[])acbFile.Rows[0]["AwbFile"];
|
|
byte[] streamAwbAfs2Header = (byte[])acbFile.Rows[0]["StreamAwbAfs2Header"];
|
|
|
|
cpkMode = !(awbFile != null && awbFile.Length >= 4 && Encoding.ASCII.GetString(awbFile, 0, 4) == "AFS2") && (streamAwbAfs2Header == null || streamAwbAfs2Header.Length == 0);
|
|
|
|
using (CriTableReader reader = CriTableReader.Create((byte[])acbFile.Rows[0]["WaveformTable"]))
|
|
{
|
|
while (reader.Read())
|
|
{
|
|
byte encodeType = reader.GetByte("EncodeType");
|
|
bool streaming = reader.GetBoolean("Streaming");
|
|
|
|
ushort id =
|
|
reader.ContainsField("MemoryAwbId") ?
|
|
streaming ? reader.GetUInt16("StreamAwbId") : reader.GetUInt16("MemoryAwbId") :
|
|
reader.GetUInt16("Id");
|
|
|
|
string inputName = id.ToString("D5");
|
|
if (streaming)
|
|
{
|
|
inputName += "_streaming";
|
|
}
|
|
|
|
inputName += GetExtension(encodeType);
|
|
inputName = Path.Combine(args[0], inputName);
|
|
|
|
if (!File.Exists(inputName))
|
|
{
|
|
throw new FileNotFoundException($"Cannot find audio file with id {id} for replacement.\nPath attempt: {inputName}");
|
|
}
|
|
|
|
if (cpkMode)
|
|
{
|
|
CriCpkEntry entry = new CriCpkEntry();
|
|
entry.FilePath = new FileInfo(inputName);
|
|
entry.Id = id;
|
|
|
|
if (streaming)
|
|
{
|
|
extCpkArchive.Add(entry);
|
|
}
|
|
|
|
else
|
|
{
|
|
cpkArchive.Add(entry);
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
CriAfs2Entry entry = new CriAfs2Entry();
|
|
entry.FilePath = new FileInfo(inputName);
|
|
entry.Id = id;
|
|
|
|
if (streaming)
|
|
{
|
|
extAfs2Archive.Add(entry);
|
|
}
|
|
|
|
else
|
|
{
|
|
afs2Archive.Add(entry);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
acbFile.Rows[0]["AwbFile"] = null;
|
|
acbFile.Rows[0]["StreamAwbAfs2Header"] = null;
|
|
|
|
string subKeyFilePath = Path.Combine(args[0], ".subkey");
|
|
if (File.Exists(subKeyFilePath))
|
|
{
|
|
using (var stream = File.OpenRead(subKeyFilePath))
|
|
afs2Archive.SubKey = DataStream.ReadUInt16(stream);
|
|
}
|
|
|
|
string subKeyStreamingFilePath = Path.Combine(args[0], ".subkey_streaming");
|
|
if (File.Exists(subKeyStreamingFilePath))
|
|
{
|
|
using (var stream = File.OpenRead(subKeyStreamingFilePath))
|
|
extAfs2Archive.SubKey = DataStream.ReadUInt16(stream);
|
|
}
|
|
|
|
if (afs2Archive.Count > 0 || cpkArchive.Count > 0)
|
|
{
|
|
Console.WriteLine("Saving internal AWB...");
|
|
acbFile.Rows[0]["AwbFile"] = cpkMode ? cpkArchive.Save() : afs2Archive.Save();
|
|
Console.WriteLine();
|
|
}
|
|
|
|
if (extAfs2Archive.Count > 0 || extCpkArchive.Count > 0)
|
|
{
|
|
Console.WriteLine("Saving external AWB...");
|
|
if (cpkMode)
|
|
{
|
|
extCpkArchive.Save(awbPath, Settings.Default.BufferSize);
|
|
}
|
|
|
|
else
|
|
{
|
|
extAfs2Archive.Save(awbPath, Settings.Default.BufferSize);
|
|
|
|
if (Encoding.UTF8.GetString(streamAwbAfs2Header, 0, 4) == "@UTF")
|
|
{
|
|
CriTable headerTable = new CriTable();
|
|
headerTable.Load(streamAwbAfs2Header);
|
|
|
|
headerTable.Rows[0]["Header"] = extAfs2Archive.Header;
|
|
headerTable.WriterSettings = CriTableWriterSettings.Adx2Settings;
|
|
acbFile.Rows[0]["StreamAwbAfs2Header"] = headerTable.Save();
|
|
}
|
|
|
|
else
|
|
{
|
|
acbFile.Rows[0]["StreamAwbAfs2Header"] = extAfs2Archive.Header;
|
|
}
|
|
}
|
|
}
|
|
|
|
acbFile.WriterSettings = CriTableWriterSettings.Adx2Settings;
|
|
acbFile.Save(acbPath, Settings.Default.BufferSize);
|
|
}
|
|
#if !DEBUG
|
|
}
|
|
|
|
catch (Exception exception)
|
|
{
|
|
MessageBox.Show($"{exception.Message}", "ACB Editor", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static string GetExtension(byte encodeType)
|
|
{
|
|
switch (encodeType)
|
|
{
|
|
case 0:
|
|
case 3:
|
|
return ".adx";
|
|
case 1:
|
|
return ".ahx";
|
|
case 2:
|
|
return ".hca";
|
|
case 4:
|
|
return ".wiiadpcm";
|
|
case 5:
|
|
return ".dsadpcm";
|
|
case 6:
|
|
return ".hcamx";
|
|
case 10:
|
|
case 7:
|
|
return ".vag";
|
|
case 8:
|
|
return ".at3";
|
|
case 9:
|
|
return ".bcwav";
|
|
case 18:
|
|
case 11:
|
|
return ".at9";
|
|
case 12:
|
|
return ".xma";
|
|
case 13:
|
|
return ".dsp";
|
|
default:
|
|
return ".bin";
|
|
}
|
|
}
|
|
|
|
static bool CheckIfAfs2(Stream source)
|
|
{
|
|
long oldPosition = source.Position;
|
|
bool result = DataStream.ReadCString(source, 4) == "AFS2";
|
|
source.Seek(oldPosition, SeekOrigin.Begin);
|
|
|
|
return result;
|
|
}
|
|
|
|
private static string buffer = new string(' ', 17);
|
|
|
|
private static void OnProgressChanged(object sender, ProgressChangedEventArgs e)
|
|
{
|
|
int left = Console.CursorLeft;
|
|
int top = Console.CursorTop;
|
|
|
|
Console.Write(buffer);
|
|
Console.SetCursorPosition(left, top);
|
|
Console.WriteLine("Progress: {0}%", e.Progress);
|
|
Console.SetCursorPosition(left, top);
|
|
}
|
|
}
|
|
}
|