initial commit of media shit
This commit is contained in:
commit
8c0a5e3aa7
38 changed files with 9195 additions and 0 deletions
83
acb_extract/acb_extract.csproj
Normal file
83
acb_extract/acb_extract.csproj
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{3951D6E5-61BE-4D7C-A464-F44D98E9CE83}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>acb_extract</RootNamespace>
|
||||
<AssemblyName>acb_extract</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<FileUpgradeFlags>
|
||||
</FileUpgradeFlags>
|
||||
<UpgradeBackupLocation>
|
||||
</UpgradeBackupLocation>
|
||||
<OldToolsVersion>3.5</OldToolsVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\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>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="src\Program.cs" />
|
||||
<Compile Include="src\CriAcbFile.cs" />
|
||||
<Compile Include="src\CriAfs2Archive.cs" />
|
||||
<Compile Include="src\CriUtfTable.cs" />
|
||||
<Compile Include="src\ParseFile.cs" />
|
||||
<Compile Include="src\Constants.cs" />
|
||||
<Compile Include="src\OffsetDescription.cs" />
|
||||
<Compile Include="src\CalculatingOffsetDescription.cs" />
|
||||
<Compile Include="src\FileUtil.cs" />
|
||||
<Compile Include="src\ByteConversion.cs" />
|
||||
<Compile Include="src\MathUtil.cs" />
|
||||
</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>
|
||||
23
acb_extract/acb_extract.sln
Normal file
23
acb_extract/acb_extract.sln
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2012
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "acb_extract", "acb_extract.csproj", "{3951D6E5-61BE-4D7C-A464-F44D98E9CE83}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{3951D6E5-61BE-4D7C-A464-F44D98E9CE83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3951D6E5-61BE-4D7C-A464-F44D98E9CE83}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3951D6E5-61BE-4D7C-A464-F44D98E9CE83}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{3951D6E5-61BE-4D7C-A464-F44D98E9CE83}.Debug|x86.Build.0 = Debug|x86
|
||||
{3951D6E5-61BE-4D7C-A464-F44D98E9CE83}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3951D6E5-61BE-4D7C-A464-F44D98E9CE83}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3951D6E5-61BE-4D7C-A464-F44D98E9CE83}.Release|x86.ActiveCfg = Release|x86
|
||||
{3951D6E5-61BE-4D7C-A464-F44D98E9CE83}.Release|x86.Build.0 = Release|x86
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
13
acb_extract/acb_extract.userprefs
Normal file
13
acb_extract/acb_extract.userprefs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<Properties StartupItem="acb_extract.csproj">
|
||||
<MonoDevelop.Ide.Workspace ActiveConfiguration="Debug" />
|
||||
<MonoDevelop.Ide.Workbench ActiveDocument="src/CriUtfTable.cs">
|
||||
<Files>
|
||||
<File FileName="src/CriAcbFile.cs" Line="247" Column="71" />
|
||||
<File FileName="src/CriUtfTable.cs" Line="175" Column="25" />
|
||||
</Files>
|
||||
</MonoDevelop.Ide.Workbench>
|
||||
<MonoDevelop.Ide.DebuggingService.Breakpoints>
|
||||
<BreakpointStore />
|
||||
</MonoDevelop.Ide.DebuggingService.Breakpoints>
|
||||
<MonoDevelop.Ide.DebuggingService.PinnedWatches />
|
||||
</Properties>
|
||||
252
acb_extract/src/ByteConversion.cs
Normal file
252
acb_extract/src/ByteConversion.cs
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace vgm_acb.extract
|
||||
{
|
||||
/// <summary>
|
||||
/// Class containing static text conversion functions.
|
||||
/// </summary>
|
||||
public sealed class ByteConversion
|
||||
{
|
||||
/// <summary>
|
||||
/// Codepage value for Shift JIS (Jp)
|
||||
/// </summary>
|
||||
public const int CodePageJapan = 932;
|
||||
/// <summary>
|
||||
/// Codepage value for Cyrillic (US)
|
||||
/// </summary>
|
||||
public const int CodePageUnitedStates = 1251;
|
||||
|
||||
/// <summary>
|
||||
/// Codepage value for OEM DOS
|
||||
/// </summary>
|
||||
public const int CodePageOEM = 437;
|
||||
|
||||
private ByteConversion() { }
|
||||
|
||||
/// <summary>
|
||||
/// Get string from bytes.
|
||||
/// </summary>
|
||||
/// <param name="pBytes">Bytes to convert to a string.</param>
|
||||
/// <param name="codePage">Codepage to use in converting bytes.</param>
|
||||
/// <returns>String encoding using the input Codepage.</returns>
|
||||
public static string GetEncodedText(byte[] value, int codePage)
|
||||
{
|
||||
//return Encoding.Unicode.GetString(value);
|
||||
return System.Text.Encoding.GetEncoding(codePage).GetString(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get text encoded in Shift JIS
|
||||
/// </summary>
|
||||
/// <param name="pBytes">Bytes to decode.</param>
|
||||
/// <returns>String encoded using the Shift JIS codepage.</returns>
|
||||
public static string GetJapaneseEncodedText(byte[] value)
|
||||
{
|
||||
return GetEncodedText(value, CodePageJapan);
|
||||
}
|
||||
/// <summary>
|
||||
/// Get text encoded in Cyrillic
|
||||
/// </summary>
|
||||
/// <param name="pBytes">Bytes to decode.</param>
|
||||
/// <returns>String encoded using the Cyrillic codepage.</returns>
|
||||
public static string GetUnitedStatesEncodedText(byte[] value)
|
||||
{
|
||||
return GetEncodedText(value, CodePageUnitedStates);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get text encoded in ASCII
|
||||
/// </summary>
|
||||
/// <param name="pBytes">Bytes to decode.</param>
|
||||
/// <returns>String encoded using ASCII.</returns>
|
||||
public static string GetAsciiText(byte[] value)
|
||||
{
|
||||
System.Text.Encoding ascii = System.Text.Encoding.ASCII;
|
||||
return ascii.GetString(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get text encoded in ASCII, stops at null.
|
||||
/// </summary>
|
||||
/// <param name="pBytes">Bytes to decode.</param>
|
||||
/// <param name="offset">Offet within byte array of string.</param>
|
||||
/// <returns>String encoded using ASCII.</returns>
|
||||
public static string GetAsciiText(byte[] value, long offset)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
System.Text.Encoding ascii = System.Text.Encoding.ASCII;
|
||||
|
||||
for (long i = offset; i < value.Length; i++)
|
||||
{
|
||||
if (value[i] == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append((char)value[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string GetUtf16LeText(byte[] value)
|
||||
{
|
||||
System.Text.Encoding encoding = System.Text.Encoding.Unicode;
|
||||
return encoding.GetString(value);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Convert input string to a long. Works for Decimal and Hexidecimal (use 0x prefix).
|
||||
/// </summary>
|
||||
/// <param name="pStringNumber">String containing a Decimal and Hexidecimal number.</param>
|
||||
/// <returns>Long representing the input string.</returns>
|
||||
public static long GetLongValueFromString(string value)
|
||||
{
|
||||
long ret;
|
||||
bool isNegative = false;
|
||||
string parseValue;
|
||||
|
||||
if (value.StartsWith("-"))
|
||||
{
|
||||
parseValue = value.Substring(1);
|
||||
isNegative = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
parseValue = value;
|
||||
}
|
||||
|
||||
if (parseValue.StartsWith("0x", StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
parseValue = parseValue.Substring(2);
|
||||
ret = long.Parse(parseValue, System.Globalization.NumberStyles.HexNumber, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = long.Parse(parseValue, System.Globalization.NumberStyles.Integer, null);
|
||||
}
|
||||
|
||||
if (isNegative)
|
||||
{
|
||||
ret *= -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the UInt32 Value of the Incoming Byte Array, which is in Big Endian order.
|
||||
/// </summary>
|
||||
/// <param name="pBytes">Bytes to convert.</param>
|
||||
/// <returns>The UInt32 Value of the Incoming Byte Array.</returns>
|
||||
public static UInt32 GetUInt32BigEndian(byte[] value)
|
||||
{
|
||||
byte[] workingArray = new byte[value.Length];
|
||||
Array.Copy(value, 0, workingArray, 0, value.Length);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(workingArray);
|
||||
}
|
||||
return BitConverter.ToUInt32(workingArray, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the UInt16 Value of the Incoming Byte Array, which is in Big Endian order.
|
||||
/// </summary>
|
||||
/// <param name="pBytes">Bytes to convert.</param>
|
||||
/// <returns>The UInt16 Value of the Incoming Byte Array.</returns>
|
||||
public static UInt16 GetUInt16BigEndian(byte[] value)
|
||||
{
|
||||
byte[] workingArray = new byte[value.Length];
|
||||
Array.Copy(value, 0, workingArray, 0, value.Length);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(workingArray);
|
||||
}
|
||||
return BitConverter.ToUInt16(workingArray, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Predicts Code Page between Cyrillic and Shift-JIS based on whether high ASCII is included or not.
|
||||
/// </summary>
|
||||
/// <param name="tagBytes">Bytes containing the tags in an unknown language.</param>
|
||||
/// <returns>Integer representing the predicted code page.</returns>
|
||||
public static int GetPredictedCodePageForTags(byte[] tagBytes)
|
||||
{
|
||||
int predictedCodePage = CodePageUnitedStates;
|
||||
|
||||
foreach (byte b in tagBytes)
|
||||
{
|
||||
if ((int)b > 0x7F)
|
||||
{
|
||||
predictedCodePage = CodePageJapan;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return predictedCodePage;
|
||||
}
|
||||
|
||||
public static byte GetHighNibble(byte value)
|
||||
{
|
||||
return (byte)(((value) >> 4) & 0x0F);
|
||||
}
|
||||
|
||||
public static byte GetLowNibble(byte value)
|
||||
{
|
||||
return (byte)((value) & 0x0F); ;
|
||||
}
|
||||
|
||||
public static byte[] GetBytesFromHexString(string hexValue)
|
||||
{
|
||||
int j = 0;
|
||||
byte[] bytes = new byte[hexValue.Length / 2];
|
||||
|
||||
// convert the string to bytes
|
||||
for (int i = 0; i < hexValue.Length; i += 2)
|
||||
{
|
||||
bytes[j] = BitConverter.GetBytes(Int16.Parse(hexValue.Substring(i, 2), System.Globalization.NumberStyles.AllowHexSpecifier, CultureInfo.CurrentCulture))[0];
|
||||
j++;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static byte[] GetBytesBigEndian(uint value)
|
||||
{
|
||||
byte[] ret = BitConverter.GetBytes(value);
|
||||
Array.Reverse(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static DateTime GetDateTimeFromFAT32Date(int value)
|
||||
{
|
||||
short xDate = (short)(value >> 0x10);
|
||||
short xTime = (short)(value & 0xFFFF);
|
||||
|
||||
if (xDate == 0 && xTime == 0)
|
||||
{
|
||||
return DateTime.Now;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new DateTime(
|
||||
(((xDate & 0xFE00) >> 9) + 0x7BC),
|
||||
((xDate & 0x1E0) >> 5),
|
||||
(xDate & 0x1F),
|
||||
((xTime & 0xF800) >> 0xB),
|
||||
((xTime & 0x7E0) >> 5),
|
||||
((xTime & 0x1F) * 2));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
13
acb_extract/src/CalculatingOffsetDescription.cs
Normal file
13
acb_extract/src/CalculatingOffsetDescription.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace vgm_acb.extract
|
||||
{
|
||||
public class CalculatingOffsetDescription : OffsetDescription
|
||||
{
|
||||
public const string OFFSET_VARIABLE_STRING = "$V";
|
||||
|
||||
public string CalculationString { set; get; }
|
||||
}
|
||||
}
|
||||
42
acb_extract/src/ChecksumUtil.cs
Normal file
42
acb_extract/src/ChecksumUtil.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// <copyright file="ChecksumUtil.cs" company="N/A">
|
||||
// Copyright (c) 2008 All Rights Reserved
|
||||
// </copyright>
|
||||
// <author></author>
|
||||
// <email></email>
|
||||
// <date></date>
|
||||
// <summary>Contains the ChecksumUtil class.</summary>
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace vgm_acb.extract
|
||||
{
|
||||
/// <summary>
|
||||
/// Class containing static functions related to checksum generation.
|
||||
/// </summary>
|
||||
public sealed class ChecksumUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// Prevents a default instance of the ChecksumUtil class from being created.
|
||||
/// </summary>
|
||||
private ChecksumUtil()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the MD5 checksum of the input stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">File Stream for which to generate the checksum.</param>
|
||||
/// <returns>String containing the hexidecimal representation of the MD5 of the input stream.</returns>
|
||||
public static string GetMd5OfFullFile(FileStream stream)
|
||||
{
|
||||
MD5CryptoServiceProvider hashMd5 = new MD5CryptoServiceProvider();
|
||||
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
hashMd5.ComputeHash(stream);
|
||||
return ParseFile.ByteArrayToString(hashMd5.Hash);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
453
acb_extract/src/Constants.cs
Normal file
453
acb_extract/src/Constants.cs
Normal file
|
|
@ -0,0 +1,453 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace vgm_acb.extract
|
||||
{
|
||||
/// <summary>
|
||||
/// Struct containing criteria used to find offsets.
|
||||
/// </summary>
|
||||
public struct FindOffsetStruct
|
||||
{
|
||||
private string searchString;
|
||||
private string startingOffset;
|
||||
private bool treatSearchStringAsHex;
|
||||
private bool cutFile;
|
||||
private string searchStringOffset;
|
||||
private string cutSize;
|
||||
private string cutSizeOffsetSize;
|
||||
private bool isCutSizeAnOffset;
|
||||
private string outputFileExtension;
|
||||
private bool isLittleEndian;
|
||||
private bool useTerminatorForCutSize;
|
||||
private string terminatorString;
|
||||
private bool treatTerminatorStringAsHex;
|
||||
private bool includeTerminatorLength;
|
||||
private string extraCutSizeBytes;
|
||||
|
||||
public bool DoSearchStringModulo
|
||||
{
|
||||
set;
|
||||
get;
|
||||
}
|
||||
|
||||
public string SearchStringModuloDivisor
|
||||
{
|
||||
set;
|
||||
get;
|
||||
}
|
||||
|
||||
public string SearchStringModuloResult
|
||||
{
|
||||
set;
|
||||
get;
|
||||
}
|
||||
|
||||
public bool DoTerminatorModulo
|
||||
{
|
||||
set;
|
||||
get;
|
||||
}
|
||||
|
||||
public string TerminatorStringModuloDivisor
|
||||
{
|
||||
set;
|
||||
get;
|
||||
}
|
||||
|
||||
public string TerminatorStringModuloResult
|
||||
{
|
||||
set;
|
||||
get;
|
||||
}
|
||||
|
||||
public string MinimumSize
|
||||
{
|
||||
set;
|
||||
get;
|
||||
}
|
||||
|
||||
public string SearchString
|
||||
{
|
||||
get { return searchString; }
|
||||
set { searchString = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets offset to being searching at
|
||||
/// </summary>
|
||||
public string StartingOffset
|
||||
{
|
||||
set { startingOffset = value; }
|
||||
get { return startingOffset; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets flag to indicate search string is a hex value.
|
||||
/// </summary>
|
||||
public bool TreatSearchStringAsHex
|
||||
{
|
||||
get { return treatSearchStringAsHex; }
|
||||
set { treatSearchStringAsHex = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets flag to cut the file when the offset is found.
|
||||
/// </summary>
|
||||
public bool CutFile
|
||||
{
|
||||
get { return cutFile; }
|
||||
set { cutFile = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets offset within destination file Search String would reside.
|
||||
/// </summary>
|
||||
public string SearchStringOffset
|
||||
{
|
||||
get { return searchStringOffset; }
|
||||
set { searchStringOffset = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets size to cut from file
|
||||
/// </summary>
|
||||
public string CutSize
|
||||
{
|
||||
get { return cutSize; }
|
||||
set { cutSize = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets size of offset holding cut size
|
||||
/// </summary>
|
||||
public string CutSizeOffsetSize
|
||||
{
|
||||
get { return cutSizeOffsetSize; }
|
||||
set { cutSizeOffsetSize = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets flag indicating that cut size is an offset.
|
||||
/// </summary>
|
||||
public bool IsCutSizeAnOffset
|
||||
{
|
||||
get { return isCutSizeAnOffset; }
|
||||
set { isCutSizeAnOffset = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets file extension to use for cut files.
|
||||
/// </summary>
|
||||
public string OutputFileExtension
|
||||
{
|
||||
get { return outputFileExtension; }
|
||||
set { outputFileExtension = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets flag indicating that offset based cut size is stored in Little Endian byte order.
|
||||
/// </summary>
|
||||
public bool IsLittleEndian
|
||||
{
|
||||
get { return isLittleEndian; }
|
||||
set { isLittleEndian = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets flag indicating that a terminator should be used to determine the cut size.
|
||||
/// </summary>
|
||||
|
||||
public bool UseLengthMultiplier { set; get; }
|
||||
public string LengthMultiplier { set; get; }
|
||||
|
||||
public bool UseTerminatorForCutSize
|
||||
{
|
||||
get { return useTerminatorForCutSize; }
|
||||
set { useTerminatorForCutSize = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets terminator string to search for.
|
||||
/// </summary>
|
||||
public string TerminatorString
|
||||
{
|
||||
get { return terminatorString; }
|
||||
set { terminatorString = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets flag indicating that Terminator String is hex.
|
||||
/// </summary>
|
||||
public bool TreatTerminatorStringAsHex
|
||||
{
|
||||
get { return treatTerminatorStringAsHex; }
|
||||
set { treatTerminatorStringAsHex = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets flag indicating that the length of the terminator should be included in the cut size.
|
||||
/// </summary>
|
||||
public bool IncludeTerminatorLength
|
||||
{
|
||||
get { return includeTerminatorLength; }
|
||||
set { includeTerminatorLength = value; }
|
||||
}
|
||||
|
||||
public bool CutToEofIfTerminatorNotFound { set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets additional bytes to include in the cut size.
|
||||
/// </summary>
|
||||
public string ExtraCutSizeBytes
|
||||
{
|
||||
get { return extraCutSizeBytes; }
|
||||
set { extraCutSizeBytes = value; }
|
||||
}
|
||||
|
||||
public string OutputFolder { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Struct used to send messages conveying progress.
|
||||
/// </summary>
|
||||
public struct ProgressStruct
|
||||
{
|
||||
/// <summary>
|
||||
/// File name to display in progress bar.
|
||||
/// </summary>
|
||||
private string fileName;
|
||||
|
||||
/// <summary>
|
||||
/// Error message to display in output window.
|
||||
/// </summary>
|
||||
private string errorMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Generic message to display in output window.
|
||||
/// </summary>
|
||||
private string genericMessage;
|
||||
|
||||
/// <summary>
|
||||
/// New tree node to add to a TreeView.
|
||||
/// </summary>
|
||||
private TreeNode newNode;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets fileName.
|
||||
/// </summary>
|
||||
public string FileName
|
||||
{
|
||||
get { return fileName; }
|
||||
set { fileName = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets errorMessage.
|
||||
/// </summary>
|
||||
public string ErrorMessage
|
||||
{
|
||||
get { return errorMessage; }
|
||||
set { errorMessage = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets genericMessage.
|
||||
/// </summary>
|
||||
public string GenericMessage
|
||||
{
|
||||
get { return genericMessage; }
|
||||
set { genericMessage = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets newNode.
|
||||
/// </summary>
|
||||
public TreeNode NewNode
|
||||
{
|
||||
get { return newNode; }
|
||||
set { newNode = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset this node's values
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
fileName = String.Empty;
|
||||
errorMessage = String.Empty;
|
||||
genericMessage = String.Empty;
|
||||
newNode = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Struct used to allow TreeView to select a specific form and modify the originating node upon completion of a task.
|
||||
/// </summary>
|
||||
public struct NodeTagStruct
|
||||
{
|
||||
/// <summary>
|
||||
/// Class name of the Form this node will bring to focus.
|
||||
/// </summary>
|
||||
private string formClass;
|
||||
|
||||
/// <summary>
|
||||
/// Object type this node represents.
|
||||
/// </summary>
|
||||
private string objectType;
|
||||
|
||||
/// <summary>
|
||||
/// File path of the file this node represents.
|
||||
/// </summary>
|
||||
private string filePath;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets formClass
|
||||
/// </summary>
|
||||
public string FormClass
|
||||
{
|
||||
get { return formClass; }
|
||||
set { formClass = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets objectType
|
||||
/// </summary>
|
||||
public string ObjectType
|
||||
{
|
||||
get { return objectType; }
|
||||
set { objectType = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets filePath
|
||||
/// </summary>
|
||||
public string FilePath
|
||||
{
|
||||
get { return filePath; }
|
||||
set { filePath = value; }
|
||||
}
|
||||
}
|
||||
|
||||
public enum VfsFileRecordRelativeOffsetLocationType
|
||||
{
|
||||
FileRecordStart,
|
||||
FileRecordEnd
|
||||
}
|
||||
|
||||
public struct VfsExtractionStruct
|
||||
{
|
||||
// header size
|
||||
public bool UseStaticHeaderSize { set; get; }
|
||||
public string StaticHeaderSize { set; get; }
|
||||
public bool UseHeaderSizeOffset { set; get; }
|
||||
public OffsetDescription HeaderSizeOffsetDescription { set; get; }
|
||||
public bool ReadHeaderToEof { set; get; }
|
||||
|
||||
// file count
|
||||
public bool UseStaticFileCount { set; get; }
|
||||
public string StaticFileCount { set; get; }
|
||||
public bool UseFileCountOffset { set; get; }
|
||||
public OffsetDescription FileCountOffsetDescription { set; get; }
|
||||
|
||||
// file record basic information
|
||||
public string FileRecordsStartOffset { set; get; }
|
||||
public string FileRecordSize { set; get; }
|
||||
|
||||
// file offset
|
||||
public bool UseFileOffsetOffset { set; get; }
|
||||
public CalculatingOffsetDescription FileOffsetOffsetDescription { set; get; }
|
||||
|
||||
public bool UsePreviousFilesSizeToDetermineOffset { set; get; }
|
||||
public string BeginCuttingFilesAtOffset { set; get; }
|
||||
public bool UseByteAlignmentValue { set; get; }
|
||||
public string ByteAlignmentValue { set; get; }
|
||||
|
||||
// file length/size
|
||||
public bool UseFileLengthOffset { set; get; }
|
||||
public CalculatingOffsetDescription FileLengthOffsetDescription { set; get; }
|
||||
|
||||
public bool UseLocationOfNextFileToDetermineLength { set; get; }
|
||||
|
||||
// file name
|
||||
public bool FileNameIsPresent { set; get; }
|
||||
|
||||
public bool UseStaticFileNameOffsetWithinRecord { set; get; }
|
||||
public string StaticFileNameOffsetWithinRecord { set; get; }
|
||||
|
||||
public bool UseAbsoluteFileNameOffset { set; get; }
|
||||
public OffsetDescription AbsoluteFileNameOffsetDescription { set; get; }
|
||||
|
||||
public bool UseRelativeFileNameOffset { set; get; }
|
||||
public OffsetDescription RelativeFileNameOffsetDescription { set; get; }
|
||||
public VfsFileRecordRelativeOffsetLocationType FileRecordNameRelativeOffsetLocation { set; get; }
|
||||
|
||||
// name size
|
||||
public bool UseStaticFileNameLength { set; get; }
|
||||
public string StaticFileNameLength { set; get; }
|
||||
|
||||
public bool UseFileNameTerminatorString { set; get; }
|
||||
public string FileNameTerminatorString { set; get; }
|
||||
}
|
||||
|
||||
public struct SimpleFileExtractionStruct
|
||||
{
|
||||
public string FilePath { set; get; }
|
||||
public long FileOffset { set; get; }
|
||||
public long FileLength { set; get; }
|
||||
public long FileNameLength { set; get; }
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
this.FilePath = String.Empty;
|
||||
this.FileOffset = -1;
|
||||
this.FileLength = -1;
|
||||
this.FileNameLength = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class containing universal constants.
|
||||
/// </summary>
|
||||
public sealed class Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// Chunk size to use when reading from files. Used to grab maximum buffer
|
||||
/// size without using the large object heap which has poor collection.
|
||||
/// </summary>
|
||||
public const int FileReadChunkSize = 71680;
|
||||
|
||||
/// <summary>
|
||||
/// Constant used to send an ignore the value message to the progress bar.
|
||||
/// </summary>
|
||||
public const int IgnoreProgress = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Constant used to send a generic message to the progress bar.
|
||||
/// </summary>
|
||||
public const int ProgressMessageOnly = -2;
|
||||
|
||||
/// <summary>
|
||||
/// Text description to use when describing a Big Endian option
|
||||
/// </summary>
|
||||
public const string BigEndianByteOrder = "Big Endian";
|
||||
|
||||
/// <summary>
|
||||
/// Text description to use when describing a Little Endian option
|
||||
/// </summary>
|
||||
public const string LittleEndianByteOrder = "Little Endian";
|
||||
|
||||
public static readonly byte[] RiffHeaderBytes = new byte[] { 0x52, 0x49, 0x46, 0x46 };
|
||||
public static readonly byte[] RiffDataBytes = new byte[] { 0x64, 0x61, 0x74, 0x61 };
|
||||
public static readonly byte[] RiffWaveBytes = new byte[] { 0x57, 0x41, 0x56, 0x45 };
|
||||
public static readonly byte[] RiffFmtBytes = new byte[] { 0x66, 0x6D, 0x74, 0x20 };
|
||||
|
||||
public static readonly byte[] NullByteArray = new byte[] { 0x00 };
|
||||
|
||||
public const string StringNullTerminator = "\0";
|
||||
|
||||
// empty constructor
|
||||
private Constants() { }
|
||||
}
|
||||
}
|
||||
684
acb_extract/src/CriAcbFile.cs
Normal file
684
acb_extract/src/CriAcbFile.cs
Normal file
|
|
@ -0,0 +1,684 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
//using VGMToolbox.util;
|
||||
|
||||
namespace vgm_acb.extract
|
||||
{
|
||||
public class CriAcbCueRecord
|
||||
{
|
||||
public uint CueId { set; get; }
|
||||
public byte ReferenceType { set; get; }
|
||||
public ushort ReferenceIndex { set; get; }
|
||||
|
||||
public ushort numWaveforms { set; get; }
|
||||
public bool IsWaveformIdentified { set; get; }
|
||||
public ushort WaveformIndex { set; get; }
|
||||
public ushort WaveformId { set; get; }
|
||||
public byte EncodeType { set; get; }
|
||||
public bool IsStreaming { set; get; }
|
||||
|
||||
public string CueName { set; get; }
|
||||
}
|
||||
|
||||
public class CriAcbSeqRecord
|
||||
{
|
||||
public ushort NumTracks { set; get; }
|
||||
public ushort[] TrackIndex { set; get; }
|
||||
public byte Type { set; get; }
|
||||
}
|
||||
public class CriAcbFile : CriUtfTable
|
||||
{
|
||||
public const string EXTRACTION_FOLDER_FORMAT = "_vgmt_acb_ext_{0}";
|
||||
public static readonly string ACB_AWB_EXTRACTION_FOLDER = "acb" + Path.DirectorySeparatorChar + "awb";
|
||||
public static readonly string EXT_AWB_EXTRACTION_FOLDER = "awb";
|
||||
|
||||
public const byte WAVEFORM_ENCODE_TYPE_ADX = 0;
|
||||
public const byte WAVEFORM_ENCODE_TYPE_HCA = 2;
|
||||
public const byte WAVEFORM_ENCODE_TYPE_VAG = 7;
|
||||
public const byte WAVEFORM_ENCODE_TYPE_ATRAC3 = 8;
|
||||
public const byte WAVEFORM_ENCODE_TYPE_BCWAV = 9;
|
||||
public const byte WAVEFORM_ENCODE_TYPE_ATRAC9 = 11;
|
||||
public const byte WAVEFORM_ENCODE_TYPE_NINTENDO_DSP = 13;
|
||||
|
||||
public const string AWB_FORMAT1 = "{0}_streamfiles.awb";
|
||||
public const string AWB_FORMAT2 = "{0}.awb";
|
||||
public const string AWB_FORMAT3 = "{0}_STR.awb";
|
||||
|
||||
protected enum AwbToExtract { Internal, External };
|
||||
|
||||
public CriAcbFile(FileStream fs, long offset, bool includeCueIdInFileName)
|
||||
{
|
||||
// initialize UTF
|
||||
this.Initialize(fs, offset);
|
||||
|
||||
// initialize ACB specific items
|
||||
this.InitializeCueList(fs);
|
||||
this.InitializeCueNameToWaveformMap(fs, includeCueIdInFileName);
|
||||
|
||||
// initialize internal AWB
|
||||
if (this.InternalAwbFileSize > 0)
|
||||
{
|
||||
this.InternalAwb = new CriAfs2Archive(fs, (long)this.InternalAwbFileOffset);
|
||||
}
|
||||
|
||||
// initialize external AWB
|
||||
if (this.StreamAwbAfs2HeaderSize > 0)
|
||||
{
|
||||
this.ExternalAwb = this.InitializeExternalAwbArchive();
|
||||
}
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return (string)CriUtfTable.GetUtfFieldForRow(this, 0, "Name");
|
||||
}
|
||||
}
|
||||
public string VersionString
|
||||
{
|
||||
get
|
||||
{
|
||||
return (string)CriUtfTable.GetUtfFieldForRow(this, 0, "VersionString");
|
||||
}
|
||||
}
|
||||
|
||||
public ulong InternalAwbFileOffset
|
||||
{
|
||||
get
|
||||
{
|
||||
return (ulong)CriUtfTable.GetOffsetForUtfFieldForRow(this, 0, "AwbFile");
|
||||
}
|
||||
|
||||
}
|
||||
public ulong InternalAwbFileSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return (ulong)CriUtfTable.GetSizeForUtfFieldForRow(this, 0, "AwbFile");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public ulong CueTableOffset
|
||||
{
|
||||
get
|
||||
{
|
||||
return CriUtfTable.GetOffsetForUtfFieldForRow(this, 0, "CueTable");
|
||||
}
|
||||
}
|
||||
public ulong CueNameTableOffset
|
||||
{
|
||||
get
|
||||
{
|
||||
return CriUtfTable.GetOffsetForUtfFieldForRow(this, 0, "CueNameTable");
|
||||
}
|
||||
}
|
||||
public ulong WaveformTableOffset
|
||||
{
|
||||
get
|
||||
{
|
||||
return CriUtfTable.GetOffsetForUtfFieldForRow(this, 0, "WaveformTable");
|
||||
}
|
||||
}
|
||||
public ulong SynthTableOffset
|
||||
{
|
||||
get
|
||||
{
|
||||
return CriUtfTable.GetOffsetForUtfFieldForRow(this, 0, "SynthTable");
|
||||
}
|
||||
}
|
||||
|
||||
public ulong SeqTableOffset
|
||||
{
|
||||
get
|
||||
{
|
||||
return CriUtfTable.GetOffsetForUtfFieldForRow(this, 0, "SequenceTable");
|
||||
}
|
||||
}
|
||||
|
||||
public CriAcbCueRecord[] CueList { set; get; }
|
||||
public CriAcbSeqRecord[] SeqList { set; get; }
|
||||
|
||||
public Dictionary<string, ushort> CueNamesToWaveforms { set; get; }
|
||||
|
||||
public byte[] AcfMd5Hash
|
||||
{
|
||||
get
|
||||
{
|
||||
return (byte[])CriUtfTable.GetUtfFieldForRow(this, 0, "AcfMd5Hash");
|
||||
}
|
||||
}
|
||||
public ulong AwbFileOffset
|
||||
{
|
||||
get
|
||||
{
|
||||
return CriUtfTable.GetOffsetForUtfFieldForRow(this, 0, "AwbFile");
|
||||
}
|
||||
}
|
||||
public CriAfs2Archive InternalAwb { set; get; }
|
||||
|
||||
public byte[] StreamAwbHash
|
||||
{
|
||||
get
|
||||
{
|
||||
return (byte[])CriUtfTable.GetUtfFieldForRow(this, 0, "StreamAwbHash");
|
||||
}
|
||||
}
|
||||
public ulong StreamAwbAfs2HeaderOffset
|
||||
{
|
||||
get
|
||||
{
|
||||
return (ulong)CriUtfTable.GetOffsetForUtfFieldForRow(this, 0, "StreamAwbAfs2Header");
|
||||
}
|
||||
}
|
||||
public ulong StreamAwbAfs2HeaderSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return (ulong)CriUtfTable.GetSizeForUtfFieldForRow(this, 0, "StreamAwbAfs2Header");
|
||||
}
|
||||
}
|
||||
public CriAfs2Archive ExternalAwb { set; get; }
|
||||
|
||||
public void ExtractAll()
|
||||
{
|
||||
string baseExtractionFolder = Path.Combine(Path.GetDirectoryName(this.SourceFile),
|
||||
String.Format(EXTRACTION_FOLDER_FORMAT, Path.GetFileNameWithoutExtension(this.SourceFile)));
|
||||
|
||||
this.ExtractAllUsingCueList(baseExtractionFolder);
|
||||
}
|
||||
|
||||
|
||||
protected void InitializeCueNameToWaveformMap(FileStream fs, bool includeCueIdInFileName)
|
||||
{
|
||||
ushort cueIndex;
|
||||
string cueName;
|
||||
|
||||
CriUtfTable cueNameTableUtf = new CriUtfTable();
|
||||
cueNameTableUtf.Initialize(fs, (long)this.CueNameTableOffset);
|
||||
|
||||
for (int i = 0; i < cueNameTableUtf.NumberOfRows; i++)
|
||||
{
|
||||
cueIndex = (ushort)CriUtfTable.GetUtfFieldForRow(cueNameTableUtf, i, "CueIndex");
|
||||
|
||||
// skip cues with unidentified waveforms (see 'vc05_0140.acb, vc10_0372.acb' in Kidou_Senshi_Gundam_AGE_Universe_Accel (PSP))
|
||||
if (this.CueList[cueIndex].IsWaveformIdentified)
|
||||
{
|
||||
cueName = (string)CriUtfTable.GetUtfFieldForRow(cueNameTableUtf, i, "CueName");
|
||||
|
||||
this.CueList[cueIndex].CueName = cueName;
|
||||
this.CueList[cueIndex].CueName += CriAcbFile.GetFileExtensionForEncodeType(this.CueList[cueIndex].EncodeType);
|
||||
|
||||
if (includeCueIdInFileName)
|
||||
{
|
||||
this.CueList[cueIndex].CueName = String.Format("{0}_{1}",
|
||||
this.CueList[cueIndex].CueId.ToString("D5"), this.CueList[cueIndex].CueName);
|
||||
}
|
||||
|
||||
this.CueNamesToWaveforms.Add(this.CueList[cueIndex].CueName, this.CueList[cueIndex].WaveformId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void InitializeCueList(FileStream fs)
|
||||
{
|
||||
this.CueNamesToWaveforms = new Dictionary<string, ushort>();
|
||||
|
||||
ulong referenceItemsOffset = 0;
|
||||
ulong referenceItemsSize = 0;
|
||||
ulong referenceCorrection = 0;
|
||||
|
||||
object dummy;
|
||||
byte isStreaming = 0;
|
||||
|
||||
CriUtfTable cueTableUtf = new CriUtfTable();
|
||||
cueTableUtf.Initialize(fs, (long)this.CueTableOffset);
|
||||
|
||||
CriUtfTable waveformTableUtf = new CriUtfTable();
|
||||
waveformTableUtf.Initialize(fs, (long)this.WaveformTableOffset);
|
||||
|
||||
CriUtfTable synthTableUtf = new CriUtfTable();
|
||||
synthTableUtf.Initialize(fs, (long)this.SynthTableOffset);
|
||||
|
||||
CriUtfTable seqTableUtf = new CriUtfTable();
|
||||
seqTableUtf.Initialize(fs, (long)this.SeqTableOffset);
|
||||
|
||||
|
||||
this.SeqList = new CriAcbSeqRecord[seqTableUtf.NumberOfRows];
|
||||
this.CueList = new CriAcbCueRecord[cueTableUtf.NumberOfRows];
|
||||
|
||||
for (int i = 0; i < seqTableUtf.NumberOfRows; i++)
|
||||
{
|
||||
this.SeqList[i] = new CriAcbSeqRecord();
|
||||
|
||||
this.SeqList[i].NumTracks = (ushort)CriUtfTable.GetUtfFieldForRow(seqTableUtf, i, "NumTracks");
|
||||
this.SeqList[i].Type = (byte)CriUtfTable.GetUtfFieldForRow(seqTableUtf, i, "Type");
|
||||
|
||||
var tindex = (byte[])CriUtfTable.GetUtfFieldForRow(seqTableUtf, i, "TrackIndex");
|
||||
this.SeqList[i].TrackIndex = new ushort[(int)Math.Ceiling(tindex.Length / 2.0)];
|
||||
|
||||
Buffer.BlockCopy(tindex, 0, this.SeqList[i].TrackIndex, 0, tindex.Length);
|
||||
|
||||
for (int j = 0; j < this.SeqList[i].NumTracks; j++) {
|
||||
// i'm a lazy bastard and don't want to look up a better way
|
||||
// byte order is dumb
|
||||
ushort a = (ushort) (this.SeqList[i].TrackIndex[j] & 0xff00u);
|
||||
ushort b = (ushort) (this.SeqList[i].TrackIndex[j] & 0xffu);
|
||||
this.SeqList[i].TrackIndex[j] = (ushort) ((b << 8) | (a >> 8));
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < cueTableUtf.NumberOfRows; i++)
|
||||
{
|
||||
this.CueList[i] = new CriAcbCueRecord();
|
||||
this.CueList[i].IsWaveformIdentified = false;
|
||||
|
||||
this.CueList[i].CueId = (uint)CriUtfTable.GetUtfFieldForRow(cueTableUtf, i, "CueId");
|
||||
this.CueList[i].ReferenceType = (byte)CriUtfTable.GetUtfFieldForRow(cueTableUtf, i, "ReferenceType");
|
||||
this.CueList[i].ReferenceIndex = (ushort)CriUtfTable.GetUtfFieldForRow(cueTableUtf, i, "ReferenceIndex");
|
||||
|
||||
this.CueList[i].numWaveforms = this.SeqList[i].NumTracks;
|
||||
|
||||
switch (this.CueList[i].ReferenceType)
|
||||
{
|
||||
case 2:
|
||||
referenceItemsOffset = (ulong)CriUtfTable.GetOffsetForUtfFieldForRow(synthTableUtf, this.CueList[i].ReferenceIndex, "ReferenceItems");
|
||||
referenceItemsSize = CriUtfTable.GetSizeForUtfFieldForRow(synthTableUtf, this.CueList[i].ReferenceIndex, "ReferenceItems");
|
||||
referenceCorrection = referenceItemsSize + 2;
|
||||
break;
|
||||
case 3:
|
||||
case 8:
|
||||
if (i == 0)
|
||||
{
|
||||
referenceItemsOffset = (ulong)CriUtfTable.GetOffsetForUtfFieldForRow(synthTableUtf, 0, "ReferenceItems");
|
||||
referenceItemsSize = CriUtfTable.GetSizeForUtfFieldForRow(synthTableUtf, 0, "ReferenceItems");
|
||||
referenceCorrection = referenceItemsSize - 2; // samples found have only had a '01 00' record => Always Waveform[0]?.
|
||||
}
|
||||
else
|
||||
{
|
||||
//referenceCorrection += 4; // relative to previous offset, do not lookup
|
||||
// @TODO: Should this do a referenceItemsSize - 2 for the ReferenceIndex? Need to find
|
||||
// one where size > 4.
|
||||
//referenceItemsOffset = (ulong)CriUtfTable.GetOffsetForUtfFieldForRow(synthTableUtf, this.CueList[i].ReferenceIndex, "ReferenceItems");
|
||||
//referenceItemsSize = CriUtfTable.GetSizeForUtfFieldForRow(synthTableUtf, this.CueList[i].ReferenceIndex, "ReferenceItems");
|
||||
//referenceCorrection = referenceItemsSize - 2;
|
||||
referenceCorrection += 4 * (ulong)this.SeqList[i-1].NumTracks;// * (this.CueList[i].CueId - this.CueList[i-1].CueId);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new FormatException(String.Format(" Unexpected ReferenceType: '{0}' for CueIndex: '{1}.' Please report to VGMToolbox thread at hcs64.com forums, see link in 'Other' menu item.", this.CueList[i].ReferenceType.ToString("D"), i.ToString("D")));
|
||||
}
|
||||
|
||||
if (referenceItemsSize != 0)
|
||||
{
|
||||
// get wave form info
|
||||
this.CueList[i].WaveformIndex = ParseFile.ReadUshortBE(fs, (long)(referenceItemsOffset + referenceCorrection));
|
||||
|
||||
// get Streaming flag, 0 = in ACB files, 1 = in AWB file
|
||||
dummy = CriUtfTable.GetUtfFieldForRow(waveformTableUtf, this.CueList[i].WaveformIndex, "Streaming");
|
||||
|
||||
if (dummy != null) // check to see if this Id actually exists in the WaveformIndex
|
||||
{
|
||||
isStreaming = (byte)dummy;
|
||||
this.CueList[i].IsStreaming = isStreaming == 0 ? false : true;
|
||||
|
||||
// get waveform id and encode type from corresponding waveform
|
||||
var waveformId = CriUtfTable.GetUtfFieldForRow(waveformTableUtf, this.CueList[i].WaveformIndex, "Id");
|
||||
|
||||
if (waveformId != null)
|
||||
{
|
||||
// early revisions of ACB spec used "Id"
|
||||
this.CueList[i].WaveformId = (ushort)waveformId;
|
||||
}
|
||||
else
|
||||
{
|
||||
// later versions using "MemoryAwbId" and "StreamingAwbId"
|
||||
if (this.CueList[i].IsStreaming)
|
||||
{
|
||||
this.CueList[i].WaveformId = (ushort)CriUtfTable.GetUtfFieldForRow(waveformTableUtf, this.CueList[i].WaveformIndex, "StreamAwbId");
|
||||
}
|
||||
else
|
||||
{
|
||||
this.CueList[i].WaveformId = (ushort)CriUtfTable.GetUtfFieldForRow(waveformTableUtf, this.CueList[i].WaveformIndex, "MemoryAwbId");
|
||||
}
|
||||
}
|
||||
|
||||
this.CueList[i].EncodeType = (byte)CriUtfTable.GetUtfFieldForRow(waveformTableUtf, this.CueList[i].WaveformIndex, "EncodeType");
|
||||
|
||||
|
||||
// update flag
|
||||
this.CueList[i].IsWaveformIdentified = true;
|
||||
} // if (dummy != null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void ExtractAwbWithCueNames(string destinationFolder, AwbToExtract whichAwb)
|
||||
{
|
||||
CriUtfTable waveformTableUtf;
|
||||
byte encodeType;
|
||||
string rawFileName;
|
||||
|
||||
CriAfs2Archive awb;
|
||||
string rawFileFormat = "{0}_{1}{2}";
|
||||
|
||||
if (whichAwb == AwbToExtract.Internal)
|
||||
{
|
||||
awb = this.InternalAwb;
|
||||
}
|
||||
else
|
||||
{
|
||||
awb = this.ExternalAwb;
|
||||
}
|
||||
|
||||
using (FileStream fs = File.Open(awb.SourceFile, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
// use files names for internal AWB
|
||||
foreach (string key in this.CueNamesToWaveforms.Keys)
|
||||
{
|
||||
// extract file
|
||||
ParseFile.ExtractChunkToFile64(fs,
|
||||
(ulong)awb.Files[this.CueNamesToWaveforms[key]].FileOffsetByteAligned,
|
||||
(ulong)awb.Files[this.CueNamesToWaveforms[key]].FileLength,
|
||||
Path.Combine(destinationFolder, FileUtil.CleanFileName(key)), false, false);
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
// extract any items without a CueName
|
||||
//-------------------------------------
|
||||
using (FileStream acbStream = File.Open(this.SourceFile, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
waveformTableUtf = new CriUtfTable();
|
||||
waveformTableUtf.Initialize(acbStream, (long)this.WaveformTableOffset);
|
||||
}
|
||||
|
||||
// get list of unextracted files
|
||||
var unextractedFiles = awb.Files.Keys.Where(x => !this.CueNamesToWaveforms.ContainsValue(x));
|
||||
foreach (ushort key in unextractedFiles)
|
||||
{
|
||||
encodeType = (byte)CriUtfTable.GetUtfFieldForRow(waveformTableUtf, key, "EncodeType");
|
||||
|
||||
rawFileName = String.Format(rawFileFormat,
|
||||
Path.GetFileNameWithoutExtension(awb.SourceFile), key.ToString("D5"),
|
||||
CriAcbFile.GetFileExtensionForEncodeType(encodeType));
|
||||
|
||||
// extract file
|
||||
ParseFile.ExtractChunkToFile64(fs,
|
||||
(ulong)awb.Files[key].FileOffsetByteAligned,
|
||||
(ulong)awb.Files[key].FileLength,
|
||||
Path.Combine(destinationFolder, rawFileName), false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void ExtractAllUsingCueList(string destinationFolder)
|
||||
{
|
||||
CriUtfTable waveformTableUtf;
|
||||
ushort waveformIndex;
|
||||
byte encodeType;
|
||||
string rawFileName;
|
||||
string rawFileFormat = "{0}.{1}{2}";
|
||||
|
||||
FileStream internalFs = null;
|
||||
FileStream externalFs = null;
|
||||
|
||||
ArrayList internalIdsExtracted = new ArrayList();
|
||||
ArrayList externalIdsExtracted = new ArrayList();
|
||||
|
||||
string acbAwbDestinationFolder = Path.Combine(destinationFolder, ACB_AWB_EXTRACTION_FOLDER);
|
||||
string extAwbDestinationFolder = Path.Combine(destinationFolder, EXT_AWB_EXTRACTION_FOLDER);
|
||||
|
||||
try
|
||||
{
|
||||
// open streams
|
||||
if (this.InternalAwb != null)
|
||||
{
|
||||
internalFs = File.Open(this.InternalAwb.SourceFile, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
}
|
||||
|
||||
if (this.ExternalAwb != null)
|
||||
{
|
||||
externalFs = File.Open(this.ExternalAwb.SourceFile, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
}
|
||||
|
||||
// loop through cues and extract
|
||||
|
||||
for (int i = 0; i < CueList.Length; i++)
|
||||
{
|
||||
CriAcbCueRecord cue = CueList[i];
|
||||
|
||||
if (cue.IsWaveformIdentified)
|
||||
{
|
||||
if (cue.IsStreaming) // external AWB file
|
||||
{
|
||||
ParseFile.ExtractChunkToFile64(externalFs,
|
||||
(ulong)this.ExternalAwb.Files[cue.WaveformId].FileOffsetByteAligned,
|
||||
(ulong)this.ExternalAwb.Files[cue.WaveformId].FileLength,
|
||||
Path.Combine(extAwbDestinationFolder, FileUtil.CleanFileName(cue.CueName)), false, false);
|
||||
|
||||
externalIdsExtracted.Add(cue.WaveformId);
|
||||
}
|
||||
else // internal AWB file (inside ACB)
|
||||
{
|
||||
ParseFile.ExtractChunkToFile64(internalFs,
|
||||
(ulong)this.InternalAwb.Files[cue.WaveformId].FileOffsetByteAligned,
|
||||
(ulong)this.InternalAwb.Files[cue.WaveformId].FileLength,
|
||||
Path.Combine(acbAwbDestinationFolder, FileUtil.CleanFileName(cue.CueName)), false, false);
|
||||
|
||||
internalIdsExtracted.Add(cue.WaveformId);
|
||||
}
|
||||
} // if (cue.IsWaveformIdentified)
|
||||
}
|
||||
|
||||
// extract leftovers
|
||||
using (FileStream acbStream = File.Open(this.SourceFile, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
waveformTableUtf = new CriUtfTable();
|
||||
waveformTableUtf.Initialize(acbStream, (long)this.WaveformTableOffset);
|
||||
}
|
||||
|
||||
if (this.ExternalAwb != null)
|
||||
{
|
||||
var unextractedExternalFiles = this.ExternalAwb.Files.Keys.Where(x => !externalIdsExtracted.Contains(x));
|
||||
foreach (ushort key in unextractedExternalFiles)
|
||||
{
|
||||
waveformIndex = GetWaveformRowIndexForWaveformId(waveformTableUtf, key, true);
|
||||
|
||||
encodeType = (byte)CriUtfTable.GetUtfFieldForRow(waveformTableUtf, waveformIndex, "EncodeType");
|
||||
|
||||
rawFileName = String.Format(rawFileFormat,
|
||||
Path.GetFileName(this.ExternalAwb.SourceFile), key.ToString("D5"),
|
||||
CriAcbFile.GetFileExtensionForEncodeType(encodeType));
|
||||
|
||||
// extract file
|
||||
ParseFile.ExtractChunkToFile64(externalFs,
|
||||
(ulong)this.ExternalAwb.Files[key].FileOffsetByteAligned,
|
||||
(ulong)this.ExternalAwb.Files[key].FileLength,
|
||||
Path.Combine(extAwbDestinationFolder, rawFileName), false, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.InternalAwb != null)
|
||||
{
|
||||
var unextractedInternalFiles = this.InternalAwb.Files.Keys.Where(x => !internalIdsExtracted.Contains(x));
|
||||
foreach (ushort key in unextractedInternalFiles)
|
||||
{
|
||||
waveformIndex = GetWaveformRowIndexForWaveformId(waveformTableUtf, key, false);
|
||||
|
||||
encodeType = (byte)CriUtfTable.GetUtfFieldForRow(waveformTableUtf, waveformIndex, "EncodeType");
|
||||
|
||||
rawFileName = String.Format(rawFileFormat,
|
||||
Path.GetFileName(this.InternalAwb.SourceFile), key.ToString("D5"),
|
||||
CriAcbFile.GetFileExtensionForEncodeType(encodeType));
|
||||
|
||||
// extract file
|
||||
ParseFile.ExtractChunkToFile64(internalFs,
|
||||
(ulong)this.InternalAwb.Files[key].FileOffsetByteAligned,
|
||||
(ulong)this.InternalAwb.Files[key].FileLength,
|
||||
Path.Combine(acbAwbDestinationFolder, rawFileName), false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (internalFs != null)
|
||||
{
|
||||
internalFs.Close();
|
||||
internalFs.Dispose();
|
||||
}
|
||||
|
||||
if (externalFs != null)
|
||||
{
|
||||
externalFs.Close();
|
||||
externalFs.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected CriAfs2Archive InitializeExternalAwbArchive()
|
||||
{
|
||||
CriAfs2Archive afs2 = null;
|
||||
|
||||
string awbDirectory;
|
||||
string awbMask;
|
||||
string acbBaseFileName;
|
||||
string[] awbFiles;
|
||||
|
||||
byte[] awbHashStored;
|
||||
byte[] awbHashCalculated;
|
||||
|
||||
awbDirectory = Path.GetDirectoryName(Path.GetFullPath(this.SourceFile));
|
||||
|
||||
// try format 1
|
||||
acbBaseFileName = Path.GetFileNameWithoutExtension(this.SourceFile);
|
||||
awbMask = String.Format(AWB_FORMAT1, acbBaseFileName);
|
||||
awbFiles = Directory.GetFiles(awbDirectory, awbMask, SearchOption.TopDirectoryOnly);
|
||||
|
||||
if (awbFiles.Length < 1)
|
||||
{
|
||||
// try format 2
|
||||
awbMask = String.Format(AWB_FORMAT2, acbBaseFileName);
|
||||
awbFiles = Directory.GetFiles(awbDirectory, awbMask, SearchOption.TopDirectoryOnly);
|
||||
}
|
||||
|
||||
if (awbFiles.Length < 1)
|
||||
{
|
||||
// try format 3
|
||||
awbMask = String.Format(AWB_FORMAT3, acbBaseFileName);
|
||||
awbFiles = Directory.GetFiles(awbDirectory, awbMask, SearchOption.TopDirectoryOnly);
|
||||
}
|
||||
|
||||
// file not found
|
||||
if (awbFiles.Length < 1)
|
||||
{
|
||||
throw new FileNotFoundException(String.Format("Cannot find AWB file. Please verify corresponding AWB file is named '{0}', '{1}', or '{2}'.",
|
||||
String.Format(AWB_FORMAT1, acbBaseFileName), String.Format(AWB_FORMAT2, acbBaseFileName), String.Format(AWB_FORMAT3, acbBaseFileName)));
|
||||
}
|
||||
|
||||
if (awbFiles.Length > 1)
|
||||
{
|
||||
throw new FileNotFoundException(String.Format("More than one matching AWB file for this ACB. Please verify only one AWB file is named '{1}' or '{2}'.",
|
||||
String.Format(AWB_FORMAT1, acbBaseFileName), String.Format(AWB_FORMAT2, acbBaseFileName)));
|
||||
}
|
||||
|
||||
// initialize AFS2 file
|
||||
using (FileStream fs = File.Open(awbFiles[0], FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
awbHashStored = this.StreamAwbHash;
|
||||
|
||||
if (awbHashStored.Length == 0x10) // MD5, hash in newer format unknown
|
||||
{
|
||||
// validate MD5 checksum
|
||||
/*
|
||||
awbHashCalculated = ByteConversion.GetBytesFromHexString(ChecksumUtil.GetMd5OfFullFile(fs));
|
||||
|
||||
if (!ParseFile.CompareSegment(awbHashCalculated, 0, awbHashStored))
|
||||
{
|
||||
throw new FormatException(String.Format("AWB file, <{0}>, did not match checksum inside ACB file.", Path.GetFileName(fs.Name)));
|
||||
}
|
||||
Validate my dick */
|
||||
}
|
||||
|
||||
afs2 = new CriAfs2Archive(fs, 0);
|
||||
}
|
||||
|
||||
return afs2;
|
||||
}
|
||||
|
||||
public static ushort GetWaveformRowIndexForWaveformId(CriUtfTable utfTable, ushort waveformId, bool isStreamed)
|
||||
{
|
||||
ushort ret = ushort.MaxValue;
|
||||
|
||||
for (int i = 0; i < utfTable.NumberOfRows; i++)
|
||||
{
|
||||
var tempId = CriUtfTable.GetUtfFieldForRow(utfTable, i, "Id");
|
||||
|
||||
// newer archives use different labels
|
||||
if (tempId == null)
|
||||
{
|
||||
if (isStreamed)
|
||||
{
|
||||
tempId = CriUtfTable.GetUtfFieldForRow(utfTable, i, "StreamAwbId");
|
||||
}
|
||||
else
|
||||
{
|
||||
tempId = CriUtfTable.GetUtfFieldForRow(utfTable, i, "MemoryAwbId");
|
||||
}
|
||||
}
|
||||
|
||||
if ((ushort)tempId == waveformId)
|
||||
{
|
||||
ret = (ushort)i;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static string GetFileExtensionForEncodeType(byte encodeType)
|
||||
{
|
||||
string ext;
|
||||
|
||||
switch (encodeType)
|
||||
{
|
||||
case WAVEFORM_ENCODE_TYPE_ADX:
|
||||
ext = ".adx";
|
||||
break;
|
||||
case WAVEFORM_ENCODE_TYPE_HCA:
|
||||
ext = ".hca";
|
||||
break;
|
||||
case WAVEFORM_ENCODE_TYPE_ATRAC3:
|
||||
ext = ".at3";
|
||||
break;
|
||||
case WAVEFORM_ENCODE_TYPE_ATRAC9:
|
||||
ext = ".at9";
|
||||
break;
|
||||
case WAVEFORM_ENCODE_TYPE_VAG:
|
||||
ext = ".vag";
|
||||
break;
|
||||
case WAVEFORM_ENCODE_TYPE_BCWAV:
|
||||
ext = ".bcwav";
|
||||
break;
|
||||
case WAVEFORM_ENCODE_TYPE_NINTENDO_DSP:
|
||||
ext = ".dsp";
|
||||
break;
|
||||
default:
|
||||
ext = String.Format(".EncodeType-{0}.bin", encodeType.ToString("D2"));
|
||||
break;
|
||||
}
|
||||
|
||||
return ext;
|
||||
}
|
||||
}
|
||||
}
|
||||
155
acb_extract/src/CriAfs2Archive.cs
Normal file
155
acb_extract/src/CriAfs2Archive.cs
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
//using VGMToolbox.util;
|
||||
|
||||
namespace vgm_acb.extract
|
||||
{
|
||||
public class CriAfs2File
|
||||
{
|
||||
public ushort CueId { set; get; }
|
||||
public long FileOffsetRaw { set; get; }
|
||||
public long FileOffsetByteAligned { set; get; }
|
||||
public long FileLength { set; get; }
|
||||
|
||||
// public string FileName { set; get; } // comes from ACB for now, maybe included in archive later?
|
||||
}
|
||||
|
||||
public class CriAfs2Archive
|
||||
{
|
||||
public static readonly byte[] SIGNATURE = new byte[] { 0x41, 0x46, 0x53, 0x32 };
|
||||
public const string EXTRACTION_FOLDER_FORMAT = "_vgmt_awb_ext_{0}";
|
||||
|
||||
public string SourceFile { set; get; }
|
||||
public byte[] MagicBytes { set; get; }
|
||||
public byte[] Version { set; get; }
|
||||
public uint FileCount { set; get; }
|
||||
public uint ByteAlignment { set; get; }
|
||||
public Dictionary <ushort, CriAfs2File> Files { set; get; }
|
||||
//public CriAfs2File[] Files { set; get; }
|
||||
|
||||
public CriAfs2Archive(FileStream fs, long offset)
|
||||
{
|
||||
ushort previousCueId = ushort.MaxValue;
|
||||
|
||||
if (IsCriAfs2Archive(fs, offset))
|
||||
{
|
||||
this.SourceFile = fs.Name;
|
||||
long afs2FileSize = fs.Length;
|
||||
|
||||
this.MagicBytes = ParseFile.ParseSimpleOffset(fs, offset, SIGNATURE.Length);
|
||||
this.Version = ParseFile.ParseSimpleOffset(fs, offset + 4, 4);
|
||||
this.FileCount = ParseFile.ReadUintLE(fs, offset + 8);
|
||||
|
||||
// setup offset field size
|
||||
int offsetFieldSize = this.Version[1]; // known values: 2 and 4. 4 is most common. I've only seen 2 in 'se_enemy_gurdon_galaga_bee.acb' from Sonic Lost World.
|
||||
uint offsetMask = 0;
|
||||
|
||||
for (int j = 0; j < offsetFieldSize; j++)
|
||||
{
|
||||
offsetMask |= (uint)((byte)0xFF << (j * 8));
|
||||
}
|
||||
|
||||
if (this.FileCount > ushort.MaxValue)
|
||||
{
|
||||
throw new FormatException(String.Format("ERROR, file count exceeds max value for ushort. Please report this at official feedback forums (see 'Other' menu item).", fs.Name));
|
||||
}
|
||||
|
||||
this.ByteAlignment = ParseFile.ReadUintLE(fs, offset + 0xC);
|
||||
this.Files = new Dictionary<ushort, CriAfs2File>((int)this.FileCount);
|
||||
|
||||
CriAfs2File dummy;
|
||||
|
||||
for (ushort i = 0; i < this.FileCount; i++)
|
||||
{
|
||||
dummy = new CriAfs2File();
|
||||
|
||||
dummy.CueId = ParseFile.ReadUshortLE(fs, offset + (0x10 + (2 * i)));
|
||||
dummy.FileOffsetRaw = ParseFile.ReadUintLE(fs, offset + (0x10 + (this.FileCount * 2) + (offsetFieldSize * i)));
|
||||
|
||||
// mask off unneeded info
|
||||
dummy.FileOffsetRaw &= offsetMask;
|
||||
|
||||
// add offset
|
||||
dummy.FileOffsetRaw += offset; // for AFS2 files inside of other files (ACB, etc.)
|
||||
|
||||
// set file offset to byte alignment
|
||||
if ((dummy.FileOffsetRaw % this.ByteAlignment) != 0)
|
||||
{
|
||||
dummy.FileOffsetByteAligned = MathUtil.RoundUpToByteAlignment(dummy.FileOffsetRaw, this.ByteAlignment);
|
||||
}
|
||||
else
|
||||
{
|
||||
dummy.FileOffsetByteAligned = dummy.FileOffsetRaw;
|
||||
}
|
||||
|
||||
//---------------
|
||||
// set file size
|
||||
//---------------
|
||||
// last file will use final offset entry
|
||||
if (i == this.FileCount - 1)
|
||||
{
|
||||
dummy.FileLength = (ParseFile.ReadUintLE(fs, offset + (0x10 + (this.FileCount * 2) + ((offsetFieldSize) * i)) + offsetFieldSize) + offset) - dummy.FileOffsetByteAligned;
|
||||
}
|
||||
|
||||
// else set length for previous cue id
|
||||
if (previousCueId != ushort.MaxValue)
|
||||
{
|
||||
this.Files[previousCueId].FileLength = dummy.FileOffsetRaw - this.Files[previousCueId].FileOffsetByteAligned;
|
||||
}
|
||||
|
||||
this.Files.Add(dummy.CueId, dummy);
|
||||
previousCueId = dummy.CueId;
|
||||
} // for (uint i = 0; i < this.FileCount; i++)
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new FormatException(String.Format("AFS2 magic bytes not found at offset: 0x{0}.", offset.ToString("X8")));
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsCriAfs2Archive(FileStream fs, long offset)
|
||||
{
|
||||
bool ret = false;
|
||||
byte[] checkBytes = ParseFile.ParseSimpleOffset(fs, offset, SIGNATURE.Length);
|
||||
|
||||
if (ParseFile.CompareSegment(checkBytes, 0, SIGNATURE))
|
||||
{
|
||||
ret = true;
|
||||
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void ExtractAll()
|
||||
{
|
||||
string baseExtractionFolder = Path.Combine(Path.GetDirectoryName(this.SourceFile),
|
||||
String.Format(EXTRACTION_FOLDER_FORMAT, Path.GetFileNameWithoutExtension(this.SourceFile)));
|
||||
|
||||
this.ExtractAllRaw(baseExtractionFolder);
|
||||
}
|
||||
|
||||
public void ExtractAllRaw(string destinationFolder)
|
||||
{
|
||||
string rawFileFormat = "{0}.{1}.bin";
|
||||
|
||||
using (FileStream fs = File.Open(this.SourceFile, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
|
||||
foreach (ushort key in this.Files.Keys)
|
||||
{
|
||||
ParseFile.ExtractChunkToFile64(fs,
|
||||
(ulong)this.Files[key].FileOffsetByteAligned,
|
||||
(ulong)this.Files[key].FileLength,
|
||||
Path.Combine(destinationFolder,
|
||||
String.Format(rawFileFormat, Path.GetFileName(this.SourceFile), key.ToString("D5"))), false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
864
acb_extract/src/CriUtfTable.cs
Normal file
864
acb_extract/src/CriUtfTable.cs
Normal file
|
|
@ -0,0 +1,864 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
//using VGMToolbox.util;
|
||||
|
||||
namespace vgm_acb.extract
|
||||
{
|
||||
public class CriField
|
||||
{
|
||||
public byte Type { set; get; }
|
||||
public string Name { set; get; }
|
||||
public object Value { set; get; }
|
||||
public ulong Offset { set; get; }
|
||||
public ulong Size { set; get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
string value = String.Empty;
|
||||
|
||||
sb.AppendFormat("0x{0} {1} {2} = {3}",
|
||||
this.Offset.ToString("X8"),
|
||||
this.Type.ToString("X2"),
|
||||
this.Name,
|
||||
this.GetStringValue());
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public string ToString(FileStream fs)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
string value = String.Empty;
|
||||
|
||||
sb.AppendFormat("0x{0} {1} {2} = {3}",
|
||||
this.Offset.ToString("X8"),
|
||||
this.Type.ToString("X2"),
|
||||
this.Name,
|
||||
this.GetStringValue(fs));
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public string GetStringValue(FileStream utfStream = null)
|
||||
{
|
||||
ulong numericValue;
|
||||
byte maskedType = (byte)(this.Type & CriUtfTable.COLUMN_TYPE_MASK);
|
||||
string stringValue = String.Empty;
|
||||
|
||||
switch (maskedType)
|
||||
{
|
||||
case CriUtfTable.COLUMN_TYPE_DATA:
|
||||
if (utfStream == null)
|
||||
{
|
||||
stringValue = this.Value.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CriUtfTable.IsUtfTable(utfStream, (long)this.Offset))
|
||||
{
|
||||
CriUtfTable newUtf = new CriUtfTable();
|
||||
newUtf.Initialize(utfStream, (long)this.Offset);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CriUtfTable.COLUMN_TYPE_STRING:
|
||||
stringValue = (string)this.Value;
|
||||
break;
|
||||
case CriUtfTable.COLUMN_TYPE_FLOAT:
|
||||
stringValue = Convert.ToSingle(this.Value).ToString();
|
||||
break;
|
||||
case CriUtfTable.COLUMN_TYPE_8BYTE:
|
||||
case CriUtfTable.COLUMN_TYPE_4BYTE2:
|
||||
case CriUtfTable.COLUMN_TYPE_4BYTE:
|
||||
case CriUtfTable.COLUMN_TYPE_2BYTE2:
|
||||
case CriUtfTable.COLUMN_TYPE_2BYTE:
|
||||
case CriUtfTable.COLUMN_TYPE_1BYTE2:
|
||||
case CriUtfTable.COLUMN_TYPE_1BYTE:
|
||||
numericValue = Convert.ToUInt64(this.Value);
|
||||
stringValue = String.Format("0x{0}", numericValue.ToString("X8"));
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return stringValue;
|
||||
}
|
||||
}
|
||||
|
||||
public class CriUtfTocFileInfo
|
||||
{
|
||||
public string DirName { set; get; }
|
||||
public string FileName { set; get; }
|
||||
public uint FileSize { set; get; }
|
||||
public uint ExtractSize { set; get; }
|
||||
public ulong FileOffset { set; get; }
|
||||
}
|
||||
|
||||
public class CriUtfTable
|
||||
{
|
||||
// thanks to hcs for all of this!!!
|
||||
|
||||
#region Constants
|
||||
|
||||
public static readonly byte[] SIGNATURE_BYTES = new byte[] { 0x40, 0x55, 0x54, 0x46 };
|
||||
public const string LCG_SEED_KEY = "SEED";
|
||||
public const string LCG_INCREMENT_KEY = "INC";
|
||||
public const string TEMP_UTF_TABLE_FILE = "VGMT_UTF.BIN";
|
||||
|
||||
public const byte COLUMN_STORAGE_MASK = 0xF0;
|
||||
public const byte COLUMN_STORAGE_PERROW = 0x50;
|
||||
public const byte COLUMN_STORAGE_CONSTANT = 0x30;
|
||||
public const byte COLUMN_STORAGE_CONSTANT2 = 0x70;
|
||||
public const byte COLUMN_STORAGE_ZERO = 0x10;
|
||||
|
||||
// I suspect that "type 2" is signed
|
||||
public const byte COLUMN_TYPE_MASK = 0x0F;
|
||||
public const byte COLUMN_TYPE_DATA = 0x0B;
|
||||
public const byte COLUMN_TYPE_STRING = 0x0A;
|
||||
// 0x09 double?
|
||||
public const byte COLUMN_TYPE_FLOAT = 0x08;
|
||||
// 0x07 signed 8byte?
|
||||
public const byte COLUMN_TYPE_8BYTE = 0x06;
|
||||
public const byte COLUMN_TYPE_4BYTE2 = 0x05;
|
||||
public const byte COLUMN_TYPE_4BYTE = 0x04;
|
||||
public const byte COLUMN_TYPE_2BYTE2 = 0x03;
|
||||
public const byte COLUMN_TYPE_2BYTE = 0x02;
|
||||
public const byte COLUMN_TYPE_1BYTE2 = 0x01;
|
||||
public const byte COLUMN_TYPE_1BYTE = 0x00;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Members
|
||||
|
||||
public string SourceFile { set; get; }
|
||||
public long BaseOffset { set; get; }
|
||||
public string UtfTableFile { set; get; }
|
||||
|
||||
public byte[] MagicBytes { set; get; }
|
||||
public bool IsEncrypted { set; get; }
|
||||
public uint TableSize { set; get; }
|
||||
|
||||
public ushort Unknown1 { set; get; }
|
||||
|
||||
public uint RowOffset { set; get; }
|
||||
public uint StringTableOffset { set; get; }
|
||||
public uint DataOffset { set; get; }
|
||||
|
||||
public uint TableNameOffset { set; get; }
|
||||
public string TableName { set; get; }
|
||||
|
||||
public ushort NumberOfFields { set; get; }
|
||||
|
||||
public ushort RowSize { set; get; }
|
||||
public uint NumberOfRows { set; get; }
|
||||
|
||||
public Dictionary<string, CriField>[] Rows { set; get; }
|
||||
|
||||
public CriUtfReader UtfReader { set; get; }
|
||||
|
||||
#endregion
|
||||
|
||||
public void Initialize(FileStream fs, long offset)
|
||||
{
|
||||
this.SourceFile = fs.Name;
|
||||
this.BaseOffset = offset;
|
||||
|
||||
try
|
||||
{
|
||||
this.MagicBytes = ParseFile.ParseSimpleOffset(fs, this.BaseOffset, 4);
|
||||
|
||||
// check if file is decrypted and get decryption keys if needed
|
||||
this.checkEncryption(fs);
|
||||
|
||||
// write (decrypted) utf header to file
|
||||
this.UtfTableFile = this.WriteTableToTempFile(fs, offset);
|
||||
|
||||
this.IsEncrypted = false; // since we've decrypted to a temp file
|
||||
this.UtfReader.IsEncrypted = false;
|
||||
|
||||
if (ParseFile.CompareSegment(this.MagicBytes, 0, SIGNATURE_BYTES))
|
||||
{
|
||||
using (FileStream utfTableStream = File.Open(this.UtfTableFile, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
// read header
|
||||
this.initializeUtfHeader(utfTableStream);
|
||||
|
||||
// initialize rows
|
||||
this.Rows = new Dictionary<string, CriField>[this.NumberOfRows];
|
||||
|
||||
// read schema
|
||||
if (this.TableSize > 0)
|
||||
{
|
||||
this.initializeUtfSchema(fs, utfTableStream, 0x20);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Dictionary<string, byte> foo = GetKeysForEncryptedUtfTable(this.MagicBytes);
|
||||
throw new FormatException(String.Format("@UTF signature not found at offset <0x{0}>.", offset.ToString("X8")));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!String.IsNullOrEmpty(this.UtfTableFile))
|
||||
{
|
||||
File.Delete(this.UtfTableFile);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
CriField field;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
using (FileStream fs = File.Open(this.SourceFile, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
for (int i = 0; i < this.Rows.Length; i++)
|
||||
{
|
||||
var d = this.Rows[i];
|
||||
|
||||
foreach (var key in d.Keys)
|
||||
{
|
||||
field = d[key];
|
||||
sb.AppendLine(field.ToString(fs));
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private void checkEncryption(FileStream fs)
|
||||
{
|
||||
if (ParseFile.CompareSegment(this.MagicBytes, 0, SIGNATURE_BYTES))
|
||||
{
|
||||
this.IsEncrypted = false;
|
||||
this.UtfReader = new CriUtfReader();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.IsEncrypted = true;
|
||||
Dictionary<string, byte> lcgKeys = GetKeysForEncryptedUtfTable(this.MagicBytes);
|
||||
|
||||
if (lcgKeys.Count != 2)
|
||||
{
|
||||
throw new FormatException(String.Format("Unable to decrypt UTF table at offset: 0x{0}", this.BaseOffset.ToString("X8")));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.UtfReader = new CriUtfReader(lcgKeys[LCG_SEED_KEY], lcgKeys[LCG_INCREMENT_KEY], this.IsEncrypted);
|
||||
}
|
||||
|
||||
this.MagicBytes = this.UtfReader.GetBytes(fs, this.BaseOffset, 4, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void initializeUtfHeader(FileStream UtfTableFs)
|
||||
{
|
||||
this.TableSize = ParseFile.ReadUintBE(UtfTableFs, 4);
|
||||
|
||||
this.Unknown1 = ParseFile.ReadUshortBE(UtfTableFs, 8);
|
||||
|
||||
this.RowOffset = (uint)ParseFile.ReadUshortBE(UtfTableFs, 0xA) + 8;
|
||||
this.StringTableOffset = ParseFile.ReadUintBE(UtfTableFs, 0xC) + 8;
|
||||
this.DataOffset = ParseFile.ReadUintBE(UtfTableFs, 0x10) + 8;
|
||||
|
||||
this.TableNameOffset = ParseFile.ReadUintBE(UtfTableFs, 0x14);
|
||||
this.TableName = ParseFile.ReadAsciiString(UtfTableFs, this.StringTableOffset + this.TableNameOffset);
|
||||
|
||||
this.NumberOfFields = ParseFile.ReadUshortBE(UtfTableFs, 0x18);
|
||||
|
||||
this.RowSize = ParseFile.ReadUshortBE(UtfTableFs, 0x1A);
|
||||
this.NumberOfRows = ParseFile.ReadUintBE(UtfTableFs, 0x1C);
|
||||
}
|
||||
|
||||
private void initializeUtfSchema(FileStream SourceFs, FileStream UtfTableFs, long schemaOffset)
|
||||
{
|
||||
long nameOffset;
|
||||
|
||||
long constantOffset;
|
||||
|
||||
long dataOffset;
|
||||
long dataSize;
|
||||
|
||||
long rowDataOffset;
|
||||
long rowDataSize;
|
||||
|
||||
long currentOffset = schemaOffset;
|
||||
long currentRowBase;
|
||||
long currentRowOffset = 0;
|
||||
|
||||
CriField field;
|
||||
|
||||
for (uint i = 0; i < this.NumberOfRows; i++)
|
||||
{
|
||||
//if (i == 0x1a2a)
|
||||
//{
|
||||
// int yuuuu = 1;
|
||||
//}
|
||||
//try
|
||||
//{
|
||||
currentOffset = schemaOffset;
|
||||
currentRowBase = this.RowOffset + (this.RowSize * i);
|
||||
currentRowOffset = 0;
|
||||
this.Rows[i] = new Dictionary<string, CriField>();
|
||||
|
||||
// parse fields
|
||||
for (ushort j = 0; j < this.NumberOfFields; j++)
|
||||
{
|
||||
field = new CriField();
|
||||
|
||||
field.Type = ParseFile.ReadByte(UtfTableFs, currentOffset);
|
||||
nameOffset = ParseFile.ReadUintBE(UtfTableFs, currentOffset + 1);
|
||||
field.Name = ParseFile.ReadAsciiString(UtfTableFs, this.StringTableOffset + nameOffset);
|
||||
|
||||
// each row will have a constant
|
||||
if (((field.Type & COLUMN_STORAGE_MASK) == COLUMN_STORAGE_CONSTANT) ||
|
||||
((field.Type & COLUMN_STORAGE_MASK) == COLUMN_STORAGE_CONSTANT2))
|
||||
{
|
||||
// capture offset of constant
|
||||
constantOffset = currentOffset + 5;
|
||||
|
||||
// read the constant depending on the type
|
||||
switch (field.Type & COLUMN_TYPE_MASK)
|
||||
{
|
||||
case COLUMN_TYPE_STRING:
|
||||
dataOffset = ParseFile.ReadUintBE(UtfTableFs, constantOffset);
|
||||
field.Value = ParseFile.ReadAsciiString(UtfTableFs, this.StringTableOffset + dataOffset);
|
||||
currentOffset += 4;
|
||||
break;
|
||||
case COLUMN_TYPE_8BYTE:
|
||||
field.Value = ParseFile.ReadUlongBE(UtfTableFs, constantOffset);
|
||||
currentOffset += 8;
|
||||
break;
|
||||
case COLUMN_TYPE_DATA:
|
||||
dataOffset = ParseFile.ReadUintBE(UtfTableFs, constantOffset);
|
||||
dataSize = ParseFile.ReadUintBE(UtfTableFs, constantOffset + 4);
|
||||
field.Offset = (ulong)(this.BaseOffset + this.DataOffset + dataOffset);
|
||||
field.Size = (ulong)dataSize;
|
||||
|
||||
// don't think this is encrypted, need to check
|
||||
field.Value = ParseFile.ParseSimpleOffset(SourceFs, (long)field.Offset, (int)dataSize);
|
||||
currentOffset += 8;
|
||||
break;
|
||||
case COLUMN_TYPE_FLOAT:
|
||||
field.Value = ParseFile.ReadFloatBE(UtfTableFs, constantOffset);
|
||||
currentOffset += 4;
|
||||
break;
|
||||
case COLUMN_TYPE_4BYTE2:
|
||||
field.Value = ParseFile.ReadInt32BE(UtfTableFs, constantOffset);
|
||||
currentOffset += 4;
|
||||
break;
|
||||
case COLUMN_TYPE_4BYTE:
|
||||
field.Value = ParseFile.ReadUintBE(UtfTableFs, constantOffset);
|
||||
currentOffset += 4;
|
||||
break;
|
||||
case COLUMN_TYPE_2BYTE2:
|
||||
field.Value = ParseFile.ReadInt16BE(UtfTableFs, constantOffset);
|
||||
currentOffset += 2;
|
||||
break;
|
||||
case COLUMN_TYPE_2BYTE:
|
||||
field.Value = ParseFile.ReadUshortBE(UtfTableFs, constantOffset);
|
||||
currentOffset += 2;
|
||||
break;
|
||||
case COLUMN_TYPE_1BYTE2:
|
||||
field.Value = ParseFile.ReadSByte(UtfTableFs, constantOffset);
|
||||
currentOffset += 1;
|
||||
break;
|
||||
case COLUMN_TYPE_1BYTE:
|
||||
field.Value = ParseFile.ReadByte(UtfTableFs, constantOffset);
|
||||
currentOffset += 1;
|
||||
break;
|
||||
default:
|
||||
throw new FormatException(String.Format("Unknown COLUMN TYPE at offset: 0x{0}", currentOffset.ToString("X8")));
|
||||
|
||||
} // switch (field.Type & COLUMN_TYPE_MASK)
|
||||
}
|
||||
else if ((field.Type & COLUMN_STORAGE_MASK) == COLUMN_STORAGE_PERROW)
|
||||
{
|
||||
// read the constant depending on the type
|
||||
switch (field.Type & COLUMN_TYPE_MASK)
|
||||
{
|
||||
case COLUMN_TYPE_STRING:
|
||||
rowDataOffset = ParseFile.ReadUintBE(UtfTableFs, currentRowBase + currentRowOffset);
|
||||
field.Value = ParseFile.ReadAsciiString(UtfTableFs, this.StringTableOffset + rowDataOffset);
|
||||
currentRowOffset += 4;
|
||||
break;
|
||||
case COLUMN_TYPE_8BYTE:
|
||||
field.Value = ParseFile.ReadUlongBE(UtfTableFs, currentRowBase + currentRowOffset);
|
||||
currentRowOffset += 8;
|
||||
break;
|
||||
case COLUMN_TYPE_DATA:
|
||||
rowDataOffset = ParseFile.ReadUintBE(UtfTableFs, currentRowBase + currentRowOffset);
|
||||
rowDataSize = ParseFile.ReadUintBE(UtfTableFs, currentRowBase + currentRowOffset + 4);
|
||||
field.Offset = (ulong)(this.BaseOffset + this.DataOffset + rowDataOffset);
|
||||
field.Size = (ulong)rowDataSize;
|
||||
|
||||
// don't think this is encrypted
|
||||
field.Value = ParseFile.ParseSimpleOffset(SourceFs, (long)field.Offset, (int)rowDataSize);
|
||||
currentRowOffset += 8;
|
||||
break;
|
||||
case COLUMN_TYPE_FLOAT:
|
||||
field.Value = ParseFile.ReadFloatBE(UtfTableFs, currentRowBase + currentRowOffset);
|
||||
currentRowOffset += 4;
|
||||
break;
|
||||
case COLUMN_TYPE_4BYTE2:
|
||||
field.Value = ParseFile.ReadInt32BE(UtfTableFs, currentRowBase + currentRowOffset);
|
||||
currentRowOffset += 4;
|
||||
break;
|
||||
case COLUMN_TYPE_4BYTE:
|
||||
field.Value = ParseFile.ReadUintBE(UtfTableFs, currentRowBase + currentRowOffset);
|
||||
currentRowOffset += 4;
|
||||
break;
|
||||
case COLUMN_TYPE_2BYTE2:
|
||||
field.Value = ParseFile.ReadInt16BE(UtfTableFs, currentRowBase + currentRowOffset);
|
||||
currentRowOffset += 2;
|
||||
break;
|
||||
case COLUMN_TYPE_2BYTE:
|
||||
field.Value = ParseFile.ReadUshortBE(UtfTableFs, currentRowBase + currentRowOffset);
|
||||
currentRowOffset += 2;
|
||||
break;
|
||||
case COLUMN_TYPE_1BYTE2:
|
||||
field.Value = ParseFile.ReadSByte(UtfTableFs, currentRowBase + currentRowOffset);
|
||||
currentRowOffset += 1;
|
||||
break;
|
||||
case COLUMN_TYPE_1BYTE:
|
||||
field.Value = ParseFile.ReadByte(UtfTableFs, currentRowBase + currentRowOffset);
|
||||
currentRowOffset += 1;
|
||||
break;
|
||||
default:
|
||||
throw new FormatException(String.Format("Unknown COLUMN TYPE at offset: 0x{0}", currentOffset.ToString("X8")));
|
||||
|
||||
} // switch (field.Type & COLUMN_TYPE_MASK)
|
||||
|
||||
} // if ((fields[i].Type & COLUMN_STORAGE_MASK) == COLUMN_STORAGE_CONSTANT)
|
||||
|
||||
// add field to dictionary
|
||||
this.Rows[i].Add(field.Name, field);
|
||||
|
||||
// move to next field
|
||||
currentOffset += 5; // sizeof(CriField.Type + CriField.NameOffset)
|
||||
|
||||
} // for (ushort j = 0; j < this.NumberOfFields; j++)
|
||||
//}
|
||||
//catch (Exception ex)
|
||||
//{
|
||||
// int xxxx = 1;
|
||||
//}
|
||||
} // for (uint i = 0; i < this.NumberOfRows; i++)
|
||||
}
|
||||
|
||||
public string WriteTableToTempFile(FileStream fs, long offsetToUtfTable)
|
||||
{
|
||||
string pathToUtf = null;
|
||||
|
||||
int totalBytesRead = 0;
|
||||
int maxRead;
|
||||
byte[] buffer = new byte[Constants.FileReadChunkSize];
|
||||
|
||||
int tableSize = (int)this.UtfReader.ReadUint(fs, offsetToUtfTable, 4) + 8;
|
||||
|
||||
pathToUtf = Path.Combine(Path.GetTempPath(), TEMP_UTF_TABLE_FILE);
|
||||
|
||||
if (this.IsEncrypted)
|
||||
{
|
||||
using (FileStream tempStream = File.Open(pathToUtf, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
{
|
||||
while (totalBytesRead < tableSize)
|
||||
{
|
||||
if ((tableSize - totalBytesRead) > buffer.Length)
|
||||
{
|
||||
maxRead = buffer.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
maxRead = (tableSize - totalBytesRead);
|
||||
}
|
||||
|
||||
buffer = this.UtfReader.GetBytes(fs, offsetToUtfTable, maxRead, totalBytesRead);
|
||||
tempStream.Write(buffer, 0, maxRead);
|
||||
totalBytesRead += maxRead;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (File.Exists(pathToUtf))
|
||||
{
|
||||
File.Delete(pathToUtf);
|
||||
}
|
||||
|
||||
ParseFile.ExtractChunkToFile64(fs, (ulong)offsetToUtfTable, (ulong)tableSize, pathToUtf, false, false);
|
||||
}
|
||||
|
||||
return pathToUtf;
|
||||
}
|
||||
|
||||
public static object GetUtfFieldForRow(CriUtfTable utfTable, int rowIndex, string key)
|
||||
{
|
||||
object ret = null;
|
||||
|
||||
if (utfTable.Rows.GetLength(0) > rowIndex)
|
||||
{
|
||||
if (utfTable.Rows[rowIndex].ContainsKey(key))
|
||||
{
|
||||
ret = utfTable.Rows[rowIndex][key].Value;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static ulong GetOffsetForUtfFieldForRow(CriUtfTable utfTable, int rowIndex, string key)
|
||||
{
|
||||
ulong ret = 0;
|
||||
|
||||
if (utfTable.Rows.GetLength(0) > rowIndex)
|
||||
{
|
||||
if (utfTable.Rows[rowIndex].ContainsKey(key))
|
||||
{
|
||||
ret = utfTable.Rows[rowIndex][key].Offset;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static ulong GetSizeForUtfFieldForRow(CriUtfTable utfTable, int rowIndex, string key)
|
||||
{
|
||||
ulong ret = 0;
|
||||
|
||||
if (utfTable.Rows.GetLength(0) > rowIndex)
|
||||
{
|
||||
if (utfTable.Rows[rowIndex].ContainsKey(key))
|
||||
{
|
||||
ret = utfTable.Rows[rowIndex][key].Size;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static Dictionary<string, byte> GetKeysForEncryptedUtfTable(byte[] encryptedUtfSignature)
|
||||
{
|
||||
Dictionary<string, byte> keys = new Dictionary<string, byte>();
|
||||
byte m, t;
|
||||
|
||||
byte[] xorBytes = new byte[SIGNATURE_BYTES.Length];
|
||||
bool keysFound = false;
|
||||
|
||||
for (byte seed = 0; seed <= byte.MaxValue; seed++)
|
||||
{
|
||||
if (!keysFound)
|
||||
{
|
||||
// match first char
|
||||
if ((encryptedUtfSignature[0] ^ seed) == SIGNATURE_BYTES[0])
|
||||
{
|
||||
for (byte increment = 0; increment <= byte.MaxValue; increment++)
|
||||
{
|
||||
if (!keysFound)
|
||||
{
|
||||
m = (byte)(seed * increment);
|
||||
|
||||
if ((encryptedUtfSignature[1] ^ m) == SIGNATURE_BYTES[1])
|
||||
{
|
||||
t = increment;
|
||||
|
||||
for (int j = 2; j < SIGNATURE_BYTES.Length; j++)
|
||||
{
|
||||
m *= t;
|
||||
|
||||
if ((encryptedUtfSignature[j] ^ m) != SIGNATURE_BYTES[j])
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (j == (SIGNATURE_BYTES.Length - 1))
|
||||
{
|
||||
keys.Add(LCG_SEED_KEY, seed);
|
||||
keys.Add(LCG_INCREMENT_KEY, increment);
|
||||
keysFound = true;
|
||||
}
|
||||
}
|
||||
} // if ((encryptedUtfSignature[1] ^ m) == SIGNATURE_BYTES[1])
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
} // if (!keysFound)
|
||||
} // for (byte inc = 0; inc <= byte.MaxValue; inc++)
|
||||
} // if ((encryptedUtfSignature[0] ^ mult) == SIGNATURE_BYTES[0])
|
||||
} // if (!keysFound)
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
} // for (byte mult = 0; mult <= byte.MaxValue; mult++)
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
public static bool IsUtfTable(FileStream fs, long offset)
|
||||
{
|
||||
bool ret = false;
|
||||
CriUtfTable utf = new CriUtfTable();
|
||||
|
||||
utf.SourceFile = fs.Name;
|
||||
utf.BaseOffset = offset;
|
||||
|
||||
try
|
||||
{
|
||||
utf.MagicBytes = ParseFile.ParseSimpleOffset(fs, utf.BaseOffset, 4);
|
||||
|
||||
// check if file is decrypted and get decryption keys if needed
|
||||
utf.checkEncryption(fs);
|
||||
|
||||
// write (decrypted) utf header to file
|
||||
utf.UtfTableFile = utf.WriteTableToTempFile(fs, offset);
|
||||
|
||||
utf.IsEncrypted = false; // since we've decrypted to a temp file
|
||||
utf.UtfReader.IsEncrypted = false;
|
||||
|
||||
if (ParseFile.CompareSegment(utf.MagicBytes, 0, SIGNATURE_BYTES))
|
||||
{
|
||||
ret = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!String.IsNullOrEmpty(utf.UtfTableFile))
|
||||
{
|
||||
File.Delete(utf.UtfTableFile);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public class CriUtfReader
|
||||
{
|
||||
public byte Seed { set; get; }
|
||||
public byte Increment { set; get; }
|
||||
public bool IsEncrypted { set; get; }
|
||||
|
||||
public byte CurrentXor { set; get; }
|
||||
public long CurrentUtfOffset { set; get; }
|
||||
|
||||
public byte CurrentStringXor { set; get; }
|
||||
public long CurrentUtfStringOffset { set; get; }
|
||||
|
||||
public CriUtfReader()
|
||||
{
|
||||
this.IsEncrypted = false;
|
||||
}
|
||||
|
||||
public CriUtfReader(byte seed, byte increment, bool isEncrypted)
|
||||
{
|
||||
this.Seed = seed;
|
||||
this.Increment = increment;
|
||||
this.IsEncrypted = isEncrypted;
|
||||
}
|
||||
|
||||
public byte[] GetBytes(FileStream fs, long BaseOffset, int Size, long UtfOffset)
|
||||
{
|
||||
byte[] ret;
|
||||
|
||||
ret = ParseFile.ParseSimpleOffset(fs, BaseOffset + UtfOffset, Size);
|
||||
|
||||
if (this.IsEncrypted)
|
||||
{
|
||||
if (UtfOffset < this.CurrentUtfOffset)
|
||||
{
|
||||
// reset, maybe add some sort of index later?
|
||||
this.CurrentUtfOffset = 0;
|
||||
}
|
||||
|
||||
if (this.CurrentUtfOffset == 0)
|
||||
{
|
||||
// reset or initialize
|
||||
this.CurrentXor = this.Seed;
|
||||
}
|
||||
|
||||
// catch up to this offset
|
||||
for (long j = this.CurrentUtfOffset; j < UtfOffset; j++)
|
||||
{
|
||||
if (j > 0)
|
||||
{
|
||||
this.CurrentXor *= this.Increment;
|
||||
}
|
||||
|
||||
this.CurrentUtfOffset++;
|
||||
}
|
||||
|
||||
// decrypt this offset
|
||||
for (long i = 0; i < Size; i++)
|
||||
{
|
||||
// first byte of UTF table must be XOR'd with the seed
|
||||
if ((this.CurrentUtfOffset != 0) || (i > 0))
|
||||
{
|
||||
this.CurrentXor *= this.Increment;
|
||||
}
|
||||
|
||||
ret[i] ^= this.CurrentXor;
|
||||
this.CurrentUtfOffset++;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public string ReadAsciiString(FileStream fs, long BaseOffset, long UtfOffset)
|
||||
{
|
||||
byte encryptedByte;
|
||||
byte decryptedByte;
|
||||
|
||||
StringBuilder asciiVal = new StringBuilder();
|
||||
byte xorByte;
|
||||
long fileSize = fs.Length;
|
||||
|
||||
if (this.IsEncrypted)
|
||||
{
|
||||
fs.Position = (BaseOffset + UtfOffset);
|
||||
|
||||
if (UtfOffset < this.CurrentUtfStringOffset)
|
||||
{
|
||||
// reset, maybe add some sort of index later?
|
||||
this.CurrentUtfStringOffset = 0;
|
||||
}
|
||||
|
||||
if (this.CurrentUtfStringOffset == 0)
|
||||
{
|
||||
// reset or initialize
|
||||
this.CurrentStringXor = this.Seed;
|
||||
}
|
||||
|
||||
for (long j = this.CurrentUtfStringOffset; j < UtfOffset; j++)
|
||||
{
|
||||
if (j > 0)
|
||||
{
|
||||
this.CurrentStringXor *= this.Increment;
|
||||
}
|
||||
|
||||
this.CurrentUtfStringOffset++;
|
||||
}
|
||||
|
||||
for (long i = UtfOffset; i < (fileSize - (BaseOffset + UtfOffset)); i++)
|
||||
{
|
||||
this.CurrentStringXor *= this.Increment;
|
||||
this.CurrentUtfStringOffset++;
|
||||
|
||||
encryptedByte = (byte)fs.ReadByte();
|
||||
decryptedByte = (byte)(encryptedByte ^ this.CurrentStringXor);
|
||||
|
||||
if (decryptedByte == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
asciiVal.Append(Convert.ToChar(decryptedByte));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
asciiVal.Append(ParseFile.ReadAsciiString(fs, BaseOffset + UtfOffset));
|
||||
}
|
||||
|
||||
return asciiVal.ToString();
|
||||
}
|
||||
|
||||
public byte ReadByte(FileStream fs, long BaseOffset, long UtfOffset)
|
||||
{
|
||||
return this.GetBytes(fs, BaseOffset, 1, UtfOffset)[0];
|
||||
}
|
||||
|
||||
public SByte ReadSByte(FileStream fs, long BaseOffset, long UtfOffset)
|
||||
{
|
||||
return (SByte)this.GetBytes(fs, BaseOffset, 1, UtfOffset)[0];
|
||||
}
|
||||
|
||||
public ushort ReadUshort(FileStream fs, long BaseOffset, long UtfOffset)
|
||||
{
|
||||
byte[] temp = this.GetBytes(fs, BaseOffset, 2, UtfOffset);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(temp);
|
||||
}
|
||||
return BitConverter.ToUInt16(temp, 0);
|
||||
}
|
||||
|
||||
public short ReadShort(FileStream fs, long BaseOffset, long UtfOffset)
|
||||
{
|
||||
byte[] temp = this.GetBytes(fs, BaseOffset, 2, UtfOffset);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(temp);
|
||||
}
|
||||
return BitConverter.ToInt16(temp, 0);
|
||||
}
|
||||
|
||||
public uint ReadUint(FileStream fs, long BaseOffset, long UtfOffset)
|
||||
{
|
||||
byte[] temp = this.GetBytes(fs, BaseOffset, 4, UtfOffset);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(temp);
|
||||
}
|
||||
return BitConverter.ToUInt32(temp, 0);
|
||||
}
|
||||
|
||||
public int ReadInt(FileStream fs, long BaseOffset, long UtfOffset)
|
||||
{
|
||||
byte[] temp = this.GetBytes(fs, BaseOffset, 4, UtfOffset);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(temp);
|
||||
}
|
||||
return BitConverter.ToInt32(temp, 0);
|
||||
}
|
||||
|
||||
public ulong ReadUlong(FileStream fs, long BaseOffset, long UtfOffset)
|
||||
{
|
||||
byte[] temp = this.GetBytes(fs, BaseOffset, 8, UtfOffset);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(temp);
|
||||
}
|
||||
return BitConverter.ToUInt64(temp, 0);
|
||||
}
|
||||
|
||||
public float ReadFloat(FileStream fs, long BaseOffset, long UtfOffset)
|
||||
{
|
||||
byte[] temp = this.GetBytes(fs, BaseOffset, 4, UtfOffset);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
Array.Reverse(temp);
|
||||
}
|
||||
return BitConverter.ToSingle(temp, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
667
acb_extract/src/FileUtil.cs
Normal file
667
acb_extract/src/FileUtil.cs
Normal file
|
|
@ -0,0 +1,667 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace vgm_acb.extract
|
||||
{
|
||||
public sealed class FileUtil
|
||||
{
|
||||
private FileUtil() { }
|
||||
|
||||
/// <summary>
|
||||
/// Reads data into a complete array, throwing an EndOfStreamException
|
||||
/// if the stream runs out of data first, or if an IOException
|
||||
/// naturally occurs.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to read data from</param>
|
||||
/// <param name="data">The array to read bytes into. The array
|
||||
/// will be completely filled from the stream, so an appropriate
|
||||
/// size must be given.</param>
|
||||
public static void ReadWholeArray(Stream stream, byte[] data)
|
||||
{
|
||||
ReadWholeArray(stream, data, data.Length);
|
||||
}
|
||||
|
||||
public static void ReadWholeArray(Stream stream, byte[] data, int pLength)
|
||||
{
|
||||
int offset = 0;
|
||||
int remaining = pLength;
|
||||
while (remaining > 0)
|
||||
{
|
||||
int read = stream.Read(data, offset, remaining);
|
||||
if (read <= 0)
|
||||
{
|
||||
throw new EndOfStreamException(
|
||||
String.Format(CultureInfo.CurrentCulture, "End of stream reached with {0} bytes left to read", remaining));
|
||||
}
|
||||
|
||||
remaining -= read;
|
||||
offset += read;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces 0x00 with 0x20 in an array of bytes.
|
||||
/// </summary>
|
||||
/// <param name="value">Array of bytes to alter.</param>
|
||||
/// <returns>Original array with 0x00 replaced by 0x20.</returns>
|
||||
public static byte[] ReplaceNullByteWithSpace(byte[] value)
|
||||
{
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
{
|
||||
if (value[i] == 0x00)
|
||||
{
|
||||
value[i] = 0x20;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the count of files contained in the input directories and their subdirectories.
|
||||
/// </summary>
|
||||
/// <param name="paths">Paths to count files within.</param>
|
||||
/// <returns>Number of files in the incoming directories and their subdirectories.</returns>
|
||||
public static int GetFileCount(string[] paths)
|
||||
{
|
||||
return GetFileCount(paths, true);
|
||||
}
|
||||
|
||||
public static int GetFileCount(string[] paths, bool includeSubdirs)
|
||||
{
|
||||
int totalFileCount = 0;
|
||||
|
||||
foreach (string path in paths)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
totalFileCount++;
|
||||
}
|
||||
else if (Directory.Exists(path))
|
||||
{
|
||||
if (includeSubdirs)
|
||||
{
|
||||
totalFileCount += Directory.GetFiles(path, "*.*", SearchOption.AllDirectories).Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
totalFileCount += Directory.GetFiles(path, "*.*", SearchOption.TopDirectoryOnly).Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return totalFileCount;
|
||||
}
|
||||
|
||||
public static string CleanFileName(string pDirtyFileName)
|
||||
{
|
||||
foreach (char c in Path.GetInvalidFileNameChars())
|
||||
{
|
||||
pDirtyFileName = pDirtyFileName.Replace(c, '_');
|
||||
}
|
||||
|
||||
return pDirtyFileName;
|
||||
}
|
||||
|
||||
public static void UpdateTextField(
|
||||
string pFilePath,
|
||||
string pFieldValue,
|
||||
int pOffset,
|
||||
int pMaxLength)
|
||||
{
|
||||
System.Text.Encoding enc = System.Text.Encoding.ASCII;
|
||||
|
||||
using (BinaryWriter bw =
|
||||
new BinaryWriter(File.Open(pFilePath, FileMode.Open, FileAccess.ReadWrite)))
|
||||
{
|
||||
byte[] newBytes = new byte[pMaxLength];
|
||||
byte[] convertedBytes = enc.GetBytes(pFieldValue);
|
||||
|
||||
int numBytesToCopy =
|
||||
convertedBytes.Length <= pMaxLength ? convertedBytes.Length : pMaxLength;
|
||||
Array.ConstrainedCopy(convertedBytes, 0, newBytes, 0, numBytesToCopy);
|
||||
|
||||
bw.Seek(pOffset, SeekOrigin.Begin);
|
||||
bw.Write(newBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public static void UpdateChunk(
|
||||
string pFilePath,
|
||||
int pOffset,
|
||||
byte[] value)
|
||||
{
|
||||
using (BinaryWriter bw =
|
||||
new BinaryWriter(File.Open(pFilePath, FileMode.Open, FileAccess.ReadWrite)))
|
||||
{
|
||||
bw.Seek(pOffset, SeekOrigin.Begin);
|
||||
bw.Write(value);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReplaceFileChunk(
|
||||
string pSourceFilePath,
|
||||
long pSourceOffset,
|
||||
long pLength,
|
||||
string pDestinationFilePath,
|
||||
long pDestinationOffset)
|
||||
{
|
||||
int read = 0;
|
||||
long maxread;
|
||||
int totalBytes = 0;
|
||||
byte[] bytes = new byte[Constants.FileReadChunkSize];
|
||||
|
||||
using (BinaryWriter bw =
|
||||
new BinaryWriter(File.Open(pDestinationFilePath, FileMode.Open, FileAccess.ReadWrite)))
|
||||
{
|
||||
using (BinaryReader br =
|
||||
new BinaryReader(File.Open(pSourceFilePath, FileMode.Open, FileAccess.Read)))
|
||||
{
|
||||
br.BaseStream.Position = pSourceOffset;
|
||||
bw.BaseStream.Position = pDestinationOffset;
|
||||
|
||||
maxread = pLength > bytes.Length ? bytes.Length : pLength;
|
||||
|
||||
while ((read = br.Read(bytes, 0, (int)maxread)) > 0)
|
||||
{
|
||||
bw.Write(bytes, 0, read);
|
||||
totalBytes += read;
|
||||
|
||||
maxread = (pLength - totalBytes) > bytes.Length ? bytes.Length : (pLength - totalBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ZeroOutFileChunk(string pPath, long pOffset, int pLength)
|
||||
{
|
||||
int bytesToWrite = pLength;
|
||||
byte[] bytes;
|
||||
|
||||
int maxWrite = bytesToWrite > Constants.FileReadChunkSize ? Constants.FileReadChunkSize : bytesToWrite;
|
||||
|
||||
using (BinaryWriter bw =
|
||||
new BinaryWriter(File.Open(pPath, FileMode.Open, FileAccess.Write)))
|
||||
{
|
||||
bw.BaseStream.Position = pOffset;
|
||||
|
||||
while (bytesToWrite > 0)
|
||||
{
|
||||
bytes = new byte[maxWrite];
|
||||
bw.Write(bytes);
|
||||
bytesToWrite -= maxWrite;
|
||||
maxWrite = bytesToWrite > bytes.Length ? bytes.Length : bytesToWrite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void TrimFileToLength(string path, long totalLength)
|
||||
{
|
||||
string fullPath = Path.GetFullPath(path);
|
||||
|
||||
if (File.Exists(fullPath))
|
||||
{
|
||||
string destinationPath = Path.ChangeExtension(fullPath, ".trimmed");
|
||||
|
||||
using (FileStream fs = File.OpenRead(path))
|
||||
{
|
||||
ParseFile.ExtractChunkToFile(fs, 0, totalLength, destinationPath);
|
||||
}
|
||||
|
||||
File.Copy(destinationPath, path, true);
|
||||
File.Delete(destinationPath);
|
||||
}
|
||||
}
|
||||
|
||||
public static string RemoveChunkFromFile(string path, long startingOffset, long length)
|
||||
{
|
||||
string fullPath = Path.GetFullPath(path);
|
||||
|
||||
int bytesRead;
|
||||
byte[] bytes = new byte[1024];
|
||||
|
||||
string ret = String.Empty;
|
||||
|
||||
if (File.Exists(fullPath))
|
||||
{
|
||||
string destinationPath = Path.ChangeExtension(fullPath, ".cut");
|
||||
|
||||
using (FileStream sourceFs = File.OpenRead(fullPath))
|
||||
{
|
||||
// extract initial chunk
|
||||
ParseFile.ExtractChunkToFile(sourceFs, 0, startingOffset, destinationPath);
|
||||
|
||||
// append remainder
|
||||
using (FileStream outFs = File.Open(destinationPath, FileMode.Append, FileAccess.Write))
|
||||
{
|
||||
sourceFs.Position = startingOffset + length;
|
||||
bytesRead = sourceFs.Read(bytes, 0, bytes.Length);
|
||||
|
||||
while (bytesRead > 0)
|
||||
{
|
||||
outFs.Write(bytes, 0, bytesRead);
|
||||
bytesRead = sourceFs.Read(bytes, 0, bytes.Length);
|
||||
}
|
||||
}
|
||||
|
||||
ret = destinationPath;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static string RemoveAllChunksFromFile(FileStream fs, byte[] chunkToRemove)
|
||||
{
|
||||
int bytesRead;
|
||||
|
||||
long currentReadOffset = 0;
|
||||
long totalBytesRead = 0;
|
||||
int maxReadSize = 0;
|
||||
long currentChunkSize;
|
||||
|
||||
byte[] bytes = new byte[Constants.FileReadChunkSize];
|
||||
long[] offsets = ParseFile.GetAllOffsets(fs, 0, chunkToRemove, false, -1, -1, true);
|
||||
|
||||
string destinationPath = Path.ChangeExtension(fs.Name, ".cut");
|
||||
|
||||
using (FileStream destinationFs = File.OpenWrite(destinationPath))
|
||||
{
|
||||
for (int i = 0; i < offsets.Length; i++)
|
||||
{
|
||||
// move position
|
||||
fs.Position = currentReadOffset;
|
||||
|
||||
// get length of current size to write
|
||||
currentChunkSize = offsets[i] - currentReadOffset;
|
||||
|
||||
// calculcate max cut size for this loop iteration
|
||||
maxReadSize = (currentChunkSize - totalBytesRead) > (long)bytes.Length ? bytes.Length : (int)(currentChunkSize - totalBytesRead);
|
||||
|
||||
while ((bytesRead = fs.Read(bytes, 0, maxReadSize)) > 0)
|
||||
{
|
||||
destinationFs.Write(bytes, 0, bytesRead);
|
||||
totalBytesRead += (long)bytesRead;
|
||||
|
||||
maxReadSize = (currentChunkSize - totalBytesRead) > (long)bytes.Length ? bytes.Length : (int)(currentChunkSize - totalBytesRead);
|
||||
}
|
||||
|
||||
totalBytesRead = 0;
|
||||
currentReadOffset = offsets[i] + chunkToRemove.Length;
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
// write remainder of file
|
||||
////////////////////////////
|
||||
// move position
|
||||
fs.Position = currentReadOffset;
|
||||
|
||||
// get length of current size to write
|
||||
currentChunkSize = fs.Length - currentReadOffset;
|
||||
|
||||
// calculcate max cut size
|
||||
maxReadSize = (currentChunkSize - totalBytesRead) > (long)bytes.Length ? bytes.Length : (int)(currentChunkSize - totalBytesRead);
|
||||
|
||||
while ((bytesRead = fs.Read(bytes, 0, maxReadSize)) > 0)
|
||||
{
|
||||
destinationFs.Write(bytes, 0, bytesRead);
|
||||
totalBytesRead += (long)bytesRead;
|
||||
|
||||
maxReadSize = (currentChunkSize - totalBytesRead) > (long)bytes.Length ? bytes.Length : (int)(currentChunkSize - totalBytesRead);
|
||||
}
|
||||
}
|
||||
|
||||
return destinationPath;
|
||||
}
|
||||
|
||||
public static string RemoveAllChunksFromFile(string path, byte[] chunkToRemove)
|
||||
{
|
||||
string destinationPath;
|
||||
|
||||
using (FileStream fs = File.OpenRead(path))
|
||||
{
|
||||
destinationPath = RemoveAllChunksFromFile(fs, chunkToRemove);
|
||||
}
|
||||
|
||||
return destinationPath;
|
||||
}
|
||||
|
||||
public static bool ExecuteExternalProgram(
|
||||
string pathToExecuatable,
|
||||
string arguments,
|
||||
string workingDirectory,
|
||||
out string standardOut,
|
||||
out string standardError)
|
||||
{
|
||||
Process externalExecutable;
|
||||
bool isSuccess = false;
|
||||
|
||||
standardOut = String.Empty;
|
||||
standardError = String.Empty;
|
||||
|
||||
using (externalExecutable = new Process())
|
||||
{
|
||||
externalExecutable.StartInfo = new ProcessStartInfo(pathToExecuatable, arguments);
|
||||
externalExecutable.StartInfo.WorkingDirectory = workingDirectory;
|
||||
externalExecutable.StartInfo.UseShellExecute = false;
|
||||
externalExecutable.StartInfo.CreateNoWindow = true;
|
||||
|
||||
externalExecutable.StartInfo.RedirectStandardOutput = true;
|
||||
externalExecutable.StartInfo.RedirectStandardError = true;
|
||||
isSuccess = externalExecutable.Start();
|
||||
|
||||
standardOut = externalExecutable.StandardOutput.ReadToEnd();
|
||||
standardError = externalExecutable.StandardError.ReadToEnd();
|
||||
externalExecutable.WaitForExit();
|
||||
}
|
||||
|
||||
return isSuccess;
|
||||
}
|
||||
|
||||
public static void AddHeaderToFile(byte[] headerBytes, string sourceFile, string destinationFile)
|
||||
{
|
||||
int bytesRead;
|
||||
byte[] readBuffer = new byte[Constants.FileReadChunkSize];
|
||||
|
||||
using (FileStream destinationStream = File.Open(destinationFile, FileMode.CreateNew, FileAccess.Write))
|
||||
{
|
||||
// write header
|
||||
destinationStream.Write(headerBytes, 0, headerBytes.Length);
|
||||
|
||||
// write the source file
|
||||
using (FileStream sourceStream = File.Open(sourceFile, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
while ((bytesRead = sourceStream.Read(readBuffer, 0, readBuffer.Length)) > 0)
|
||||
{
|
||||
destinationStream.Write(readBuffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void RenameFileUsingInternalName(string path,
|
||||
long offset, int length, byte[] terminatorBytes, bool maintainFileExtension)
|
||||
{
|
||||
string destinationDirectory = Path.GetDirectoryName(path);
|
||||
string destinationFile;
|
||||
string originalExtension;
|
||||
|
||||
int nameLength;
|
||||
byte[] nameByteArray;
|
||||
|
||||
using (FileStream fs = File.OpenRead(path))
|
||||
{
|
||||
if (terminatorBytes != null)
|
||||
{
|
||||
nameLength = ParseFile.GetSegmentLength(fs, (int)offset, terminatorBytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
nameLength = length;
|
||||
}
|
||||
|
||||
if (nameLength < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("Name Length", "Name Length is less than 1.");
|
||||
}
|
||||
|
||||
if (maintainFileExtension)
|
||||
{
|
||||
originalExtension = Path.GetExtension(path);
|
||||
}
|
||||
|
||||
nameByteArray = ParseFile.ParseSimpleOffset(fs, offset, nameLength);
|
||||
destinationFile = ByteConversion.GetAsciiText(FileUtil.ReplaceNullByteWithSpace(nameByteArray)).Trim();
|
||||
destinationFile = Path.Combine(destinationDirectory, destinationFile);
|
||||
|
||||
if (maintainFileExtension)
|
||||
{
|
||||
originalExtension = Path.GetExtension(path);
|
||||
destinationFile = Path.ChangeExtension(destinationFile, originalExtension);
|
||||
}
|
||||
}
|
||||
|
||||
// try to copy using the new name
|
||||
if (!path.Equals(destinationFile))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(Path.GetDirectoryName(destinationFile)))
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(destinationFile));
|
||||
}
|
||||
|
||||
if (File.Exists(destinationFile))
|
||||
{
|
||||
string[] sameNamedFiles = Directory.GetFiles(Path.GetDirectoryName(destinationFile), Path.GetFileNameWithoutExtension(destinationFile) + "*");
|
||||
|
||||
// rename to prevent overwrite
|
||||
destinationFile = Path.Combine(Path.GetDirectoryName(destinationFile), String.Format("{0}_{1}{2}", Path.GetFileNameWithoutExtension(destinationFile), sameNamedFiles.Length.ToString("D4"), Path.GetExtension(destinationFile)));
|
||||
}
|
||||
|
||||
File.Copy(path, destinationFile);
|
||||
File.Delete(path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void InterleaveFiles(string[] sourceFiles, uint interleaveValue,
|
||||
long startOffest, byte[] paddingBytes, string destinationFile)
|
||||
{
|
||||
long currentOffset = 0;
|
||||
long maxLength = 0;
|
||||
long sizeDifference = 0;
|
||||
long bytesToWrite;
|
||||
long bytesRemaining;
|
||||
|
||||
FileStream[] inputStreams = new FileStream[sourceFiles.Length];
|
||||
long[] fileLengths = new long[sourceFiles.Length];
|
||||
destinationFile = Path.GetFullPath(destinationFile);
|
||||
|
||||
// open destination file for writing
|
||||
using (FileStream destinationStream = File.OpenWrite(destinationFile))
|
||||
{
|
||||
try
|
||||
{
|
||||
// build input streams array
|
||||
for (int i = 0; i < sourceFiles.Length; i++)
|
||||
{
|
||||
inputStreams[i] = File.OpenRead(sourceFiles[i]);
|
||||
fileLengths[i] = inputStreams[i].Length;
|
||||
|
||||
// get max file length
|
||||
if (maxLength < fileLengths[i])
|
||||
{
|
||||
maxLength = fileLengths[i];
|
||||
}
|
||||
}
|
||||
|
||||
// write out blocks
|
||||
currentOffset = startOffest;
|
||||
|
||||
while (currentOffset < maxLength)
|
||||
{
|
||||
for (int i = 0; i < sourceFiles.Length; i++)
|
||||
{
|
||||
if (currentOffset + interleaveValue < fileLengths[i])
|
||||
{
|
||||
// write from file
|
||||
destinationStream.Write(
|
||||
ParseFile.ParseSimpleOffset(inputStreams[i], currentOffset, (int)interleaveValue),
|
||||
0, (int)interleaveValue);
|
||||
}
|
||||
else if (currentOffset < fileLengths[i])
|
||||
{
|
||||
// write some from file, some from padding
|
||||
sizeDifference = (currentOffset + interleaveValue) - fileLengths[i];
|
||||
|
||||
// write from file
|
||||
destinationStream.Write(
|
||||
ParseFile.ParseSimpleOffset(inputStreams[i], currentOffset, (int)(interleaveValue - sizeDifference)),
|
||||
0, (int)(int)(interleaveValue - sizeDifference));
|
||||
|
||||
// write padding bytes
|
||||
bytesRemaining = sizeDifference;
|
||||
|
||||
while (bytesRemaining > 0)
|
||||
{
|
||||
bytesToWrite = bytesRemaining > paddingBytes.Length ? paddingBytes.Length : bytesRemaining;
|
||||
destinationStream.Write(paddingBytes, 0, (int)bytesToWrite);
|
||||
bytesRemaining -= bytesToWrite;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// write padding bytes
|
||||
bytesRemaining = interleaveValue;
|
||||
|
||||
while (bytesRemaining > 0)
|
||||
{
|
||||
bytesToWrite = bytesRemaining > paddingBytes.Length ? paddingBytes.Length : bytesRemaining;
|
||||
destinationStream.Write(paddingBytes, 0, (int)bytesToWrite);
|
||||
bytesRemaining -= bytesToWrite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// increment offset
|
||||
currentOffset += interleaveValue;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception(ex.Message, ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// close all readers
|
||||
for (int i = 0; i < inputStreams.Length; i++)
|
||||
{
|
||||
if (inputStreams[i].CanRead)
|
||||
{
|
||||
inputStreams[i].Close();
|
||||
inputStreams[i].Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetNonDuplicateFileName(string destinationFile)
|
||||
{
|
||||
if (File.Exists(destinationFile))
|
||||
{
|
||||
string[] sameNamedFiles = Directory.GetFiles(Path.GetDirectoryName(destinationFile), Path.GetFileNameWithoutExtension(destinationFile) + "*");
|
||||
|
||||
// rename to prevent overwrite
|
||||
destinationFile = Path.Combine(Path.GetDirectoryName(destinationFile), String.Format("{0}_{1}{2}", Path.GetFileNameWithoutExtension(destinationFile), sameNamedFiles.Length.ToString("D4"), Path.GetExtension(destinationFile)));
|
||||
}
|
||||
|
||||
return destinationFile;
|
||||
}
|
||||
|
||||
public static string[] SplitFile(string sourceFile, long startingOffset, ulong chunkSizeInBytes, string outputFolder)
|
||||
{
|
||||
string[] outputFileList;
|
||||
string outputFileName;
|
||||
ArrayList outputFiles = new ArrayList();
|
||||
|
||||
long fileLength;
|
||||
ulong currentOffset;
|
||||
|
||||
int chunkCount;
|
||||
|
||||
using (FileStream sourceStream = File.OpenRead(sourceFile))
|
||||
{
|
||||
// get file length
|
||||
fileLength = sourceStream.Length;
|
||||
|
||||
// init counters
|
||||
chunkCount = 1;
|
||||
currentOffset = (ulong)startingOffset;
|
||||
|
||||
while (currentOffset < (ulong)fileLength)
|
||||
{
|
||||
// construct output file name
|
||||
outputFileName = Path.Combine(outputFolder,
|
||||
String.Format("{0}.{1}",
|
||||
Path.GetFileName(sourceFile),
|
||||
chunkCount.ToString("D3")));
|
||||
|
||||
ParseFile.ExtractChunkToFile64(sourceStream, currentOffset, chunkSizeInBytes,
|
||||
outputFileName, true, true);
|
||||
|
||||
|
||||
outputFiles.Add(outputFileName);
|
||||
currentOffset += chunkSizeInBytes;
|
||||
chunkCount++;
|
||||
}
|
||||
}
|
||||
|
||||
outputFileList = (string[])outputFiles.ToArray(typeof(string));
|
||||
return outputFileList;
|
||||
}
|
||||
|
||||
public static long? GetFileSize(string path)
|
||||
{
|
||||
long? fileSize;
|
||||
|
||||
try
|
||||
{
|
||||
FileInfo fi = new FileInfo(Path.GetFullPath(path));
|
||||
fileSize = fi.Length;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
fileSize = null;
|
||||
}
|
||||
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
public static void CreateFileFromByteArray(string destinationFile, byte[] sourceBytes)
|
||||
{
|
||||
string destinationDirectory = Path.GetDirectoryName(destinationFile);
|
||||
|
||||
if (!Directory.Exists(destinationDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(destinationDirectory);
|
||||
}
|
||||
|
||||
using (FileStream outStream = File.Open(destinationFile, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
{
|
||||
outStream.Write(sourceBytes, 0, sourceBytes.Length);
|
||||
}
|
||||
}
|
||||
|
||||
public static void CreateFileFromString(string destinationFile, string sourceText)
|
||||
{
|
||||
string destinationDirectory = Path.GetDirectoryName(destinationFile);
|
||||
|
||||
if (!Directory.Exists(destinationDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(destinationDirectory);
|
||||
}
|
||||
|
||||
using ( FileStream outStream = File.Open(destinationFile, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||
{
|
||||
using (StreamWriter sw = new StreamWriter(outStream, Encoding.ASCII))
|
||||
{
|
||||
sw.Write(sourceText);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
acb_extract/src/MathUtil.cs
Normal file
26
acb_extract/src/MathUtil.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
|
||||
namespace vgm_acb.extract
|
||||
{
|
||||
public class MathUtil
|
||||
{
|
||||
public static long RoundUpToByteAlignment(long valueToRound, long byteAlignment)
|
||||
{
|
||||
long roundedValue = -1;
|
||||
|
||||
roundedValue = (valueToRound + byteAlignment - 1) / byteAlignment * byteAlignment;
|
||||
|
||||
return roundedValue;
|
||||
}
|
||||
|
||||
public static ulong RoundUpToByteAlignment(ulong valueToRound, ulong byteAlignment)
|
||||
{
|
||||
ulong roundedValue;
|
||||
|
||||
roundedValue = (valueToRound + byteAlignment - 1) / byteAlignment * byteAlignment;
|
||||
|
||||
return roundedValue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
9
acb_extract/src/OffsetDescription.cs
Normal file
9
acb_extract/src/OffsetDescription.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
namespace vgm_acb.extract
|
||||
{
|
||||
public class OffsetDescription
|
||||
{
|
||||
public string OffsetValue { set; get; }
|
||||
public string OffsetSize { set; get; }
|
||||
public string OffsetByteOrder { set; get; }
|
||||
}
|
||||
}
|
||||
864
acb_extract/src/ParseFile.cs
Normal file
864
acb_extract/src/ParseFile.cs
Normal file
|
|
@ -0,0 +1,864 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace vgm_acb.extract
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for Parsing Files.
|
||||
/// </summary>
|
||||
public sealed class ParseFile
|
||||
{
|
||||
public const string LogFileName = "vgmt_extraction_log.txt";
|
||||
public const string SnakeBiteBatchFileName = "vgmt_extraction_log.bat";
|
||||
public const string VirtualFileSystemExtractionFolder = "vgmt_vfs_cut";
|
||||
/// <summary>
|
||||
/// Extract a section from the incoming byte array.
|
||||
/// </summary>
|
||||
/// <param name="sourceArray">Bytes to extract from.</param>
|
||||
/// <param name="startingOffset">Offset to begin cutting from.</param>
|
||||
/// <param name="lengthToCut">Number of bytes to cut.</param>
|
||||
/// <returns>Byte array containing the extracted section.</returns>
|
||||
public static byte[] ParseSimpleOffset(byte[] sourceArray, int startingOffset, int lengthToCut)
|
||||
{
|
||||
byte[] ret = new byte[lengthToCut];
|
||||
uint j = 0;
|
||||
|
||||
for (int i = startingOffset; i < startingOffset + lengthToCut; i++)
|
||||
{
|
||||
ret[j] = sourceArray[i];
|
||||
j++;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract a section from the incoming stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">Stream to extract the chunk from.</param>
|
||||
/// <param name="startingOffset">Offset to begin cutting from.</param>
|
||||
/// <param name="lengthToCut">Number of bytes to cut.</param>
|
||||
/// <returns>Byte array containing the extracted section.</returns>
|
||||
public static byte[] ParseSimpleOffset(Stream stream, int startingOffset, int lengthToCut)
|
||||
{
|
||||
byte[] ret = new byte[lengthToCut];
|
||||
long currentStreamPosition = stream.Position;
|
||||
|
||||
stream.Seek((long)startingOffset, SeekOrigin.Begin);
|
||||
BinaryReader br = new BinaryReader(stream);
|
||||
ret = br.ReadBytes((int)lengthToCut);
|
||||
|
||||
stream.Position = currentStreamPosition;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract a section from the incoming stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">Stream to extract the chunk from.</param>
|
||||
/// <param name="startingOffset">Offset to begin cutting from.</param>
|
||||
/// <param name="lengthToCut">Number of bytes to cut.</param>
|
||||
/// <returns>Byte array containing the extracted section.</returns>
|
||||
public static byte[] ParseSimpleOffset(Stream stream, long startingOffset, int lengthToCut)
|
||||
{
|
||||
byte[] ret = new byte[lengthToCut];
|
||||
long currentStreamPosition = stream.Position;
|
||||
|
||||
stream.Seek(startingOffset, SeekOrigin.Begin);
|
||||
BinaryReader br = new BinaryReader(stream);
|
||||
ret = br.ReadBytes(lengthToCut);
|
||||
|
||||
stream.Position = currentStreamPosition;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static byte[] SimpleArrayCopy(byte[] SourceArray, long offset, long length)
|
||||
{
|
||||
byte[] value = new byte[length];
|
||||
Array.Copy(SourceArray, offset, value, 0, length);
|
||||
return value;
|
||||
}
|
||||
|
||||
public static float ReadFloatLE(Stream inStream, long offset)
|
||||
{
|
||||
return BitConverter.ToSingle(ParseSimpleOffset(inStream, offset, 4), 0);
|
||||
}
|
||||
|
||||
public static float ReadFloatBE(Stream inStream, long offset)
|
||||
{
|
||||
byte[] val = ParseSimpleOffset(inStream, offset, 4);
|
||||
Array.Reverse(val);
|
||||
|
||||
return BitConverter.ToSingle(val, 0);
|
||||
}
|
||||
|
||||
public static uint ReadUintLE(Stream inStream, long offset)
|
||||
{
|
||||
return BitConverter.ToUInt32(ParseSimpleOffset(inStream, offset, 4), 0);
|
||||
}
|
||||
|
||||
public static uint ReadUintLE(byte[] inBytes, long offset)
|
||||
{
|
||||
byte[] val = new byte[4];
|
||||
|
||||
Array.Copy(inBytes, offset, val, 0, 4);
|
||||
|
||||
return BitConverter.ToUInt32(val, 0);
|
||||
}
|
||||
|
||||
public static uint ReadUintBE(Stream inStream, long offset)
|
||||
{
|
||||
byte[] val = ParseSimpleOffset(inStream, offset, 4);
|
||||
Array.Reverse(val);
|
||||
|
||||
return BitConverter.ToUInt32(val, 0);
|
||||
}
|
||||
|
||||
public static uint ReadUintBE(byte[] inBytes, long offset)
|
||||
{
|
||||
byte[] val = new byte[4];
|
||||
|
||||
Array.Copy(inBytes, offset, val, 0, 4);
|
||||
Array.Reverse(val);
|
||||
|
||||
return BitConverter.ToUInt32(val, 0);
|
||||
}
|
||||
|
||||
public static ushort ReadUshortLE(Stream inStream, long offset)
|
||||
{
|
||||
return BitConverter.ToUInt16(ParseSimpleOffset(inStream, offset, 2), 0);
|
||||
}
|
||||
|
||||
public static ushort ReadUshortLE(byte[] inBytes, long offset)
|
||||
{
|
||||
byte[] val = new byte[2];
|
||||
|
||||
Array.Copy(inBytes, offset, val, 0, 2);
|
||||
|
||||
return BitConverter.ToUInt16(val, 0);
|
||||
}
|
||||
|
||||
public static ushort ReadUshortBE(Stream inStream, long offset)
|
||||
{
|
||||
byte[] val = ParseSimpleOffset(inStream, offset, 2);
|
||||
Array.Reverse(val);
|
||||
|
||||
return BitConverter.ToUInt16(val, 0);
|
||||
}
|
||||
|
||||
public static ushort ReadUshortBE(byte[] inBytes, long offset)
|
||||
{
|
||||
byte[] val = new byte[2];
|
||||
|
||||
Array.Copy(inBytes, offset, val, 0, 2);
|
||||
Array.Reverse(val);
|
||||
|
||||
return BitConverter.ToUInt16(val, 0);
|
||||
}
|
||||
|
||||
public static byte ReadByte(Stream inStream, long offset)
|
||||
{
|
||||
return (byte)ParseSimpleOffset(inStream, offset, 1)[0];
|
||||
}
|
||||
|
||||
public static SByte ReadSByte(Stream inStream, long offset)
|
||||
{
|
||||
return (SByte)ParseSimpleOffset(inStream, offset, 1)[0];
|
||||
}
|
||||
|
||||
public static int ReadInt32LE(Stream inStream, long offset)
|
||||
{
|
||||
return BitConverter.ToInt32(ParseSimpleOffset(inStream, offset, 4), 0);
|
||||
}
|
||||
|
||||
public static int ReadInt32BE(Stream inStream, long offset)
|
||||
{
|
||||
byte[] val = ParseSimpleOffset(inStream, offset, 4);
|
||||
Array.Reverse(val);
|
||||
|
||||
return BitConverter.ToInt32(val, 0);
|
||||
}
|
||||
|
||||
public static short ReadInt16LE(Stream inStream, long offset)
|
||||
{
|
||||
return BitConverter.ToInt16(ParseSimpleOffset(inStream, offset, 2), 0);
|
||||
}
|
||||
|
||||
public static short ReadInt16BE(Stream inStream, long offset)
|
||||
{
|
||||
byte[] val = ParseSimpleOffset(inStream, offset, 2);
|
||||
Array.Reverse(val);
|
||||
|
||||
return BitConverter.ToInt16(val, 0);
|
||||
}
|
||||
|
||||
public static int ReadInt24LE(Stream inStream, long offset)
|
||||
{
|
||||
byte[] val = ParseSimpleOffset(inStream, offset, 4);
|
||||
val[3] = 0;
|
||||
|
||||
return BitConverter.ToInt32(val, 0);
|
||||
}
|
||||
|
||||
public static int ReadInt24BE(Stream inStream, long offset)
|
||||
{
|
||||
byte[] val = ParseSimpleOffset(inStream, offset, 4);
|
||||
val[3] = 0;
|
||||
Array.Reverse(val);
|
||||
|
||||
return (BitConverter.ToInt32(val, 0) >> 8);
|
||||
}
|
||||
|
||||
public static uint ReadUint24LE(Stream inStream, long offset)
|
||||
{
|
||||
byte[] val = ParseSimpleOffset(inStream, offset, 4);
|
||||
val[3] = 0;
|
||||
|
||||
return BitConverter.ToUInt32(val, 0);
|
||||
}
|
||||
|
||||
public static uint ReadUint24BE(Stream inStream, long offset)
|
||||
{
|
||||
byte[] val = ParseSimpleOffset(inStream, offset, 4);
|
||||
val[3] = 0;
|
||||
Array.Reverse(val);
|
||||
|
||||
return (BitConverter.ToUInt32(val, 0) >> 8);
|
||||
}
|
||||
|
||||
public static ulong ReadUlongLE(Stream inStream, long offset)
|
||||
{
|
||||
return BitConverter.ToUInt64(ParseSimpleOffset(inStream, offset, 8), 0);
|
||||
}
|
||||
|
||||
public static ulong ReadUlongLE(byte[] inBytes, long offset)
|
||||
{
|
||||
byte[] val = new byte[8];
|
||||
|
||||
Array.Copy(inBytes, offset, val, 0, 8);
|
||||
|
||||
return BitConverter.ToUInt64(val, 0);
|
||||
}
|
||||
|
||||
public static ulong ReadUlongBE(Stream inStream, long offset)
|
||||
{
|
||||
byte[] val = ParseSimpleOffset(inStream, offset, 8);
|
||||
Array.Reverse(val);
|
||||
|
||||
return BitConverter.ToUInt64(val, 0);
|
||||
}
|
||||
|
||||
public static void ExtractChunkToFile(Stream stream, long startingOffset, long length,
|
||||
string filePath)
|
||||
{
|
||||
ExtractChunkToFile(stream, startingOffset, length, filePath, false, false);
|
||||
}
|
||||
|
||||
public static void ExtractChunkToFile(Stream stream, long startingOffset, long length,
|
||||
string filePath, bool outputLogFile)
|
||||
{
|
||||
ExtractChunkToFile(stream, startingOffset, length, filePath, outputLogFile, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts a section of the incoming stream to a file.
|
||||
/// </summary>
|
||||
/// <param name="stream">Stream to extract from.</param>
|
||||
/// <param name="startingOffset">Offset to begin the cut.</param>
|
||||
/// <param name="length">Number of bytes to cut.</param>
|
||||
/// <param name="filePath">File path to output the extracted chunk to.</param>
|
||||
public static void ExtractChunkToFile(
|
||||
Stream stream,
|
||||
long startingOffset,
|
||||
long length,
|
||||
string filePath,
|
||||
bool outputLogFile,
|
||||
bool outputSnakebiteBatchFile)
|
||||
{
|
||||
bool makeBatchFile = (outputSnakebiteBatchFile && (stream is FileStream));
|
||||
StringBuilder logInfo = new StringBuilder();
|
||||
StringBuilder snakeBiteBatch = new StringBuilder();
|
||||
BinaryWriter bw = null;
|
||||
string fullFilePath = Path.GetFullPath(filePath);
|
||||
string fullOutputDirectory = Path.GetDirectoryName(fullFilePath);
|
||||
|
||||
// create output folder if needed
|
||||
if (!Directory.Exists(fullOutputDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(fullOutputDirectory);
|
||||
|
||||
if (outputLogFile)
|
||||
{
|
||||
logInfo.AppendLine("Created Directory: " + fullOutputDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
// check if file exists and change name as needed
|
||||
if (File.Exists(fullFilePath))
|
||||
{
|
||||
int fileCount = Directory.GetFiles(fullOutputDirectory, (Path.GetFileNameWithoutExtension(fullFilePath) + "*" + Path.GetExtension(fullFilePath)), SearchOption.TopDirectoryOnly).Length;
|
||||
fullFilePath = Path.Combine(fullOutputDirectory, String.Format("{0}_{1}{2}", Path.GetFileNameWithoutExtension(fullFilePath), fileCount.ToString("X3"), Path.GetExtension(fullFilePath)));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
bw = new BinaryWriter(File.Open(fullFilePath, FileMode.Create, FileAccess.Write));
|
||||
|
||||
int read = 0;
|
||||
long totalBytes = 0;
|
||||
byte[] bytes = new byte[Constants.FileReadChunkSize];
|
||||
stream.Seek((long)startingOffset, SeekOrigin.Begin);
|
||||
|
||||
int maxread = length > (long)bytes.Length ? bytes.Length : (int)length;
|
||||
|
||||
while ((read = stream.Read(bytes, 0, maxread)) > 0)
|
||||
{
|
||||
bw.Write(bytes, 0, read);
|
||||
totalBytes += (long)read;
|
||||
|
||||
maxread = (length - totalBytes) > (long)bytes.Length ? bytes.Length : (int)(length - totalBytes);
|
||||
}
|
||||
|
||||
if (outputLogFile)
|
||||
{
|
||||
logInfo.AppendLine(
|
||||
String.Format("Extracted - Offset: 0x{0} Length: 0x{1} File: {2}",
|
||||
startingOffset.ToString("X8"),
|
||||
length.ToString("X8"),
|
||||
Path.GetFileName(fullFilePath)));
|
||||
|
||||
using (StreamWriter logWriter = new StreamWriter(Path.Combine(fullOutputDirectory, ParseFile.LogFileName), true))
|
||||
{
|
||||
logWriter.Write(logInfo.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
if (makeBatchFile)
|
||||
{
|
||||
snakeBiteBatch.AppendLine(
|
||||
String.Format("snakebite.exe \"{0}\" \"{1}\" 0x{2} 0x{3}",
|
||||
Path.GetFileName(((FileStream)stream).Name),
|
||||
Path.GetFileNameWithoutExtension(((FileStream)stream).Name) + Path.DirectorySeparatorChar + Path.GetFileName(fullFilePath),
|
||||
startingOffset.ToString("X8"),
|
||||
(startingOffset + length - 1).ToString("X8")));
|
||||
|
||||
using (StreamWriter batchWriter = new StreamWriter(Path.Combine(fullOutputDirectory, ParseFile.SnakeBiteBatchFileName), true))
|
||||
{
|
||||
batchWriter.Write(snakeBiteBatch.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (bw != null)
|
||||
{
|
||||
bw.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ExtractChunkToFile64(
|
||||
Stream stream,
|
||||
ulong startingOffset,
|
||||
ulong length,
|
||||
string filePath,
|
||||
bool outputLogFile,
|
||||
bool outputSnakebiteBatchFile)
|
||||
{
|
||||
bool makeBatchFile = (outputSnakebiteBatchFile && (stream is FileStream));
|
||||
StringBuilder logInfo = new StringBuilder();
|
||||
StringBuilder snakeBiteBatch = new StringBuilder();
|
||||
BinaryWriter bw = null;
|
||||
string fullFilePath = Path.GetFullPath(filePath);
|
||||
string fullOutputDirectory = Path.GetDirectoryName(fullFilePath);
|
||||
|
||||
// create output folder if needed
|
||||
if (!Directory.Exists(fullOutputDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(fullOutputDirectory);
|
||||
|
||||
if (outputLogFile)
|
||||
{
|
||||
logInfo.AppendLine("Created Directory: " + fullOutputDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
// check if file exists and change name as needed
|
||||
if (File.Exists(fullFilePath))
|
||||
{
|
||||
int fileCount = Directory.GetFiles(fullOutputDirectory, (Path.GetFileName(fullFilePath) + "*"), SearchOption.TopDirectoryOnly).Length;
|
||||
fullFilePath = Path.Combine(fullOutputDirectory, String.Format("{0}_{1}{2}", Path.GetFileNameWithoutExtension(fullFilePath), fileCount.ToString("X3"), Path.GetExtension(fullFilePath)));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
bw = new BinaryWriter(File.Open(fullFilePath, FileMode.Create, FileAccess.Write));
|
||||
|
||||
int read = 0;
|
||||
ulong totalBytes = 0;
|
||||
byte[] bytes = new byte[Constants.FileReadChunkSize];
|
||||
|
||||
// reset location
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// move stream pointer in long size chunks
|
||||
while (startingOffset > long.MaxValue) //
|
||||
{
|
||||
stream.Seek(long.MaxValue, SeekOrigin.Current);
|
||||
startingOffset -= long.MaxValue;
|
||||
}
|
||||
|
||||
// less than a long now, should be ok
|
||||
stream.Seek((long)startingOffset, SeekOrigin.Current);
|
||||
|
||||
int maxread = length > (ulong)bytes.Length ? bytes.Length : (int)length;
|
||||
|
||||
while ((read = stream.Read(bytes, 0, maxread)) > 0)
|
||||
{
|
||||
bw.Write(bytes, 0, read);
|
||||
totalBytes += (ulong)read;
|
||||
|
||||
maxread = (length - totalBytes) > (ulong)bytes.Length ? bytes.Length : (int)(length - totalBytes);
|
||||
}
|
||||
|
||||
if (outputLogFile)
|
||||
{
|
||||
logInfo.AppendLine(
|
||||
String.Format("Extracted - Offset: 0x{0} Length: 0x{1} File: {2}",
|
||||
startingOffset.ToString("X8"),
|
||||
length.ToString("X8"),
|
||||
Path.GetFileName(fullFilePath)));
|
||||
|
||||
using (StreamWriter logWriter = new StreamWriter(Path.Combine(fullOutputDirectory, ParseFile.LogFileName), true))
|
||||
{
|
||||
logWriter.Write(logInfo.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
if (makeBatchFile)
|
||||
{
|
||||
snakeBiteBatch.AppendLine(
|
||||
String.Format("snakebite.exe \"{0}\" \"{1}\" 0x{2} 0x{3}",
|
||||
Path.GetFileName(((FileStream)stream).Name),
|
||||
Path.GetFileNameWithoutExtension(((FileStream)stream).Name) + Path.DirectorySeparatorChar + Path.GetFileName(fullFilePath),
|
||||
startingOffset.ToString("X8"),
|
||||
(startingOffset + length - 1).ToString("X8")));
|
||||
|
||||
using (StreamWriter batchWriter = new StreamWriter(Path.Combine(fullOutputDirectory, ParseFile.SnakeBiteBatchFileName), true))
|
||||
{
|
||||
batchWriter.Write(snakeBiteBatch.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (bw != null)
|
||||
{
|
||||
bw.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] ExtractChunkToFile64ReturningHash(
|
||||
Stream stream,
|
||||
ulong startingOffset,
|
||||
ulong length,
|
||||
string filePath,
|
||||
HashAlgorithm checksumAlgorithm,
|
||||
bool outputLogFile,
|
||||
bool outputSnakebiteBatchFile)
|
||||
{
|
||||
byte[] checksumHash;
|
||||
bool makeBatchFile = (outputSnakebiteBatchFile && (stream is FileStream));
|
||||
StringBuilder logInfo = new StringBuilder();
|
||||
StringBuilder snakeBiteBatch = new StringBuilder();
|
||||
//BinaryWriter bw = null;
|
||||
string fullFilePath = Path.GetFullPath(filePath);
|
||||
string fullOutputDirectory = Path.GetDirectoryName(fullFilePath);
|
||||
|
||||
// create output folder if needed
|
||||
if (!Directory.Exists(fullOutputDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(fullOutputDirectory);
|
||||
|
||||
if (outputLogFile)
|
||||
{
|
||||
logInfo.AppendLine("Created Directory: " + fullOutputDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
// check if file exists and change name as needed
|
||||
if (File.Exists(fullFilePath))
|
||||
{
|
||||
int fileCount = Directory.GetFiles(fullOutputDirectory, (Path.GetFileName(fullFilePath) + "*"), SearchOption.TopDirectoryOnly).Length;
|
||||
fullFilePath = Path.Combine(fullOutputDirectory, String.Format("{0}_{1}{2}", Path.GetFileNameWithoutExtension(fullFilePath), fileCount.ToString("X3"), Path.GetExtension(fullFilePath)));
|
||||
}
|
||||
|
||||
using (FileStream fs = File.Open(fullFilePath, FileMode.Create, FileAccess.Write))
|
||||
using (CryptoStream hashStream = new CryptoStream(fs, checksumAlgorithm, CryptoStreamMode.Write))
|
||||
{
|
||||
int read = 0;
|
||||
ulong totalBytes = 0;
|
||||
byte[] bytes = new byte[Constants.FileReadChunkSize];
|
||||
|
||||
// reset location
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
// move stream pointer in long size chunks
|
||||
while (startingOffset > long.MaxValue) //
|
||||
{
|
||||
stream.Seek(long.MaxValue, SeekOrigin.Current);
|
||||
startingOffset -= long.MaxValue;
|
||||
}
|
||||
|
||||
// less than a long now, should be ok
|
||||
stream.Seek((long)startingOffset, SeekOrigin.Current);
|
||||
|
||||
int maxread = length > (ulong)bytes.Length ? bytes.Length : (int)length;
|
||||
|
||||
while ((read = stream.Read(bytes, 0, maxread)) > 0)
|
||||
{
|
||||
hashStream.Write(bytes, 0, read);
|
||||
totalBytes += (ulong)read;
|
||||
|
||||
maxread = (length - totalBytes) > (ulong)bytes.Length ? bytes.Length : (int)(length - totalBytes);
|
||||
}
|
||||
|
||||
hashStream.FlushFinalBlock();
|
||||
checksumHash = checksumAlgorithm.Hash;
|
||||
|
||||
#region Log Files
|
||||
if (outputLogFile)
|
||||
{
|
||||
logInfo.AppendLine(
|
||||
String.Format("Extracted - Offset: 0x{0} Length: 0x{1} File: {2}",
|
||||
startingOffset.ToString("X8"),
|
||||
length.ToString("X8"),
|
||||
Path.GetFileName(fullFilePath)));
|
||||
|
||||
using (StreamWriter logWriter = new StreamWriter(Path.Combine(fullOutputDirectory, ParseFile.LogFileName), true))
|
||||
{
|
||||
logWriter.Write(logInfo.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
if (makeBatchFile)
|
||||
{
|
||||
snakeBiteBatch.AppendLine(
|
||||
String.Format("snakebite.exe \"{0}\" \"{1}\" 0x{2} 0x{3}",
|
||||
Path.GetFileName(((FileStream)stream).Name),
|
||||
Path.GetFileNameWithoutExtension(((FileStream)stream).Name) + Path.DirectorySeparatorChar + Path.GetFileName(fullFilePath),
|
||||
startingOffset.ToString("X8"),
|
||||
(startingOffset + length - 1).ToString("X8")));
|
||||
|
||||
using (StreamWriter batchWriter = new StreamWriter(Path.Combine(fullOutputDirectory, ParseFile.SnakeBiteBatchFileName), true))
|
||||
{
|
||||
batchWriter.Write(snakeBiteBatch.ToString());
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
return checksumHash;
|
||||
}
|
||||
}
|
||||
|
||||
public static string ReadAsciiString(Stream inStream, long offset)
|
||||
{
|
||||
long streamLength = inStream.Length;
|
||||
int stringLength = 0;
|
||||
int codePage;
|
||||
|
||||
int dummy;
|
||||
byte[] stringBytes;
|
||||
string ret = String.Empty;
|
||||
|
||||
// move pointer
|
||||
inStream.Position = offset;
|
||||
|
||||
// read until NULL
|
||||
for (long i = offset; i <= streamLength; i++)
|
||||
{
|
||||
dummy = inStream.ReadByte();
|
||||
|
||||
if (dummy > 0)
|
||||
{
|
||||
stringLength++;
|
||||
}
|
||||
else if (dummy <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// parse string
|
||||
stringBytes = ParseSimpleOffset(inStream, offset, stringLength);
|
||||
codePage = ByteConversion.GetPredictedCodePageForTags(stringBytes);
|
||||
ret = ByteConversion.GetEncodedText(stringBytes, codePage);
|
||||
//ret = ByteConversion.GetAsciiText(stringBytes);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare bytes at input offset to target bytes.
|
||||
/// </summary>
|
||||
/// <param name="sourceArray">Bytes to compare.</param>
|
||||
/// <param name="offset">Offset to begin comparison of pBytes to pTarget.</param>
|
||||
/// <param name="target">Target bytes to compare.</param>
|
||||
/// <returns>True if the bytes at pOffset match the pTarget bytes.</returns>
|
||||
public static bool CompareSegment(byte[] sourceArray, int offset, byte[] target)
|
||||
{
|
||||
bool ret = true;
|
||||
uint j = 0;
|
||||
|
||||
if (sourceArray.Length > 0)
|
||||
{
|
||||
for (int i = offset; i < target.Length; i++)
|
||||
{
|
||||
if (sourceArray[i] != target[j])
|
||||
{
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
|
||||
j++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = false;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
/// <summary>
|
||||
/// Get the length from the input offset to the location of the input terminator bytes or zero.
|
||||
/// </summary>
|
||||
/// <param name="sourceBytes">Bytes to check.</param>
|
||||
/// <param name="searchOffset">Offset at which to begin searching for the terminator bytes.</param>
|
||||
/// <param name="terminatorBytes">Bytes to search for.</param>
|
||||
/// <returns>Length of distance between offset and terminator or zero if not found.</returns>
|
||||
public static int GetSegmentLength(byte[] sourceBytes, int searchOffset, byte[] terminatorBytes)
|
||||
{
|
||||
int ret;
|
||||
bool terminatorFound = false;
|
||||
int i = searchOffset;
|
||||
|
||||
while (i < sourceBytes.Length)
|
||||
{
|
||||
// first char match
|
||||
if (sourceBytes[i] == terminatorBytes[0])
|
||||
{
|
||||
if (CompareSegment(sourceBytes, i, terminatorBytes))
|
||||
{
|
||||
terminatorFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
} // while (!terminatorFound)
|
||||
|
||||
if (terminatorFound)
|
||||
{
|
||||
ret = i - searchOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the length from the input offset to the location of the input terminator bytes or zero.
|
||||
/// </summary>
|
||||
/// <param name="stream">Stream to check.</param>
|
||||
/// <param name="searchOffset">Offset at which to begin searching for the terminator bytes.</param>
|
||||
/// <param name="terminatorBytes">Bytes to search for.</param>
|
||||
/// <returns>Length of distance between offset and terminator or zero if not found.</returns>
|
||||
public static int GetSegmentLength(Stream stream, int searchOffset, byte[] terminatorBytes)
|
||||
{
|
||||
int ret;
|
||||
bool terminatorFound = false;
|
||||
int i = searchOffset;
|
||||
byte[] checkBytes = new byte[terminatorBytes.Length];
|
||||
|
||||
while (i < stream.Length)
|
||||
{
|
||||
stream.Seek(i, SeekOrigin.Begin);
|
||||
stream.Read(checkBytes, 0, 1);
|
||||
|
||||
// first char match
|
||||
if (checkBytes[0] == terminatorBytes[0])
|
||||
{
|
||||
stream.Seek(i, SeekOrigin.Begin);
|
||||
stream.Read(checkBytes, 0, terminatorBytes.Length);
|
||||
|
||||
if (CompareSegment(checkBytes, 0, terminatorBytes))
|
||||
{
|
||||
terminatorFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
} // while (!terminatorFound)
|
||||
|
||||
if (terminatorFound)
|
||||
{
|
||||
ret = i - searchOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
public static Dictionary<byte[], long[]> GetAllOffsets(Stream stream, long startingOffset,
|
||||
byte[][] searchByteArrays, bool returnStreamToIncomingPosition)
|
||||
{
|
||||
long initialStreamPosition = 0;
|
||||
|
||||
if (returnStreamToIncomingPosition)
|
||||
{
|
||||
initialStreamPosition = stream.Position;
|
||||
}
|
||||
|
||||
int maxSearchBytesLength = 0;
|
||||
long absoluteOffset = startingOffset;
|
||||
long relativeOffset;
|
||||
byte[] checkBytes = new byte[Constants.FileReadChunkSize];
|
||||
byte[] compareBytes;
|
||||
|
||||
Dictionary<byte[], ArrayList> tempOutput = new Dictionary<byte[], ArrayList>();
|
||||
Dictionary<byte[], long[]> ret = new Dictionary<byte[], long[]>();
|
||||
|
||||
foreach (byte[] b in searchByteArrays)
|
||||
{
|
||||
tempOutput.Add(b, new ArrayList());
|
||||
|
||||
if (b.Length > maxSearchBytesLength)
|
||||
{
|
||||
maxSearchBytesLength = b.Length;
|
||||
}
|
||||
}
|
||||
|
||||
while (absoluteOffset < stream.Length)
|
||||
{
|
||||
stream.Position = absoluteOffset;
|
||||
stream.Read(checkBytes, 0, Constants.FileReadChunkSize);
|
||||
relativeOffset = 0;
|
||||
|
||||
while (relativeOffset < Constants.FileReadChunkSize)
|
||||
{
|
||||
foreach (byte[] searchBytes in searchByteArrays)
|
||||
{
|
||||
if ((relativeOffset + searchBytes.Length) < checkBytes.Length)
|
||||
{
|
||||
compareBytes = new byte[searchBytes.Length];
|
||||
Array.Copy(checkBytes, relativeOffset, compareBytes, 0, searchBytes.Length);
|
||||
|
||||
if (CompareSegment(compareBytes, 0, searchBytes))
|
||||
{
|
||||
tempOutput[searchBytes].Add(absoluteOffset + relativeOffset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // foreach (byte[] searchBytes in searchByteArrays)
|
||||
|
||||
relativeOffset++;
|
||||
}
|
||||
|
||||
absoluteOffset += Constants.FileReadChunkSize - maxSearchBytesLength;
|
||||
}
|
||||
|
||||
// return stream to incoming position
|
||||
if (returnStreamToIncomingPosition)
|
||||
{
|
||||
stream.Position = initialStreamPosition;
|
||||
}
|
||||
|
||||
// sort offsets and add to output
|
||||
foreach (byte[] key in tempOutput.Keys)
|
||||
{
|
||||
tempOutput[key].Sort();
|
||||
ret.Add(key, (long[])tempOutput[key].ToArray(typeof(long)));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static long[] GetAllOffsets(Stream stream, long startingOffset,
|
||||
byte[] searchBytes, bool doOffsetModulo, long offsetModuloDivisor,
|
||||
long offsetModuloResult, bool returnStreamToIncomingPosition)
|
||||
{
|
||||
long initialStreamPosition = 0;
|
||||
|
||||
if (returnStreamToIncomingPosition)
|
||||
{
|
||||
initialStreamPosition = stream.Position;
|
||||
}
|
||||
|
||||
long absoluteOffset = startingOffset;
|
||||
long relativeOffset;
|
||||
long actualOffset;
|
||||
byte[] checkBytes = new byte[Constants.FileReadChunkSize];
|
||||
int checkBytesRead;
|
||||
byte[] compareBytes;
|
||||
|
||||
ArrayList offsetList = new ArrayList();
|
||||
long[] ret;
|
||||
|
||||
while (absoluteOffset < stream.Length)
|
||||
{
|
||||
stream.Position = absoluteOffset;
|
||||
checkBytesRead = stream.Read(checkBytes, 0, Constants.FileReadChunkSize);
|
||||
relativeOffset = 0;
|
||||
|
||||
while (relativeOffset < checkBytesRead)
|
||||
{
|
||||
actualOffset = absoluteOffset + relativeOffset;
|
||||
|
||||
if ((!doOffsetModulo) ||
|
||||
(actualOffset % offsetModuloDivisor == offsetModuloResult))
|
||||
{
|
||||
if ((relativeOffset + searchBytes.Length) < checkBytes.Length)
|
||||
{
|
||||
compareBytes = new byte[searchBytes.Length];
|
||||
Array.Copy(checkBytes, relativeOffset, compareBytes, 0, searchBytes.Length);
|
||||
|
||||
if (CompareSegment(compareBytes, 0, searchBytes))
|
||||
{
|
||||
offsetList.Add(actualOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
relativeOffset++;
|
||||
}
|
||||
|
||||
absoluteOffset += Constants.FileReadChunkSize - searchBytes.Length;
|
||||
}
|
||||
|
||||
// return stream to incoming position
|
||||
if (returnStreamToIncomingPosition)
|
||||
{
|
||||
stream.Position = initialStreamPosition;
|
||||
}
|
||||
|
||||
ret = (long[])offsetList.ToArray(typeof(long));
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
acb_extract/src/Program.cs
Normal file
17
acb_extract/src/Program.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace vgm_acb.extract
|
||||
{
|
||||
static class extract
|
||||
{
|
||||
static void Main(string[] args) {
|
||||
foreach (string s in args) {
|
||||
using (FileStream fs = File.Open(s, FileMode.Open, FileAccess.Read, FileShare.Read)) {
|
||||
CriAcbFile acb = new CriAcbFile(fs, 0, false);
|
||||
acb.ExtractAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue