Skip to content

Commit

Permalink
Merge pull request #25 from hozuki/fixes-feb2017
Browse files Browse the repository at this point in the history
HCA and ACB improvements
  • Loading branch information
hozuki committed Feb 5, 2017
2 parents 770d0a0 + b8f25ed commit d9bf306
Show file tree
Hide file tree
Showing 44 changed files with 1,027 additions and 747 deletions.
61 changes: 31 additions & 30 deletions DereTore.ACB/UtfReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ public UtfReader() {
_isEncrypted = false;
}

public UtfReader(byte seed, byte increment, bool isEncrypted) {
public UtfReader(byte seed, byte increment) {
_seed = seed;
_increment = increment;
_isEncrypted = isEncrypted;
_isEncrypted = true;
}

public bool IsEncrypted => _isEncrypted;

/// <summary>
///
///
/// </summary>
/// <param name="stream"></param>
/// <param name="baseOffset">Offset of the UTF table, starting from the beginning of ACB file.</param>
Expand Down Expand Up @@ -132,36 +132,37 @@ public float PeekSingle(Stream stream, long baseOffset, long utfOffset) {
return BitConverter.ToSingle(temp, 0);
}

public string ReadZeroEndedAsciiAsAscii(Stream stream, long baseOffset, long utfOffset) {
var asciiVal = new StringBuilder();
var fileSize = stream.Length;
public string ReadZeroEndedStringAsAscii(Stream stream, long baseOffset, long utfOffset) {
if (!IsEncrypted) {
asciiVal.Append(stream.PeekZeroEndedStringAsAscii(baseOffset + utfOffset));
} else {
stream.Position = baseOffset + utfOffset;
if (utfOffset < _currentUtfStringOffset) {
_currentUtfStringOffset = 0;
}
return stream.PeekZeroEndedStringAsAscii(baseOffset + utfOffset);
}

if (_currentUtfStringOffset == 0) {
_currentStringXor = _seed;
}
for (var j = _currentUtfStringOffset; j < utfOffset; j++) {
if (j > 0) {
_currentStringXor *= _increment;
}
_currentUtfStringOffset++;
}
for (var i = utfOffset; i < fileSize - (baseOffset + utfOffset); i++) {
stream.Position = baseOffset + utfOffset;
if (utfOffset < _currentUtfStringOffset) {
_currentUtfStringOffset = 0;
}

if (_currentUtfStringOffset == 0) {
_currentStringXor = _seed;
}
for (var j = _currentUtfStringOffset; j < utfOffset; j++) {
if (j > 0) {
_currentStringXor *= _increment;
_currentUtfStringOffset++;
var encryptedByte = (byte)stream.ReadByte();
var decryptedByte = (byte)(encryptedByte ^ _currentStringXor);
if (decryptedByte == 0) {
break;
} else {
asciiVal.Append((char)decryptedByte);
}
}
_currentUtfStringOffset++;
}

var asciiVal = new StringBuilder();
var remained = stream.Length - stream.Position - (baseOffset + utfOffset);
for (var i = 0; i < remained; i++) {
_currentStringXor *= _increment;
_currentUtfStringOffset++;
var encryptedByte = (byte)stream.ReadByte();
var decryptedByte = (byte)(encryptedByte ^ _currentStringXor);
if (decryptedByte == 0) {
break;
} else {
asciiVal.Append((char)decryptedByte);
}
}
return asciiVal.ToString();
Expand Down
90 changes: 44 additions & 46 deletions DereTore.ACB/UtfTable.Internal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ internal virtual void Initialize() {
var offset = _offset;

var magic = stream.PeekBytes(offset, 4);
magic = CheckEncryption(magic);
if (!AcbHelper.AreDataIdentical(magic, UtfSignature)) {
throw new FormatException($"'@UTF' signature is not found in '{_acbFileName}' at offset 0x{offset.ToString("x8")}.");
throw new FormatException($"'@UTF' signature (or its encrypted equivalent) is not found in '{_acbFileName}' at offset 0x{offset:x8}.");
}
CheckEncryption(magic);
using (var tableDataStream = GetTableDataStream(stream, offset)) {
using (var tableDataStream = GetTableDataStream()) {
var header = GetUtfHeader(tableDataStream);
_utfHeader = header;
_rows = new Dictionary<string, UtfField>[header.RowCount];
Expand All @@ -41,72 +41,72 @@ protected override void Dispose(bool disposing) {
}
}

private static Dictionary<string, byte> GetKeysForEncryptedUtfTable(byte[] encryptedUtfSignature) {
var keys = new Dictionary<string, byte>(2);
var keysFound = false;
for (var seed = 0; seed <= byte.MaxValue; seed++) {
if (keysFound) {
break;
}
if ((encryptedUtfSignature[0] ^ seed) != UtfSignature[0]) {
private static bool GetKeysForEncryptedUtfTable(byte[] encryptedUtfSignature, out byte seed, out byte increment) {
for (var s = 0; s <= byte.MaxValue; s++) {
if ((encryptedUtfSignature[0] ^ s) != UtfSignature[0]) {
continue;
}
for (var increment = 0; increment <= byte.MaxValue; increment++) {
if (keysFound) {
break;
}
var m = (byte)(seed * increment);
for (var i = 0; i <= byte.MaxValue; i++) {
var m = (byte)(s * i);
if ((encryptedUtfSignature[1] ^ m) != UtfSignature[1]) {
continue;
}
var t = (byte)increment;
var t = (byte)i;
for (var j = 2; j < UtfSignature.Length; j++) {
m *= t;
if ((encryptedUtfSignature[j] ^ m) != UtfSignature[j]) {
break;
}
if (j != UtfSignature.Length - 1) {
if (j < UtfSignature.Length - 1) {
continue;
}
keys.Add(LcgSeedKey, (byte)seed);
keys.Add(LcgIncrementKey, (byte)increment);
keysFound = true;
seed = (byte)s;
increment = (byte)i;
return true;
}
}
}
return keys;
seed = 0;
increment = 0;
return false;
}

private void CheckEncryption(byte[] magicBytes) {
private byte[] CheckEncryption(byte[] magicBytes) {
if (AcbHelper.AreDataIdentical(magicBytes, UtfSignature)) {
_isEncrypted = false;
_utfReader = new UtfReader();
return magicBytes;
} else {
_isEncrypted = true;
var lcgKeys = GetKeysForEncryptedUtfTable(magicBytes);
if (lcgKeys.Count != 2) {
throw new FormatException($"Unable to decrypt UTF table at offset 0x{_offset.ToString("x8")}");
byte seed, increment;
var keysFound = GetKeysForEncryptedUtfTable(magicBytes, out seed, out increment);
if (!keysFound) {
throw new FormatException($"Unable to decrypt UTF table at offset 0x{_offset:x8}");
} else {
_utfReader = new UtfReader(lcgKeys[LcgSeedKey], lcgKeys[LcgIncrementKey], IsEncrypted);
_utfReader = new UtfReader(seed, increment);
}
return UtfSignature;
}
}

private Stream GetTableDataStream(Stream stream, long offset) {
private Stream GetTableDataStream() {
var stream = _stream;
var offset = _offset;
var tableSize = (int)_utfReader.PeekUInt32(stream, offset, 4) + 8;
if (!IsEncrypted) {
return AcbHelper.ExtractToNewStream(stream, offset, tableSize);
}
// Another reading process. Unlike the one with direct reading, this may encounter UTF table decryption.
var originalPosition = stream.Position;
stream.Seek(offset, SeekOrigin.Begin);
var totalBytesRead = 0;
var memory = new byte[tableSize];
var currentIndex = 0;
var currentOffset = offset;
do {
var shouldRead = tableSize - totalBytesRead;
var buffer = _utfReader.PeekBytes(stream, offset, shouldRead, totalBytesRead);
var buffer = _utfReader.PeekBytes(stream, currentOffset, shouldRead, totalBytesRead);
Array.Copy(buffer, 0, memory, currentIndex, buffer.Length);
currentOffset += buffer.Length;
currentIndex += buffer.Length;
totalBytesRead += buffer.Length;
} while (totalBytesRead < tableSize);
Expand All @@ -127,15 +127,15 @@ private static UtfHeader GetUtfHeader(Stream stream, long offset) {
stream.Seek(offset, SeekOrigin.Begin);
}
var header = new UtfHeader {
TableSize = stream.PeekUInt32BE(4),
Unknown1 = stream.PeekUInt16BE(8),
PerRowDataOffset = (uint)stream.PeekUInt16BE(10) + 8,
StringTableOffset = stream.PeekUInt32BE(12) + 8,
ExtraDataOffset = stream.PeekUInt32BE(16) + 8,
TableNameOffset = stream.PeekUInt32BE(20),
FieldCount = stream.PeekUInt16BE(24),
RowSize = stream.PeekUInt16BE(26),
RowCount = stream.PeekUInt32BE(28)
TableSize = stream.PeekUInt32BE(offset + 4),
Unknown1 = stream.PeekUInt16BE(offset + 8),
PerRowDataOffset = (uint)stream.PeekUInt16BE(offset + 10) + 8,
StringTableOffset = stream.PeekUInt32BE(offset + 12) + 8,
ExtraDataOffset = stream.PeekUInt32BE(offset + 16) + 8,
TableNameOffset = stream.PeekUInt32BE(offset + 20),
FieldCount = stream.PeekUInt16BE(offset + 24),
RowSize = stream.PeekUInt16BE(offset + 26),
RowCount = stream.PeekUInt32BE(offset + 28)
};
header.TableName = stream.PeekZeroEndedStringAsAscii(header.StringTableOffset + header.TableNameOffset);
return header;
Expand All @@ -147,10 +147,10 @@ private void InitializeUtfSchema(Stream sourceStream, Stream tableDataStream, lo
var baseOffset = _offset;
for (uint i = 0; i < header.RowCount; i++) {
var currentOffset = schemaOffset;
long currentRowBase = header.PerRowDataOffset + header.RowSize * i;
long currentRowOffset = 0;
var row = new Dictionary<string, UtfField>();
rows[i] = row;
long currentRowOffset = 0;
long currentRowBase = header.PerRowDataOffset + header.RowSize * i;

for (var j = 0; j < header.FieldCount; j++) {
var field = new UtfField {
Expand Down Expand Up @@ -224,7 +224,7 @@ private void InitializeUtfSchema(Stream sourceStream, Stream tableDataStream, lo
currentOffset += 1;
break;
default:
throw new FormatException($"Unknown column type at offset: 0x{currentOffset.ToString("x8")}");
throw new FormatException($"Unknown column type at offset: 0x{currentOffset:x8}");
}
break;
case ColumnStorage.PerRow:
Expand Down Expand Up @@ -286,12 +286,12 @@ private void InitializeUtfSchema(Stream sourceStream, Stream tableDataStream, lo
currentRowOffset += 1;
break;
default:
throw new FormatException($"Unknown column type at offset: 0x{currentOffset.ToString("x8")}");
throw new FormatException($"Unknown column type at offset: 0x{currentOffset:x8}");
}
field.ConstrainedType = (ColumnType)field.Type;
break;
default:
throw new FormatException($"Unknown column storage at offset: 0x{currentOffset.ToString("x8")}");
throw new FormatException($"Unknown column storage at offset: 0x{currentOffset:x8}");
}
// Union polyfill
field.ConstrainedType = constrainedType;
Expand Down Expand Up @@ -355,8 +355,6 @@ internal byte[] GetFieldValueAsData(int rowIndex, string fieldName) {
}

internal static readonly byte[] UtfSignature = { 0x40, 0x55, 0x54, 0x46 }; // '@UTF'
private static readonly string LcgSeedKey = "SEED";
private static readonly string LcgIncrementKey = "INC";

private readonly string _acbFileName;
private readonly Stream _stream;
Expand Down
4 changes: 2 additions & 2 deletions DereTore.Applications.AcbMaker/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ private static int Main(string[] args) {

private static HeaderTable GetFullTable(string hcaFileName, string songName) {
HcaInfo info;
int lengthInSamples;
uint lengthInSamples;
float lengthInSeconds;
using (var fileStream = File.Open(hcaFileName, FileMode.Open, FileAccess.Read)) {
var decoder = new OneWayHcaDecoder(fileStream);
Expand Down Expand Up @@ -82,7 +82,7 @@ private static HeaderTable GetFullTable(string hcaFileName, string songName) {
NumChannels = (byte)info.ChannelCount,
LoopFlag = 1,
SamplingRate = (ushort)info.SamplingRate,
NumSamples = (uint)lengthInSamples,
NumSamples = lengthInSamples,
ExtensionData = ushort.MaxValue
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,21 @@
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="CommandLine, Version=1.9.71.2, Culture=neutral, PublicKeyToken=de6f01bd326f8c32, processorArchitecture=MSIL">
<HintPath>..\packages\CommandLineParser.1.9.71\lib\net40\CommandLine.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Compile Include="Options.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DereTore.HCA\DereTore.HCA.csproj">
Expand Down
33 changes: 33 additions & 0 deletions DereTore.Applications.Hca2Wav/Options.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using CommandLine;
using DereTore.HCA;
using DereTore.StarlightStage;

namespace DereTore.Applications.Hca2Wav {
public sealed class Options {

[Option('i', "in", Required = true)]
public string InputFileName { get; set; } = string.Empty;

[Option('o', "out", Required = false)]
public string OutputFileName { get; set; } = string.Empty;

[Option('a', "key1", Required = false)]
public string Key1 { get; set; } = CgssCipher.Key1.ToString("x8");

[Option('b', "key2", Required = false)]
public string Key2 { get; set; } = CgssCipher.Key2.ToString("x8");

[Option("infinite", Required = false)]
public bool InfiniteLoop { get; set; } = AudioParams.Default.InfiniteLoop;

[Option('l', "loop", Required = false)]
public uint SimulatedLoopCount { get; set; } = AudioParams.Default.SimulatedLoopCount;

[Option('e', "header", Required = false)]
public bool OutputWaveHeader { get; set; } = true;

[Option('c', "cipher", Required = false)]
public uint OverriddenCipherType { get; set; }

}
}
Loading

0 comments on commit d9bf306

Please sign in to comment.