mirror of
https://github.com/blueskythlikesclouds/SonicAudioTools.git
synced 2024-11-14 18:38:02 +01:00
Add multi threading and progress percentages
This commit is contained in:
parent
608f94c646
commit
2131ac6799
@ -1,8 +1,7 @@
|
||||
# Sonic Audio Tools
|
||||
Sonic Audio Tools is a set of tools for editing the audio formats seen in Sonic the Hedgehog games. Currently, it's mostly focused on CRIWARE's audio formats, this means that these tools can be used to edit files from any game, as long as they are CSB, CPK, ACB or AWB.
|
||||
## Releases
|
||||
[Here.](https://ci.appveyor.com/project/blueskythlikesclouds/sonicaudiotools)
|
||||
You can get the binaries from Artifacts tab.
|
||||
[Here.](https://ci.appveyor.com/project/blueskythlikesclouds/sonicaudiotools/build/artifacts)
|
||||
|
||||
## Building
|
||||
1. Clone from [GitHub](https://github.com/blueskythlikesclouds/SonicAudioTools.git) `git clone https://github.com/blueskythlikesclouds/SonicAudioTools.git`
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26228.4
|
||||
VisualStudioVersion = 15.0.26403.7
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SonicAudioLib", "Source\SonicAudioLib\SonicAudioLib.csproj", "{63138773-1F47-474C-9345-15EB6183ECC6}"
|
||||
EndProject
|
||||
|
@ -51,9 +51,18 @@
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Properties\Settings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
<None Include="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SonicAudioLib\SonicAudioLib.csproj">
|
||||
|
@ -1,6 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
|
||||
</startup>
|
||||
<configSections>
|
||||
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
|
||||
<section name="AcbEditor.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
|
||||
</sectionGroup>
|
||||
</configSections>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
|
||||
</startup>
|
||||
<userSettings>
|
||||
<AcbEditor.Properties.Settings>
|
||||
<setting name="EnableThreading" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="MaxThreads" serializeAs="String">
|
||||
<value>4</value>
|
||||
</setting>
|
||||
<setting name="BufferSize" serializeAs="String">
|
||||
<value>4096</value>
|
||||
</setting>
|
||||
</AcbEditor.Properties.Settings>
|
||||
</userSettings>
|
||||
</configuration>
|
@ -2,7 +2,11 @@
|
||||
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.Archive;
|
||||
@ -22,9 +26,16 @@ namespace AcbEditor
|
||||
#if !DEBUG
|
||||
try
|
||||
{
|
||||
#endif
|
||||
#endif
|
||||
if (args[0].EndsWith(".acb"))
|
||||
{
|
||||
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;
|
||||
@ -58,6 +69,7 @@ namespace AcbEditor
|
||||
|
||||
bool cpkMode = true;
|
||||
|
||||
long awbPosition = acbReader.GetPosition("AwbFile");
|
||||
if (acbReader.GetLength("AwbFile") > 0)
|
||||
{
|
||||
using (Substream afs2Stream = acbReader.GetSubstream("AwbFile"))
|
||||
@ -109,8 +121,6 @@ namespace AcbEditor
|
||||
outputName += GetExtension(encodeType);
|
||||
outputName = Path.Combine(outputDirectoryPath, outputName);
|
||||
|
||||
Console.WriteLine("Extracting {0} file with id {1}...", GetExtension(encodeType).ToUpper(), id);
|
||||
|
||||
if (streaming)
|
||||
{
|
||||
if (!found)
|
||||
@ -121,7 +131,7 @@ namespace AcbEditor
|
||||
else if (extCpkArchive == null && cpkMode)
|
||||
{
|
||||
extCpkArchive = new CriCpkArchive();
|
||||
extCpkArchive.Load(extAfs2ArchivePath);
|
||||
extCpkArchive.Load(extAfs2ArchivePath, Settings.Default.BufferSize);
|
||||
}
|
||||
|
||||
EntryBase afs2Entry = null;
|
||||
@ -136,12 +146,7 @@ namespace AcbEditor
|
||||
afs2Entry = extAfs2Archive.GetById(id);
|
||||
}
|
||||
|
||||
using (Stream extAfs2Stream = File.OpenRead(extAfs2ArchivePath))
|
||||
using (Stream afs2EntryStream = afs2Entry.Open(extAfs2Stream))
|
||||
using (Stream afs2EntryDestination = File.Create(outputName))
|
||||
{
|
||||
afs2EntryStream.CopyTo(afs2EntryDestination);
|
||||
}
|
||||
extractor.Add(extAfs2ArchivePath, outputName, afs2Entry.Position, afs2Entry.Length);
|
||||
}
|
||||
|
||||
else
|
||||
@ -158,16 +163,13 @@ namespace AcbEditor
|
||||
afs2Entry = afs2Archive.GetById(id);
|
||||
}
|
||||
|
||||
using (Substream afs2Stream = acbReader.GetSubstream("AwbFile"))
|
||||
using (Stream afs2EntryStream = afs2Entry.Open(afs2Stream))
|
||||
using (Stream afs2EntryDestination = File.Create(outputName))
|
||||
{
|
||||
afs2EntryStream.CopyTo(afs2EntryDestination);
|
||||
}
|
||||
extractor.Add(args[0], outputName, awbPosition + afs2Entry.Position, afs2Entry.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extractor.Run();
|
||||
}
|
||||
|
||||
else if (File.GetAttributes(args[0]).HasFlag(FileAttributes.Directory))
|
||||
@ -191,11 +193,11 @@ namespace AcbEditor
|
||||
|
||||
if (!File.Exists(acbPath))
|
||||
{
|
||||
throw new Exception("Cannot find the .ACB file for this directory. Please ensure that the .ACB file is stored in the directory where this directory is.");
|
||||
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);
|
||||
acbFile.Load(acbPath, Settings.Default.BufferSize);
|
||||
|
||||
CriAfs2Archive afs2Archive = new CriAfs2Archive();
|
||||
CriAfs2Archive extAfs2Archive = new CriAfs2Archive();
|
||||
@ -204,6 +206,11 @@ namespace AcbEditor
|
||||
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"];
|
||||
@ -222,7 +229,7 @@ namespace AcbEditor
|
||||
string inputName = id.ToString("D5");
|
||||
if (streaming)
|
||||
{
|
||||
inputName += "_streaming";
|
||||
inputName += "_streaming";
|
||||
}
|
||||
|
||||
inputName += GetExtension(encodeType);
|
||||
@ -230,11 +237,9 @@ namespace AcbEditor
|
||||
|
||||
if (!File.Exists(inputName))
|
||||
{
|
||||
throw new Exception($"Cannot find audio file with id {id} for replacement.\nPath attempt: {inputName}");
|
||||
throw new FileNotFoundException($"Cannot find audio file with id {id} for replacement.\nPath attempt: {inputName}");
|
||||
}
|
||||
|
||||
Console.WriteLine("Adding {0}...", Path.GetFileName(inputName));
|
||||
|
||||
if (cpkMode)
|
||||
{
|
||||
CriCpkEntry entry = new CriCpkEntry();
|
||||
@ -278,6 +283,7 @@ namespace AcbEditor
|
||||
{
|
||||
Console.WriteLine("Saving internal AWB...");
|
||||
acbFile.Rows[0]["AwbFile"] = cpkMode ? cpkArchive.Save() : afs2Archive.Save();
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
if (extAfs2Archive.Count > 0 || extCpkArchive.Count > 0)
|
||||
@ -285,30 +291,19 @@ namespace AcbEditor
|
||||
Console.WriteLine("Saving external AWB...");
|
||||
if (cpkMode)
|
||||
{
|
||||
extCpkArchive.Save(awbPath);
|
||||
extCpkArchive.Save(awbPath, Settings.Default.BufferSize);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
extAfs2Archive.Save(awbPath);
|
||||
|
||||
byte[] afs2Header = new byte[16 +
|
||||
(extAfs2Archive.Count * extAfs2Archive.IdFieldLength) +
|
||||
(extAfs2Archive.Count * extAfs2Archive.PositionFieldLength) +
|
||||
extAfs2Archive.PositionFieldLength];
|
||||
|
||||
using (FileStream fileStream = File.OpenRead(awbPath))
|
||||
{
|
||||
fileStream.Read(afs2Header, 0, afs2Header.Length);
|
||||
}
|
||||
|
||||
acbFile.Rows[0]["StreamAwbAfs2Header"] = afs2Header;
|
||||
extAfs2Archive.Save(awbPath, Settings.Default.BufferSize);
|
||||
acbFile.Rows[0]["StreamAwbAfs2Header"] = extAfs2Archive.Header;
|
||||
}
|
||||
}
|
||||
|
||||
acbFile.WriterSettings = CriTableWriterSettings.Adx2Settings;
|
||||
acbFile.Save(acbPath);
|
||||
}
|
||||
acbFile.Save(acbPath, Settings.Default.BufferSize);
|
||||
}
|
||||
#if !DEBUG
|
||||
}
|
||||
|
||||
@ -363,5 +358,18 @@ namespace AcbEditor
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -137,6 +137,19 @@ files, or remove any of them.
|
||||
To pack the .ACB file back, you gotta have the extracted directory
|
||||
and the .ACB file in the same directory, and also the external .AWB
|
||||
file if it exists. Drag and drop the directory to the .EXE file,
|
||||
it will collect all the files inside directory and pack them back.</value>
|
||||
it will collect all the files inside directory and pack them back.
|
||||
|
||||
Settings (.exe.config)
|
||||
=======================
|
||||
BufferSize: Buffer size used to load/save data from files. Higher
|
||||
values may make the progress faster or slower.
|
||||
|
||||
EnableThreading: Determines whether to use multiple threads to
|
||||
extract data from files.
|
||||
|
||||
MaxThreads: Maximum amount of threads to extract data from files.
|
||||
Higher values may make the progress faster or slower.
|
||||
|
||||
^ Go up.</value>
|
||||
</data>
|
||||
</root>
|
62
Source/AcbEditor/Properties/Settings.Designer.cs
generated
Normal file
62
Source/AcbEditor/Properties/Settings.Designer.cs
generated
Normal file
@ -0,0 +1,62 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace AcbEditor.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.1.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("True")]
|
||||
public bool EnableThreading {
|
||||
get {
|
||||
return ((bool)(this["EnableThreading"]));
|
||||
}
|
||||
set {
|
||||
this["EnableThreading"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("4")]
|
||||
public int MaxThreads {
|
||||
get {
|
||||
return ((int)(this["MaxThreads"]));
|
||||
}
|
||||
set {
|
||||
this["MaxThreads"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("4096")]
|
||||
public int BufferSize {
|
||||
get {
|
||||
return ((int)(this["BufferSize"]));
|
||||
}
|
||||
set {
|
||||
this["BufferSize"] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
Source/AcbEditor/Properties/Settings.settings
Normal file
15
Source/AcbEditor/Properties/Settings.settings
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="AcbEditor.Properties" GeneratedClassName="Settings">
|
||||
<Profiles />
|
||||
<Settings>
|
||||
<Setting Name="EnableThreading" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">True</Value>
|
||||
</Setting>
|
||||
<Setting Name="MaxThreads" Type="System.Int32" Scope="User">
|
||||
<Value Profile="(Default)">4</Value>
|
||||
</Setting>
|
||||
<Setting Name="BufferSize" Type="System.Int32" Scope="User">
|
||||
<Value Profile="(Default)">4096</Value>
|
||||
</Setting>
|
||||
</Settings>
|
||||
</SettingsFile>
|
@ -1,18 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<configSections>
|
||||
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
|
||||
<section name="CsbBuilder.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
|
||||
</sectionGroup>
|
||||
</configSections>
|
||||
<startup useLegacyV2RuntimeActivationPolicy="true">
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
|
||||
</startup>
|
||||
<userSettings>
|
||||
<CsbBuilder.Properties.Settings>
|
||||
<setting name="NameNodeAfterItsParent" serializeAs="String">
|
||||
<value>False</value>
|
||||
</setting>
|
||||
</CsbBuilder.Properties.Settings>
|
||||
</userSettings>
|
||||
</configuration>
|
@ -224,6 +224,8 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="NAudio.LICENSE.txt" />
|
||||
<None Include="Resources\Redo_grey_16x.png" />
|
||||
<None Include="Resources\Undo_grey_16x.png" />
|
||||
<None Include="Resources\Settings_16x.png" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
|
@ -32,7 +32,7 @@ namespace CsbBuilder
|
||||
private void CopyReport(object sender, EventArgs e)
|
||||
{
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.AppendLine($"Application: {Program.ApplicationVersion}");
|
||||
stringBuilder.AppendLine($"Application: {Program.ApplicationTitle}");
|
||||
stringBuilder.AppendLine($"Exception Message: {exception.Message}");
|
||||
stringBuilder.AppendLine($"Exception Details:");
|
||||
stringBuilder.AppendLine(exception.StackTrace);
|
||||
|
@ -22,6 +22,11 @@ namespace CsbBuilder.Importer
|
||||
{
|
||||
public static void Import(string path, CsbProject project)
|
||||
{
|
||||
var extractor = new DataExtractor();
|
||||
extractor.BufferSize = MainForm.Settings.BufferSize;
|
||||
extractor.EnableThreading = MainForm.Settings.EnableThreading;
|
||||
extractor.MaxThreads = MainForm.Settings.MaxCores;
|
||||
|
||||
// Find the CPK first
|
||||
string cpkPath = Path.ChangeExtension(path, "cpk");
|
||||
bool exists = File.Exists(cpkPath);
|
||||
@ -74,27 +79,27 @@ namespace CsbBuilder.Importer
|
||||
soundElementNode.ChannelCount = soundElementTable.NumberChannels;
|
||||
soundElementNode.SampleRate = soundElementTable.SoundFrequency;
|
||||
soundElementNode.Streaming = soundElementTable.Streaming;
|
||||
soundElementNode.SampleCount = soundElementTable.NumberSamples;
|
||||
|
||||
CriAaxArchive aaxArchive = new CriAaxArchive();
|
||||
|
||||
byte[] aaxData = soundElementTable.Data;
|
||||
|
||||
if (exists && soundElementNode.Streaming)
|
||||
CriCpkEntry cpkEntry = null;
|
||||
if (soundElementNode.Streaming)
|
||||
{
|
||||
using (Stream source = File.OpenRead(cpkPath))
|
||||
using (Stream entrySource = cpkArchive.GetByPath(soundElementTable.Name).Open(source))
|
||||
using (Stream entrySource = (cpkEntry = cpkArchive.GetByPath(soundElementTable.Name)).Open(source))
|
||||
{
|
||||
aaxData = ((Substream)entrySource).ToArray();
|
||||
aaxArchive.Read(entrySource);
|
||||
}
|
||||
}
|
||||
|
||||
aaxArchive.Load(aaxData);
|
||||
else
|
||||
{
|
||||
aaxArchive.Load(soundElementTable.Data);
|
||||
}
|
||||
|
||||
foreach (CriAaxEntry entry in aaxArchive)
|
||||
{
|
||||
byte[] data = new byte[entry.Length];
|
||||
Array.Copy(aaxData, entry.Position, data, 0, data.Length);
|
||||
|
||||
string outputFileName = Path.Combine(project.AudioDirectory.FullName, soundElementTable.Name.Replace('/', '_'));
|
||||
if (entry.Flag == CriAaxEntryFlag.Intro)
|
||||
{
|
||||
@ -108,10 +113,15 @@ namespace CsbBuilder.Importer
|
||||
soundElementNode.Loop = Path.GetFileName(outputFileName);
|
||||
}
|
||||
|
||||
File.WriteAllBytes(outputFileName, data);
|
||||
if (soundElementNode.Streaming)
|
||||
{
|
||||
extractor.Add(cpkPath, outputFileName, cpkEntry.Position + entry.Position, entry.Length);
|
||||
}
|
||||
|
||||
// Read the samples just in case
|
||||
soundElementNode.SampleCount += AdxFileReader.LoadHeader(outputFileName).SampleCount;
|
||||
else
|
||||
{
|
||||
extractor.Add(soundElementTable.Data, outputFileName, entry.Position, entry.Length);
|
||||
}
|
||||
}
|
||||
|
||||
project.SoundElementNodes.Add(soundElementNode);
|
||||
@ -300,6 +310,9 @@ namespace CsbBuilder.Importer
|
||||
synthNode.VoiceLimitGroupReference = synthTable.VoiceLimitGroupName;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract everything
|
||||
extractor.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
14
Source/CsbBuilder/MainForm.Designer.cs
generated
14
Source/CsbBuilder/MainForm.Designer.cs
generated
@ -385,7 +385,7 @@
|
||||
this.aAXToolStripMenuItem});
|
||||
this.toolStripMenuItem49.Image = global::CsbBuilder.Properties.Resources.Sound;
|
||||
this.toolStripMenuItem49.Name = "toolStripMenuItem49";
|
||||
this.toolStripMenuItem49.Size = new System.Drawing.Size(152, 22);
|
||||
this.toolStripMenuItem49.Size = new System.Drawing.Size(138, 22);
|
||||
this.toolStripMenuItem49.Text = "Audio Tools";
|
||||
//
|
||||
// aDXToolStripMenuItem
|
||||
@ -393,7 +393,7 @@
|
||||
this.aDXToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.convertADXsToWAVToolStripMenuItem});
|
||||
this.aDXToolStripMenuItem.Name = "aDXToolStripMenuItem";
|
||||
this.aDXToolStripMenuItem.Size = new System.Drawing.Size(152, 22);
|
||||
this.aDXToolStripMenuItem.Size = new System.Drawing.Size(97, 22);
|
||||
this.aDXToolStripMenuItem.Text = "ADX";
|
||||
//
|
||||
// convertADXsToWAVToolStripMenuItem
|
||||
@ -409,33 +409,33 @@
|
||||
this.extractAAXToFolderToolStripMenuItem,
|
||||
this.packFolderToAAXToolStripMenuItem});
|
||||
this.aAXToolStripMenuItem.Name = "aAXToolStripMenuItem";
|
||||
this.aAXToolStripMenuItem.Size = new System.Drawing.Size(152, 22);
|
||||
this.aAXToolStripMenuItem.Size = new System.Drawing.Size(97, 22);
|
||||
this.aAXToolStripMenuItem.Text = "AAX";
|
||||
//
|
||||
// extractAAXToFolderToolStripMenuItem
|
||||
//
|
||||
this.extractAAXToFolderToolStripMenuItem.Name = "extractAAXToFolderToolStripMenuItem";
|
||||
this.extractAAXToFolderToolStripMenuItem.Size = new System.Drawing.Size(198, 22);
|
||||
this.extractAAXToFolderToolStripMenuItem.Size = new System.Drawing.Size(196, 22);
|
||||
this.extractAAXToFolderToolStripMenuItem.Text = "Extract AAX(s) to folder";
|
||||
this.extractAAXToFolderToolStripMenuItem.Click += new System.EventHandler(this.extractAAXToFolderToolStripMenuItem_Click);
|
||||
//
|
||||
// packFolderToAAXToolStripMenuItem
|
||||
//
|
||||
this.packFolderToAAXToolStripMenuItem.Name = "packFolderToAAXToolStripMenuItem";
|
||||
this.packFolderToAAXToolStripMenuItem.Size = new System.Drawing.Size(198, 22);
|
||||
this.packFolderToAAXToolStripMenuItem.Size = new System.Drawing.Size(196, 22);
|
||||
this.packFolderToAAXToolStripMenuItem.Text = "Pack ADX(s) to AAX";
|
||||
this.packFolderToAAXToolStripMenuItem.Click += new System.EventHandler(this.packFolderToAAXToolStripMenuItem_Click);
|
||||
//
|
||||
// toolStripSeparator26
|
||||
//
|
||||
this.toolStripSeparator26.Name = "toolStripSeparator26";
|
||||
this.toolStripSeparator26.Size = new System.Drawing.Size(149, 6);
|
||||
this.toolStripSeparator26.Size = new System.Drawing.Size(135, 6);
|
||||
//
|
||||
// settingsButton
|
||||
//
|
||||
this.settingsButton.Image = global::CsbBuilder.Properties.Resources.Settings;
|
||||
this.settingsButton.Name = "settingsButton";
|
||||
this.settingsButton.Size = new System.Drawing.Size(152, 22);
|
||||
this.settingsButton.Size = new System.Drawing.Size(138, 22);
|
||||
this.settingsButton.Text = "Settings";
|
||||
this.settingsButton.Click += new System.EventHandler(this.OpenSettings);
|
||||
//
|
||||
|
@ -100,7 +100,7 @@ namespace CsbBuilder
|
||||
{
|
||||
project = null;
|
||||
ClearTreeViews();
|
||||
Text = "CSB Builder";
|
||||
Text = Program.ApplicationTitle;
|
||||
saved = true;
|
||||
}
|
||||
|
||||
@ -1918,7 +1918,7 @@ namespace CsbBuilder
|
||||
|
||||
private void ShowAbout(object sender, EventArgs e)
|
||||
{
|
||||
MessageBox.Show($"{Program.ApplicationVersion} by Skyth (blueskythlikesclouds)", "CSB Builder");
|
||||
MessageBox.Show($"{Program.ApplicationTitle} by Skyth (blueskythlikesclouds)", "CSB Builder");
|
||||
}
|
||||
|
||||
private void CreateChildTrackNode(object sender, EventArgs e)
|
||||
|
@ -22,12 +22,12 @@ namespace CsbBuilder
|
||||
}
|
||||
}
|
||||
|
||||
public static string ApplicationVersion
|
||||
public static string ApplicationTitle
|
||||
{
|
||||
get
|
||||
{
|
||||
AssemblyName assemblyName = Assembly.GetEntryAssembly().GetName();
|
||||
return $"{assemblyName.Name} (Version {assemblyName.Version.ToString()})";
|
||||
return $"CSB Builder {assemblyName.Version.ToString()}";
|
||||
}
|
||||
}
|
||||
|
||||
|
20
Source/CsbBuilder/Properties/Resources.Designer.cs
generated
20
Source/CsbBuilder/Properties/Resources.Designer.cs
generated
@ -200,6 +200,16 @@ namespace CsbBuilder.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap Redo {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("Redo", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
@ -289,5 +299,15 @@ namespace CsbBuilder.Properties {
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap Undo {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("Undo", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -187,4 +187,10 @@
|
||||
<data name="Settings" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\Settings_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="Redo" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\Redo_grey_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="Undo" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\Undo_grey_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
</root>
|
BIN
Source/CsbBuilder/Resources/Redo_grey_16x.png
Normal file
BIN
Source/CsbBuilder/Resources/Redo_grey_16x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 416 B |
BIN
Source/CsbBuilder/Resources/Undo_grey_16x.png
Normal file
BIN
Source/CsbBuilder/Resources/Undo_grey_16x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 334 B |
@ -42,14 +42,22 @@ namespace CsbBuilder.Project
|
||||
[DisplayName("Rename Sound node to referenced Sound Element node"), Category("Application")]
|
||||
public bool RenameToSoundElement { get; set; }
|
||||
|
||||
[DisplayName("Enable threading"), Category("Stream")]
|
||||
[Description("Determines whether to use threads to extract data from CSB/CPK files during importing. It may make the importing faster or slower.")]
|
||||
public bool EnableThreading { get; set; }
|
||||
|
||||
[DisplayName("Maximum amount of cores"), Category("Stream")]
|
||||
[Description("Maximum amount of threads used to extract data from CSB/CPK files during importing.")]
|
||||
public int MaxCores { get; set; }
|
||||
|
||||
public static Settings Load()
|
||||
{
|
||||
string path = Path.ChangeExtension(Application.ExecutablePath, "xml");
|
||||
|
||||
Settings settings = null;
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
XmlSerializer serializer = new XmlSerializer(typeof(Settings));
|
||||
|
||||
using (Stream source = File.OpenRead(path))
|
||||
@ -90,6 +98,8 @@ namespace CsbBuilder.Project
|
||||
ProjectsName = "New CSB Project";
|
||||
ImportedCsbProjectDirectory = ProjectDirectory.DirectoryOfCsb;
|
||||
RenameToSoundElement = true;
|
||||
EnableThreading = true;
|
||||
MaxCores = 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
|
||||
</startup>
|
||||
<configSections>
|
||||
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
|
||||
<section name="CsbEditor.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
|
||||
</sectionGroup>
|
||||
</configSections>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
|
||||
</startup>
|
||||
<userSettings>
|
||||
<CsbEditor.Properties.Settings>
|
||||
<setting name="EnableThreading" serializeAs="String">
|
||||
<value>True</value>
|
||||
</setting>
|
||||
<setting name="MaxThreads" serializeAs="String">
|
||||
<value>4</value>
|
||||
</setting>
|
||||
<setting name="BufferSize" serializeAs="String">
|
||||
<value>4096</value>
|
||||
</setting>
|
||||
</CsbEditor.Properties.Settings>
|
||||
</userSettings>
|
||||
</configuration>
|
@ -51,9 +51,19 @@
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Properties\Settings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
<None Include="Properties\Settings.settings">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SonicAudioLib\SonicAudioLib.csproj">
|
||||
|
@ -4,6 +4,9 @@ using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using CsbEditor.Properties;
|
||||
|
||||
using SonicAudioLib;
|
||||
using SonicAudioLib.CriMw;
|
||||
using SonicAudioLib.IO;
|
||||
using SonicAudioLib.Archive;
|
||||
@ -18,7 +21,7 @@ namespace CsbEditor
|
||||
{
|
||||
if (args.Length < 1)
|
||||
{
|
||||
Console.WriteLine(Properties.Resources.Description);
|
||||
Console.WriteLine(Resources.Description);
|
||||
Console.ReadLine();
|
||||
return;
|
||||
}
|
||||
@ -29,6 +32,13 @@ namespace CsbEditor
|
||||
#endif
|
||||
if (args[0].EndsWith(".csb"))
|
||||
{
|
||||
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 outputDirectoryName = Path.Combine(baseDirectory, Path.GetFileNameWithoutExtension(args[0]));
|
||||
|
||||
@ -42,6 +52,7 @@ namespace CsbEditor
|
||||
{
|
||||
if (reader.GetString("name") == "SOUND_ELEMENT")
|
||||
{
|
||||
long tablePosition = reader.GetPosition("utf");
|
||||
using (CriTableReader sdlReader = CriTableReader.Create(reader.GetSubstream("utf")))
|
||||
{
|
||||
while (sdlReader.Read())
|
||||
@ -60,15 +71,13 @@ namespace CsbEditor
|
||||
else if (streaming && found && cpkArchive == null)
|
||||
{
|
||||
cpkArchive = new CriCpkArchive();
|
||||
cpkArchive.Load(cpkPath);
|
||||
cpkArchive.Load(cpkPath, Settings.Default.BufferSize);
|
||||
}
|
||||
|
||||
string sdlName = sdlReader.GetString("name");
|
||||
DirectoryInfo destinationPath = new DirectoryInfo(Path.Combine(outputDirectoryName, sdlName));
|
||||
destinationPath.Create();
|
||||
|
||||
Console.WriteLine("Extracting {0}...", sdlName);
|
||||
|
||||
CriAaxArchive aaxArchive = new CriAaxArchive();
|
||||
|
||||
if (streaming)
|
||||
@ -84,12 +93,10 @@ namespace CsbEditor
|
||||
|
||||
foreach (CriAaxEntry entry in aaxArchive)
|
||||
{
|
||||
using (Stream destination = File.Create(Path.Combine(destinationPath.FullName,
|
||||
entry.Flag == CriAaxEntryFlag.Intro ? "Intro.adx" : "Loop.adx")))
|
||||
using (Stream entrySource = entry.Open(aaxSource))
|
||||
{
|
||||
entrySource.CopyTo(destination);
|
||||
}
|
||||
extractor.Add(cpkPath,
|
||||
Path.Combine(destinationPath.FullName,
|
||||
entry.Flag == CriAaxEntryFlag.Intro ? "Intro.adx" : "Loop.adx"),
|
||||
cpkEntry.Position + entry.Position, entry.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -97,18 +104,17 @@ namespace CsbEditor
|
||||
|
||||
else
|
||||
{
|
||||
long aaxPosition = sdlReader.GetPosition("data");
|
||||
using (Stream aaxSource = sdlReader.GetSubstream("data"))
|
||||
{
|
||||
aaxArchive.Read(aaxSource);
|
||||
|
||||
foreach (CriAaxEntry entry in aaxArchive)
|
||||
{
|
||||
using (Stream destination = File.Create(Path.Combine(destinationPath.FullName,
|
||||
entry.Flag == CriAaxEntryFlag.Intro ? "Intro.adx" : "Loop.adx")))
|
||||
using (Stream entrySource = entry.Open(aaxSource))
|
||||
{
|
||||
entrySource.CopyTo(destination);
|
||||
}
|
||||
extractor.Add(args[0],
|
||||
Path.Combine(destinationPath.FullName,
|
||||
entry.Flag == CriAaxEntryFlag.Intro ? "Intro.adx" : "Loop.adx"),
|
||||
tablePosition + aaxPosition + entry.Position, entry.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -119,6 +125,8 @@ namespace CsbEditor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extractor.Run();
|
||||
}
|
||||
|
||||
else if (File.GetAttributes(args[0]).HasFlag(FileAttributes.Directory))
|
||||
@ -132,9 +140,10 @@ namespace CsbEditor
|
||||
}
|
||||
|
||||
CriCpkArchive cpkArchive = new CriCpkArchive();
|
||||
cpkArchive.ProgressChanged += OnProgressChanged;
|
||||
|
||||
CriTable csbFile = new CriTable();
|
||||
csbFile.Load(csbPath);
|
||||
csbFile.Load(csbPath, Settings.Default.BufferSize);
|
||||
|
||||
CriRow soundElementRow = csbFile.Rows.First(row => (string)row["name"] == "SOUND_ELEMENT");
|
||||
|
||||
@ -158,8 +167,6 @@ namespace CsbEditor
|
||||
uint sampleRate = (uint)sdlRow["sfreq"];
|
||||
byte numberChannels = (byte)sdlRow["nch"];
|
||||
|
||||
Console.WriteLine("Adding {0}...", sdlName);
|
||||
|
||||
CriAaxArchive aaxArchive = new CriAaxArchive();
|
||||
foreach (FileInfo file in sdlDirectory.GetFiles("*.adx"))
|
||||
{
|
||||
@ -193,7 +200,7 @@ namespace CsbEditor
|
||||
junks.Add(entry.FilePath);
|
||||
|
||||
cpkArchive.Add(entry);
|
||||
aaxArchive.Save(entry.FilePath.FullName);
|
||||
aaxArchive.Save(entry.FilePath.FullName, Settings.Default.BufferSize);
|
||||
}
|
||||
|
||||
else
|
||||
@ -209,11 +216,11 @@ namespace CsbEditor
|
||||
soundElementRow["utf"] = soundElementTable.Save();
|
||||
|
||||
csbFile.WriterSettings = CriTableWriterSettings.AdxSettings;
|
||||
csbFile.Save(args[0] + ".csb");
|
||||
csbFile.Save(args[0] + ".csb", Settings.Default.BufferSize);
|
||||
|
||||
if (cpkArchive.Count > 0)
|
||||
{
|
||||
cpkArchive.Save(args[0] + ".cpk");
|
||||
cpkArchive.Save(args[0] + ".cpk", Settings.Default.BufferSize);
|
||||
}
|
||||
|
||||
foreach (FileInfo junk in junks)
|
||||
@ -240,5 +247,18 @@ namespace CsbEditor
|
||||
sampleRate = EndianStream.ReadUInt32BE(source);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -139,6 +139,19 @@ rate or channel count than original.
|
||||
To pack the .CSB file back, you gotta have the extracted directory
|
||||
and the .CSB file in the same directory. Drag and drop the directory
|
||||
to the .EXE file, it will collect all the files inside directory
|
||||
and pack them back.</value>
|
||||
and pack them back.
|
||||
|
||||
Settings (.exe.config)
|
||||
=======================
|
||||
BufferSize: Buffer size used to load/save data from files. Higher
|
||||
values may make the progress faster or slower.
|
||||
|
||||
EnableThreading: Determines whether to use multiple threads to
|
||||
extract data from files.
|
||||
|
||||
MaxThreads: Maximum amount of threads to extract data from files.
|
||||
Higher values may make the progress faster or slower.
|
||||
|
||||
^ Go up.</value>
|
||||
</data>
|
||||
</root>
|
62
Source/CsbEditor/Properties/Settings.Designer.cs
generated
Normal file
62
Source/CsbEditor/Properties/Settings.Designer.cs
generated
Normal file
@ -0,0 +1,62 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace CsbEditor.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.1.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("True")]
|
||||
public bool EnableThreading {
|
||||
get {
|
||||
return ((bool)(this["EnableThreading"]));
|
||||
}
|
||||
set {
|
||||
this["EnableThreading"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("4")]
|
||||
public int MaxThreads {
|
||||
get {
|
||||
return ((int)(this["MaxThreads"]));
|
||||
}
|
||||
set {
|
||||
this["MaxThreads"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("4096")]
|
||||
public int BufferSize {
|
||||
get {
|
||||
return ((int)(this["BufferSize"]));
|
||||
}
|
||||
set {
|
||||
this["BufferSize"] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
Source/CsbEditor/Properties/Settings.settings
Normal file
15
Source/CsbEditor/Properties/Settings.settings
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="CsbEditor.Properties" GeneratedClassName="Settings">
|
||||
<Profiles />
|
||||
<Settings>
|
||||
<Setting Name="EnableThreading" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">True</Value>
|
||||
</Setting>
|
||||
<Setting Name="MaxThreads" Type="System.Int32" Scope="User">
|
||||
<Value Profile="(Default)">4</Value>
|
||||
</Setting>
|
||||
<Setting Name="BufferSize" Type="System.Int32" Scope="User">
|
||||
<Value Profile="(Default)">4096</Value>
|
||||
</Setting>
|
||||
</Settings>
|
||||
</SettingsFile>
|
@ -1,16 +1,16 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.ComponentModel;
|
||||
using System.Collections;
|
||||
|
||||
using SonicAudioLib;
|
||||
using SonicAudioLib.Archive;
|
||||
using SonicAudioLib.IO;
|
||||
using SonicAudioLib.CriMw;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using System.Xml;
|
||||
|
||||
@ -22,19 +22,26 @@ namespace SonicAudioCmd
|
||||
{
|
||||
try
|
||||
{
|
||||
var extractor = new DataExtractor();
|
||||
extractor.ProgressChanged += OnProgressChanged;
|
||||
|
||||
CriCpkArchive archive = new CriCpkArchive();
|
||||
archive.Load(args[0]);
|
||||
|
||||
using (Stream source = File.OpenRead(args[0]))
|
||||
foreach (CriCpkEntry entry in archive)
|
||||
{
|
||||
foreach (CriCpkEntry entry in archive)
|
||||
{
|
||||
using (Stream destination = File.Create(entry.Name))
|
||||
{
|
||||
EndianStream.CopyPartTo(source, destination, entry.Position, entry.Length, ushort.MaxValue);
|
||||
}
|
||||
}
|
||||
extractor.Add(args[0], Path.Combine(Path.ChangeExtension(args[0], null), entry.DirectoryName, entry.Name), entry.Position, entry.Length, entry.IsCompressed);
|
||||
}
|
||||
|
||||
|
||||
DateTime dateTime = DateTime.Now;
|
||||
extractor.Run();
|
||||
|
||||
Console.WriteLine("Elapsed time: {0}", DateTime.Now - dateTime);
|
||||
Console.ReadLine();
|
||||
|
||||
/*archive.EnableMask = true;
|
||||
archive.Save("test.cpk");*/
|
||||
}
|
||||
|
||||
catch (Exception exception)
|
||||
@ -43,5 +50,18 @@ namespace SonicAudioCmd
|
||||
Console.ReadLine();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,8 @@ namespace SonicAudioLib.Archive
|
||||
{
|
||||
protected List<T> entries = new List<T>();
|
||||
|
||||
public virtual event ProgressChanged ProgressChanged;
|
||||
|
||||
public virtual T this[int index]
|
||||
{
|
||||
get
|
||||
@ -108,6 +110,11 @@ namespace SonicAudioLib.Archive
|
||||
return entries.GetEnumerator();
|
||||
}
|
||||
|
||||
protected void OnProgressChanged(object sender, ProgressChangedEventArgs e)
|
||||
{
|
||||
ProgressChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public void Print()
|
||||
{
|
||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Diagnostics;
|
||||
|
||||
using SonicAudioLib.IO;
|
||||
using SonicAudioLib.Module;
|
||||
@ -11,17 +12,39 @@ namespace SonicAudioLib.Archive
|
||||
{
|
||||
public class CriAfs2Entry : EntryBase
|
||||
{
|
||||
public ushort Id { get; set; }
|
||||
public uint Id { get; set; }
|
||||
}
|
||||
|
||||
public class CriAfs2Archive : ArchiveBase<CriAfs2Entry>
|
||||
{
|
||||
public uint Align { get; set; }
|
||||
public uint IdFieldLength { get; set; }
|
||||
public uint PositionFieldLength { get; set; }
|
||||
|
||||
/// <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; }
|
||||
|
||||
public override void Read(Stream source)
|
||||
{
|
||||
long ReadByLength(uint length)
|
||||
{
|
||||
switch (length)
|
||||
{
|
||||
case 2:
|
||||
return EndianStream.ReadUInt16(source);
|
||||
|
||||
case 4:
|
||||
return EndianStream.ReadUInt32(source);
|
||||
|
||||
case 8:
|
||||
return EndianStream.ReadInt64(source);
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unimplemented field length ({length})", nameof(length));
|
||||
}
|
||||
}
|
||||
|
||||
if (EndianStream.ReadCString(source, 4) != "AFS2")
|
||||
{
|
||||
throw new InvalidDataException("'AFS2' signature could not be found.");
|
||||
@ -35,10 +58,10 @@ namespace SonicAudioLib.Archive
|
||||
throw new InvalidDataException($"Unknown AFS2 type ({type}). Please report this error with the file(s).");
|
||||
}
|
||||
|
||||
IdFieldLength = (information >> 16) & 0xFF;
|
||||
PositionFieldLength = (information >> 8) & 0xFF;
|
||||
uint idFieldLength = (information >> 16) & 0xFF;
|
||||
uint positionFieldLength = (information >> 8) & 0xFF;
|
||||
|
||||
ushort entryCount = (ushort)EndianStream.ReadUInt32(source);
|
||||
uint entryCount = EndianStream.ReadUInt32(source);
|
||||
Align = EndianStream.ReadUInt32(source);
|
||||
|
||||
CriAfs2Entry previousEntry = null;
|
||||
@ -46,35 +69,13 @@ namespace SonicAudioLib.Archive
|
||||
{
|
||||
CriAfs2Entry afs2Entry = new CriAfs2Entry();
|
||||
|
||||
long idPosition = 16 + (i * IdFieldLength);
|
||||
long idPosition = 16 + (i * idFieldLength);
|
||||
source.Seek(idPosition, SeekOrigin.Begin);
|
||||
afs2Entry.Id = (uint)ReadByLength(idFieldLength);
|
||||
|
||||
switch (IdFieldLength)
|
||||
{
|
||||
case 2:
|
||||
afs2Entry.Id = EndianStream.ReadUInt16(source);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidDataException($"Unknown IdFieldLength ({IdFieldLength}). Please report this error with the file(s).");
|
||||
}
|
||||
|
||||
long positionPosition = 16 + (entryCount * IdFieldLength) + (i * PositionFieldLength);
|
||||
long positionPosition = 16 + (entryCount * idFieldLength) + (i * positionFieldLength);
|
||||
source.Seek(positionPosition, SeekOrigin.Begin);
|
||||
|
||||
switch (PositionFieldLength)
|
||||
{
|
||||
case 2:
|
||||
afs2Entry.Position = EndianStream.ReadUInt16(source);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
afs2Entry.Position = EndianStream.ReadUInt32(source);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidDataException($"Unknown PositionFieldLength ({PositionFieldLength}). Please report this error with the file(s).");
|
||||
}
|
||||
afs2Entry.Position = ReadByLength(positionFieldLength);
|
||||
|
||||
if (previousEntry != null)
|
||||
{
|
||||
@ -85,16 +86,7 @@ namespace SonicAudioLib.Archive
|
||||
|
||||
if (i == entryCount - 1)
|
||||
{
|
||||
switch (PositionFieldLength)
|
||||
{
|
||||
case 2:
|
||||
afs2Entry.Length = EndianStream.ReadUInt16(source) - afs2Entry.Position;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
afs2Entry.Length = EndianStream.ReadUInt32(source) - afs2Entry.Position;
|
||||
break;
|
||||
}
|
||||
afs2Entry.Length = ReadByLength(positionFieldLength) - afs2Entry.Position;
|
||||
}
|
||||
|
||||
entries.Add(afs2Entry);
|
||||
@ -103,68 +95,108 @@ namespace SonicAudioLib.Archive
|
||||
}
|
||||
|
||||
public override void Write(Stream destination)
|
||||
{
|
||||
uint headerLength = (uint)(16 + (entries.Count * IdFieldLength) + (entries.Count * PositionFieldLength) + PositionFieldLength);
|
||||
{
|
||||
long GetHeaderLength(uint idFieldLen, uint positionFieldLen)
|
||||
{
|
||||
return 16 + (idFieldLen * entries.Count) + (positionFieldLen * entries.Count) + positionFieldLen;
|
||||
}
|
||||
|
||||
EndianStream.WriteCString(destination, "AFS2", 4);
|
||||
EndianStream.WriteUInt32(destination, 1 | (IdFieldLength << 16) | (PositionFieldLength << 8));
|
||||
EndianStream.WriteUInt32(destination, (ushort)entries.Count);
|
||||
EndianStream.WriteUInt32(destination, Align);
|
||||
long Calculate(out uint idFieldLen, out uint positionFieldLen)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
dataLength = Helpers.Align(dataLength, Align);
|
||||
dataLength += entry.Length;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return headerLen;
|
||||
}
|
||||
|
||||
var mDestination = new MemoryStream();
|
||||
|
||||
void WriteByLength(uint length, long value)
|
||||
{
|
||||
switch (length)
|
||||
{
|
||||
case 2:
|
||||
EndianStream.WriteUInt16(mDestination, (ushort)value);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
EndianStream.WriteUInt32(mDestination, (uint)value);
|
||||
break;
|
||||
|
||||
case 8:
|
||||
EndianStream.WriteInt64(mDestination, value);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unimplemented field length ({length})", nameof(length));
|
||||
}
|
||||
}
|
||||
|
||||
long headerLength = Calculate(out uint idFieldLength, out uint positionFieldLength);
|
||||
|
||||
EndianStream.WriteCString(mDestination, "AFS2", 4);
|
||||
EndianStream.WriteUInt32(mDestination, 1 | (idFieldLength << 16) | (positionFieldLength << 8));
|
||||
EndianStream.WriteUInt32(mDestination, (uint)entries.Count);
|
||||
EndianStream.WriteUInt32(mDestination, Align);
|
||||
|
||||
VldPool vldPool = new VldPool(Align, headerLength);
|
||||
vldPool.ProgressChanged += OnProgressChanged;
|
||||
|
||||
var orderedEntries = entries.OrderBy(entry => entry.Id);
|
||||
foreach (CriAfs2Entry afs2Entry in orderedEntries)
|
||||
{
|
||||
switch (IdFieldLength)
|
||||
{
|
||||
case 2:
|
||||
EndianStream.WriteUInt16(destination, (ushort)afs2Entry.Id);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Unimplemented IdFieldLength ({IdFieldLength}). Implemented length: (2)");
|
||||
}
|
||||
WriteByLength(idFieldLength, afs2Entry.Id);
|
||||
}
|
||||
|
||||
foreach (CriAfs2Entry afs2Entry in orderedEntries)
|
||||
{
|
||||
uint entryPosition = (uint)vldPool.Length;
|
||||
long entryPosition = vldPool.Length;
|
||||
vldPool.Put(afs2Entry.FilePath);
|
||||
|
||||
switch (PositionFieldLength)
|
||||
{
|
||||
case 2:
|
||||
EndianStream.WriteUInt16(destination, (ushort)entryPosition);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
EndianStream.WriteUInt32(destination, entryPosition);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidDataException($"Unimplemented PositionFieldLength ({PositionFieldLength}). Implemented lengths: (2, 4)");
|
||||
}
|
||||
|
||||
afs2Entry.Position = entryPosition;
|
||||
WriteByLength(positionFieldLength, entryPosition);
|
||||
}
|
||||
|
||||
EndianStream.WriteUInt32(destination, (uint)vldPool.Length);
|
||||
WriteByLength(positionFieldLength, vldPool.Length);
|
||||
|
||||
// Copy the header to Header property and save it to destination
|
||||
Header = mDestination.ToArray();
|
||||
mDestination.Close();
|
||||
|
||||
destination.Write(Header, 0, Header.Length);
|
||||
vldPool.Write(destination);
|
||||
vldPool.Clear();
|
||||
}
|
||||
|
||||
public CriAfs2Entry GetById(uint cueIndex)
|
||||
public CriAfs2Entry GetById(uint id)
|
||||
{
|
||||
return entries.FirstOrDefault(e => (e.Id == cueIndex));
|
||||
return entries.FirstOrDefault(e => (e.Id == id));
|
||||
}
|
||||
|
||||
public CriAfs2Archive()
|
||||
{
|
||||
Align = 32;
|
||||
IdFieldLength = 2;
|
||||
PositionFieldLength = 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ namespace SonicAudioLib.Archive
|
||||
|
||||
public override void Read(Stream source)
|
||||
{
|
||||
using (CriTableReader reader = CriCpkSection.Open(source, source.Position))
|
||||
using (CriTableReader reader = CriCpkSection.Open(source, source.Position, "CPK "))
|
||||
{
|
||||
reader.Read();
|
||||
|
||||
@ -156,8 +156,8 @@ namespace SonicAudioLib.Archive
|
||||
|
||||
if (mode == CriCpkMode.FileName || mode == CriCpkMode.FileNameAndId)
|
||||
{
|
||||
using (CriTableReader tocReader = CriCpkSection.Open(source, tocPosition))
|
||||
using (CriTableReader etocReader = CriCpkSection.Open(source, etocPosition))
|
||||
using (CriTableReader tocReader = CriCpkSection.Open(source, tocPosition, "TOC "))
|
||||
using (CriTableReader etocReader = CriCpkSection.Open(source, etocPosition, "ETOC"))
|
||||
{
|
||||
while (tocReader.Read())
|
||||
{
|
||||
@ -191,7 +191,7 @@ namespace SonicAudioLib.Archive
|
||||
|
||||
if (mode == CriCpkMode.FileNameAndId && isLatestVersion)
|
||||
{
|
||||
using (CriTableReader itocReader = CriCpkSection.Open(source, itocPosition))
|
||||
using (CriTableReader itocReader = CriCpkSection.Open(source, itocPosition, "ITOC"))
|
||||
{
|
||||
while (itocReader.Read())
|
||||
{
|
||||
@ -203,7 +203,7 @@ namespace SonicAudioLib.Archive
|
||||
|
||||
else if (mode == CriCpkMode.Id)
|
||||
{
|
||||
using (CriTableReader itocReader = CriCpkSection.Open(source, itocPosition))
|
||||
using (CriTableReader itocReader = CriCpkSection.Open(source, itocPosition, "ITOC"))
|
||||
{
|
||||
while (itocReader.Read())
|
||||
{
|
||||
@ -269,6 +269,7 @@ namespace SonicAudioLib.Archive
|
||||
}
|
||||
|
||||
VldPool vldPool = new VldPool(Align, 2048);
|
||||
vldPool.ProgressChanged += OnProgressChanged;
|
||||
|
||||
using (CriCpkSection cpkSection = new CriCpkSection(destination, "CPK ", enableMask))
|
||||
{
|
||||
@ -391,7 +392,7 @@ namespace SonicAudioLib.Archive
|
||||
|
||||
etocSection.Writer.WriteRow(true,
|
||||
CpkDateTimeFromDateTime(entry.UpdateDateTime),
|
||||
entry.FilePath.DirectoryName.Replace('\\', '/'));
|
||||
entry.DirectoryName);
|
||||
|
||||
vldPool.Put(entry.FilePath);
|
||||
}
|
||||
@ -586,27 +587,121 @@ namespace SonicAudioLib.Archive
|
||||
});
|
||||
}
|
||||
|
||||
private DateTime DateTimeFromCpkDateTime(ulong dateTime)
|
||||
public static void Decompress(Stream source, long position, Stream destination)
|
||||
{
|
||||
if (dateTime == 0)
|
||||
source.Position = position;
|
||||
|
||||
if (EndianStream.ReadCString(source, 8) != "CRILAYLA")
|
||||
{
|
||||
return new DateTime();
|
||||
throw new InvalidDataException("'CRILAYLA' signature could not be found.");
|
||||
}
|
||||
|
||||
return new DateTime(
|
||||
(int)(uint)(dateTime >> 32) >> 16, // year
|
||||
(int)(uint)(dateTime >> 32) >> 8 & byte.MaxValue, // month
|
||||
(int)(uint)(dateTime >> 32) & byte.MaxValue, // day
|
||||
(int)(uint)dateTime >> 24, // hour
|
||||
(int)(uint)dateTime >> 16 & byte.MaxValue, // minute
|
||||
(int)(uint)dateTime >> 8 & byte.MaxValue // second
|
||||
);
|
||||
uint uncompressedLength = EndianStream.ReadUInt32(source);
|
||||
uint compressedLength = EndianStream.ReadUInt32(source);
|
||||
|
||||
// Copy the uncompressed header to destination
|
||||
EndianStream.CopyPartTo(source, destination, position + 0x10 + compressedLength, 0x100, 4096);
|
||||
|
||||
// Bit methods
|
||||
int bitCount = 0;
|
||||
int bitData = 0;
|
||||
|
||||
int ReadBits(int count)
|
||||
{
|
||||
if (bitCount < count)
|
||||
{
|
||||
int neededBytes = ((24 - bitCount) >> 3) + 1;
|
||||
bitCount += neededBytes * 8;
|
||||
|
||||
while (neededBytes > 0)
|
||||
{
|
||||
bitData = (bitData << 8) | source.ReadByte();
|
||||
neededBytes--;
|
||||
source.Position -= 2;
|
||||
}
|
||||
}
|
||||
|
||||
bitCount -= count;
|
||||
return (bitData >> bitCount) & ((1 << count) - 1);
|
||||
}
|
||||
|
||||
// Deflate levels
|
||||
IEnumerable<int> GetDeflateLevels()
|
||||
{
|
||||
yield return 2;
|
||||
yield return 3;
|
||||
yield return 5;
|
||||
|
||||
while (true)
|
||||
{
|
||||
yield return 8;
|
||||
}
|
||||
}
|
||||
|
||||
// Decompression
|
||||
source.Position = position + 0x10 + compressedLength - 1;
|
||||
|
||||
long writtenBytes = 0;
|
||||
byte[] buffer = new byte[uncompressedLength];
|
||||
|
||||
while (writtenBytes < uncompressedLength)
|
||||
{
|
||||
// Verbatim byte, directly copy
|
||||
if (ReadBits(1) == 0)
|
||||
{
|
||||
buffer[uncompressedLength - (writtenBytes++) - 1] = (byte)ReadBits(8);
|
||||
}
|
||||
|
||||
// Referenced bytes
|
||||
else
|
||||
{
|
||||
long pos = uncompressedLength - 1 - writtenBytes + ReadBits(13) + 3;
|
||||
long length = 3;
|
||||
|
||||
foreach (var deflateLevel in GetDeflateLevels())
|
||||
{
|
||||
long len = ReadBits(deflateLevel);
|
||||
length += len;
|
||||
|
||||
if (len != ((1 << deflateLevel) - 1))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (length > 0)
|
||||
{
|
||||
buffer[uncompressedLength - 1 - (writtenBytes++)] = buffer[pos--];
|
||||
length--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
destination.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
private DateTime DateTimeFromCpkDateTime(ulong dateTime)
|
||||
{
|
||||
return dateTime > 0 ?
|
||||
new DateTime(
|
||||
(int)(dateTime >> 48), // Year (ushort)
|
||||
(int)(dateTime >> 40) & 0xFF, // Month (byte)
|
||||
(int)(dateTime >> 32) & 0xFF, // Day (byte)
|
||||
(int)(dateTime >> 24) & 0xFF, // Hour (byte)
|
||||
(int)(dateTime >> 16) & 0xFF, // Minute (byte)
|
||||
(int)(dateTime >> 8) & 0xFF) // Second (byte)
|
||||
: new DateTime();
|
||||
}
|
||||
|
||||
private ulong CpkDateTimeFromDateTime(DateTime dateTime)
|
||||
{
|
||||
return ((((ulong)dateTime.Year * 0x100 + (uint)dateTime.Month) * 0x100 + (uint)dateTime.Day) * 0x100000000) +
|
||||
((((ulong)dateTime.Hour * 0x100 + (uint)dateTime.Minute) * 0x100 + (uint)dateTime.Second) * 0x100);
|
||||
return
|
||||
(ulong)dateTime.Year << 48 |
|
||||
(ulong)dateTime.Month << 40 |
|
||||
(ulong)dateTime.Day << 32 |
|
||||
(ulong)dateTime.Hour << 24 |
|
||||
(ulong)dateTime.Minute << 16 |
|
||||
(ulong)dateTime.Second << 8;
|
||||
}
|
||||
|
||||
private class CriCpkSection : IDisposable
|
||||
@ -637,16 +732,19 @@ namespace SonicAudioLib.Archive
|
||||
destination.Seek(position, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
public static CriTableReader Open(Stream source, long position)
|
||||
public static CriTableReader Open(Stream source, long position, string expectedSignature)
|
||||
{
|
||||
source.Seek(position, SeekOrigin.Begin);
|
||||
|
||||
string signature = EndianStream.ReadCString(source, 4);
|
||||
if (EndianStream.ReadCString(source, 4) != expectedSignature)
|
||||
{
|
||||
throw new InvalidDataException($"'{expectedSignature}' signature could not be found.");
|
||||
}
|
||||
|
||||
uint flag = EndianStream.ReadUInt32(source);
|
||||
uint tableLength = EndianStream.ReadUInt32(source);
|
||||
uint unknown = EndianStream.ReadUInt32(source);
|
||||
|
||||
|
||||
return CriTableReader.Create(new Substream(source, source.Position, tableLength));
|
||||
}
|
||||
|
||||
@ -656,10 +754,10 @@ namespace SonicAudioLib.Archive
|
||||
headerPosition = destination.Position;
|
||||
|
||||
EndianStream.WriteCString(destination, signature, 4);
|
||||
EndianStream.WriteUInt32(destination, byte.MaxValue);
|
||||
EndianStream.WriteUInt32(destination, 0xFF);
|
||||
destination.Seek(8, SeekOrigin.Current);
|
||||
|
||||
writer = CriTableWriter.Create(destination, new CriTableWriterSettings() { LeaveOpen = true, EnableMask = enableMask });
|
||||
writer = CriTableWriter.Create(destination, new CriTableWriterSettings() { LeaveOpen = true, EnableMask = enableMask, MaskXor = 25951, MaskXorMultiplier = 16661 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SonicAudioLib.CriMw
|
||||
{
|
||||
@ -52,4 +53,81 @@ namespace SonicAudioLib.CriMw
|
||||
public uint Length;
|
||||
public object Value;
|
||||
}
|
||||
|
||||
static class CriTableMasker
|
||||
{
|
||||
public static void FindKeys(byte[] signature, out uint xor, out uint xorMultiplier)
|
||||
{
|
||||
for (byte x = 0; x <= byte.MaxValue; x++)
|
||||
{
|
||||
// Find XOR using first byte
|
||||
if ((signature[0] ^ x) == CriTableHeader.Signature[0])
|
||||
{
|
||||
// Matched the first byte, try finding the multiplier with the second byte
|
||||
for (byte m = 0; m <= byte.MaxValue; m++)
|
||||
{
|
||||
// Matched the second byte, now make sure the other bytes match as well
|
||||
if ((signature[1] ^ (byte)(x * m)) == CriTableHeader.Signature[1])
|
||||
{
|
||||
byte _x = (byte)(x * m);
|
||||
|
||||
bool allMatches = true;
|
||||
for (int i = 2; i < 4; i++)
|
||||
{
|
||||
_x *= m;
|
||||
|
||||
if ((signature[i] ^ _x) != CriTableHeader.Signature[i])
|
||||
{
|
||||
allMatches = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// All matches, return the xor and multiplier
|
||||
if (allMatches)
|
||||
{
|
||||
xor = x;
|
||||
xorMultiplier = m;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidDataException("'@UTF' signature could not be found.");
|
||||
}
|
||||
|
||||
public static void Mask(Stream source, Stream destination, long length, uint xor, uint xorMultiplier)
|
||||
{
|
||||
uint currentXor = xor;
|
||||
long currentPosition = source.Position;
|
||||
|
||||
while (source.Position < currentPosition + length)
|
||||
{
|
||||
byte maskedByte = (byte)(source.ReadByte() ^ currentXor);
|
||||
currentXor *= xorMultiplier;
|
||||
|
||||
destination.WriteByte(maskedByte);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Mask(Stream source, long length, uint xor, uint xorMultiplier)
|
||||
{
|
||||
if (source.CanRead && source.CanWrite)
|
||||
{
|
||||
uint currentXor = xor;
|
||||
long currentPosition = source.Position;
|
||||
|
||||
while (source.Position < currentPosition + length)
|
||||
{
|
||||
byte maskedByte = (byte)(source.ReadByte() ^ currentXor);
|
||||
currentXor *= xorMultiplier;
|
||||
|
||||
source.Position--;
|
||||
source.WriteByte(maskedByte);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,19 +86,13 @@ namespace SonicAudioLib.CriMw
|
||||
|
||||
if (EndianStream.ReadCString(source, 4) != CriTableHeader.Signature)
|
||||
{
|
||||
// try to decrypt (currently only for CPK files since those are the only examples I have)
|
||||
source.Seek(headerPosition, SeekOrigin.Begin);
|
||||
|
||||
MemoryStream unmaskedSource = new MemoryStream();
|
||||
Helpers.MaskCriTable(source, unmaskedSource, source.Length);
|
||||
|
||||
// try again
|
||||
unmaskedSource.Seek(0, SeekOrigin.Begin);
|
||||
source.Position = headerPosition;
|
||||
CriTableMasker.FindKeys(EndianStream.ReadBytes(source, 4), out uint x, out uint m);
|
||||
|
||||
if (EndianStream.ReadCString(unmaskedSource, 4) != CriTableHeader.Signature)
|
||||
{
|
||||
throw new InvalidDataException("'@UTF' signature could not be found.");
|
||||
}
|
||||
source.Position = headerPosition;
|
||||
CriTableMasker.Mask(source, unmaskedSource, source.Length, x, m);
|
||||
|
||||
// Close the old stream
|
||||
if (!leaveOpen)
|
||||
@ -107,6 +101,7 @@ namespace SonicAudioLib.CriMw
|
||||
}
|
||||
|
||||
source = unmaskedSource;
|
||||
source.Position = 4;
|
||||
}
|
||||
|
||||
header.Length = ReadUInt32() + 0x8;
|
||||
@ -143,7 +138,6 @@ namespace SonicAudioLib.CriMw
|
||||
for (ushort i = 0; i < header.NumberOfFields; i++)
|
||||
{
|
||||
CriTableField field = new CriTableField();
|
||||
|
||||
field.Flag = (CriFieldFlag)ReadByte();
|
||||
|
||||
if (field.Flag.HasFlag(CriFieldFlag.Name))
|
||||
@ -155,10 +149,7 @@ namespace SonicAudioLib.CriMw
|
||||
{
|
||||
if (field.Flag.HasFlag(CriFieldFlag.Data))
|
||||
{
|
||||
ReadData(out uint vldPosition, out uint vldLength);
|
||||
|
||||
field.Position = vldPosition;
|
||||
field.Length = vldLength;
|
||||
ReadData(out field.Position, out field.Length);
|
||||
}
|
||||
|
||||
else
|
||||
@ -525,7 +516,7 @@ namespace SonicAudioLib.CriMw
|
||||
|
||||
GoToValue(fieldIndex);
|
||||
ReadData(out uint vldPosition, out uint vldLength);
|
||||
return (uint)(headerPosition + header.DataPoolPosition + vldPosition);
|
||||
return headerPosition + header.DataPoolPosition + vldPosition;
|
||||
}
|
||||
|
||||
public uint GetPosition(string fieldName)
|
||||
@ -623,7 +614,7 @@ namespace SonicAudioLib.CriMw
|
||||
|
||||
source.Position = previousPosition;
|
||||
|
||||
if (readString == "<NULL>" || (readString == header.TableName && stringPosition == 0))
|
||||
if (readString == StringPool.AdxBlankString || (readString == header.TableName && stringPosition == 0))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ namespace SonicAudioLib.CriMw
|
||||
if (settings.EnableMask)
|
||||
{
|
||||
destination.Position = headerPosition;
|
||||
Helpers.MaskCriTable(destination, header.Length);
|
||||
CriTableMasker.Mask(destination, header.Length, settings.MaskXor, settings.MaskXorMultiplier);
|
||||
}
|
||||
|
||||
destination.Seek(previousPosition, SeekOrigin.Begin);
|
||||
@ -699,6 +699,9 @@ namespace SonicAudioLib.CriMw
|
||||
}
|
||||
}
|
||||
|
||||
public uint MaskXor { get; set; }
|
||||
public uint MaskXorMultiplier { get; set; }
|
||||
|
||||
public static CriTableWriterSettings AdxSettings
|
||||
{
|
||||
get
|
||||
|
21
Source/SonicAudioLib/Helpers.cs
Normal file
21
Source/SonicAudioLib/Helpers.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
|
||||
namespace SonicAudioLib
|
||||
{
|
||||
public class Helpers
|
||||
{
|
||||
public static long Align(long value, long alignment)
|
||||
{
|
||||
while ((value % alignment) != 0)
|
||||
{
|
||||
value++;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
107
Source/SonicAudioLib/IO/DataExtractor.cs
Normal file
107
Source/SonicAudioLib/IO/DataExtractor.cs
Normal file
@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
|
||||
using SonicAudioLib.Archive;
|
||||
|
||||
namespace SonicAudioLib.IO
|
||||
{
|
||||
public class DataExtractor
|
||||
{
|
||||
public enum LoggingType
|
||||
{
|
||||
Progress,
|
||||
Message,
|
||||
}
|
||||
|
||||
private class Item
|
||||
{
|
||||
public object Source { get; set; }
|
||||
public string DestinationFileName { get; set; }
|
||||
public long Position { get; set; }
|
||||
public long Length { get; set; }
|
||||
public bool DecompressFromCriLayla { get; set; }
|
||||
}
|
||||
|
||||
private List<Item> items = new List<Item>();
|
||||
|
||||
public int BufferSize { get; set; }
|
||||
public bool EnableThreading { get; set; }
|
||||
public int MaxThreads { get; set; }
|
||||
|
||||
public event ProgressChanged ProgressChanged;
|
||||
|
||||
public void Add(object source, string destinationFileName, long position, long length, bool decompressFromCriLayla = false)
|
||||
{
|
||||
items.Add(new Item { Source = source, DestinationFileName = destinationFileName, Position = position, Length = length, DecompressFromCriLayla = decompressFromCriLayla });
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
double progress = 0.0;
|
||||
double factor = 100.0 / items.Count;
|
||||
|
||||
object lockTarget = new object();
|
||||
|
||||
Action<Item> action = item =>
|
||||
{
|
||||
if (ProgressChanged != null)
|
||||
{
|
||||
lock (lockTarget)
|
||||
{
|
||||
progress += factor;
|
||||
ProgressChanged(this, new ProgressChangedEventArgs(progress));
|
||||
}
|
||||
}
|
||||
|
||||
FileInfo destinationFileName = new FileInfo(item.DestinationFileName);
|
||||
|
||||
if (!destinationFileName.Directory.Exists)
|
||||
{
|
||||
destinationFileName.Directory.Create();
|
||||
}
|
||||
|
||||
using (Stream source =
|
||||
item.Source is string fileName ? new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize) :
|
||||
item.Source is byte[] byteArray ? new MemoryStream(byteArray) :
|
||||
item.Source is Stream stream ? stream :
|
||||
throw new ArgumentException("Unknown source in item", nameof(item.Source))
|
||||
)
|
||||
using (Stream destination = destinationFileName.Create())
|
||||
{
|
||||
if (item.DecompressFromCriLayla)
|
||||
{
|
||||
CriCpkArchive.Decompress(source, item.Position, destination);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
EndianStream.CopyPartTo(source, destination, item.Position, item.Length, BufferSize);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (EnableThreading)
|
||||
{
|
||||
Parallel.ForEach(items, new ParallelOptions { MaxDegreeOfParallelism = MaxThreads }, action);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
items.ForEach(action);
|
||||
}
|
||||
|
||||
items.Clear();
|
||||
}
|
||||
|
||||
public DataExtractor()
|
||||
{
|
||||
BufferSize = 4096;
|
||||
EnableThreading = true;
|
||||
MaxThreads = 4;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
|
||||
namespace SonicAudioLib.IO
|
||||
{
|
||||
public class Helpers
|
||||
{
|
||||
public static long Align(long value, long alignment)
|
||||
{
|
||||
while ((value % alignment) != 0)
|
||||
{
|
||||
value++;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// This one masks the source to destination
|
||||
public static void MaskCriTable(Stream source, Stream destination, long length)
|
||||
{
|
||||
uint currentXor = 25951;
|
||||
long currentPosition = source.Position;
|
||||
|
||||
while (source.Position < currentPosition + length)
|
||||
{
|
||||
byte maskedByte = (byte)(EndianStream.ReadByte(source) ^ currentXor);
|
||||
currentXor *= 16661;
|
||||
|
||||
EndianStream.WriteByte(destination, maskedByte);
|
||||
}
|
||||
}
|
||||
|
||||
// This one masks the source to itself
|
||||
public static void MaskCriTable(Stream source, long length)
|
||||
{
|
||||
if (source.CanRead && source.CanWrite)
|
||||
{
|
||||
uint currentXor = 25951;
|
||||
long currentPosition = source.Position;
|
||||
|
||||
while (source.Position < currentPosition + length)
|
||||
{
|
||||
byte maskedByte = (byte)(EndianStream.ReadByte(source) ^ currentXor);
|
||||
currentXor *= 16661;
|
||||
|
||||
EndianStream.WriteByteAt(source, maskedByte, source.Position - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ namespace SonicAudioLib.IO
|
||||
private long startPosition = 0;
|
||||
private uint align = 1;
|
||||
private long length = 0;
|
||||
private long baseLength = 0;
|
||||
|
||||
public long Position
|
||||
{
|
||||
@ -37,6 +38,8 @@ namespace SonicAudioLib.IO
|
||||
}
|
||||
}
|
||||
|
||||
public event ProgressChanged ProgressChanged;
|
||||
|
||||
public long Put(byte[] data)
|
||||
{
|
||||
if (data == null || data.Length <= 0)
|
||||
@ -132,6 +135,8 @@ namespace SonicAudioLib.IO
|
||||
{
|
||||
module.Write(destination);
|
||||
}
|
||||
|
||||
ProgressChanged?.Invoke(this, new ProgressChangedEventArgs(((destination.Position - startPosition) / (double)(length - baseLength)) * 100.0));
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,7 +148,9 @@ namespace SonicAudioLib.IO
|
||||
public VldPool(uint align, long baseLength)
|
||||
{
|
||||
this.align = align;
|
||||
length = baseLength;
|
||||
|
||||
this.baseLength = baseLength;
|
||||
length = this.baseLength;
|
||||
}
|
||||
|
||||
public VldPool(uint align)
|
||||
|
@ -8,6 +8,8 @@ namespace SonicAudioLib.Module
|
||||
{
|
||||
public abstract class ModuleBase
|
||||
{
|
||||
protected int bufferSize = 4096;
|
||||
|
||||
public abstract void Read(Stream source);
|
||||
public abstract void Write(Stream destination);
|
||||
|
||||
@ -17,6 +19,8 @@ namespace SonicAudioLib.Module
|
||||
{
|
||||
Read(source);
|
||||
}
|
||||
|
||||
this.bufferSize = bufferSize;
|
||||
}
|
||||
|
||||
public virtual void Load(string sourceFileName)
|
||||
@ -43,6 +47,8 @@ namespace SonicAudioLib.Module
|
||||
{
|
||||
Write(destination);
|
||||
}
|
||||
|
||||
this.bufferSize = bufferSize;
|
||||
}
|
||||
|
||||
public virtual byte[] Save()
|
||||
|
20
Source/SonicAudioLib/ProgressChangedEvent.cs
Normal file
20
Source/SonicAudioLib/ProgressChangedEvent.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SonicAudioLib
|
||||
{
|
||||
public class ProgressChangedEventArgs : EventArgs
|
||||
{
|
||||
public double Progress { get; private set; }
|
||||
|
||||
public ProgressChangedEventArgs(double progress)
|
||||
{
|
||||
Progress = Math.Round(progress, 2, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void ProgressChanged(object sender, ProgressChangedEventArgs e);
|
||||
}
|
@ -20,6 +20,7 @@
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
@ -58,11 +59,13 @@
|
||||
<Compile Include="CriMw\Serialization\CriTableSerializer.cs" />
|
||||
<Compile Include="CriMw\CriTableWriter.cs" />
|
||||
<Compile Include="IO\EndianStream.cs" />
|
||||
<Compile Include="IO\DataExtractor.cs" />
|
||||
<Compile Include="IO\StringPool.cs" />
|
||||
<Compile Include="IO\Substream.cs" />
|
||||
<Compile Include="IO\Helpers.cs" />
|
||||
<Compile Include="Helpers.cs" />
|
||||
<Compile Include="IO\VldPool.cs" />
|
||||
<Compile Include="Module\ModuleBase.cs" />
|
||||
<Compile Include="ProgressChangedEvent.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
|
Loading…
Reference in New Issue
Block a user