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