commit e3aa58f1358b3673ee903e4d5ce311399f7a857b
Author: Skyth <19259897+blueskythlikesclouds@users.noreply.github.com>
Date: Sat Nov 12 19:02:48 2016 +0300
Initial commit
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..1ff0c42
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg binary
+#*.png binary
+#*.gif binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bdc6f31
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,244 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]eleases/
+[Xx]64/
+[Xx]86/
+[Bb]uild/
+bld/
+[Bb]in/
+[Oo]bj/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+
+# TODO: Un-comment the next line if you do not want to checkin
+# your web deploy settings because they may include unencrypted
+# passwords
+#*.pubxml
+*.publishproj
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Microsoft Azure ApplicationInsights config file
+ApplicationInsights.config
+
+# Windows Store app package directory
+AppPackages/
+BundleArtifacts/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+[Ss]tyle[Cc]op.*
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# LightSwitch generated files
+GeneratedArtifacts/
+ModelManifest.xml
+
+# Paket dependency manager
+.paket/paket.exe
+
+# FAKE - F# Make
+.fake/
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..7ac8e60
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2016 Skyth
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Release/AcbEditor.exe b/Release/AcbEditor.exe
new file mode 100644
index 0000000..bea929a
Binary files /dev/null and b/Release/AcbEditor.exe differ
diff --git a/Release/CsbEditor.exe b/Release/CsbEditor.exe
new file mode 100644
index 0000000..c0cd99c
Binary files /dev/null and b/Release/CsbEditor.exe differ
diff --git a/Release/SonicAudioLib.dll b/Release/SonicAudioLib.dll
new file mode 100644
index 0000000..976b77f
Binary files /dev/null and b/Release/SonicAudioLib.dll differ
diff --git a/SonicAudioTools.sln b/SonicAudioTools.sln
new file mode 100644
index 0000000..3a7a590
--- /dev/null
+++ b/SonicAudioTools.sln
@@ -0,0 +1,39 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SonicAudioLib", "Source\SonicAudioLib\SonicAudioLib.csproj", "{63138773-1F47-474C-9345-15EB6183ECC6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SonicAudioCmd", "Source\SonicAudioCmd\SonicAudioCmd.csproj", "{C412FC07-0C07-4361-88BA-C9C182CF8D70}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AcbEditor", "Source\AcbEditor\AcbEditor.csproj", "{45A72DAF-370A-42D1-833E-CEE27EA5E311}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CsbEditor", "Source\CsbEditor\CsbEditor.csproj", "{91F6B6A6-5D95-4C7A-B22E-A35BD32DB67A}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {63138773-1F47-474C-9345-15EB6183ECC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {63138773-1F47-474C-9345-15EB6183ECC6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {63138773-1F47-474C-9345-15EB6183ECC6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {63138773-1F47-474C-9345-15EB6183ECC6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C412FC07-0C07-4361-88BA-C9C182CF8D70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C412FC07-0C07-4361-88BA-C9C182CF8D70}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C412FC07-0C07-4361-88BA-C9C182CF8D70}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {45A72DAF-370A-42D1-833E-CEE27EA5E311}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {45A72DAF-370A-42D1-833E-CEE27EA5E311}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {45A72DAF-370A-42D1-833E-CEE27EA5E311}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {45A72DAF-370A-42D1-833E-CEE27EA5E311}.Release|Any CPU.Build.0 = Release|Any CPU
+ {91F6B6A6-5D95-4C7A-B22E-A35BD32DB67A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {91F6B6A6-5D95-4C7A-B22E-A35BD32DB67A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {91F6B6A6-5D95-4C7A-B22E-A35BD32DB67A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {91F6B6A6-5D95-4C7A-B22E-A35BD32DB67A}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/Source/AcbEditor/AcbEditor.csproj b/Source/AcbEditor/AcbEditor.csproj
new file mode 100644
index 0000000..9b7cdad
--- /dev/null
+++ b/Source/AcbEditor/AcbEditor.csproj
@@ -0,0 +1,78 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {45A72DAF-370A-42D1-833E-CEE27EA5E311}
+ Exe
+ Properties
+ AcbEditor
+ AcbEditor
+ v4.5.2
+ 512
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ ..\..\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ ..\..\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Resources.resx
+
+
+
+
+
+
+
+ {63138773-1f47-474c-9345-15eb6183ecc6}
+ SonicAudioLib
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/AcbEditor/App.config b/Source/AcbEditor/App.config
new file mode 100644
index 0000000..88fa402
--- /dev/null
+++ b/Source/AcbEditor/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/AcbEditor/Program.cs b/Source/AcbEditor/Program.cs
new file mode 100644
index 0000000..86ae447
--- /dev/null
+++ b/Source/AcbEditor/Program.cs
@@ -0,0 +1,272 @@
+using System;
+using System.IO;
+using System.Windows.Forms;
+
+using SonicAudioLib.CriMw;
+using SonicAudioLib.IO;
+using SonicAudioLib.Archive;
+
+namespace AcbEditor
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ if (args.Length < 1)
+ {
+ Console.WriteLine(Properties.Resources.Description);
+ Console.ReadLine();
+ return;
+ }
+
+ try
+ {
+ if (args[0].EndsWith(".acb"))
+ {
+ string baseDirectory = Path.GetDirectoryName(args[0]);
+ string outputDirectoryPath = Path.Combine(baseDirectory, Path.GetFileNameWithoutExtension(args[0]));
+ string extAfs2ArchivePath = string.Empty;
+
+ Directory.CreateDirectory(outputDirectoryPath);
+
+ using (CriTableReader acbReader = CriTableReader.Create(args[0]))
+ {
+ acbReader.Read();
+
+ CriAfs2Archive afs2Archive = new CriAfs2Archive();
+ CriAfs2Archive extAfs2Archive = new CriAfs2Archive();
+
+ if (acbReader.GetLength("AwbFile") > 0)
+ {
+ using (Substream afs2Stream = acbReader.GetSubstream("AwbFile"))
+ {
+ afs2Archive.Read(afs2Stream);
+ }
+ }
+
+ if (acbReader.GetLength("StreamAwbAfs2Header") > 0)
+ {
+ using (Substream extAfs2Stream = acbReader.GetSubstream("StreamAwbAfs2Header"))
+ {
+ extAfs2Archive.Read(extAfs2Stream);
+ }
+
+ // cheatingggggg
+ 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);
+ }
+
+ 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.");
+ }
+ }
+
+ using (Substream waveformTableStream = acbReader.GetSubstream("WaveformTable"))
+ using (CriTableReader waveformReader = CriTableReader.Create(waveformTableStream))
+ {
+ while (waveformReader.Read())
+ {
+ ushort index = waveformReader.GetUInt16("Id");
+ byte encodeType = waveformReader.GetByte("EncodeType");
+ bool streaming = waveformReader.GetBoolean("Streaming");
+
+ string outputName = index.ToString("D5");
+ if (streaming)
+ {
+ outputName += "_streaming";
+ }
+
+ outputName += GetExtension(encodeType);
+ outputName = Path.Combine(outputDirectoryPath, outputName);
+
+ Console.WriteLine("Extracting {0} file with index {1}...", GetExtension(encodeType).ToUpper(), index);
+
+ if (streaming)
+ {
+ CriAfs2Entry afs2Entry = extAfs2Archive.GetByCueIndex(index);
+
+ using (Stream extAfs2Stream = File.OpenRead(extAfs2ArchivePath))
+ using (Stream afs2EntryStream = afs2Entry.Open(extAfs2Stream))
+ using (Stream afs2EntryDestination = File.Create(outputName))
+ {
+ afs2EntryStream.CopyTo(afs2EntryDestination);
+ }
+ }
+
+ else
+ {
+ CriAfs2Entry entry = afs2Archive.GetByCueIndex(index);
+
+ using (Substream afs2Stream = acbReader.GetSubstream("AwbFile"))
+ using (Stream afs2EntryStream = entry.Open(afs2Stream))
+ using (Stream afs2EntryDestination = File.Create(outputName))
+ {
+ afs2EntryStream.CopyTo(afs2EntryDestination);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ 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 Exception("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);
+
+ CriAfs2Archive afs2Archive = new CriAfs2Archive();
+ CriAfs2Archive extAfs2Archive = new CriAfs2Archive();
+
+ using (CriTableReader reader = CriTableReader.Create((byte[])acbFile.Rows[0]["WaveformTable"]))
+ {
+ while (reader.Read())
+ {
+ ushort index = reader.GetUInt16("Id");
+ byte encodeType = reader.GetByte("EncodeType");
+ bool streaming = reader.GetBoolean("Streaming");
+
+ string inputName = index.ToString("D5");
+ if (streaming)
+ {
+ inputName += "_streaming";
+ }
+
+ inputName += GetExtension(encodeType);
+ inputName = Path.Combine(args[0], inputName);
+
+ if (!File.Exists(inputName))
+ {
+ throw new Exception($"Cannot find audio file with index {index} for replacement.\nPath attempt: {inputName}");
+ }
+
+ CriAfs2Entry entry = new CriAfs2Entry();
+ entry.CueIndex = index;
+ entry.FilePath = new FileInfo(inputName);
+
+ Console.WriteLine("Adding {0}...", Path.GetFileName(inputName));
+
+ if (streaming)
+ {
+ extAfs2Archive.Add(entry);
+ }
+
+ else
+ {
+ afs2Archive.Add(entry);
+ }
+ }
+ }
+
+ acbFile.Rows[0]["AwbFile"] = null;
+ acbFile.Rows[0]["StreamAwbAfs2Header"] = null;
+
+ if (afs2Archive.Count > 0)
+ {
+ afs2Archive.Order();
+
+ Console.WriteLine("Saving internal AWB...");
+ acbFile.Rows[0]["AwbFile"] = afs2Archive.Save();
+ }
+
+ if (extAfs2Archive.Count > 0)
+ {
+ extAfs2Archive.Order();
+
+ Console.WriteLine("Saving external AWB...");
+ extAfs2Archive.Save(awbPath);
+
+ byte[] afs2Header = new byte[16 +
+ (extAfs2Archive.Count * extAfs2Archive.CueIndexFieldLength) +
+ (extAfs2Archive.Count * extAfs2Archive.PositionFieldLength) +
+ extAfs2Archive.PositionFieldLength];
+
+ using (FileStream fileStream = File.OpenRead(awbPath))
+ {
+ fileStream.Read(afs2Header, 0, afs2Header.Length);
+ }
+
+ acbFile.Rows[0]["StreamAwbAfs2Header"] = afs2Header;
+ }
+
+ acbFile.WriterSettings = CriTableWriterSettings.Adx2Settings;
+ acbFile.Save(acbPath);
+ }
+ }
+
+ catch (Exception exception)
+ {
+ MessageBox.Show($"{exception.Message}", "ACB Editor", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ }
+
+ 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 ".3dsadpcm";
+ case 18:
+ case 11:
+ return ".at9";
+ case 12:
+ return ".xma";
+ case 13:
+ return ".wiiuadpcm";
+ default:
+ return ".bin";
+ }
+ }
+ }
+}
diff --git a/Source/AcbEditor/Properties/AssemblyInfo.cs b/Source/AcbEditor/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..c031983
--- /dev/null
+++ b/Source/AcbEditor/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("AcbEditor")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("AcbEditor")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("45a72daf-370a-42d1-833e-cee27ea5e311")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Source/AcbEditor/Properties/Resources.Designer.cs b/Source/AcbEditor/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..446764f
--- /dev/null
+++ b/Source/AcbEditor/Properties/Resources.Designer.cs
@@ -0,0 +1,86 @@
+//------------------------------------------------------------------------------
+//
+// 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.
+//
+//------------------------------------------------------------------------------
+
+namespace AcbEditor.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AcbEditor.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to ACB Editor
+ ///==========
+ ///
+ ///Usage:
+ ///Drag and drop an .ACB file to unpack its contents to a directory.
+ ///The directory will have the same name as the .ACB file, but without
+ ///its extension.
+ ///
+ ///In the directory, there will be the audio files, extracted straight
+ ///from the .ACB file (and the external .AWB file if it is present.)
+ ///Files with "_streaming" suffix means that it was extracted from the
+ ///external .AWB file.
+ ///
+ ///You can edit those files as you want, but you shouldn't rename the
+ ///files, or remove any of them [rest of string was truncated]";.
+ ///
+ internal static string Description {
+ get {
+ return ResourceManager.GetString("Description", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Source/AcbEditor/Properties/Resources.resx b/Source/AcbEditor/Properties/Resources.resx
new file mode 100644
index 0000000..10b1e80
--- /dev/null
+++ b/Source/AcbEditor/Properties/Resources.resx
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ ACB Editor
+==========
+
+Usage:
+Drag and drop an .ACB file to unpack its contents to a directory.
+The directory will have the same name as the .ACB file, but without
+its extension.
+
+In the directory, there will be the audio files, extracted straight
+from the .ACB file (and the external .AWB file if it is present.)
+Files with "_streaming" suffix means that it was extracted from the
+external .AWB file.
+
+You can edit those files as you want, but you shouldn't rename the
+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.
+
+
\ No newline at end of file
diff --git a/Source/CsbEditor/App.config b/Source/CsbEditor/App.config
new file mode 100644
index 0000000..88fa402
--- /dev/null
+++ b/Source/CsbEditor/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/CsbEditor/CsbEditor.csproj b/Source/CsbEditor/CsbEditor.csproj
new file mode 100644
index 0000000..6a2c3ba
--- /dev/null
+++ b/Source/CsbEditor/CsbEditor.csproj
@@ -0,0 +1,78 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {91F6B6A6-5D95-4C7A-B22E-A35BD32DB67A}
+ Exe
+ Properties
+ CsbEditor
+ CsbEditor
+ v4.5.2
+ 512
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ ..\..\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ ..\..\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Resources.resx
+
+
+
+
+
+
+
+ {63138773-1f47-474c-9345-15eb6183ecc6}
+ SonicAudioLib
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/CsbEditor/Program.cs b/Source/CsbEditor/Program.cs
new file mode 100644
index 0000000..44444c6
--- /dev/null
+++ b/Source/CsbEditor/Program.cs
@@ -0,0 +1,169 @@
+using System;
+using System.Linq;
+using System.IO;
+using System.Windows.Forms;
+
+using SonicAudioLib.CriMw;
+using SonicAudioLib.IO;
+using SonicAudioLib.Archive;
+
+using System.Globalization;
+
+namespace CsbEditor
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ if (args.Length < 1)
+ {
+ Console.WriteLine(Properties.Resources.Description);
+ Console.ReadLine();
+ return;
+ }
+
+ try
+ {
+ if (args[0].EndsWith(".csb"))
+ {
+ string baseDirectory = Path.GetDirectoryName(args[0]);
+ string outputDirectoryName = Path.Combine(baseDirectory, Path.GetFileNameWithoutExtension(args[0]));
+
+ using (CriTableReader reader = CriTableReader.Create(args[0]))
+ {
+ while (reader.Read())
+ {
+ if (reader.GetString("name") == "SOUND_ELEMENT")
+ {
+ using (CriTableReader sdlReader = CriTableReader.Create(reader.GetSubstream("utf")))
+ {
+ while (sdlReader.Read())
+ {
+ if (sdlReader.GetByte("stmflg") != 0)
+ {
+ throw new Exception("The given CSB file contains external audio data. Those kind of CSB files are not supported yet.");
+ }
+
+ if (sdlReader.GetByte("fmt") != 0)
+ {
+ throw new Exception("The given CSB file contains an audio file which is not an ADX. Only CSB files with ADXs are supported.");
+ }
+
+ string sdlName = sdlReader.GetString("name");
+ DirectoryInfo destinationPath = new DirectoryInfo(Path.Combine(outputDirectoryName, sdlName));
+ destinationPath.Create();
+
+ Console.WriteLine("Extracting {0}...", sdlName);
+ using (CriTableReader aaxReader = CriTableReader.Create(sdlReader.GetSubstream("data")))
+ {
+ while (aaxReader.Read())
+ {
+ string outputName = Path.Combine(destinationPath.FullName, aaxReader.GetBoolean("lpflg") ? "Loop.adx" : "Intro.adx");
+
+ using (Stream source = aaxReader.GetSubstream("data"))
+ using (Stream destination = File.Create(outputName))
+ {
+ source.CopyTo(destination);
+ }
+ }
+ }
+ }
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ else if (File.GetAttributes(args[0]).HasFlag(FileAttributes.Directory))
+ {
+ string baseDirectory = Path.GetDirectoryName(args[0]);
+ string csbPath = args[0] + ".csb";
+
+ if (!File.Exists(csbPath))
+ {
+ throw new Exception("Cannot find the .CSB file for this directory. Please ensure that the .CSB file is stored in the directory where this directory is.");
+ }
+
+ CriTable csbFile = new CriTable();
+ csbFile.Load(csbPath);
+
+ CriRow soundElementRow = csbFile.Rows.Single(row => (string)row["name"] == "SOUND_ELEMENT");
+
+ CriTable soundElementTable = new CriTable();
+ soundElementTable.Load((byte[])soundElementRow["utf"]);
+
+ foreach (CriRow sdlRow in soundElementTable.Rows)
+ {
+ string sdlName = (string)sdlRow["name"];
+
+ DirectoryInfo sdlDirectory = new DirectoryInfo(Path.Combine(args[0], sdlName));
+
+ if (!sdlDirectory.Exists)
+ {
+ throw new Exception($"Cannot find sound element directory for replacement.\nPath attempt: {sdlDirectory.FullName}");
+ }
+
+ uint sampleRate = (uint)sdlRow["sfreq"];
+ byte numberChannels = (byte)sdlRow["nch"];
+
+ Console.WriteLine("Adding {0}...", sdlName);
+
+ using (MemoryStream memoryStream = new MemoryStream())
+ using (CriTableWriter writer = CriTableWriter.Create(memoryStream, CriTableWriterSettings.AdxSettings))
+ {
+ writer.WriteStartTable("AAX");
+
+ writer.WriteField("data", typeof(byte[]));
+ writer.WriteField("lpflg", typeof(byte));
+
+ foreach (FileInfo audioFile in sdlDirectory.GetFiles("*.adx"))
+ {
+ // In Turkish, lowercase I is ı so you get the idea
+ if (audioFile.Name.ToLower(CultureInfo.GetCultureInfo("en-US")) == "intro.adx")
+ {
+ ReadAdx(audioFile, out sampleRate, out numberChannels);
+ writer.WriteRow(true, audioFile, (byte)0);
+ }
+
+ else if (audioFile.Name.ToLower() == "loop.adx")
+ {
+ ReadAdx(audioFile, out sampleRate, out numberChannels);
+ writer.WriteRow(true, audioFile, (byte)1);
+ }
+ }
+
+ writer.WriteEndTable();
+ sdlRow["data"] = memoryStream.ToArray();
+ }
+
+ sdlRow["sfreq"] = sampleRate;
+ sdlRow["nch"] = numberChannels;
+ }
+
+ soundElementTable.WriterSettings = CriTableWriterSettings.AdxSettings;
+ soundElementRow["utf"] = soundElementTable.Save();
+
+ csbFile.WriterSettings = CriTableWriterSettings.AdxSettings;
+ csbFile.Save(args[0] + ".csb");
+ }
+ }
+
+ catch (Exception exception)
+ {
+ MessageBox.Show($"{exception.Message}", "CSB Editor", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ }
+
+ static void ReadAdx(FileInfo fileInfo, out uint sampleRate, out byte numberChannels)
+ {
+ using (Stream source = fileInfo.OpenRead())
+ {
+ source.Seek(7, SeekOrigin.Begin);
+ numberChannels = EndianStream.ReadByte(source);
+ sampleRate = EndianStream.ReadUInt32BE(source);
+ }
+ }
+ }
+}
diff --git a/Source/CsbEditor/Properties/AssemblyInfo.cs b/Source/CsbEditor/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..ba36765
--- /dev/null
+++ b/Source/CsbEditor/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("CsbEditor")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("CsbEditor")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("91f6b6a6-5d95-4c7a-b22e-a35bd32db67a")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Source/CsbEditor/Properties/Resources.Designer.cs b/Source/CsbEditor/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..4f25845
--- /dev/null
+++ b/Source/CsbEditor/Properties/Resources.Designer.cs
@@ -0,0 +1,86 @@
+//------------------------------------------------------------------------------
+//
+// 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.
+//
+//------------------------------------------------------------------------------
+
+namespace CsbEditor.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CsbEditor.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to CSB Editor
+ ///==========
+ ///
+ ///Usage:
+ ///Drag and drop a .CSB file to unpack its contents to a directory.
+ ///The directory will have the same name as the .CSB file, but without
+ ///its extension.
+ ///
+ ///In the deepest directories, you will see .ADX files, named "Intro.adx"
+ ///or "Loop.adx". The ADX files are literally what the names say. You can
+ ///add/delete/modify them freely.
+ ///
+ ///The sample rate and channel count information will be automatically
+ ///updated in the CSB file if you use an ADX file with different sample
+ ///rate or [rest of string was truncated]";.
+ ///
+ internal static string Description {
+ get {
+ return ResourceManager.GetString("Description", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Source/CsbEditor/Properties/Resources.resx b/Source/CsbEditor/Properties/Resources.resx
new file mode 100644
index 0000000..0a0879c
--- /dev/null
+++ b/Source/CsbEditor/Properties/Resources.resx
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ CSB Editor
+==========
+
+Usage:
+Drag and drop a .CSB file to unpack its contents to a directory.
+The directory will have the same name as the .CSB file, but without
+its extension.
+
+In the deepest directories, you will see .ADX files, named "Intro.adx"
+or "Loop.adx". The ADX files are literally what the names say. You can
+add/delete/modify them freely.
+
+The sample rate and channel count information will be automatically
+updated in the CSB file if you use an ADX file with different sample
+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.
+
+
\ No newline at end of file
diff --git a/Source/SonicAudioCmd/App.config b/Source/SonicAudioCmd/App.config
new file mode 100644
index 0000000..88fa402
--- /dev/null
+++ b/Source/SonicAudioCmd/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/SonicAudioCmd/Program.cs b/Source/SonicAudioCmd/Program.cs
new file mode 100644
index 0000000..a749ec9
--- /dev/null
+++ b/Source/SonicAudioCmd/Program.cs
@@ -0,0 +1,43 @@
+using System;
+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.Collections;
+using SonicAudioLib.IO;
+using SonicAudioLib.CriMw;
+
+using System.Xml;
+
+namespace SonicAudioCmd
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ CriCpkArchive archive = new CriCpkArchive();
+ archive.Load(args[0]);
+
+ foreach (CriCpkEntry entry in archive)
+ {
+ using (Stream source = File.OpenRead(args[0]), substream = entry.Open(source))
+ {
+ FileInfo fileInfo = new FileInfo(Path.Combine(Path.GetFileNameWithoutExtension(args[0]), entry.DirectoryName, entry.Name));
+ fileInfo.Directory.Create();
+ using (Stream destination = fileInfo.Create())
+ {
+ substream.CopyTo(destination);
+ }
+ }
+ }
+
+ Console.ReadLine();
+ }
+ }
+}
diff --git a/Source/SonicAudioCmd/Properties/AssemblyInfo.cs b/Source/SonicAudioCmd/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..a8563c3
--- /dev/null
+++ b/Source/SonicAudioCmd/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("SonicAudioCmd")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SonicAudioCmd")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("c412fc07-0c07-4361-88ba-c9c182cf8d70")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Source/SonicAudioCmd/Properties/Resources.Designer.cs b/Source/SonicAudioCmd/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..b3dc50a
--- /dev/null
+++ b/Source/SonicAudioCmd/Properties/Resources.Designer.cs
@@ -0,0 +1,85 @@
+//------------------------------------------------------------------------------
+//
+// 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.
+//
+//------------------------------------------------------------------------------
+
+namespace SonicAudioCmd.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SonicAudioCmd.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to CSB Compiler/Extractor
+ ///
+ ///Usage: [fileName]
+ ///
+ ///This tool will extract the contents of a CSB file into a folder,
+ ///resulting also an XML file. Just simply drag and drop your CSB file.
+ ///You can modify the .ADX files inside as you want!
+ ///
+ ///In the XML file, do not touch the paths, but modify the SampleRate
+ ///or NumberChannels elements if your custom ADX has a different
+ ///sample rate or channel count than the original. Otherwise, the
+ ///modifications in game will result pitched/faster/slower.
+ ///
+ ///To compile the CSB ba [rest of string was truncated]";.
+ ///
+ internal static string HelpText {
+ get {
+ return ResourceManager.GetString("HelpText", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Source/SonicAudioCmd/Properties/Resources.resx b/Source/SonicAudioCmd/Properties/Resources.resx
new file mode 100644
index 0000000..c608162
--- /dev/null
+++ b/Source/SonicAudioCmd/Properties/Resources.resx
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ CSB Compiler/Extractor
+
+Usage: [fileName]
+
+This tool will extract the contents of a CSB file into a folder,
+resulting also an XML file. Just simply drag and drop your CSB file.
+You can modify the .ADX files inside as you want!
+
+In the XML file, do not touch the paths, but modify the SampleRate
+or NumberChannels elements if your custom ADX has a different
+sample rate or channel count than the original. Otherwise, the
+modifications in game will result pitched/faster/slower.
+
+To compile the CSB back, you gotta have your extracted folder,
+CSB file and XML file in the same folder. Drag and drop the XML
+to the tool, it will load all the ADXs and compile them back to
+the CSB file. (The existing CSB file will be overwritten!)
+
+
\ No newline at end of file
diff --git a/Source/SonicAudioCmd/SonicAudioCmd.csproj b/Source/SonicAudioCmd/SonicAudioCmd.csproj
new file mode 100644
index 0000000..60a62ec
--- /dev/null
+++ b/Source/SonicAudioCmd/SonicAudioCmd.csproj
@@ -0,0 +1,77 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {C412FC07-0C07-4361-88BA-C9C182CF8D70}
+ Exe
+ Properties
+ SonicAudioCmd
+ SonicAudioCmd
+ v4.5.2
+ 512
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ ..\..\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ ..\..\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Resources.resx
+
+
+
+
+
+
+
+ {63138773-1f47-474c-9345-15eb6183ecc6}
+ SonicAudioLib
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/SonicAudioCmd/acb_extractor.txt b/Source/SonicAudioCmd/acb_extractor.txt
new file mode 100644
index 0000000..4c9c9b6
--- /dev/null
+++ b/Source/SonicAudioCmd/acb_extractor.txt
@@ -0,0 +1,43 @@
+using System;
+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.Collections;
+using SonicAudioLib.IO;
+using SonicAudioLib.CriMw;
+
+using System.Xml;
+
+namespace SonicAudioCmd
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ using (CriTableReader reader = new CriTableReader(args[0]))
+ {
+ reader.Read();
+
+ using (Stream substream = reader.GetSubstream("AwbFile"))
+ {
+ Afs2Archive archive = new Afs2Archive(substream);
+
+ foreach (Afs2Entry entry in archive.Entries)
+ {
+ using (Stream entryStream = entry.Open(substream), outStream = File.Create(entry.CueIndex.ToString() + ".hca"))
+ {
+ entryStream.CopyTo(outStream);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Source/SonicAudioCmd/csb_file_replacer.txt b/Source/SonicAudioCmd/csb_file_replacer.txt
new file mode 100644
index 0000000..89ce9aa
--- /dev/null
+++ b/Source/SonicAudioCmd/csb_file_replacer.txt
@@ -0,0 +1,171 @@
+using System;
+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.Collections;
+using SonicAudioLib.IO;
+using SonicAudioLib.CriMw;
+
+using System.Xml;
+using System.Xml.Serialization;
+
+namespace SonicAudioCmd
+{
+ [Serializable]
+ public class SoundElement
+ {
+ public string Path { get; set; }
+ public uint SampleRate { get; set; }
+ public byte NumberChannels { get; set; }
+ public List Sounds = new List();
+ }
+
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ if (args.Length < 1)
+ {
+ Console.WriteLine(Properties.Resources.HelpText);
+ Console.ReadLine();
+ return;
+ }
+
+ if (args[0].EndsWith(".csb"))
+ {
+ string directoryName = Path.Combine(Path.GetDirectoryName(args[0]), Path.GetFileNameWithoutExtension(args[0]));
+
+ List soundElements = new List();
+ using (CriTableReader reader = CriTableReader.Create(args[0]))
+ {
+ while (reader.Read())
+ {
+ if (reader.GetString("name") == "SOUND_ELEMENT")
+ {
+ using (CriTableReader sdlReader = CriTableReader.Create(reader.GetSubstream("utf")))
+ {
+ while (sdlReader.Read())
+ {
+ SoundElement soundElement = new SoundElement();
+ soundElement.Path = sdlReader.GetString("name");
+ soundElement.SampleRate = sdlReader.GetUInt32("sfreq");
+ soundElement.NumberChannels = sdlReader.GetByte("nch");
+ using (CriTableReader aaxReader = CriTableReader.Create(sdlReader.GetSubstream("data")))
+ {
+ while (aaxReader.Read())
+ {
+ string fileName = Path.Combine(directoryName, soundElement.Path,
+ $"{soundElement.Path.Replace('/', '_')}_{aaxReader.GetByte("lpflg")}.adx");
+ soundElement.Sounds.Add(fileName);
+
+ FileInfo fileInfo = new FileInfo(fileName);
+ fileInfo.Directory.Create();
+
+ using (Stream inStream = aaxReader.GetSubstream("data"), outStream = fileInfo.Create())
+ {
+ inStream.CopyTo(outStream);
+ }
+ }
+ }
+ soundElements.Add(soundElement);
+ }
+ }
+
+ XmlSerializer xmlSerializer = new XmlSerializer(typeof(List));
+
+ using (XmlWriter xmlWriter = XmlWriter.Create(directoryName + ".xml", new XmlWriterSettings() { Indent = true }))
+ {
+ xmlSerializer.Serialize(xmlWriter, soundElements);
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ else if (args[0].EndsWith(".xml"))
+ {
+ XmlSerializer xmlSerializer = new XmlSerializer(typeof(List));
+ List soundElements;
+
+ using (XmlReader xmlReader = XmlReader.Create(args[0]))
+ {
+ soundElements = (List)xmlSerializer.Deserialize(xmlReader);
+ }
+
+ CriTable criTable = new CriTable();
+ string directoryName = Path.Combine(Path.GetDirectoryName(args[0]), Path.GetFileNameWithoutExtension(args[0]));
+
+ if (!File.Exists(directoryName + ".csb"))
+ {
+ throw new FileNotFoundException("No valid CSB file found for this XML!");
+ }
+
+ criTable.Load(directoryName + ".csb");
+
+ Directory.CreateDirectory(directoryName + "-temp");
+
+ CriTable soundElementTable = new CriTable();
+ CriRow soundElementRow = criTable.Rows.Single(row => (string)row["name"] == "SOUND_ELEMENT");
+
+ soundElementTable.Load((byte[])soundElementRow["utf"]);
+
+ foreach (SoundElement soundElement in soundElements)
+ {
+ CriRow elementRow = soundElementTable.Rows.Single(row => (string)row["name"] == soundElement.Path);
+ elementRow["nch"] = soundElement.NumberChannels;
+ elementRow["sfreq"] = soundElement.SampleRate;
+
+ using (CriTableWriter aaxWriter = CriTableWriter.Create($"{directoryName}-temp/{soundElement.Path.Replace('/', '_')}"))
+ {
+ aaxWriter.WriteStartTable("AAX");
+ aaxWriter.WriteStartFieldCollection();
+ aaxWriter.WriteField("data", typeof(byte[]));
+ aaxWriter.WriteField("lpflg", typeof(byte));
+ aaxWriter.WriteEndFieldCollection();
+
+ foreach (string sound in soundElement.Sounds)
+ {
+ if (!File.Exists(sound))
+ {
+ Directory.Delete(directoryName + "-temp", true);
+ throw new FileNotFoundException($"Can't find file! {sound}");
+ }
+
+ string baseName = Path.GetFileNameWithoutExtension(sound);
+ int lastUnderscore = baseName.LastIndexOf('_');
+ baseName = baseName.Substring(lastUnderscore + 1);
+
+ byte loopFlag = byte.Parse(baseName);
+
+ aaxWriter.WriteRow(true, new FileInfo(sound), loopFlag);
+ }
+
+ aaxWriter.WriteEndTable();
+ }
+
+ elementRow["data"] = File.ReadAllBytes($"{directoryName}-temp/{soundElement.Path.Replace('/', '_')}");
+ }
+
+ soundElementTable.Save($"{directoryName}-temp/SOUND_ELEMENT");
+ soundElementRow["utf"] = File.ReadAllBytes($"{directoryName}-temp/SOUND_ELEMENT");
+ criTable.Save(directoryName + ".csb");
+
+ Directory.Delete(directoryName + "-temp", true);
+ }
+
+ else
+ {
+ throw new FileNotFoundException("No valid file found!");
+ }
+ }
+ }
+}
diff --git a/Source/SonicAudioCmd/csb_frequence_subdivision.txt b/Source/SonicAudioCmd/csb_frequence_subdivision.txt
new file mode 100644
index 0000000..476ca7c
--- /dev/null
+++ b/Source/SonicAudioCmd/csb_frequence_subdivision.txt
@@ -0,0 +1,45 @@
+using System;
+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.Collections;
+using SonicAudioLib.IO;
+using SonicAudioLib.CriMw;
+
+using System.Xml;
+using System.Xml.Serialization;
+
+namespace SonicAudioCmd
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ CriTable table = new CriTable();
+ table.Load(args[0]);
+
+ CriRow soundElementTableRow = table.Rows.Single(row => (string)row["name"] == "SOUND_ELEMENT");
+
+ CriTable sdlTable = new CriTable();
+ sdlTable.Load(soundElementTableRow["utf"] as byte[]);
+
+ foreach (CriRow criRow in sdlTable.Rows)
+ {
+ criRow["sfreq"] = (uint)criRow["sfreq"] / 2;
+ }
+
+ sdlTable.WriterSettings = CriTableWriterSettings.AdxSettings;
+ soundElementTableRow["utf"] = sdlTable.Save();
+
+ table.WriterSettings = CriTableWriterSettings.AdxSettings;
+ table.Save(args[0]);
+ }
+ }
+}
diff --git a/Source/SonicAudioCmd/file_replacer_acb.txt b/Source/SonicAudioCmd/file_replacer_acb.txt
new file mode 100644
index 0000000..54c86da
--- /dev/null
+++ b/Source/SonicAudioCmd/file_replacer_acb.txt
@@ -0,0 +1,116 @@
+using System;
+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.Collections;
+using SonicAudioLib.IO;
+using SonicAudioLib.CriMw;
+
+using System.Xml;
+
+namespace SonicAudioCmd
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ if (args.Length < 1)
+ {
+ Console.WriteLine("Usage: [path]");
+ Console.WriteLine("Drag and drop an ACB file to unpack its contents.");
+ Console.WriteLine("Drag and drop a folder to pack its contents back into the ACB. (The ACB file must be in the same directory.)");
+ Console.ReadLine();
+ return;
+ }
+
+ if (args[0].EndsWith(".acb"))
+ {
+ string baseDirectory = Path.Combine(
+ Path.GetDirectoryName(args[0]), Path.GetFileNameWithoutExtension(args[0]));
+
+ Directory.CreateDirectory(baseDirectory);
+
+ using (CriTableReader reader = CriTableReader.Create(args[0]))
+ {
+ reader.Read();
+
+ using (Stream afs2Stream = reader.GetSubstream("AwbFile"))
+ {
+ Afs2Archive archive = new Afs2Archive(afs2Stream);
+
+ using (CriTableReader waveformReader = CriTableReader.Create(reader.GetSubstream("WaveformTable")))
+ {
+ while (waveformReader.Read())
+ {
+ if (!waveformReader.GetBoolean("Streaming"))
+ {
+ ushort cueIndex = waveformReader.GetUInt16("Id");
+ Afs2Entry entry = archive.GetEntryByCueIndex(cueIndex);
+
+ string fileName = entry.CueIndex.ToString();
+ switch (waveformReader.GetByte("EncodeType"))
+ {
+ case 0:
+ fileName += ".adx";
+ break;
+ case 2:
+ fileName += ".hca";
+ break;
+ case 13:
+ fileName += ".dsp";
+ break;
+ default:
+ fileName += ".bin";
+ break;
+ }
+
+ using (Stream entryIn = entry.Open(), entryOut = File.Create(Path.Combine(baseDirectory, fileName)))
+ {
+ entryIn.CopyTo(entryOut);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ else
+ {
+ CriTable criTable = new CriTable();
+ criTable.Load(args[0] + ".acb");
+
+ Afs2Archive afs2Archive = new Afs2Archive();
+
+ List files = new List();
+ files.AddRange(Directory.GetFiles(args[0], "*.adx"));
+ files.AddRange(Directory.GetFiles(args[0], "*.hca"));
+ files.AddRange(Directory.GetFiles(args[0], "*.dsp"));
+ files.AddRange(Directory.GetFiles(args[0], "*.bin"));
+
+ foreach (string file in Directory.GetFiles(args[0], "*.*"))
+ {
+ string baseName = Path.GetFileNameWithoutExtension(file);
+
+ Afs2Entry afs2Entry = new Afs2Entry();
+ afs2Entry.CueIndex = int.Parse(baseName);
+ afs2Entry.LocalFilePath = new FileInfo(file);
+ afs2Archive.Entries.Add(afs2Entry);
+ }
+
+ MemoryStream memoryStream = new MemoryStream();
+ afs2Archive.Write(memoryStream);
+
+ criTable.Rows[0]["AwbFile"] = memoryStream.ToArray();
+ criTable.Save(args[0] + ".acb", CriTableFileMode.Adx2);
+ }
+ }
+ }
+}
diff --git a/Source/SonicAudioCmd/utf_to_cs.txt b/Source/SonicAudioCmd/utf_to_cs.txt
new file mode 100644
index 0000000..cd28cfd
--- /dev/null
+++ b/Source/SonicAudioCmd/utf_to_cs.txt
@@ -0,0 +1,134 @@
+using System;
+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.Collections;
+using SonicAudioLib.IO;
+using SonicAudioLib.CriMw;
+
+using System.Xml;
+
+namespace SonicAudioCmd
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ CreateCSharpSource(File.OpenRead(args[0]));
+ }
+
+ static void CreateCSharpSource(Stream stream)
+ {
+ using (CriTableReader reader = new CriTableReader(stream))
+ using (TextWriter textWriter = File.CreateText(reader.TableName + ".cs"))
+ {
+ textWriter.WriteLine("using System.IO;");
+ textWriter.WriteLine("using SonicAudioLib.CriMw;");
+ textWriter.WriteLine();
+ textWriter.WriteLine("namespace {0} {{", "SonicAudioLib.CriMw");
+ textWriter.WriteLine(" [CriSerializable(\"{0}\")]", reader.TableName);
+ textWriter.WriteLine(" public class {0} {{", reader.TableName);
+
+ for (int i = 0; i < reader.NumberOfFields; i++)
+ {
+ textWriter.Write(" private {0} _{1}", GetSimplifiedName(reader.GetFieldType(i)), reader.GetFieldName(i));
+
+ object defaultValue = reader.GetFieldValue(i);
+ if (defaultValue != null)
+ {
+ textWriter.Write(" = {0}", defaultValue);
+ }
+
+ textWriter.WriteLine(";");
+ }
+
+ for (int i = 0; i < reader.NumberOfFields; i++)
+ {
+ string name = reader.GetFieldName(i);
+ Type type = reader.GetFieldType(i);
+ object defaultValue = reader.GetFieldValue(i);
+
+ textWriter.WriteLine();
+ if (defaultValue != null)
+ {
+ textWriter.WriteLine(" [CriField(\"{0}\", {1}, {2})]", name, defaultValue, i);
+ }
+
+ else
+ {
+ textWriter.WriteLine(" [CriField(\"{0}\", {1})]", name, i);
+ }
+
+ textWriter.WriteLine(" public {0} {1} {{", GetSimplifiedName(reader.GetFieldType(i)), name);
+ textWriter.WriteLine(" get {");
+ textWriter.WriteLine(" return _{0};", name);
+ textWriter.WriteLine(" }");
+ textWriter.WriteLine(" set {");
+ textWriter.WriteLine(" _{0} = value;", name);
+ textWriter.WriteLine(" }");
+ textWriter.WriteLine(" }");
+ }
+ textWriter.WriteLine(" }");
+ textWriter.WriteLine("}");
+
+ while (reader.Read())
+ {
+ for (int i = 0; i < reader.NumberOfFields; i++)
+ {
+ if (reader.GetFieldType(i) == typeof(byte[]))
+ {
+ stream.Position = reader.GetPosition(i);
+ if (EndianStream.ReadInt32(stream) == 0x46545540)
+ {
+ CreateCSharpSource(reader.GetSubstream(i));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ static string GetSimplifiedName(Type type)
+ {
+ switch (Type.GetTypeCode(type))
+ {
+ case TypeCode.Byte:
+ return "byte";
+ case TypeCode.SByte:
+ return "sbyte";
+ case TypeCode.Int16:
+ return "short";
+ case TypeCode.UInt16:
+ return "ushort";
+ case TypeCode.Int32:
+ return "int";
+ case TypeCode.UInt32:
+ return "uint";
+ case TypeCode.Int64:
+ return "long";
+ case TypeCode.UInt64:
+ return "ulong";
+ case TypeCode.Single:
+ return "float";
+ case TypeCode.Double:
+ return "double";
+ case TypeCode.String:
+ return "string";
+ }
+
+ if (type == typeof(byte[]))
+ {
+ return "FileInfo";
+ }
+
+ return "DBNull";
+ }
+ }
+}
diff --git a/Source/SonicAudioCmd/utf_to_cs_memory.txt b/Source/SonicAudioCmd/utf_to_cs_memory.txt
new file mode 100644
index 0000000..9f3b37f
--- /dev/null
+++ b/Source/SonicAudioCmd/utf_to_cs_memory.txt
@@ -0,0 +1,139 @@
+using System;
+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.Collections;
+using SonicAudioLib.IO;
+using SonicAudioLib.CriMw;
+
+using System.Xml;
+
+namespace SonicAudioCmd
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ byte[] bytes = File.ReadAllBytes(args[0]);
+
+ for (int i = 0; i < bytes.Length; i++)
+ {
+ if (bytes[i] == '@' && bytes[i + 1] == 'U' && bytes[i + 2] == 'T' && bytes[i + 3] == 'F')
+ {
+ using (MemoryStream memoryStream = new MemoryStream(bytes))
+ {
+ memoryStream.Position = i;
+ CreateCSharpSource(memoryStream);
+ }
+ }
+ }
+ }
+
+ static void CreateCSharpSource(Stream stream)
+ {
+ using (CriTableReader reader = CriTableReader.Create(stream))
+ using (TextWriter textWriter = File.CreateText(reader.TableName + ".cs"))
+ {
+ textWriter.WriteLine("using System.IO;");
+ textWriter.WriteLine("using SonicAudioLib.CriMw;");
+ textWriter.WriteLine();
+ textWriter.WriteLine("namespace {0} {{", "SonicAudioLib.CriMw");
+ textWriter.WriteLine(" [CriSerializable(\"{0}\")]", reader.TableName);
+ textWriter.WriteLine(" public class {0} {{", reader.TableName);
+
+ for (int i = 0; i < reader.NumberOfFields; i++)
+ {
+ textWriter.Write(" private {0} _{1}", GetSimplifiedName(reader.GetFieldType(i)), reader.GetFieldName(i));
+
+ object defaultValue = reader.GetFieldValue(i);
+ if (defaultValue != null)
+ {
+ textWriter.Write(" = {0}", defaultValue);
+ }
+
+ textWriter.WriteLine(";");
+ }
+
+ for (int i = 0; i < reader.NumberOfFields; i++)
+ {
+ string name = reader.GetFieldName(i);
+ Type type = reader.GetFieldType(i);
+ object defaultValue = reader.GetFieldValue(i);
+ string fieldFlag = reader.GetFieldFlag(i).ToString("X2");
+
+ textWriter.WriteLine();
+ textWriter.WriteLine(" // Field flag: {0}", fieldFlag);
+ textWriter.WriteLine(" [CriField(\"{0}\", {1})]", name, i);
+ textWriter.WriteLine(" public {0} {1} {{", GetSimplifiedName(reader.GetFieldType(i)), name);
+ textWriter.WriteLine(" get {");
+ textWriter.WriteLine(" return _{0};", name);
+ textWriter.WriteLine(" }");
+ textWriter.WriteLine(" set {");
+ textWriter.WriteLine(" _{0} = value;", name);
+ textWriter.WriteLine(" }");
+ textWriter.WriteLine(" }");
+ }
+ textWriter.WriteLine(" }");
+ textWriter.WriteLine("}");
+
+ while (reader.Read())
+ {
+ for (int i = 0; i < reader.NumberOfFields; i++)
+ {
+ if (reader.GetFieldType(i) == typeof(byte[]))
+ {
+ stream.Position = reader.GetPosition(i);
+ if (EndianStream.ReadInt32(stream) == 0x46545540)
+ {
+ CreateCSharpSource(reader.GetSubstream(i));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ static string GetSimplifiedName(Type type)
+ {
+ switch (Type.GetTypeCode(type))
+ {
+ case TypeCode.Byte:
+ return "byte";
+ case TypeCode.SByte:
+ return "sbyte";
+ case TypeCode.Int16:
+ return "short";
+ case TypeCode.UInt16:
+ return "ushort";
+ case TypeCode.Int32:
+ return "int";
+ case TypeCode.UInt32:
+ return "uint";
+ case TypeCode.Int64:
+ return "long";
+ case TypeCode.UInt64:
+ return "ulong";
+ case TypeCode.Single:
+ return "float";
+ case TypeCode.Double:
+ return "double";
+ case TypeCode.String:
+ return "string";
+ }
+
+ if (type == typeof(byte[]))
+ {
+ return "FileInfo";
+ }
+
+ return "DBNull";
+ }
+ }
+}
diff --git a/Source/SonicAudioLib/Archive/ArchiveBase.cs b/Source/SonicAudioLib/Archive/ArchiveBase.cs
new file mode 100644
index 0000000..9fb16a2
--- /dev/null
+++ b/Source/SonicAudioLib/Archive/ArchiveBase.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+
+using SonicAudioLib.IO;
+using SonicAudioLib.Module;
+using System.Collections;
+
+namespace SonicAudioLib.Archive
+{
+ public abstract class EntryBase
+ {
+ protected long length;
+
+ public virtual long Position { get; set; }
+
+ public virtual long Length
+ {
+ get
+ {
+ if (FilePath != null)
+ {
+ return FilePath.Length;
+ }
+
+ return length;
+ }
+
+ set
+ {
+ length = value;
+ }
+ }
+
+ public virtual FileInfo FilePath { get; set; }
+
+ public virtual Stream Open(Stream source)
+ {
+ return new Substream(source, Position, length);
+ }
+
+ public virtual Stream Open()
+ {
+ return FilePath.OpenRead();
+ }
+ }
+
+ public abstract class ArchiveBase : ModuleBase, IEnumerable
+ {
+ protected List entries = new List();
+
+ public virtual T this[int index]
+ {
+ get
+ {
+ return entries[index];
+ }
+ }
+
+ public virtual int Count
+ {
+ get
+ {
+ return entries.Count;
+ }
+ }
+
+ public virtual void Add(T item)
+ {
+ entries.Add(item);
+ }
+
+ public virtual T Get(int index)
+ {
+ return entries[index];
+ }
+
+ public virtual void Clear()
+ {
+ entries.Clear();
+ }
+
+ public virtual bool Contains(T item)
+ {
+ return entries.Contains(item);
+ }
+
+ public virtual void CopyTo(T[] array, int arrayIndex)
+ {
+ entries.CopyTo(array, arrayIndex);
+ }
+
+ public virtual IEnumerator GetEnumerator()
+ {
+ return entries.GetEnumerator();
+ }
+
+ public virtual bool Remove(T item)
+ {
+ return entries.Remove(item);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return entries.GetEnumerator();
+ }
+ }
+}
diff --git a/Source/SonicAudioLib/Archive/CriAfs2Archive.cs b/Source/SonicAudioLib/Archive/CriAfs2Archive.cs
new file mode 100644
index 0000000..9e53087
--- /dev/null
+++ b/Source/SonicAudioLib/Archive/CriAfs2Archive.cs
@@ -0,0 +1,194 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+
+using SonicAudioLib.IO;
+using SonicAudioLib.Module;
+
+namespace SonicAudioLib.Archive
+{
+ public class CriAfs2Entry : EntryBase
+ {
+ public ushort CueIndex { get; set; }
+ }
+
+ public class CriAfs2Archive : ArchiveBase
+ {
+ public uint Align { get; set; }
+ public uint CueIndexFieldLength { get; set; }
+ public uint PositionFieldLength { get; set; }
+
+ public override void Read(Stream source)
+ {
+ if (EndianStream.ReadCString(source, 4) != "AFS2")
+ {
+ throw new Exception("No AFS2 signature found.");
+ }
+
+ uint information = EndianStream.ReadUInt32(source);
+
+ uint type = information & 0xFF;
+ if (type != 1)
+ {
+ throw new Exception($"Invalid AFS2 type ({type}). Please report the error with the AWB file.");
+ }
+
+ CueIndexFieldLength = (information & 0x00FF0000) >> 16;
+ PositionFieldLength = (information & 0x0000FF00) >> 8;
+
+ ushort entryCount = (ushort)EndianStream.ReadUInt32(source);
+ Align = EndianStream.ReadUInt32(source);
+
+ CriAfs2Entry previousEntry = null;
+ for (uint i = 0; i < entryCount; i++)
+ {
+ CriAfs2Entry afs2Entry = new CriAfs2Entry();
+
+ long cueIndexPosition = 16 + (i * CueIndexFieldLength);
+ source.Seek(cueIndexPosition, SeekOrigin.Begin);
+
+ switch (CueIndexFieldLength)
+ {
+ case 2:
+ afs2Entry.CueIndex = EndianStream.ReadUInt16(source);
+ break;
+
+ default:
+ throw new Exception($"Unknown CueIndexFieldLength ({CueIndexFieldLength}). Please report the error with the AWB file.");
+ }
+
+ long positionPosition = 16 + (entryCount * CueIndexFieldLength) + (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 Exception($"Unknown PositionFieldLength ({PositionFieldLength}). Please report the error with the AWB file.");
+ }
+
+ if (previousEntry != null)
+ {
+ previousEntry.Length = afs2Entry.Position - previousEntry.Position;
+ }
+
+ while ((afs2Entry.Position % Align) != 0)
+ {
+ afs2Entry.Position++;
+ }
+
+ 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;
+ }
+ }
+
+ entries.Add(afs2Entry);
+ previousEntry = afs2Entry;
+ }
+ }
+
+ public override void Write(Stream destination)
+ {
+ uint headerLength = (uint)(16 + (entries.Count * CueIndexFieldLength) + (entries.Count * PositionFieldLength) + PositionFieldLength);
+
+ EndianStream.WriteCString(destination, "AFS2", 4);
+ EndianStream.WriteUInt32(destination, 1 | (CueIndexFieldLength << 16) | (PositionFieldLength << 8));
+ EndianStream.WriteUInt32(destination, (ushort)entries.Count);
+ EndianStream.WriteUInt32(destination, 1);
+
+ // FIXME: Alignment support
+ VldPool vldPool = new VldPool(1);
+
+ foreach (CriAfs2Entry afs2Entry in entries)
+ {
+ switch (CueIndexFieldLength)
+ {
+ case 2:
+ EndianStream.WriteUInt16(destination, (ushort)afs2Entry.CueIndex);
+ break;
+
+ default:
+ throw new Exception($"Unknown CueIndexFieldLength ({CueIndexFieldLength}). Please set a valid length.");
+ }
+ }
+
+ foreach (CriAfs2Entry afs2Entry in entries)
+ {
+ uint entryPosition = (uint)(headerLength + vldPool.Put(afs2Entry.FilePath));
+
+ switch (PositionFieldLength)
+ {
+ case 2:
+ EndianStream.WriteUInt16(destination, (ushort)entryPosition);
+ break;
+
+ case 4:
+ EndianStream.WriteUInt32(destination, entryPosition);
+ break;
+
+ default:
+ throw new Exception($"Unknown PositionFieldLength ({PositionFieldLength}). Please set a valid length.");
+ }
+
+ afs2Entry.Position = entryPosition;
+ }
+
+ EndianStream.WriteUInt32(destination, (uint)(headerLength + vldPool.Length));
+
+ vldPool.Write(destination);
+ vldPool.Clear();
+ }
+
+ public CriAfs2Entry GetByCueIndex(uint cueIndex)
+ {
+ return entries.Single(e => e.CueIndex == cueIndex);
+ }
+
+ public override long CalculateLength()
+ {
+ long length = 16 + (entries.Count * CueIndexFieldLength) + (entries.Count * PositionFieldLength) + PositionFieldLength;
+
+ foreach (CriAfs2Entry afs2Entry in entries)
+ {
+ while ((length % Align) != 0)
+ {
+ length++;
+ }
+
+ length += afs2Entry.Length;
+ }
+
+ return length;
+ }
+
+ public void Order()
+ {
+ entries = entries.OrderBy(entry => entry.CueIndex).ToList();
+ }
+
+ public CriAfs2Archive()
+ {
+ Align = 32;
+ CueIndexFieldLength = 2;
+ PositionFieldLength = 4;
+ }
+ }
+}
diff --git a/Source/SonicAudioLib/Archive/CriCpkArchive.cs b/Source/SonicAudioLib/Archive/CriCpkArchive.cs
new file mode 100644
index 0000000..31aa296
--- /dev/null
+++ b/Source/SonicAudioLib/Archive/CriCpkArchive.cs
@@ -0,0 +1,136 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+using SonicAudioLib.IO;
+using SonicAudioLib.CriMw;
+
+namespace SonicAudioLib.Archive
+{
+ public class CriCpkEntry : EntryBase
+ {
+ public string DirectoryName { get; set; }
+ public string Name { get; set; }
+ public uint Index { get; set; }
+ public string Comment { get; set; }
+ public bool IsCompressed { get; set; }
+ }
+
+ public class CriCpkArchive : ArchiveBase
+ {
+ public override void Read(Stream source)
+ {
+ using (CriTableReader reader = CriCpkSection.Open(source, source.Position))
+ {
+ reader.Read();
+
+ if (reader.GetUInt32("CpkMode") != 1)
+ {
+ throw new Exception("Unsupported CPK type! Only TOC CPKs are supported for now.");
+ }
+
+ long tocPosition = (long)reader.GetUInt64("TocOffset");
+ long contentPosition = (long)reader.GetUInt64("ContentOffset");
+ ushort align = reader.GetUInt16("Align");
+
+ using (CriTableReader tocReader = CriCpkSection.Open(source, tocPosition))
+ {
+ while (tocReader.Read())
+ {
+ CriCpkEntry entry = new CriCpkEntry();
+ entry.DirectoryName = tocReader.GetString("DirName");
+ entry.Name = tocReader.GetString("FileName");
+ entry.Length = tocReader.GetUInt32("FileSize");
+ entry.Position = (long)tocReader.GetUInt64("FileOffset");
+ entry.Index = tocReader.GetUInt32("ID");
+ entry.Comment = tocReader.GetString("UserString");
+
+ if (entry.Length != tocReader.GetUInt32("ExtractSize"))
+ {
+ entry.IsCompressed = true;
+ }
+
+ if (contentPosition < tocPosition)
+ {
+ entry.Position += contentPosition;
+ }
+
+ else
+ {
+ entry.Position += tocPosition;
+ }
+
+ while ((entry.Position % align) != 0)
+ {
+ entry.Position++;
+ }
+
+ entries.Add(entry);
+
+ Console.WriteLine(Path.Combine(entry.DirectoryName, entry.Name));
+ }
+ }
+ }
+ }
+
+ public override void Write(Stream destination)
+ {
+ throw new NotImplementedException();
+ }
+
+ private class CriCpkSection : IDisposable
+ {
+ private Stream destination;
+ private long headerPosition;
+
+ private CriTableWriter writer;
+
+ public CriTableWriter Writer
+ {
+ get
+ {
+ return writer;
+ }
+ }
+
+ public void Dispose()
+ {
+ writer.Dispose();
+
+ long position = destination.Position;
+ uint length = (uint)(position - (headerPosition - 8));
+
+ destination.Seek(headerPosition + 8, SeekOrigin.Begin);
+ EndianStream.WriteUInt32(destination, length);
+
+ destination.Seek(position, SeekOrigin.Begin);
+ }
+
+ public static CriTableReader Open(Stream source, long position)
+ {
+ source.Seek(position, SeekOrigin.Begin);
+
+ string signature = EndianStream.ReadCString(source, 4);
+ uint flag = EndianStream.ReadUInt32(source);
+ uint tableLength = EndianStream.ReadUInt32(source);
+ uint unknown = EndianStream.ReadUInt32(source);
+
+ return CriTableReader.Create(new Substream(source, source.Position, tableLength));
+ }
+
+ public CriCpkSection(Stream destination, string signature)
+ {
+ this.destination = destination;
+ headerPosition = destination.Position;
+
+ EndianStream.WriteCString(destination, signature, 4);
+ EndianStream.WriteUInt32(destination, byte.MaxValue);
+ destination.Seek(8, SeekOrigin.Begin);
+
+ writer = CriTableWriter.Create(destination);
+ }
+ }
+ }
+}
diff --git a/Source/SonicAudioLib/Archive/HeroesPacArchive.cs b/Source/SonicAudioLib/Archive/HeroesPacArchive.cs
new file mode 100644
index 0000000..75ecd8d
--- /dev/null
+++ b/Source/SonicAudioLib/Archive/HeroesPacArchive.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+
+using SonicAudioLib.IO;
+using SonicAudioLib.Module;
+
+namespace SonicAudioLib.Archive
+{
+ public class HeroesPacEntry : EntryBase
+ {
+ public uint GlobalIndex { get; set; }
+ }
+
+ public class HeroesPacArchive : ArchiveBase
+ {
+ public override void Read(Stream source)
+ {
+ uint entryCount = EndianStream.ReadUInt32(source);
+ uint tablePosition = EndianStream.ReadUInt32(source);
+ uint vldPoolLength = EndianStream.ReadUInt32(source);
+ uint vldPoolPosition = EndianStream.ReadUInt32(source);
+
+ source.Seek(tablePosition, SeekOrigin.Begin);
+
+ HeroesPacEntry previousEntry = null;
+ for (uint i = 0; i < entryCount; i++)
+ {
+ HeroesPacEntry pacEntry = new HeroesPacEntry();
+
+ pacEntry.GlobalIndex = EndianStream.ReadUInt32(source);
+ pacEntry.Position = vldPoolPosition + EndianStream.ReadUInt32(source);
+
+ if (previousEntry != null)
+ {
+ previousEntry.Length = pacEntry.Position - previousEntry.Position;
+ }
+
+ if (i == entryCount - 1)
+ {
+ pacEntry.Length = (uint)source.Length - pacEntry.Position;
+ }
+
+ entries.Add(pacEntry);
+ previousEntry = pacEntry;
+
+ source.Seek(8, SeekOrigin.Current);
+ }
+ }
+
+ public override void Write(Stream destination)
+ {
+ long headerPosition = destination.Position;
+
+ uint headerLength = 16;
+ for (uint i = 0; i < headerLength; i++)
+ {
+ destination.WriteByte(0);
+ }
+
+ while ((destination.Position % 32) != 0)
+ {
+ destination.WriteByte(0);
+ }
+
+ uint tableLength = (uint)(entries.Count * 16);
+ uint tablePosition = (uint)destination.Position;
+
+ VldPool vldPool = new VldPool();
+
+ foreach (HeroesPacEntry pacEntry in entries)
+ {
+ uint entryPosition = (uint)vldPool.Put(pacEntry.FilePath);
+
+ EndianStream.WriteUInt32(destination, pacEntry.GlobalIndex);
+ EndianStream.WriteUInt32(destination, entryPosition);
+
+ while ((destination.Position % 16) != 0)
+ {
+ destination.WriteByte(0);
+ }
+
+ pacEntry.Position = tablePosition + tableLength + entryPosition;
+ }
+
+ vldPool.Write(destination);
+ vldPool.Clear();
+
+ destination.Seek(0, SeekOrigin.Begin);
+ EndianStream.WriteUInt32(destination, (uint)entries.Count);
+ EndianStream.WriteUInt32(destination, tablePosition);
+ EndianStream.WriteUInt32(destination, (uint)vldPool.Length);
+ EndianStream.WriteUInt32(destination, (uint)vldPool.Position);
+ }
+ }
+}
diff --git a/Source/SonicAudioLib/Collections/OrderedDictionary.cs b/Source/SonicAudioLib/Collections/OrderedDictionary.cs
new file mode 100644
index 0000000..1d06bc0
--- /dev/null
+++ b/Source/SonicAudioLib/Collections/OrderedDictionary.cs
@@ -0,0 +1,175 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace SonicAudioLib.Collections
+{
+ ///
+ /// Represents a key/value pair for an .
+ ///
+ ///
+ ///
+ public class KeyValuePair
+ {
+ public TKey Key { get; set; }
+ public TValue Value { get; set; }
+
+ public KeyValuePair(TKey key, TValue value)
+ {
+ Key = key;
+ Value = value;
+ }
+ }
+
+ ///
+ /// Represents a key/value pair collection that is accessable by its key or index.
+ ///
+ public class OrderedDictionary : IEnumerable>
+ {
+ private List> items = new List>();
+
+ ///
+ /// Gets the count of key/value pairs.
+ ///
+ public int Count
+ {
+ get
+ {
+ return items.Count;
+ }
+ }
+
+ ///
+ /// Gets the value at the specified index.
+ ///
+ public TValue this[int index]
+ {
+ get
+ {
+ return items[index].Value;
+ }
+
+ set
+ {
+ items[index].Value = value;
+ }
+ }
+
+ ///
+ /// Gets the value by the specified key.
+ ///
+ public TValue this[TKey key]
+ {
+ get
+ {
+ return items.Single(k => (k.Key).Equals(key)).Value;
+ }
+
+ set
+ {
+ items.Single(k => (k.Key).Equals(key)).Value = value;
+ }
+ }
+
+ ///
+ /// Determines whether the collection contains the specified key.
+ ///
+ public bool ContainsKey(TKey key)
+ {
+ return items.Any(k => (k.Key).Equals(key));
+ }
+
+ ///
+ /// Adds a key/value pair to end of the collection.
+ ///
+ public void Add(TKey key, TValue value)
+ {
+ items.Add(new KeyValuePair(key, value));
+ }
+
+ ///
+ /// Removes the key/value pair by its key.
+ ///
+ public bool Remove(TKey key)
+ {
+ return items.Remove(items.Single(k => (k.Key).Equals(key)));
+ }
+
+ ///
+ /// Clears all the key/value pairs.
+ ///
+ public void Clear()
+ {
+ items.Clear();
+ }
+
+ ///
+ /// Gets the index of the specified key.
+ ///
+ public int IndexOf(TKey key)
+ {
+ return items.IndexOf(items.Single(k => (k.Key).Equals(key)));
+ }
+
+ ///
+ /// Inserts a key/value pair to the specified index.
+ ///
+ public void Insert(int index, TKey key, TValue value)
+ {
+ items.Insert(index, new KeyValuePair(key, value));
+ }
+
+ ///
+ /// Removes key/value pair at the specified index.
+ ///
+ public void RemoveAt(int index)
+ {
+ items.RemoveAt(index);
+ }
+
+ ///
+ /// Gets key at the specified index.
+ ///
+ public TKey GetKeyByIndex(int index)
+ {
+ return items[index].Key;
+ }
+
+ ///
+ /// Gets value at the specified index.
+ ///
+ public TValue GetValueByIndex(int index)
+ {
+ return items[index].Value;
+ }
+
+ ///
+ /// Sets key at the specified index.
+ ///
+ public void SetKeyByIndex(int index, TKey key)
+ {
+ items[index].Key = key;
+ }
+
+ ///
+ /// Sets value at the specified index.
+ ///
+ public void SetValueByIndex(int index, TValue value)
+ {
+ items[index].Value = value;
+ }
+
+ ///
+ /// Returns an enumerator of key/value pairs.
+ ///
+ public IEnumerator> GetEnumerator()
+ {
+ return items.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return items.GetEnumerator();
+ }
+ }
+}
diff --git a/Source/SonicAudioLib/CriMw/CriField.cs b/Source/SonicAudioLib/CriMw/CriField.cs
new file mode 100644
index 0000000..24a5a9e
--- /dev/null
+++ b/Source/SonicAudioLib/CriMw/CriField.cs
@@ -0,0 +1,134 @@
+using System;
+using System.ComponentModel;
+
+namespace SonicAudioLib.CriMw
+{
+ public class CriField
+ {
+ public static readonly Type[] FieldTypes =
+ {
+ typeof(byte),
+ typeof(sbyte),
+ typeof(ushort),
+ typeof(short),
+ typeof(uint),
+ typeof(int),
+ typeof(ulong),
+ typeof(long),
+ typeof(float),
+ typeof(double),
+ typeof(string),
+ typeof(byte[]),
+ typeof(Guid),
+ };
+
+ public static object[] NullValues =
+ {
+ (byte)0,
+ (sbyte)0,
+ (ushort)0,
+ (short)0,
+ (uint)0,
+ (int)0,
+ (ulong)0,
+ (long)0,
+ (float)0.0f,
+ (double)0.0f,
+ (string)string.Empty,
+ (byte[])new byte[0],
+ (Guid)Guid.Empty,
+ };
+
+ private Type fieldType;
+ private string fieldName;
+ private object defaultValue;
+ private CriTable parent;
+
+ public int FieldTypeIndex
+ {
+ get
+ {
+ return Array.IndexOf(FieldTypes, fieldType);
+ }
+ }
+
+ public Type FieldType
+ {
+ get
+ {
+ return fieldType;
+ }
+ }
+
+ public object DefaultValue
+ {
+ get
+ {
+ return defaultValue;
+ }
+
+ set
+ {
+ defaultValue = ConvertObject(value);
+ }
+ }
+
+ public string FieldName
+ {
+ get
+ {
+ return fieldName;
+ }
+ }
+
+ public object ConvertObject(object obj)
+ {
+ if (obj == null)
+ {
+ return NullValues[FieldTypeIndex];
+ }
+
+ Type typ = obj.GetType();
+
+ if (typ == fieldType)
+ {
+ return obj;
+ }
+
+ TypeConverter typeConverter = TypeDescriptor.GetConverter(fieldType);
+
+ if (typeConverter.CanConvertFrom(typ))
+ {
+ return typeConverter.ConvertFrom(obj);
+ }
+
+ return DefaultValue;
+ }
+
+ public CriTable Parent
+ {
+ get
+ {
+ return parent;
+ }
+
+ internal set
+ {
+ parent = value;
+ }
+ }
+
+ public CriField(string name, Type type)
+ {
+ fieldName = name;
+ fieldType = type;
+ }
+
+ public CriField(string name, Type type, object defaultValue)
+ {
+ fieldName = name;
+ fieldType = type;
+ this.defaultValue = ConvertObject(defaultValue);
+ }
+ }
+}
diff --git a/Source/SonicAudioLib/CriMw/CriFieldCollection.cs b/Source/SonicAudioLib/CriMw/CriFieldCollection.cs
new file mode 100644
index 0000000..c0f9bc1
--- /dev/null
+++ b/Source/SonicAudioLib/CriMw/CriFieldCollection.cs
@@ -0,0 +1,121 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace SonicAudioLib.CriMw
+{
+ public class CriFieldCollection : IEnumerable
+ {
+ private CriTable parent;
+ private List fields = new List();
+
+ public CriField this[int index]
+ {
+ get
+ {
+ return fields[index];
+ }
+ }
+
+ public CriField this[string name]
+ {
+ get
+ {
+ return fields.Single(f => f.FieldName == name);
+ }
+ }
+
+ public int Count
+ {
+ get
+ {
+ return fields.Count;
+ }
+ }
+
+ public CriTable Parent
+ {
+ get
+ {
+ return parent;
+ }
+
+ internal set
+ {
+ parent = value;
+ }
+ }
+
+ public void Add(CriField criField)
+ {
+ criField.Parent = parent;
+ fields.Add(criField);
+ }
+
+ public CriField Add(string name, Type type)
+ {
+ CriField criField = new CriField(name, type);
+ Add(criField);
+
+ return criField;
+ }
+
+ public CriField Add(string name, Type type, object defaultValue)
+ {
+ CriField criField = new CriField(name, type, defaultValue);
+ Add(criField);
+
+ return criField;
+ }
+
+ public void Insert(int index, CriField criField)
+ {
+ if (index >= fields.Count || index < 0)
+ {
+ fields.Add(criField);
+ }
+
+ else
+ {
+ fields.Insert(index, criField);
+ }
+ }
+
+ public void Remove(CriField criField)
+ {
+ fields.Remove(criField);
+
+ // Update the objects
+ foreach (CriRow criRow in parent.Rows)
+ {
+ criRow.Records.Remove(criField);
+ }
+ }
+
+ public void RemoveAt(int index)
+ {
+ Remove(fields[index]);
+ }
+
+ internal void Clear()
+ {
+ fields.Clear();
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return ((IEnumerable)fields).GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable)fields).GetEnumerator();
+ }
+
+ public CriFieldCollection(CriTable parent)
+ {
+ this.parent = parent;
+ }
+ }
+}
diff --git a/Source/SonicAudioLib/CriMw/CriRow.cs b/Source/SonicAudioLib/CriMw/CriRow.cs
new file mode 100644
index 0000000..c95f91f
--- /dev/null
+++ b/Source/SonicAudioLib/CriMw/CriRow.cs
@@ -0,0 +1,107 @@
+using SonicAudioLib.Collections;
+using System.Collections;
+using System.Linq;
+
+namespace SonicAudioLib.CriMw
+{
+ public class CriRow : IEnumerable
+ {
+ private OrderedDictionary records = new OrderedDictionary();
+ private CriTable parent;
+
+ public object this[CriField criField]
+ {
+ get
+ {
+ return records[criField];
+ }
+
+ set
+ {
+ records[criField] = value;
+ }
+ }
+
+ public object this[int index]
+ {
+ get
+ {
+ return records[index];
+ }
+
+ set
+ {
+ records[index] = value;
+ }
+ }
+
+ public object this[string name]
+ {
+ get
+ {
+ return this[records.Single(k => (k.Key).FieldName == name).Key];
+ }
+
+ set
+ {
+ this[records.Single(k => (k.Key).FieldName == name).Key] = value;
+ }
+ }
+
+ public CriTable Parent
+ {
+ get
+ {
+ return parent;
+ }
+
+ internal set
+ {
+ parent = value;
+ }
+ }
+
+ internal OrderedDictionary Records
+ {
+ get
+ {
+ return records;
+ }
+ }
+
+ public int FieldCount
+ {
+ get
+ {
+ return records.Count;
+ }
+ }
+
+ public object[] GetValueArray()
+ {
+ object[] values = new object[records.Count];
+
+ for (int i = 0; i < records.Count; i++)
+ {
+ values[i] = records[i];
+ }
+
+ return values;
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ foreach (var keyValPair in records)
+ {
+ yield return keyValPair.Value;
+ }
+
+ yield break;
+ }
+
+ internal CriRow(CriTable parent)
+ {
+ this.parent = parent;
+ }
+ }
+}
diff --git a/Source/SonicAudioLib/CriMw/CriRowCollection.cs b/Source/SonicAudioLib/CriMw/CriRowCollection.cs
new file mode 100644
index 0000000..569b384
--- /dev/null
+++ b/Source/SonicAudioLib/CriMw/CriRowCollection.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace SonicAudioLib.CriMw
+{
+ public class CriRowCollection : IEnumerable
+ {
+ private CriTable parent;
+ private List rows = new List();
+
+ public CriRow this[int index]
+ {
+ get
+ {
+ return rows[index];
+ }
+ }
+
+ public int Count
+ {
+ get
+ {
+ return rows.Count;
+ }
+ }
+
+ public CriTable Parent
+ {
+ get
+ {
+ return parent;
+ }
+
+ internal set
+ {
+ parent = value;
+ }
+ }
+
+ public void Add(CriRow criRow)
+ {
+ criRow.Parent = parent;
+ rows.Add(criRow);
+ }
+
+ public CriRow Add(params object[] objs)
+ {
+ CriRow criRow = parent.NewRow();
+
+ object[] objects = new object[criRow.FieldCount];
+ Array.Copy(objs, objects, Math.Min(objs.Length, objects.Length));
+
+ for (int i = 0; i < criRow.FieldCount; i++)
+ {
+ criRow[i] = objects[i];
+ }
+
+ Add(criRow);
+ return criRow;
+ }
+
+ internal void Clear()
+ {
+ rows.Clear();
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return ((IEnumerable)rows).GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable)rows).GetEnumerator();
+ }
+
+ public CriRowCollection(CriTable parent)
+ {
+ this.parent = parent;
+ }
+ }
+}
diff --git a/Source/SonicAudioLib/CriMw/CriTable.Internal.cs b/Source/SonicAudioLib/CriMw/CriTable.Internal.cs
new file mode 100644
index 0000000..3121250
--- /dev/null
+++ b/Source/SonicAudioLib/CriMw/CriTable.Internal.cs
@@ -0,0 +1,52 @@
+using System;
+
+namespace SonicAudioLib.CriMw
+{
+ struct CriTableHeader
+ {
+ public static readonly string Signature = "@UTF";
+ public uint Length { get; set; }
+ public bool FirstBoolean { get; set; }
+ public bool SecondBoolean { get; set; }
+ public ushort RowsPosition { get; set; }
+ public uint StringPoolPosition { get; set; }
+ public uint DataPoolPosition { get; set; }
+ public string TableName { get; set; }
+ public ushort NumberOfFields { get; set; }
+ public ushort RowLength { get; set; }
+ public uint NumberOfRows { get; set; }
+ }
+
+ [Flags]
+ enum CriFieldFlag
+ {
+ Name = 16,
+ DefaultValue = 32,
+ RowStorage = 64,
+
+ Byte = 0,
+ SByte = 1,
+ UInt16 = 2,
+ Int16 = 3,
+ UInt32 = 4,
+ Int32 = 5,
+ UInt64 = 6,
+ Int64 = 7,
+ Float = 8,
+ Double = 9,
+ String = 10,
+ Data = 11,
+ Guid = 12,
+
+ TypeMask = 15,
+ };
+
+ struct CriTableField
+ {
+ public CriFieldFlag Flag { get; set; }
+ public string Name { get; set; }
+ public uint Position { get; set; }
+ public uint Length { get; set; }
+ public object Value { get; set; }
+ }
+}
diff --git a/Source/SonicAudioLib/CriMw/CriTable.cs b/Source/SonicAudioLib/CriMw/CriTable.cs
new file mode 100644
index 0000000..d7471e9
--- /dev/null
+++ b/Source/SonicAudioLib/CriMw/CriTable.cs
@@ -0,0 +1,143 @@
+using System;
+using System.IO;
+using System.Linq;
+
+using SonicAudioLib.IO;
+using SonicAudioLib.Module;
+
+namespace SonicAudioLib.CriMw
+{
+ public class CriTable : ModuleBase
+ {
+ private CriFieldCollection fields;
+ private CriRowCollection rows;
+ private string tableName = "(no name)";
+ private CriTableWriterSettings writerSettings;
+
+ public CriFieldCollection Fields
+ {
+ get
+ {
+ return fields;
+ }
+ }
+
+ public CriRowCollection Rows
+ {
+ get
+ {
+ return rows;
+ }
+ }
+
+ public string TableName
+ {
+ get
+ {
+ return tableName;
+ }
+
+ set
+ {
+ tableName = value;
+ }
+ }
+
+ public CriTableWriterSettings WriterSettings
+ {
+ get
+ {
+ return writerSettings;
+ }
+
+ set
+ {
+ writerSettings = value;
+ }
+ }
+
+ public void Clear()
+ {
+ rows.Clear();
+ fields.Clear();
+ }
+
+ public CriRow NewRow()
+ {
+ CriRow criRow = new CriRow(this);
+
+ foreach (CriField criField in fields)
+ {
+ criRow.Records.Add(criField, criField.DefaultValue);
+ }
+
+ return criRow;
+ }
+
+ public override void Read(Stream source)
+ {
+ using (CriTableReader reader = CriTableReader.Create(source))
+ {
+ tableName = reader.TableName;
+
+ for (int i = 0; i < reader.NumberOfFields; i++)
+ {
+ fields.Add(reader.GetFieldName(i), reader.GetFieldType(i), reader.GetFieldValue(i));
+ }
+
+ while (reader.Read())
+ {
+ rows.Add(reader.GetValueArray());
+ }
+ }
+ }
+
+ public override void Write(Stream destination)
+ {
+ using (CriTableWriter writer = CriTableWriter.Create(destination, writerSettings))
+ {
+ writer.WriteStartTable(tableName);
+
+ writer.WriteStartFieldCollection();
+ foreach (CriField criField in fields)
+ {
+ if (!rows.Any(row => row[criField] != criField.DefaultValue))
+ {
+ writer.WriteField(criField.FieldName, criField.FieldType, criField.DefaultValue);
+ }
+
+ else
+ {
+ writer.WriteField(criField.FieldName, criField.FieldType);
+ }
+ }
+ writer.WriteEndFieldCollection();
+
+ foreach (CriRow criRow in rows)
+ {
+ writer.WriteRow(true, criRow.GetValueArray());
+ }
+
+ writer.WriteEndTable();
+ }
+ }
+
+ public override long CalculateLength()
+ {
+ // TODO
+ return base.CalculateLength();
+ }
+
+ public CriTable()
+ {
+ fields = new CriFieldCollection(this);
+ rows = new CriRowCollection(this);
+ writerSettings = new CriTableWriterSettings();
+ }
+
+ public CriTable(string tableName) : this()
+ {
+ this.tableName = tableName;
+ }
+ }
+}
diff --git a/Source/SonicAudioLib/CriMw/CriTableReader.cs b/Source/SonicAudioLib/CriMw/CriTableReader.cs
new file mode 100644
index 0000000..fb6bd59
--- /dev/null
+++ b/Source/SonicAudioLib/CriMw/CriTableReader.cs
@@ -0,0 +1,702 @@
+using SonicAudioLib.Collections;
+using SonicAudioLib.IO;
+using System;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace SonicAudioLib.CriMw
+{
+ public class CriTableReader : IDisposable
+ {
+ private OrderedDictionary fields;
+ private Stream source;
+ private CriTableHeader header;
+ private int rowIndex = -1;
+ private uint headerPosition;
+ private bool leaveOpen;
+
+ public object this[int fieldIndex]
+ {
+ get
+ {
+ return GetValue(fieldIndex);
+ }
+ }
+
+ public object this[string fieldName]
+ {
+ get
+ {
+ return GetValue(fieldName);
+ }
+ }
+
+ public ushort NumberOfFields
+ {
+ get
+ {
+ return header.NumberOfFields;
+ }
+ }
+
+ public uint NumberOfRows
+ {
+ get
+ {
+ return header.NumberOfRows;
+ }
+ }
+
+ public string TableName
+ {
+ get
+ {
+ return header.TableName;
+ }
+ }
+
+ public int CurrentRow
+ {
+ get
+ {
+ return rowIndex;
+ }
+ }
+
+ public Stream SourceStream
+ {
+ get
+ {
+ return source;
+ }
+ }
+
+ private void ReadTable()
+ {
+ headerPosition = (uint)source.Position;
+
+ if (EndianStream.ReadCString(source, 4) != CriTableHeader.Signature)
+ {
+ throw new Exception("No @UTF signature found.");
+ }
+
+ header.Length = ReadUInt32() + 0x8;
+ header.FirstBoolean = ReadBoolean();
+ header.SecondBoolean = ReadBoolean();
+ header.RowsPosition = (ushort)(ReadUInt16() + 0x8);
+ header.StringPoolPosition = ReadUInt32() + 0x8;
+ header.DataPoolPosition = ReadUInt32() + 0x8;
+ header.TableName = ReadString();
+ header.NumberOfFields = ReadUInt16();
+ header.RowLength = ReadUInt16();
+ header.NumberOfRows = ReadUInt32();
+
+ if (header.FirstBoolean)
+ {
+ throw new Exception($"Invalid boolean ({header.FirstBoolean}. Please report the error with the file.");
+ }
+
+ for (ushort i = 0; i < header.NumberOfFields; i++)
+ {
+ CriTableField field = new CriTableField();
+
+ field.Flag = (CriFieldFlag)ReadByte();
+
+ if (field.Flag.HasFlag(CriFieldFlag.Name))
+ {
+ field.Name = ReadString();
+ }
+
+ if (field.Flag.HasFlag(CriFieldFlag.DefaultValue))
+ {
+ if (field.Flag.HasFlag(CriFieldFlag.Data))
+ {
+ uint vldPosition;
+ uint vldLength;
+
+ ReadData(out vldPosition, out vldLength);
+
+ field.Position = vldPosition;
+ field.Length = vldLength;
+ }
+
+ else
+ {
+ field.Value = ReadValue(field.Flag);
+ }
+ }
+
+ // Not even per row, and not even constant value? Then there's no storage.
+ else if (!field.Flag.HasFlag(CriFieldFlag.RowStorage) && !field.Flag.HasFlag(CriFieldFlag.DefaultValue))
+ {
+ if (field.Flag.HasFlag(CriFieldFlag.Data))
+ {
+ field.Position = 0;
+ field.Length = 0;
+ }
+
+ else
+ {
+ field.Value = CriField.NullValues[(byte)field.Flag & 0x0F];
+ }
+ }
+
+ fields.Add(field.Name, field);
+ }
+ }
+
+ public string GetFieldName(int fieldIndex)
+ {
+ return fields[fieldIndex].Name;
+ }
+
+ public Type GetFieldType(int fieldIndex)
+ {
+ return CriField.FieldTypes[(byte)fields[fieldIndex].Flag & 0x0F];
+ }
+
+ public Type GetFieldType(string fieldName)
+ {
+ return CriField.FieldTypes[(byte)fields[fieldName].Flag & 0x0F];
+ }
+
+ public object GetFieldValue(int fieldIndex)
+ {
+ return fields[fieldIndex].Value;
+ }
+
+ public byte GetFieldFlag(string fieldName)
+ {
+ return (byte)fields[fieldName].Flag;
+ }
+
+ public byte GetFieldFlag(int fieldIndex)
+ {
+ return (byte)fields[fieldIndex].Flag;
+ }
+
+ public object GetFieldValue(string fieldName)
+ {
+ return fields[fieldName].Value;
+ }
+
+ public CriField GetField(int fieldIndex)
+ {
+ return new CriField(GetFieldName(fieldIndex), GetFieldType(fieldIndex), GetFieldValue(fieldIndex));
+ }
+
+ public CriField GetField(string fieldName)
+ {
+ return new CriField(fieldName, GetFieldType(fieldName), GetFieldValue(fieldName));
+ }
+
+ private void GoToValue(int fieldIndex)
+ {
+ long position = headerPosition + header.RowsPosition + (header.RowLength * rowIndex);
+
+ for (int i = 0; i < fieldIndex; i++)
+ {
+ if (!fields[i].Flag.HasFlag(CriFieldFlag.RowStorage))
+ {
+ continue;
+ }
+
+ switch (fields[i].Flag & CriFieldFlag.TypeMask)
+ {
+ case CriFieldFlag.Byte:
+ case CriFieldFlag.SByte:
+ position += 1;
+ break;
+ case CriFieldFlag.Int16:
+ case CriFieldFlag.UInt16:
+ position += 2;
+ break;
+ case CriFieldFlag.Int32:
+ case CriFieldFlag.UInt32:
+ case CriFieldFlag.Float:
+ case CriFieldFlag.String:
+ position += 4;
+ break;
+ case CriFieldFlag.Int64:
+ case CriFieldFlag.UInt64:
+ case CriFieldFlag.Double:
+ case CriFieldFlag.Data:
+ position += 8;
+ break;
+ }
+ }
+
+ source.Position = position;
+ }
+
+ public bool Read()
+ {
+ if (rowIndex + 1 >= header.NumberOfRows)
+ {
+ return false;
+ }
+
+ rowIndex++;
+ return true;
+ }
+
+ public bool MoveToRow(int rowIndex)
+ {
+ if (rowIndex >= header.NumberOfRows)
+ {
+ return false;
+ }
+
+ this.rowIndex = rowIndex;
+ return true;
+ }
+
+ public object[] GetValueArray()
+ {
+ object[] values = new object[header.NumberOfFields];
+
+ for (int i = 0; i < header.NumberOfFields; i++)
+ {
+ if (fields[i].Flag.HasFlag(CriFieldFlag.Data))
+ {
+ values[i] = GetData(i);
+ }
+
+ else
+ {
+ values[i] = GetValue(i);
+ }
+ }
+
+ return values;
+ }
+
+ public object GetValue(int fieldIndex)
+ {
+ if (fieldIndex < 0 || fieldIndex >= fields.Count)
+ {
+ return null;
+ }
+
+ if (!fields[fieldIndex].Flag.HasFlag(CriFieldFlag.RowStorage))
+ {
+ if (fields[fieldIndex].Flag.HasFlag(CriFieldFlag.Data))
+ {
+ return new Substream(source, 0, 0);
+ }
+
+ return fields[fieldIndex].Value;
+ }
+
+ GoToValue(fieldIndex);
+ return ReadValue(fields[fieldIndex].Flag);
+ }
+
+ public object GetValue(string fieldName)
+ {
+ return GetValue(fields.IndexOf(fieldName));
+ }
+
+ public T GetValue(int fieldIndex)
+ {
+ return (T)GetValue(fieldIndex);
+ }
+
+ public T GetValue(string fieldName)
+ {
+ return (T)GetValue(fieldName);
+ }
+
+ public byte GetByte(int fieldIndex)
+ {
+ return (byte)GetValue(fieldIndex);
+ }
+
+ public byte GetByte(string fieldName)
+ {
+ return (byte)GetValue(fieldName);
+ }
+
+ public sbyte GetSByte(int fieldIndex)
+ {
+ return (sbyte)GetValue(fieldIndex);
+ }
+
+ public sbyte GetSByte(string fieldName)
+ {
+ return (sbyte)GetValue(fieldName);
+ }
+
+ public ushort GetUInt16(int fieldIndex)
+ {
+ return (ushort)GetValue(fieldIndex);
+ }
+
+ public ushort GetUInt16(string fieldName)
+ {
+ return (ushort)GetValue(fieldName);
+ }
+
+ public short GetInt16(int fieldIndex)
+ {
+ return (short)GetValue(fieldIndex);
+ }
+
+ public short GetInt16(string fieldName)
+ {
+ return (short)GetValue(fieldName);
+ }
+
+ public uint GetUInt32(int fieldIndex)
+ {
+ return (uint)GetValue(fieldIndex);
+ }
+
+ public uint GetUInt32(string fieldName)
+ {
+ return (uint)GetValue(fieldName);
+ }
+
+ public int GetInt32(int fieldIndex)
+ {
+ return (int)GetValue(fieldIndex);
+ }
+
+ public int GetInt32(string fieldName)
+ {
+ return (int)GetValue(fieldName);
+ }
+
+ public ulong GetUInt64(int fieldIndex)
+ {
+ return (ulong)GetValue(fieldIndex);
+ }
+
+ public ulong GetUInt64(string fieldName)
+ {
+ return (ulong)GetValue(fieldName);
+ }
+
+ public long GetInt64(int fieldIndex)
+ {
+ return (long)GetValue(fieldIndex);
+ }
+
+ public long GetInt64(string fieldName)
+ {
+ return (long)GetValue(fieldName);
+ }
+
+ public float GetFloat(int fieldIndex)
+ {
+ return (float)GetValue(fieldIndex);
+ }
+
+ public float GetFloat(string fieldName)
+ {
+ return (float)GetValue(fieldName);
+ }
+
+ public double GetDouble(int fieldIndex)
+ {
+ return (double)GetValue(fieldIndex);
+ }
+
+ public double GetDouble(string fieldName)
+ {
+ return (double)GetValue(fieldName);
+ }
+
+ public string GetString(int fieldIndex)
+ {
+ return (string)GetValue(fieldIndex);
+ }
+
+ public string GetString(string fieldName)
+ {
+ return (string)GetValue(fieldName);
+ }
+
+ public Substream GetSubstream(int fieldIndex)
+ {
+ return (Substream)GetValue(fieldIndex);
+ }
+
+ public Substream GetSubstream(string fieldName)
+ {
+ return (Substream)GetValue(fieldName);
+ }
+
+ public byte[] GetData(int fieldIndex)
+ {
+ return GetSubstream(fieldIndex).ToArray();
+ }
+
+ public byte[] GetData(string fieldName)
+ {
+ return GetData(fields.IndexOf(fieldName));
+ }
+
+ public uint GetLength(int fieldIndex)
+ {
+ if (fieldIndex < 0 || fieldIndex >= fields.Count)
+ {
+ return 0;
+ }
+
+ if (!fields[fieldIndex].Flag.HasFlag(CriFieldFlag.RowStorage))
+ {
+ return fields[fieldIndex].Length;
+ }
+
+ uint vldPosition;
+ uint vldLength;
+
+ GoToValue(fieldIndex);
+ ReadData(out vldPosition, out vldLength);
+ return vldLength;
+ }
+
+ public uint GetLength(string fieldName)
+ {
+ return GetLength(fields.IndexOf(fieldName));
+ }
+
+ public uint GetPosition(int fieldIndex)
+ {
+ if (fieldIndex < 0 || fieldIndex >= fields.Count)
+ {
+ return 0;
+ }
+
+ if (!fields[fieldIndex].Flag.HasFlag(CriFieldFlag.RowStorage))
+ {
+ return fields[fieldIndex].Position;
+ }
+
+ uint vldPosition;
+ uint vldLength;
+
+ GoToValue(fieldIndex);
+ ReadData(out vldPosition, out vldLength);
+ return (uint)(headerPosition + header.DataPoolPosition + vldPosition);
+ }
+
+ public uint GetPosition(string fieldName)
+ {
+ return GetPosition(fields.IndexOf(fieldName));
+ }
+
+ public bool GetBoolean(int fieldIndex)
+ {
+ return (byte)GetValue(fieldIndex) > 0;
+ }
+
+ public bool GetBoolean(string fieldName)
+ {
+ return (byte)GetValue(fieldName) > 0;
+ }
+
+ public Guid GetGuid(int fieldIndex)
+ {
+ return (Guid)GetValue(fieldIndex);
+ }
+
+ public Guid GetGuid(string fieldName)
+ {
+ return (Guid)GetValue(fieldName);
+ }
+
+ private byte[] ReadBytes(int length)
+ {
+ byte[] buff = new byte[length];
+ source.Read(buff, 0, length);
+ return buff;
+ }
+
+ private byte ReadByte()
+ {
+ return EndianStream.ReadByte(source);
+ }
+
+ private bool ReadBoolean()
+ {
+ return EndianStream.ReadBoolean(source);
+ }
+
+ private sbyte ReadSByte()
+ {
+ return EndianStream.ReadSByte(source);
+ }
+
+ private ushort ReadUInt16()
+ {
+ return EndianStream.ReadUInt16BE(source);
+ }
+
+ private short ReadInt16()
+ {
+ return EndianStream.ReadInt16BE(source);
+ }
+
+ private uint ReadUInt32()
+ {
+ return EndianStream.ReadUInt32BE(source);
+ }
+
+ private int ReadInt32()
+ {
+ return EndianStream.ReadInt32BE(source);
+ }
+
+ private ulong ReadUInt64()
+ {
+ return EndianStream.ReadUInt64BE(source);
+ }
+
+ private long ReadInt64()
+ {
+ return EndianStream.ReadInt64BE(source);
+ }
+
+ private float ReadFloat()
+ {
+ return EndianStream.ReadFloatBE(source);
+ }
+
+ private double ReadDouble()
+ {
+ return EndianStream.ReadDoubleBE(source);
+ }
+
+ private string ReadString()
+ {
+ int stringPosition = ReadInt32();
+
+ long previousPosition = source.Position;
+
+ source.Position = headerPosition + header.StringPoolPosition + stringPosition;
+ string strResult = EndianStream.ReadCString(source, Encoding.Default);
+ source.Position = previousPosition;
+
+ if (strResult == "" ||
+ (strResult == header.TableName && stringPosition == 0))
+ {
+ return null;
+ }
+
+ return strResult;
+ }
+
+ private void ReadData(out uint vldPosition, out uint vldLength)
+ {
+ vldPosition = ReadUInt32();
+ vldLength = ReadUInt32();
+ }
+
+ private Guid ReadGuid()
+ {
+ byte[] buffer = new byte[16];
+ source.Read(buffer, 0, buffer.Length);
+ return new Guid(buffer);
+ }
+
+ private object ReadValue(CriFieldFlag fieldFlag)
+ {
+ switch (fieldFlag & CriFieldFlag.TypeMask)
+ {
+ case CriFieldFlag.Byte:
+ return ReadByte();
+ case CriFieldFlag.SByte:
+ return ReadSByte();
+ case CriFieldFlag.UInt16:
+ return ReadUInt16();
+ case CriFieldFlag.Int16:
+ return ReadInt16();
+ case CriFieldFlag.UInt32:
+ return ReadUInt32();
+ case CriFieldFlag.Int32:
+ return ReadInt32();
+ case CriFieldFlag.UInt64:
+ return ReadUInt64();
+ case CriFieldFlag.Int64:
+ return ReadInt64();
+ case CriFieldFlag.Float:
+ return ReadFloat();
+ case CriFieldFlag.Double:
+ return ReadDouble();
+ case CriFieldFlag.String:
+ return ReadString();
+ case CriFieldFlag.Data:
+ {
+ uint vldPosition;
+ uint vldLength;
+
+ ReadData(out vldPosition, out vldLength);
+
+ // SecondBoolean being true, check if utf table
+ if (vldPosition > 0 && vldLength == 0)
+ {
+ source.Position = headerPosition + header.DataPoolPosition + vldPosition;
+
+ if (Encoding.ASCII.GetString(ReadBytes(4)) == "@UTF")
+ {
+ vldLength = ReadUInt32() + 8;
+ }
+ }
+
+ return new Substream(source, headerPosition + header.DataPoolPosition + vldPosition, vldLength);
+ }
+ case CriFieldFlag.Guid:
+ return ReadGuid();
+ }
+
+ return null;
+ }
+
+ public void Dispose()
+ {
+ fields.Clear();
+
+ if (!leaveOpen)
+ {
+ source.Close();
+ }
+
+ GC.SuppressFinalize(this);
+ }
+
+ public static CriTableReader Create(byte[] sourceByteArray)
+ {
+ Stream source = new MemoryStream(sourceByteArray);
+ return Create(source);
+ }
+
+ public static CriTableReader Create(string sourceFileName)
+ {
+ Stream source = File.OpenRead(sourceFileName);
+ return Create(source);
+ }
+
+ public static CriTableReader Create(Stream source)
+ {
+ return Create(source, false);
+ }
+
+ public static CriTableReader Create(Stream source, bool leaveOpen)
+ {
+ return new CriTableReader(source, leaveOpen);
+ }
+
+ private CriTableReader(Stream source, bool leaveOpen)
+ {
+ this.source = source;
+ header = new CriTableHeader();
+ fields = new OrderedDictionary();
+ this.leaveOpen = leaveOpen;
+
+ ReadTable();
+ }
+ }
+}
diff --git a/Source/SonicAudioLib/CriMw/CriTableWriter.cs b/Source/SonicAudioLib/CriMw/CriTableWriter.cs
new file mode 100644
index 0000000..4a7b9c5
--- /dev/null
+++ b/Source/SonicAudioLib/CriMw/CriTableWriter.cs
@@ -0,0 +1,713 @@
+using System;
+using System.IO;
+using System.Text;
+using System.ComponentModel;
+
+using SonicAudioLib.Collections;
+using SonicAudioLib.IO;
+using SonicAudioLib.Module;
+
+namespace SonicAudioLib.CriMw
+{
+ public class CriTableWriter : IDisposable
+ {
+ public enum Status
+ {
+ Begin,
+ Start,
+ FieldCollection,
+ Row,
+ Idle,
+ End,
+ }
+
+ private CriTableWriterSettings settings;
+ private OrderedDictionary fields;
+ private Stream destination;
+ private CriTableHeader header;
+ private VldPool vldPool;
+ private StringPool stringPool;
+ private uint headerPosition;
+ private uint endPosition;
+
+ private Status status = Status.Begin;
+
+ public Status CurrentStatus
+ {
+ get
+ {
+ return status;
+ }
+ }
+
+ public Stream DestinationStream
+ {
+ get
+ {
+ return destination;
+ }
+ }
+
+ public void WriteStartTable()
+ {
+ WriteStartTable("(no name)");
+ }
+
+ public void WriteStartTable(string tableName)
+ {
+ if (status != Status.Begin)
+ {
+ throw new InvalidOperationException("Attempted to start table when the status wasn't Begin");
+ }
+
+ status = Status.Start;
+
+ headerPosition = (uint)destination.Position;
+ header.TableName = tableName;
+
+ if (settings.PutBlankString)
+ {
+ stringPool.Put(StringPool.AdxBlankString);
+ }
+
+ EndianStream.WriteCString(destination, CriTableHeader.Signature, 4);
+ WriteUInt32(uint.MinValue);
+ WriteBoolean(false);
+ WriteBoolean(false);
+ WriteUInt16(ushort.MinValue);
+ WriteUInt32(uint.MinValue);
+ WriteUInt32(uint.MinValue);
+ WriteString(tableName);
+ WriteUInt16(ushort.MinValue);
+ WriteUInt16(ushort.MinValue);
+ WriteUInt32(uint.MinValue);
+ }
+
+ public void WriteEndTable()
+ {
+ if (status == Status.FieldCollection)
+ {
+ WriteEndFieldCollection();
+ }
+
+ if (status == Status.Row)
+ {
+ WriteEndRow();
+ }
+
+ status = Status.End;
+
+ destination.Seek(headerPosition + header.RowsPosition + (header.RowLength * header.NumberOfRows), SeekOrigin.Begin);
+
+ stringPool.Write(destination);
+ header.StringPoolPosition = (uint)stringPool.Position - headerPosition;
+
+ while ((destination.Position % vldPool.Align) != 0)
+ {
+ destination.WriteByte(0);
+ }
+
+ vldPool.Write(destination);
+ header.DataPoolPosition = (uint)vldPool.Position - headerPosition;
+
+ while ((destination.Position % vldPool.Align) != 0)
+ {
+ destination.WriteByte(0);
+ }
+
+ header.Length = (uint)destination.Position - headerPosition;
+
+ header.FirstBoolean = false;
+ header.SecondBoolean = false;
+
+ destination.Position = headerPosition + 4;
+ WriteUInt32(header.Length - 8);
+ WriteBoolean(header.FirstBoolean);
+ WriteBoolean(header.SecondBoolean);
+ WriteUInt16((ushort)(header.RowsPosition - 8));
+ WriteUInt32(header.StringPoolPosition - 8);
+ WriteUInt32(header.DataPoolPosition - 8);
+ destination.Seek(4, SeekOrigin.Current);
+ WriteUInt16(header.NumberOfFields);
+ WriteUInt16(header.RowLength);
+ WriteUInt32(header.NumberOfRows);
+ destination.Seek(0, SeekOrigin.End);
+ }
+
+ public void WriteStartFieldCollection()
+ {
+ if (status != Status.Start)
+ {
+ throw new InvalidOperationException("Attempted to start field collection when the status wasn't Start");
+ }
+
+ status = Status.FieldCollection;
+ }
+
+ public void WriteField(string fieldName, Type fieldType, object defaultValue)
+ {
+ if (status != Status.FieldCollection)
+ {
+ WriteStartFieldCollection();
+ }
+
+ CriFieldFlag fieldFlag = (CriFieldFlag)Array.IndexOf(CriField.FieldTypes, fieldType);
+
+ if (!string.IsNullOrEmpty(fieldName))
+ {
+ fieldFlag |= CriFieldFlag.Name;
+ }
+
+ if (defaultValue != null)
+ {
+ fieldFlag |= CriFieldFlag.DefaultValue;
+ }
+
+ CriTableField field = new CriTableField
+ {
+ Flag = fieldFlag,
+ Name = fieldName,
+ Value = defaultValue
+ };
+
+ WriteByte((byte)field.Flag);
+
+ if (!string.IsNullOrEmpty(fieldName))
+ {
+ WriteString(field.Name);
+ }
+
+ if (defaultValue != null)
+ {
+ WriteValue(defaultValue);
+ }
+
+ fields.Add(fieldName, field);
+ header.NumberOfFields++;
+ }
+
+ public void WriteField(string fieldName, Type fieldType)
+ {
+ if (status != Status.FieldCollection)
+ {
+ WriteStartFieldCollection();
+ }
+
+ CriFieldFlag fieldFlag = (CriFieldFlag)Array.IndexOf(CriField.FieldTypes, fieldType) | CriFieldFlag.RowStorage;
+
+ if (!string.IsNullOrEmpty(fieldName))
+ {
+ fieldFlag |= CriFieldFlag.Name;
+ }
+
+ CriTableField field = new CriTableField
+ {
+ Flag = fieldFlag,
+ Name = fieldName
+ };
+
+ WriteByte((byte)field.Flag);
+
+ if (!string.IsNullOrEmpty(fieldName))
+ {
+ WriteString(field.Name);
+ }
+
+ fields.Add(fieldName, field);
+ header.NumberOfFields++;
+ }
+
+ public void WriteField(CriField criField)
+ {
+ WriteField(criField.FieldName, criField.FieldType);
+ }
+
+ public void WriteEndFieldCollection()
+ {
+ if (status != Status.FieldCollection)
+ {
+ throw new InvalidOperationException("Attempted to end field collection when the status wasn't FieldCollection");
+ }
+
+ status = Status.Idle;
+
+ header.RowsPosition = (ushort)(destination.Position - headerPosition);
+ header.RowLength = CalculateRowLength();
+ }
+
+ public void WriteStartRow()
+ {
+ if (status == Status.FieldCollection)
+ {
+ WriteEndFieldCollection();
+ }
+
+ if (status != Status.Idle)
+ {
+ throw new InvalidOperationException("Attempted to start row when the status wasn't Idle");
+ }
+
+ status = Status.Row;
+
+ header.NumberOfRows++;
+
+ destination.Position = headerPosition + header.RowsPosition + (header.NumberOfRows * header.RowLength);
+ byte[] buffer = new byte[header.RowLength];
+ destination.Write(buffer, 0, buffer.Length);
+ }
+
+ public void WriteValue(int fieldIndex, object rowValue)
+ {
+ if (!fields[fieldIndex].Flag.HasFlag(CriFieldFlag.RowStorage) || rowValue == null)
+ {
+ return;
+ }
+
+ GoToValue(fieldIndex);
+ WriteValue(rowValue);
+ }
+
+ public void WriteValue(string fieldName, object rowValue)
+ {
+ WriteValue(fields.IndexOf(fieldName));
+ }
+
+ private void GoToValue(int fieldIndex)
+ {
+ long position = headerPosition + header.RowsPosition + (header.RowLength * (header.NumberOfRows - 1));
+
+ for (int i = 0; i < fieldIndex; i++)
+ {
+ if (!fields[i].Flag.HasFlag(CriFieldFlag.RowStorage))
+ {
+ continue;
+ }
+
+ switch (fields[i].Flag & CriFieldFlag.TypeMask)
+ {
+ case CriFieldFlag.Byte:
+ case CriFieldFlag.SByte:
+ position += 1;
+ break;
+ case CriFieldFlag.Int16:
+ case CriFieldFlag.UInt16:
+ position += 2;
+ break;
+ case CriFieldFlag.Int32:
+ case CriFieldFlag.UInt32:
+ case CriFieldFlag.Float:
+ case CriFieldFlag.String:
+ position += 4;
+ break;
+ case CriFieldFlag.Int64:
+ case CriFieldFlag.UInt64:
+ case CriFieldFlag.Double:
+ case CriFieldFlag.Data:
+ position += 8;
+ break;
+ }
+ }
+
+ destination.Position = position;
+ }
+
+ private ushort CalculateRowLength()
+ {
+ ushort length = 0;
+
+ for (int i = 0; i < fields.Count; i++)
+ {
+ if (!fields[i].Flag.HasFlag(CriFieldFlag.RowStorage))
+ {
+ continue;
+ }
+
+ switch (fields[i].Flag & CriFieldFlag.TypeMask)
+ {
+ case CriFieldFlag.Byte:
+ case CriFieldFlag.SByte:
+ length += 1;
+ break;
+ case CriFieldFlag.Int16:
+ case CriFieldFlag.UInt16:
+ length += 2;
+ break;
+ case CriFieldFlag.Int32:
+ case CriFieldFlag.UInt32:
+ case CriFieldFlag.Float:
+ case CriFieldFlag.String:
+ length += 4;
+ break;
+ case CriFieldFlag.Int64:
+ case CriFieldFlag.UInt64:
+ case CriFieldFlag.Double:
+ case CriFieldFlag.Data:
+ length += 8;
+ break;
+ }
+ }
+
+ return length;
+ }
+
+ public void WriteEndRow()
+ {
+ if (status != Status.Row)
+ {
+ throw new InvalidOperationException("Attempted to end row when the status wasn't Row");
+ }
+
+ status = Status.Idle;
+ }
+
+ public void WriteRow(bool close, params object[] rowValues)
+ {
+ WriteStartRow();
+
+ for (int i = 0; i < Math.Min(rowValues.Length, fields.Count); i++)
+ {
+ WriteValue(i, rowValues[i]);
+ }
+
+ if (close)
+ {
+ WriteEndRow();
+ }
+ }
+
+ private void WriteByte(byte value)
+ {
+ EndianStream.WriteByte(destination, value);
+ }
+
+ private void WriteBoolean(bool value)
+ {
+ EndianStream.WriteBoolean(destination, value);
+ }
+
+ private void WriteSByte(sbyte value)
+ {
+ EndianStream.WriteSByte(destination, value);
+ }
+
+ private void WriteUInt16(ushort value)
+ {
+ EndianStream.WriteUInt16BE(destination, value);
+ }
+
+ private void WriteInt16(short value)
+ {
+ EndianStream.WriteInt16BE(destination, value);
+ }
+
+ private void WriteUInt32(uint value)
+ {
+ EndianStream.WriteUInt32BE(destination, value);
+ }
+
+ private void WriteInt32(int value)
+ {
+ EndianStream.WriteInt32BE(destination, value);
+ }
+
+ private void WriteUInt64(ulong value)
+ {
+ EndianStream.WriteUInt64BE(destination, value);
+ }
+
+ private void WriteInt64(long value)
+ {
+ EndianStream.WriteInt64BE(destination, value);
+ }
+
+ private void WriteFloat(float value)
+ {
+ EndianStream.WriteFloatBE(destination, value);
+ }
+
+ private void WriteDouble(double value)
+ {
+ EndianStream.WriteDoubleBE(destination, value);
+ }
+
+ private void WriteString(string value)
+ {
+ if (settings.RemoveDuplicateStrings && stringPool.ContainsString(value))
+ {
+ WriteUInt32((uint)stringPool.GetStringPosition(value));
+ }
+
+ else
+ {
+ WriteUInt32((uint)stringPool.Put(value));
+ }
+ }
+
+ private void WriteData(byte[] data)
+ {
+ WriteUInt32((uint)vldPool.Put(data));
+ WriteUInt32((uint)data.Length);
+ }
+
+ private void WriteStream(Stream stream)
+ {
+ WriteUInt32((uint)vldPool.Put(stream));
+ WriteUInt32((uint)stream.Length);
+ }
+
+ private void WriteFile(FileInfo fileInfo)
+ {
+ WriteUInt32((uint)vldPool.Put(fileInfo));
+ WriteUInt32((uint)fileInfo.Length);
+ }
+
+ private void WriteModule(ModuleBase module)
+ {
+ WriteUInt32((uint)vldPool.Put(module));
+ WriteUInt32((uint)module.CalculateLength());
+ }
+
+ private void WriteGuid(Guid guid)
+ {
+ byte[] buffer = guid.ToByteArray();
+ destination.Write(buffer, 0, buffer.Length);
+ }
+
+ private void WriteValue(object value)
+ {
+ if (value == null)
+ {
+ return;
+ }
+
+ if (value is byte)
+ {
+ WriteByte((byte)value);
+ }
+
+ else if (value is sbyte)
+ {
+ WriteSByte((sbyte)value);
+ }
+
+ else if (value is ushort)
+ {
+ WriteUInt16((ushort)value);
+ }
+
+ else if (value is short)
+ {
+ WriteInt16((short)value);
+ }
+
+ else if (value is uint)
+ {
+ WriteUInt32((uint)value);
+ }
+
+ else if (value is int)
+ {
+ WriteInt32((int)value);
+ }
+
+ else if (value is ulong)
+ {
+ WriteUInt64((ulong)value);
+ }
+
+ else if (value is long)
+ {
+ WriteInt64((long)value);
+ }
+
+ else if (value is float)
+ {
+ WriteFloat((float)value);
+ }
+
+ else if (value is double)
+ {
+ WriteDouble((double)value);
+ }
+
+ else if (value is string)
+ {
+ WriteString((string)value);
+ }
+
+ else if (value is byte[])
+ {
+ WriteData((byte[])value);
+ }
+
+ else if (value is Stream)
+ {
+ WriteStream((Stream)value);
+ }
+
+ else if (value is FileInfo)
+ {
+ WriteFile((FileInfo)value);
+ }
+
+ else if (value is ModuleBase)
+ {
+ WriteModule((ModuleBase)value);
+ }
+
+ else if (value is Guid)
+ {
+ WriteGuid((Guid)value);
+ }
+ }
+
+ public void Dispose()
+ {
+ fields.Clear();
+ stringPool.Clear();
+ vldPool.Clear();
+
+ if (!settings.LeaveOpen)
+ {
+ destination.Close();
+ }
+ }
+
+ public static CriTableWriter Create(string destinationFileName)
+ {
+ return Create(destinationFileName, new CriTableWriterSettings());
+ }
+
+ public static CriTableWriter Create(string destinationFileName, CriTableWriterSettings settings)
+ {
+ Stream destination = File.Create(destinationFileName);
+ return new CriTableWriter(destination, settings);
+ }
+
+ public static CriTableWriter Create(Stream destination)
+ {
+ return new CriTableWriter(destination, new CriTableWriterSettings());
+ }
+
+ public static CriTableWriter Create(Stream destination, CriTableWriterSettings settings)
+ {
+ return new CriTableWriter(destination, settings);
+ }
+
+ private CriTableWriter(Stream destination, CriTableWriterSettings settings)
+ {
+ this.destination = destination;
+ this.settings = settings;
+
+ header = new CriTableHeader();
+ fields = new OrderedDictionary();
+ stringPool = new StringPool(settings.EncodingType);
+ vldPool = new VldPool(settings.Align);
+ }
+ }
+
+ public class CriTableWriterSettings
+ {
+ private uint align = 1;
+ private bool putBlankString = true;
+ private bool leaveOpen = false;
+ private Encoding encodingType = Encoding.GetEncoding("shift-jis");
+ private bool removeDuplicateStrings = true;
+
+ public uint Align
+ {
+ get
+ {
+ return align;
+ }
+
+ set
+ {
+ if (value <= 0)
+ {
+ value = 1;
+ }
+
+ align = value;
+ }
+ }
+
+ public bool PutBlankString
+ {
+ get
+ {
+ return putBlankString;
+ }
+
+ set
+ {
+ putBlankString = value;
+ }
+ }
+
+ public bool LeaveOpen
+ {
+ get
+ {
+ return leaveOpen;
+ }
+
+ set
+ {
+ leaveOpen = true;
+ }
+ }
+
+ public Encoding EncodingType
+ {
+ get
+ {
+ return encodingType;
+ }
+
+ set
+ {
+ encodingType = value;
+ }
+ }
+
+ public bool RemoveDuplicateStrings
+ {
+ get
+ {
+ return removeDuplicateStrings;
+ }
+
+ set
+ {
+ removeDuplicateStrings = value;
+ }
+ }
+
+ public static CriTableWriterSettings AdxSettings
+ {
+ get
+ {
+ return new CriTableWriterSettings()
+ {
+ Align = 4,
+ PutBlankString = true,
+ RemoveDuplicateStrings = true,
+ };
+ }
+ }
+
+ public static CriTableWriterSettings Adx2Settings
+ {
+ get
+ {
+ return new CriTableWriterSettings()
+ {
+ Align = 32,
+ PutBlankString = false,
+ RemoveDuplicateStrings = false,
+ };
+ }
+ }
+ }
+}
diff --git a/Source/SonicAudioLib/CriMw/Serialization/CriFieldAttribute.cs b/Source/SonicAudioLib/CriMw/Serialization/CriFieldAttribute.cs
new file mode 100644
index 0000000..f6c1747
--- /dev/null
+++ b/Source/SonicAudioLib/CriMw/Serialization/CriFieldAttribute.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SonicAudioLib.CriMw.Serialization
+{
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
+ public class CriFieldAttribute : Attribute
+ {
+ private string fieldName;
+ private ushort order = ushort.MaxValue;
+
+ public string FieldName
+ {
+ get
+ {
+ return fieldName;
+ }
+ }
+
+ public ushort Order
+ {
+ get
+ {
+ return order;
+ }
+ }
+
+ public CriFieldAttribute(ushort order)
+ {
+ this.order = order;
+ }
+
+ public CriFieldAttribute(string fieldName)
+ {
+ this.fieldName = fieldName;
+ }
+
+ public CriFieldAttribute(string fieldName, ushort order)
+ {
+ this.fieldName = fieldName;
+ this.order = order;
+ }
+
+ public CriFieldAttribute() { }
+ }
+}
diff --git a/Source/SonicAudioLib/CriMw/Serialization/CriIgnoreAttribute.cs b/Source/SonicAudioLib/CriMw/Serialization/CriIgnoreAttribute.cs
new file mode 100644
index 0000000..3429104
--- /dev/null
+++ b/Source/SonicAudioLib/CriMw/Serialization/CriIgnoreAttribute.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SonicAudioLib.CriMw.Serialization
+{
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
+ public class CriIgnoreAttribute : Attribute
+ {
+ }
+}
diff --git a/Source/SonicAudioLib/CriMw/Serialization/CriSerializableAttribute.cs b/Source/SonicAudioLib/CriMw/Serialization/CriSerializableAttribute.cs
new file mode 100644
index 0000000..31f62a0
--- /dev/null
+++ b/Source/SonicAudioLib/CriMw/Serialization/CriSerializableAttribute.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SonicAudioLib.CriMw.Serialization
+{
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)]
+ public class CriSerializableAttribute : Attribute
+ {
+ private string tableName;
+
+ public string TableName
+ {
+ get
+ {
+ return tableName;
+ }
+ }
+
+ public CriSerializableAttribute(string tableName)
+ {
+ this.tableName = tableName;
+ }
+
+ public CriSerializableAttribute() { }
+ }
+}
diff --git a/Source/SonicAudioLib/CriMw/Serialization/CriTableSerializer.cs b/Source/SonicAudioLib/CriMw/Serialization/CriTableSerializer.cs
new file mode 100644
index 0000000..79122b7
--- /dev/null
+++ b/Source/SonicAudioLib/CriMw/Serialization/CriTableSerializer.cs
@@ -0,0 +1,201 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+using System.ComponentModel;
+using System.Reflection;
+using System.Diagnostics;
+
+using SonicAudioLib.IO;
+
+namespace SonicAudioLib.CriMw.Serialization
+{
+ public static class CriTableSerializer
+ {
+ public static void Serialize(Stream destination, Type type, ICollection objects, CriTableWriterSettings settings)
+ {
+ ArrayList arrayList = null;
+
+ if (objects != null)
+ {
+ arrayList = new ArrayList(objects);
+ }
+
+ CriTableWriter tableWriter = CriTableWriter.Create(destination, settings);
+
+ string tableName = type.Name;
+ CriSerializableAttribute serAttribute = type.GetCustomAttribute();
+ if (serAttribute != null && !string.IsNullOrEmpty(serAttribute.TableName))
+ {
+ tableName = serAttribute.TableName;
+ }
+
+ tableWriter.WriteStartTable(tableName);
+
+ SortedList sortedProperties = new SortedList();
+
+ foreach (PropertyInfo propertyInfo in type.GetProperties())
+ {
+ // Add the properties in order
+ CriIgnoreAttribute ignoreAttribute = propertyInfo.GetCustomAttribute();
+ if (ignoreAttribute != null)
+ {
+ continue;
+ }
+
+ // Also ignore the properties that are not supportable (except FileInfo, Stream and ICollection<>)
+ if (propertyInfo.PropertyType != typeof(FileInfo) &&
+ propertyInfo.PropertyType != typeof(Stream) &&
+ !CriField.FieldTypes.Contains(propertyInfo.PropertyType))
+ {
+ continue;
+ }
+
+ CriFieldAttribute fieldAttribute = propertyInfo.GetCustomAttribute();
+
+ int order = ushort.MaxValue;
+ if (fieldAttribute != null)
+ {
+ order = fieldAttribute.Order;
+ }
+
+ while (sortedProperties.ContainsKey(order))
+ {
+ order++;
+ }
+
+ sortedProperties.Add(order, propertyInfo);
+ }
+
+ tableWriter.WriteStartFieldCollection();
+ foreach (var keyValuePair in sortedProperties)
+ {
+ PropertyInfo propertyInfo = keyValuePair.Value;
+ CriFieldAttribute fieldAttribute = propertyInfo.GetCustomAttribute();
+
+ string fieldName = propertyInfo.Name;
+ Type fieldType = propertyInfo.PropertyType;
+ object defaultValue = null;
+
+ // Since the invalid types were cleaned, we can assume that those can be FileInfo or Stream
+ // so directly change the type to byte[]
+ if (!CriField.FieldTypes.Contains(fieldType))
+ {
+ fieldType = typeof(byte[]);
+ }
+
+ if (fieldAttribute != null)
+ {
+ if (!string.IsNullOrEmpty(fieldAttribute.FieldName))
+ {
+ fieldName = fieldAttribute.FieldName;
+ }
+ }
+
+ // Checks if all the rows have the same value for this field
+ bool useDefaultValue = true;
+
+ if (arrayList != null && arrayList.Count > 0)
+ {
+ useDefaultValue = true;
+
+ foreach (object obj in arrayList)
+ {
+ if (propertyInfo.GetValue(obj) != propertyInfo.GetValue(arrayList[0]))
+ {
+ useDefaultValue = false;
+ break;
+ }
+ }
+
+ if (useDefaultValue)
+ {
+ defaultValue = propertyInfo.GetValue(arrayList[0]);
+ }
+ }
+
+ if (useDefaultValue)
+ {
+ tableWriter.WriteField(fieldName, fieldType, defaultValue);
+ }
+
+ else
+ {
+ tableWriter.WriteField(fieldName, fieldType);
+ }
+ }
+
+ tableWriter.WriteEndFieldCollection();
+
+ // Time for objects.
+ if (arrayList != null)
+ {
+ foreach (object obj in arrayList)
+ {
+ tableWriter.WriteStartRow();
+
+ int index = 0;
+ foreach (PropertyInfo propertyInfo in sortedProperties.Values)
+ {
+ object value = propertyInfo.GetValue(obj);
+
+ Type propertyType = propertyInfo.PropertyType;
+
+ tableWriter.WriteValue(index, value);
+ index++;
+ }
+
+ tableWriter.WriteEndRow();
+ }
+ }
+
+ tableWriter.WriteEndTable();
+ tableWriter.Dispose();
+ }
+
+ public static ArrayList Deserialize(Stream source, Type type)
+ {
+ ArrayList arrayList = new ArrayList();
+
+ using (CriTableReader tableReader = CriTableReader.Create(source, true))
+ {
+ PropertyInfo[] propertyInfos = type.GetProperties();
+
+ while (tableReader.Read())
+ {
+ object obj = Activator.CreateInstance(type);
+
+ for (int i = 0; i < tableReader.NumberOfFields; i++)
+ {
+ string fieldName = tableReader.GetFieldName(i);
+
+ foreach (PropertyInfo propertyInfo in propertyInfos)
+ {
+ string fieldNameMatch = propertyInfo.Name;
+
+ CriFieldAttribute fieldAttribute = propertyInfo.GetCustomAttribute();
+
+ if (fieldAttribute != null && !string.IsNullOrEmpty(fieldAttribute.FieldName))
+ {
+ fieldNameMatch = fieldAttribute.FieldName;
+ }
+
+ if (fieldName == fieldNameMatch)
+ {
+ object value = tableReader.GetValue(i);
+ propertyInfo.SetValue(obj, value);
+ break;
+ }
+ }
+ }
+
+ arrayList.Add(obj);
+ }
+ }
+
+ return arrayList;
+ }
+ }
+}
diff --git a/Source/SonicAudioLib/IO/EndianStream.cs b/Source/SonicAudioLib/IO/EndianStream.cs
new file mode 100644
index 0000000..985ed16
--- /dev/null
+++ b/Source/SonicAudioLib/IO/EndianStream.cs
@@ -0,0 +1,395 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace SonicAudioLib.IO
+{
+ ///
+ /// Represents a static class for reading various data types in any endian format from a .
+ ///
+ public static class EndianStream
+ {
+ private static byte[] buffer;
+
+ ///
+ /// Fills in the given length.
+ ///
+ private static void FillBuffer(Stream source, int length)
+ {
+ buffer = new byte[length];
+ source.Read(buffer, 0, length);
+ }
+
+ public static void CopyTo(Stream source, Stream destination)
+ {
+ CopyTo(source, destination, 4096);
+ }
+
+ public static void CopyTo(Stream source, Stream destination, int bufferSize)
+ {
+ int read;
+ byte[] buffer = new byte[bufferSize];
+
+ while ((read = source.Read(buffer, 0, buffer.Length)) != 0)
+ {
+ destination.Write(buffer, 0, read);
+ }
+ }
+
+ public static byte[] ReadBytes(Stream source, int length)
+ {
+ FillBuffer(source, length);
+ return buffer;
+ }
+
+ public static void WriteBytes(Stream destination, byte[] value)
+ {
+ destination.Write(value, 0, value.Length);
+ }
+
+ public static void WriteBytes(Stream destination, byte[] value, int length)
+ {
+ destination.Write(value, 0, length);
+ }
+
+ public static byte ReadByte(Stream source)
+ {
+ int value = source.ReadByte();
+ if (value == -1)
+ {
+ throw new EndOfStreamException();
+ }
+
+ return (byte)value;
+ }
+
+ public static byte ReadByteAt(Stream source, long position)
+ {
+ long oldPosition = source.Position;
+ source.Position = position;
+
+ byte value = ReadByte(source);
+ source.Position = oldPosition;
+
+ return value;
+ }
+
+ public static void WriteByte(Stream destination, byte value)
+ {
+ destination.WriteByte(value);
+ }
+
+ public static void WriteByteAt(Stream destination, byte value, long position)
+ {
+ long oldPosition = destination.Position;
+ destination.Position = position;
+
+ WriteByte(destination, value);
+ destination.Position = oldPosition;
+ }
+
+ public static bool ReadBoolean(Stream source)
+ {
+ return ReadByte(source) > 0;
+ }
+
+ public static void WriteBoolean(Stream destination, bool value)
+ {
+ WriteByte(destination, (byte)(value == true ? 1 : 0));
+ }
+
+ public static sbyte ReadSByte(Stream source)
+ {
+ return (sbyte)ReadByte(source);
+ }
+
+ public static void WriteSByte(Stream source, sbyte value)
+ {
+ WriteByte(source, (byte)value);
+ }
+
+ public static ushort ReadUInt16(Stream source)
+ {
+ FillBuffer(source, 2);
+ return BitConverter.ToUInt16(buffer, 0);
+ }
+
+ public static ushort ReadUInt16BE(Stream source)
+ {
+ FillBuffer(source, 2);
+
+ Array.Reverse(buffer);
+ return BitConverter.ToUInt16(buffer, 0);
+ }
+
+ public static void WriteUInt16(Stream destination, ushort value)
+ {
+ buffer = BitConverter.GetBytes(value);
+ destination.Write(buffer, 0, 2);
+ }
+
+ public static void WriteUInt16BE(Stream destination, ushort value)
+ {
+ buffer = BitConverter.GetBytes(value);
+
+ Array.Reverse(buffer);
+ destination.Write(buffer, 0, 2);
+ }
+
+ public static short ReadInt16(Stream source)
+ {
+ FillBuffer(source, 2);
+ return BitConverter.ToInt16(buffer, 0);
+ }
+
+ public static short ReadInt16BE(Stream source)
+ {
+ FillBuffer(source, 2);
+
+ Array.Reverse(buffer);
+ return BitConverter.ToInt16(buffer, 0);
+ }
+
+ public static void WriteInt16(Stream destination, short value)
+ {
+ buffer = BitConverter.GetBytes(value);
+ destination.Write(buffer, 0, 2);
+ }
+
+ public static void WriteInt16BE(Stream destination, short value)
+ {
+ buffer = BitConverter.GetBytes(value);
+
+ Array.Reverse(buffer);
+ destination.Write(buffer, 0, 2);
+ }
+
+ public static uint ReadUInt32(Stream source)
+ {
+ FillBuffer(source, 4);
+ return BitConverter.ToUInt32(buffer, 0);
+ }
+
+ public static uint ReadUInt32BE(Stream source)
+ {
+ FillBuffer(source, 4);
+
+ Array.Reverse(buffer);
+ return BitConverter.ToUInt32(buffer, 0);
+ }
+
+ public static void WriteUInt32(Stream destination, uint value)
+ {
+ buffer = BitConverter.GetBytes(value);
+ destination.Write(buffer, 0, 4);
+ }
+
+ public static void WriteUInt32BE(Stream destination, uint value)
+ {
+ buffer = BitConverter.GetBytes(value);
+
+ Array.Reverse(buffer);
+ destination.Write(buffer, 0, 4);
+ }
+
+ public static int ReadInt32(Stream source)
+ {
+ FillBuffer(source, 4);
+ return BitConverter.ToInt32(buffer, 0);
+ }
+
+ public static int ReadInt32BE(Stream source)
+ {
+ FillBuffer(source, 4);
+
+ Array.Reverse(buffer);
+ return BitConverter.ToInt32(buffer, 0);
+ }
+
+ public static void WriteInt32(Stream destination, int value)
+ {
+ buffer = BitConverter.GetBytes(value);
+ destination.Write(buffer, 0, 4);
+ }
+
+ public static void WriteInt32BE(Stream destination, int value)
+ {
+ buffer = BitConverter.GetBytes(value);
+
+ Array.Reverse(buffer);
+ destination.Write(buffer, 0, 4);
+ }
+
+ public static ulong ReadUInt64(Stream source)
+ {
+ FillBuffer(source, 8);
+ return BitConverter.ToUInt64(buffer, 0);
+ }
+
+ public static ulong ReadUInt64BE(Stream source)
+ {
+ FillBuffer(source, 8);
+
+ Array.Reverse(buffer);
+ return BitConverter.ToUInt64(buffer, 0);
+ }
+
+ public static void WriteUInt64(Stream destination, ulong value)
+ {
+ buffer = BitConverter.GetBytes(value);
+ destination.Write(buffer, 0, 8);
+ }
+
+ public static void WriteUInt64BE(Stream destination, ulong value)
+ {
+ buffer = BitConverter.GetBytes(value);
+
+ Array.Reverse(buffer);
+ destination.Write(buffer, 0, 8);
+ }
+
+ public static long ReadInt64(Stream source)
+ {
+ FillBuffer(source, 8);
+ return BitConverter.ToInt64(buffer, 0);
+ }
+
+ public static long ReadInt64BE(Stream source)
+ {
+ FillBuffer(source, 8);
+
+ Array.Reverse(buffer);
+ return BitConverter.ToInt64(buffer, 0);
+ }
+
+ public static void WriteInt64(Stream destination, long value)
+ {
+ buffer = BitConverter.GetBytes(value);
+ destination.Write(buffer, 0, 8);
+ }
+
+ public static void WriteInt64BE(Stream destination, long value)
+ {
+ buffer = BitConverter.GetBytes(value);
+
+ Array.Reverse(buffer);
+ destination.Write(buffer, 0, 8);
+ }
+
+ public static float ReadFloat(Stream source)
+ {
+ FillBuffer(source, 4);
+ return BitConverter.ToSingle(buffer, 0);
+ }
+
+ public static float ReadFloatBE(Stream source)
+ {
+ FillBuffer(source, 4);
+
+ Array.Reverse(buffer);
+ return BitConverter.ToSingle(buffer, 0);
+ }
+
+ public static void WriteFloat(Stream destination, float value)
+ {
+ buffer = BitConverter.GetBytes(value);
+ destination.Write(buffer, 0, 4);
+ }
+
+ public static void WriteFloatBE(Stream destination, float value)
+ {
+ buffer = BitConverter.GetBytes(value);
+
+ Array.Reverse(buffer);
+ destination.Write(buffer, 0, 4);
+ }
+
+ public static double ReadDouble(Stream source)
+ {
+ FillBuffer(source, 8);
+ return BitConverter.ToDouble(buffer, 0);
+ }
+
+ public static double ReadDoubleBE(Stream source)
+ {
+ FillBuffer(source, 8);
+
+ Array.Reverse(buffer);
+ return BitConverter.ToDouble(buffer, 0);
+ }
+
+ public static void WriteDouble(Stream destination, double value)
+ {
+ buffer = BitConverter.GetBytes(value);
+ destination.Write(buffer, 0, 8);
+ }
+
+ public static void WriteDoubleBE(Stream destination, double value)
+ {
+ buffer = BitConverter.GetBytes(value);
+
+ Array.Reverse(buffer);
+ destination.Write(buffer, 0, 8);
+ }
+
+ public static string ReadCString(Stream source)
+ {
+ return ReadCString(source, Encoding.ASCII);
+ }
+
+ public static string ReadCString(Stream source, Encoding encoding)
+ {
+ var list = new List();
+
+ byte buff;
+ while ((buff = ReadByte(source)) != 0)
+ {
+ list.Add(buff);
+ }
+
+ return encoding.GetString(list.ToArray());
+ }
+
+ public static void WriteCString(Stream destination, string value)
+ {
+ WriteCString(destination, value, Encoding.ASCII);
+ }
+
+ public static void WriteCString(Stream destination, string value, Encoding encoding)
+ {
+ var buff = encoding.GetBytes(value);
+
+ foreach (byte _buff in buff)
+ {
+ WriteByte(destination, _buff);
+ }
+
+ WriteByte(destination, 0);
+ }
+
+ public static string ReadCString(Stream source, int length)
+ {
+ return ReadCString(source, length, Encoding.ASCII);
+ }
+
+ public static string ReadCString(Stream source, int length, Encoding encoding)
+ {
+ byte[] buffer = new byte[length];
+ source.Read(buffer, 0, length);
+
+ return encoding.GetString(buffer);
+ }
+
+ public static void WriteCString(Stream destination, string value, int length)
+ {
+ WriteCString(destination, value, length, Encoding.ASCII);
+ }
+
+ public static void WriteCString(Stream destination, string value, int length, Encoding encoding)
+ {
+ byte[] buffer = encoding.GetBytes(value.ToCharArray(), 0, length);
+ destination.Write(buffer, 0, length);
+ }
+ }
+}
diff --git a/Source/SonicAudioLib/IO/StringPool.cs b/Source/SonicAudioLib/IO/StringPool.cs
new file mode 100644
index 0000000..3d89019
--- /dev/null
+++ b/Source/SonicAudioLib/IO/StringPool.cs
@@ -0,0 +1,88 @@
+using System.Collections.Generic;
+using System.Text;
+using System.IO;
+using System.Linq;
+
+namespace SonicAudioLib.IO
+{
+ public class StringPool
+ {
+ private List items = new List();
+
+ private long startPosition = 0;
+ private long length = 0;
+ private Encoding encoding = Encoding.Default;
+
+ public static readonly string AdxBlankString = "";
+
+ public long Position
+ {
+ get
+ {
+ return startPosition;
+ }
+ }
+
+ public long Length
+ {
+ get
+ {
+ return length;
+ }
+ }
+
+ public long Put(string value)
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ return 0;
+ }
+
+ long position = length;
+ items.Add(new StringItem() { Value = value, Position = position });
+
+ length += value.Length + 1;
+ return position;
+ }
+
+ public void Write(Stream destination)
+ {
+ startPosition = (uint)destination.Position;
+
+ foreach (StringItem item in items)
+ {
+ EndianStream.WriteCString(destination, item.Value, encoding);
+ }
+ }
+
+ public bool ContainsString(string value)
+ {
+ return items.Any(item => item.Value == value);
+ }
+
+ public long GetStringPosition(string value)
+ {
+ return items.First(item => item.Value == value).Position;
+ }
+
+ public void Clear()
+ {
+ items.Clear();
+ }
+
+ public StringPool(Encoding encoding)
+ {
+ this.encoding = encoding;
+ }
+
+ public StringPool()
+ {
+ }
+
+ private class StringItem
+ {
+ public string Value { get; set; }
+ public long Position { get; set; }
+ }
+ }
+}
diff --git a/Source/SonicAudioLib/IO/Substream.cs b/Source/SonicAudioLib/IO/Substream.cs
new file mode 100644
index 0000000..0ff8c92
--- /dev/null
+++ b/Source/SonicAudioLib/IO/Substream.cs
@@ -0,0 +1,199 @@
+using System;
+using System.IO;
+
+namespace SonicAudioLib.IO
+{
+ ///
+ /// Represents a based substream for viewing a portion of a Stream.
+ ///
+ public class Substream : Stream
+ {
+ private Stream baseStream;
+ private long streamPosition;
+ private long streamLength;
+
+ ///
+ /// Determines whether the base Stream supports reading.
+ ///
+ public override bool CanRead
+ {
+ get
+ {
+ return baseStream.CanRead;
+ }
+ }
+
+ ///
+ /// Determines whether the base Stream supports seeking.
+ ///
+ public override bool CanSeek
+ {
+ get
+ {
+ return baseStream.CanSeek;
+ }
+ }
+
+ ///
+ /// Always returns false.
+ ///
+ public override bool CanWrite
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Gets the length of the substream.
+ ///
+ public override long Length
+ {
+ get
+ {
+ return streamLength;
+ }
+ }
+
+ ///
+ /// Gets or sets the position of the substream.
+ ///
+ public override long Position
+ {
+ get
+ {
+ return baseStream.Position - streamPosition;
+ }
+
+ set
+ {
+ baseStream.Position = value + streamPosition;
+ }
+ }
+
+ ///
+ /// Gets or sets the position of the base Stream.
+ ///
+ public long AbsolutePosition
+ {
+ get
+ {
+ return baseStream.Position;
+ }
+
+ set
+ {
+ baseStream.Position = value;
+ }
+ }
+
+ ///
+ /// Closes the substream.
+ ///
+ public override void Close()
+ {
+ base.Close();
+ }
+
+ ///
+ /// Does nothing.
+ ///
+ public override void Flush()
+ {
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ if (baseStream.Position >= streamPosition + streamLength)
+ {
+ count = 0;
+ }
+ else if (baseStream.Position + count > streamPosition + streamLength)
+ {
+ count = (int)(streamPosition + streamLength - baseStream.Position);
+ }
+
+ return baseStream.Read(buffer, offset, count);
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ if (origin == SeekOrigin.Begin)
+ {
+ offset += streamPosition;
+ }
+
+ else if (origin == SeekOrigin.End)
+ {
+ offset = streamPosition + streamLength - offset;
+ origin = SeekOrigin.Begin;
+ }
+
+ return baseStream.Seek(offset, origin);
+ }
+
+ ///
+ /// Seeks to the start of the substream.
+ ///
+ public void SeekToStart()
+ {
+ baseStream.Position = streamPosition;
+ }
+
+ ///
+ /// Throws .
+ ///
+ public override void SetLength(long value)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ /// Throws .
+ ///
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ /// Throws .
+ ///
+ public override void WriteByte(byte value)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ /// Gets an array of the data that the substream covers.
+ ///
+ public byte[] ToArray()
+ {
+ using (MemoryStream memoryStream = new MemoryStream())
+ {
+ CopyTo(memoryStream);
+ return memoryStream.ToArray();
+ }
+ }
+
+ ///
+ /// Creates a substream by the specified base Stream at the specified offset.
+ ///
+ public Substream(Stream baseStream, long streamPosition) : this(baseStream, streamPosition, baseStream.Length - streamPosition)
+ {
+ }
+
+ ///
+ /// Creates a substream by the specified base Stream at the specified offset and with the specified length.
+ ///
+ public Substream(Stream baseStream, long streamPosition, long streamLength)
+ {
+ this.baseStream = baseStream;
+ this.streamPosition = streamPosition;
+ this.streamLength = streamLength;
+
+ SeekToStart();
+ }
+ }
+}
diff --git a/Source/SonicAudioLib/IO/VldPool.cs b/Source/SonicAudioLib/IO/VldPool.cs
new file mode 100644
index 0000000..33fdd1c
--- /dev/null
+++ b/Source/SonicAudioLib/IO/VldPool.cs
@@ -0,0 +1,172 @@
+using System.Collections;
+using System.IO;
+
+using SonicAudioLib.Module;
+
+namespace SonicAudioLib.IO
+{
+ public class VldPool
+ {
+ private ArrayList items = new ArrayList();
+
+ private long startPosition = 0;
+ private uint align = 1;
+ private long length = 0;
+
+ public long Position
+ {
+ get
+ {
+ return startPosition;
+ }
+ }
+
+ public long Length
+ {
+ get
+ {
+ return length;
+ }
+ }
+
+ public long Align
+ {
+ get
+ {
+ return align;
+ }
+ }
+
+ public long Put(byte[] data)
+ {
+ if (data == null || data.Length <= 0)
+ {
+ return 0;
+ }
+
+ while ((length % align) != 0)
+ {
+ length++;
+ }
+
+ long position = length;
+ length += data.Length;
+ items.Add(data);
+
+ return position;
+ }
+
+ public long Put(Stream stream)
+ {
+ if (stream == null || stream.Length <= 0)
+ {
+ return 0;
+ }
+
+ while ((length % align) != 0)
+ {
+ length++;
+ }
+
+ long position = length;
+ length += stream.Length;
+ items.Add(stream);
+
+ return position;
+ }
+
+ public long Put(FileInfo fileInfo)
+ {
+ if (fileInfo == null || fileInfo.Length <= 0)
+ {
+ return 0;
+ }
+
+ while ((length % align) != 0)
+ {
+ length++;
+ }
+
+ long position = length;
+ length += fileInfo.Length;
+ items.Add(fileInfo);
+
+ return position;
+ }
+
+ public long Put(ModuleBase module)
+ {
+ if (module == null)
+ {
+ return 0;
+ }
+
+ while ((length % align) != 0)
+ {
+ length++;
+ }
+
+ long position = length;
+ length += module.CalculateLength();
+ items.Add(module);
+
+ return position;
+ }
+
+ public void Write(Stream destination)
+ {
+ startPosition = destination.Position;
+
+ foreach (object item in items)
+ {
+ while ((destination.Position % align) != 0)
+ {
+ destination.WriteByte(0);
+ }
+
+ if (item is byte[])
+ {
+ byte[] output = (byte[])item;
+ destination.Write(output, 0, output.Length);
+ }
+
+ else if (item is Stream)
+ {
+ Stream output = (Stream)item;
+ output.Seek(0, SeekOrigin.Begin);
+
+ output.CopyTo(destination);
+ }
+
+ else if (item is FileInfo)
+ {
+ FileInfo fileInfo = (FileInfo)item;
+
+ Stream output = fileInfo.OpenRead();
+ output.CopyTo(destination);
+ output.Close();
+ }
+
+ else if (item is ModuleBase)
+ {
+ ModuleBase module = (ModuleBase)item;
+ module.Write(destination);
+ }
+ }
+ }
+
+ public void Clear()
+ {
+ items.Clear();
+ }
+
+ public VldPool(uint align)
+ {
+ this.align = align;
+ }
+
+ public VldPool()
+ {
+ }
+ }
+}
diff --git a/Source/SonicAudioLib/Module/ModuleBase.cs b/Source/SonicAudioLib/Module/ModuleBase.cs
new file mode 100644
index 0000000..7029627
--- /dev/null
+++ b/Source/SonicAudioLib/Module/ModuleBase.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+
+namespace SonicAudioLib.Module
+{
+ public abstract class ModuleBase
+ {
+ public abstract void Read(Stream source);
+ public abstract void Write(Stream destination);
+
+ public virtual void Load(string sourceFileName)
+ {
+ using (Stream source = File.OpenRead(sourceFileName))
+ {
+ Read(source);
+ }
+ }
+
+ public virtual void Load(byte[] sourceByteArray)
+ {
+ using (Stream source = new MemoryStream(sourceByteArray))
+ {
+ Read(source);
+ }
+ }
+
+ public virtual void Save(string destinationFileName)
+ {
+ using (Stream destination = File.Create(destinationFileName))
+ {
+ Write(destination);
+ }
+ }
+
+ public virtual byte[] Save()
+ {
+ using (MemoryStream destination = new MemoryStream())
+ {
+ Write(destination);
+ return destination.ToArray();
+ }
+ }
+
+ public virtual long CalculateLength()
+ {
+ return -1;
+ }
+ }
+}
diff --git a/Source/SonicAudioLib/Properties/AssemblyInfo.cs b/Source/SonicAudioLib/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..efb864a
--- /dev/null
+++ b/Source/SonicAudioLib/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Sonic Audio Library")]
+[assembly: AssemblyDescription("Class library for various audio formats for Sonic the Hedgehog games.")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SonicAudioLib")]
+[assembly: AssemblyCopyright("Copyright © blueskythlikesclouds 2014-2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("63138773-1f47-474c-9345-15eb6183ecc6")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Source/SonicAudioLib/SonicAudioLib.csproj b/Source/SonicAudioLib/SonicAudioLib.csproj
new file mode 100644
index 0000000..cd2b557
--- /dev/null
+++ b/Source/SonicAudioLib/SonicAudioLib.csproj
@@ -0,0 +1,76 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {63138773-1F47-474C-9345-15EB6183ECC6}
+ Library
+ Properties
+ SonicAudioLib
+ SonicAudioLib
+ v4.5.2
+ 512
+
+
+ true
+ full
+ false
+ ..\..\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ ..\..\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file