Initial commit

This commit is contained in:
Skyth 2016-11-12 19:02:48 +03:00
commit e3aa58f135
55 changed files with 6616 additions and 0 deletions

63
.gitattributes vendored Normal file
View File

@ -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

244
.gitignore vendored Normal file
View File

@ -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/

21
LICENSE Normal file
View File

@ -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.

BIN
Release/AcbEditor.exe Normal file

Binary file not shown.

BIN
Release/CsbEditor.exe Normal file

Binary file not shown.

BIN
Release/SonicAudioLib.dll Normal file

Binary file not shown.

39
SonicAudioTools.sln Normal file
View File

@ -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

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{45A72DAF-370A-42D1-833E-CEE27EA5E311}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>AcbEditor</RootNamespace>
<AssemblyName>AcbEditor</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\..\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SonicAudioLib\SonicAudioLib.csproj">
<Project>{63138773-1f47-474c-9345-15eb6183ecc6}</Project>
<Name>SonicAudioLib</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
</configuration>

272
Source/AcbEditor/Program.cs Normal file
View File

@ -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";
}
}
}
}

View File

@ -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")]

View File

@ -0,0 +1,86 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace AcbEditor.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// 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() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[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;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// 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 &quot;_streaming&quot; suffix means that it was extracted from the
///external .AWB file.
///
///You can edit those files as you want, but you shouldn&apos;t rename the
///files, or remove any of them [rest of string was truncated]&quot;;.
/// </summary>
internal static string Description {
get {
return ResourceManager.GetString("Description", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,142 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Description" xml:space="preserve">
<value>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.</value>
</data>
</root>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
</configuration>

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{91F6B6A6-5D95-4C7A-B22E-A35BD32DB67A}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>CsbEditor</RootNamespace>
<AssemblyName>CsbEditor</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\..\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SonicAudioLib\SonicAudioLib.csproj">
<Project>{63138773-1f47-474c-9345-15eb6183ecc6}</Project>
<Name>SonicAudioLib</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

169
Source/CsbEditor/Program.cs Normal file
View File

@ -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);
}
}
}
}

View File

@ -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")]

View File

@ -0,0 +1,86 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace CsbEditor.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// 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() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[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;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// 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 &quot;Intro.adx&quot;
///or &quot;Loop.adx&quot;. 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]&quot;;.
/// </summary>
internal static string Description {
get {
return ResourceManager.GetString("Description", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,142 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Description" xml:space="preserve">
<value>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.</value>
</data>
</root>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
</configuration>

View File

@ -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();
}
}
}

View File

@ -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")]

View File

@ -0,0 +1,85 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace SonicAudioCmd.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// 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() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[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;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// 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]&quot;;.
/// </summary>
internal static string HelpText {
get {
return ResourceManager.GetString("HelpText", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,139 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="HelpText" xml:space="preserve">
<value>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!)</value>
</data>
</root>

View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{C412FC07-0C07-4361-88BA-C9C182CF8D70}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SonicAudioCmd</RootNamespace>
<AssemblyName>SonicAudioCmd</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\..\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SonicAudioLib\SonicAudioLib.csproj">
<Project>{63138773-1f47-474c-9345-15eb6183ecc6}</Project>
<Name>SonicAudioLib</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -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);
}
}
}
}
}
}
}

View File

@ -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<string> Sounds = new List<string>();
}
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<SoundElement> soundElements = new List<SoundElement>();
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<SoundElement>));
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<SoundElement>));
List<SoundElement> soundElements;
using (XmlReader xmlReader = XmlReader.Create(args[0]))
{
soundElements = (List<SoundElement>)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!");
}
}
}
}

View File

@ -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]);
}
}
}

View File

@ -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<string> files = new List<string>();
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);
}
}
}
}

View File

@ -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";
}
}
}

View File

@ -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";
}
}
}

View File

@ -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<T> : ModuleBase, IEnumerable<T>
{
protected List<T> entries = new List<T>();
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<T> GetEnumerator()
{
return entries.GetEnumerator();
}
public virtual bool Remove(T item)
{
return entries.Remove(item);
}
IEnumerator IEnumerable.GetEnumerator()
{
return entries.GetEnumerator();
}
}
}

View File

@ -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<CriAfs2Entry>
{
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;
}
}
}

View File

@ -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<CriCpkEntry>
{
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);
}
}
}
}

View File

@ -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<HeroesPacEntry>
{
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);
}
}
}

View File

@ -0,0 +1,175 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace SonicAudioLib.Collections
{
/// <summary>
/// Represents a key/value pair for an <see cref="OrderedDictionary{TKey, TValue}"/>.
/// </summary>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
public class KeyValuePair<TKey, TValue>
{
public TKey Key { get; set; }
public TValue Value { get; set; }
public KeyValuePair(TKey key, TValue value)
{
Key = key;
Value = value;
}
}
/// <summary>
/// Represents a key/value pair collection that is accessable by its key or index.
/// </summary>
public class OrderedDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>
{
private List<KeyValuePair<TKey, TValue>> items = new List<KeyValuePair<TKey, TValue>>();
/// <summary>
/// Gets the count of key/value pairs.
/// </summary>
public int Count
{
get
{
return items.Count;
}
}
/// <summary>
/// Gets the value at the specified index.
/// </summary>
public TValue this[int index]
{
get
{
return items[index].Value;
}
set
{
items[index].Value = value;
}
}
/// <summary>
/// Gets the value by the specified key.
/// </summary>
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;
}
}
/// <summary>
/// Determines whether the collection contains the specified key.
/// </summary>
public bool ContainsKey(TKey key)
{
return items.Any(k => (k.Key).Equals(key));
}
/// <summary>
/// Adds a key/value pair to end of the collection.
/// </summary>
public void Add(TKey key, TValue value)
{
items.Add(new KeyValuePair<TKey, TValue>(key, value));
}
/// <summary>
/// Removes the key/value pair by its key.
/// </summary>
public bool Remove(TKey key)
{
return items.Remove(items.Single(k => (k.Key).Equals(key)));
}
/// <summary>
/// Clears all the key/value pairs.
/// </summary>
public void Clear()
{
items.Clear();
}
/// <summary>
/// Gets the index of the specified key.
/// </summary>
public int IndexOf(TKey key)
{
return items.IndexOf(items.Single(k => (k.Key).Equals(key)));
}
/// <summary>
/// Inserts a key/value pair to the specified index.
/// </summary>
public void Insert(int index, TKey key, TValue value)
{
items.Insert(index, new KeyValuePair<TKey, TValue>(key, value));
}
/// <summary>
/// Removes key/value pair at the specified index.
/// </summary>
public void RemoveAt(int index)
{
items.RemoveAt(index);
}
/// <summary>
/// Gets key at the specified index.
/// </summary>
public TKey GetKeyByIndex(int index)
{
return items[index].Key;
}
/// <summary>
/// Gets value at the specified index.
/// </summary>
public TValue GetValueByIndex(int index)
{
return items[index].Value;
}
/// <summary>
/// Sets key at the specified index.
/// </summary>
public void SetKeyByIndex(int index, TKey key)
{
items[index].Key = key;
}
/// <summary>
/// Sets value at the specified index.
/// </summary>
public void SetValueByIndex(int index, TValue value)
{
items[index].Value = value;
}
/// <summary>
/// Returns an enumerator of key/value pairs.
/// </summary>
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return items.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return items.GetEnumerator();
}
}
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1,121 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace SonicAudioLib.CriMw
{
public class CriFieldCollection : IEnumerable<CriField>
{
private CriTable parent;
private List<CriField> fields = new List<CriField>();
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<CriField> GetEnumerator()
{
return ((IEnumerable<CriField>)fields).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable<CriField>)fields).GetEnumerator();
}
public CriFieldCollection(CriTable parent)
{
this.parent = parent;
}
}
}

View File

@ -0,0 +1,107 @@
using SonicAudioLib.Collections;
using System.Collections;
using System.Linq;
namespace SonicAudioLib.CriMw
{
public class CriRow : IEnumerable
{
private OrderedDictionary<CriField, object> records = new OrderedDictionary<CriField, object>();
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<CriField, object> 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;
}
}
}

View File

@ -0,0 +1,83 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace SonicAudioLib.CriMw
{
public class CriRowCollection : IEnumerable<CriRow>
{
private CriTable parent;
private List<CriRow> rows = new List<CriRow>();
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<CriRow> GetEnumerator()
{
return ((IEnumerable<CriRow>)rows).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable<CriRow>)rows).GetEnumerator();
}
public CriRowCollection(CriTable parent)
{
this.parent = parent;
}
}
}

View File

@ -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; }
}
}

View File

@ -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;
}
}
}

View File

@ -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<string, CriTableField> 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<T>(int fieldIndex)
{
return (T)GetValue(fieldIndex);
}
public T GetValue<T>(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 == "<NULL>" ||
(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<string, CriTableField>();
this.leaveOpen = leaveOpen;
ReadTable();
}
}
}

View File

@ -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<string, CriTableField> 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<string, CriTableField>();
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,
};
}
}
}
}

View File

@ -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() { }
}
}

View File

@ -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
{
}
}

View File

@ -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() { }
}
}

View File

@ -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<CriSerializableAttribute>();
if (serAttribute != null && !string.IsNullOrEmpty(serAttribute.TableName))
{
tableName = serAttribute.TableName;
}
tableWriter.WriteStartTable(tableName);
SortedList<int, PropertyInfo> sortedProperties = new SortedList<int, PropertyInfo>();
foreach (PropertyInfo propertyInfo in type.GetProperties())
{
// Add the properties in order
CriIgnoreAttribute ignoreAttribute = propertyInfo.GetCustomAttribute<CriIgnoreAttribute>();
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<CriFieldAttribute>();
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<CriFieldAttribute>();
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<CriFieldAttribute>();
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;
}
}
}

View File

@ -0,0 +1,395 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace SonicAudioLib.IO
{
/// <summary>
/// Represents a static class for reading various data types in any endian format from a <see cref="Stream"/>.
/// </summary>
public static class EndianStream
{
private static byte[] buffer;
/// <summary>
/// Fills <see cref="buffer"/> in the given length.
/// </summary>
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>();
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);
}
}
}

View File

@ -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<StringItem> items = new List<StringItem>();
private long startPosition = 0;
private long length = 0;
private Encoding encoding = Encoding.Default;
public static readonly string AdxBlankString = "<NULL>";
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; }
}
}
}

View File

@ -0,0 +1,199 @@
using System;
using System.IO;
namespace SonicAudioLib.IO
{
/// <summary>
/// Represents a <see cref="Stream"/> based substream for viewing a portion of a Stream.
/// </summary>
public class Substream : Stream
{
private Stream baseStream;
private long streamPosition;
private long streamLength;
/// <summary>
/// Determines whether the base Stream supports reading.
/// </summary>
public override bool CanRead
{
get
{
return baseStream.CanRead;
}
}
/// <summary>
/// Determines whether the base Stream supports seeking.
/// </summary>
public override bool CanSeek
{
get
{
return baseStream.CanSeek;
}
}
/// <summary>
/// Always returns false.
/// </summary>
public override bool CanWrite
{
get
{
return false;
}
}
/// <summary>
/// Gets the length of the substream.
/// </summary>
public override long Length
{
get
{
return streamLength;
}
}
/// <summary>
/// Gets or sets the position of the substream.
/// </summary>
public override long Position
{
get
{
return baseStream.Position - streamPosition;
}
set
{
baseStream.Position = value + streamPosition;
}
}
/// <summary>
/// Gets or sets the position of the base Stream.
/// </summary>
public long AbsolutePosition
{
get
{
return baseStream.Position;
}
set
{
baseStream.Position = value;
}
}
/// <summary>
/// Closes the substream.
/// </summary>
public override void Close()
{
base.Close();
}
/// <summary>
/// Does nothing.
/// </summary>
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);
}
/// <summary>
/// Seeks to the start of the substream.
/// </summary>
public void SeekToStart()
{
baseStream.Position = streamPosition;
}
/// <summary>
/// Throws <see cref="NotSupportedException"/>.
/// </summary>
public override void SetLength(long value)
{
throw new NotSupportedException();
}
/// <summary>
/// Throws <see cref="NotSupportedException"/>.
/// </summary>
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
/// <summary>
/// Throws <see cref="NotSupportedException"/>.
/// </summary>
public override void WriteByte(byte value)
{
throw new NotSupportedException();
}
/// <summary>
/// Gets an array of the data that the substream covers.
/// </summary>
public byte[] ToArray()
{
using (MemoryStream memoryStream = new MemoryStream())
{
CopyTo(memoryStream);
return memoryStream.ToArray();
}
}
/// <summary>
/// Creates a substream by the specified base Stream at the specified offset.
/// </summary>
public Substream(Stream baseStream, long streamPosition) : this(baseStream, streamPosition, baseStream.Length - streamPosition)
{
}
/// <summary>
/// Creates a substream by the specified base Stream at the specified offset and with the specified length.
/// </summary>
public Substream(Stream baseStream, long streamPosition, long streamLength)
{
this.baseStream = baseStream;
this.streamPosition = streamPosition;
this.streamLength = streamLength;
SeekToStart();
}
}
}

View File

@ -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()
{
}
}
}

View File

@ -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;
}
}
}

View File

@ -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")]

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{63138773-1F47-474C-9345-15EB6183ECC6}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SonicAudioLib</RootNamespace>
<AssemblyName>SonicAudioLib</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\..\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Archive\CriAfs2Archive.cs" />
<Compile Include="Archive\ArchiveBase.cs" />
<Compile Include="Archive\CriCpkArchive.cs" />
<Compile Include="Archive\HeroesPacArchive.cs" />
<Compile Include="Collections\OrderedDictionary.cs" />
<Compile Include="CriMw\CriTable.cs" />
<Compile Include="CriMw\CriField.cs" />
<Compile Include="CriMw\CriFieldCollection.cs" />
<Compile Include="CriMw\CriRow.cs" />
<Compile Include="CriMw\CriRowCollection.cs" />
<Compile Include="CriMw\Serialization\CriFieldAttribute.cs" />
<Compile Include="CriMw\Serialization\CriIgnoreAttribute.cs" />
<Compile Include="CriMw\Serialization\CriSerializableAttribute.cs" />
<Compile Include="CriMw\CriTable.Internal.cs" />
<Compile Include="CriMw\CriTableReader.cs" />
<Compile Include="CriMw\Serialization\CriTableSerializer.cs" />
<Compile Include="CriMw\CriTableWriter.cs" />
<Compile Include="IO\EndianStream.cs" />
<Compile Include="IO\StringPool.cs" />
<Compile Include="IO\Substream.cs" />
<Compile Include="IO\VldPool.cs" />
<Compile Include="Module\ModuleBase.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>