From 94b115af78ffc5af1b7565a78908801e3da9358f Mon Sep 17 00:00:00 2001 From: TheIndra55 Date: Fri, 24 Jan 2025 00:38:32 +0100 Subject: [PATCH 1/9] Initial refactor --- Yura.Shared/Archive/ArchiveFile.cs | 76 +++++++++++++++ Yura.Shared/Archive/ArchiveOptions.cs | 28 ++++++ Yura.Shared/Archive/ArchiveRecord.cs | 25 +++++ Yura.Shared/Archive/DefianceArchive.cs | 25 +++++ Yura.Shared/Archive/DeusExArchive.cs | 25 +++++ Yura.Shared/Archive/LegendArchive.cs | 85 ++++++++++++++++ Yura.Shared/Archive/TigerArchive.cs | 125 ++++++++++++++++++++++++ Yura.Shared/IO/DataReader.cs | 128 +++++++++++++++++++++++++ Yura.Shared/IO/DataWriter.cs | 125 ++++++++++++++++++++++++ Yura.Shared/IO/Endianness.cs | 8 ++ Yura.Shared/Util/FileList.cs | 116 ++++++++++++++++++++++ Yura.Shared/Yura.Shared.csproj | 8 ++ Yura.sln | 10 +- Yura/CommandLineOptions.cs | 5 +- Yura/IFileSettings.cs | 6 +- Yura/MainWindow.xaml.cs | 38 +++++--- Yura/OpenDialog.xaml.cs | 5 +- Yura/Windows/SearchWindow.xaml.cs | 3 +- Yura/Yura.csproj | 4 + 19 files changed, 822 insertions(+), 23 deletions(-) create mode 100644 Yura.Shared/Archive/ArchiveFile.cs create mode 100644 Yura.Shared/Archive/ArchiveOptions.cs create mode 100644 Yura.Shared/Archive/ArchiveRecord.cs create mode 100644 Yura.Shared/Archive/DefianceArchive.cs create mode 100644 Yura.Shared/Archive/DeusExArchive.cs create mode 100644 Yura.Shared/Archive/LegendArchive.cs create mode 100644 Yura.Shared/Archive/TigerArchive.cs create mode 100644 Yura.Shared/IO/DataReader.cs create mode 100644 Yura.Shared/IO/DataWriter.cs create mode 100644 Yura.Shared/IO/Endianness.cs create mode 100644 Yura.Shared/Util/FileList.cs create mode 100644 Yura.Shared/Yura.Shared.csproj diff --git a/Yura.Shared/Archive/ArchiveFile.cs b/Yura.Shared/Archive/ArchiveFile.cs new file mode 100644 index 0000000..199a5f1 --- /dev/null +++ b/Yura.Shared/Archive/ArchiveFile.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Yura.Shared.Archive +{ + public abstract class ArchiveFile + { + protected ArchiveOptions Options { get; } + + public ArchiveFile(ArchiveOptions options) + { + ArgumentNullException.ThrowIfNull(options); + + Options = options; + Records = []; + } + + /// + /// Gets the records in the archive + /// + public List Records { get; } + + /// + /// Gets the path of the archive + /// + public string Name => Options.Path; + + /// + /// Opens the archive + /// + public abstract void Open(); + + /// + /// Reads a file from the archive + /// + /// The record to read + /// The contents of the file + public abstract byte[] Read(ArchiveRecord record); + + /// + /// Gets all files in the specified directory + /// + /// The path to the directory + /// The contents of the directory + public List GetFiles(string path) + { + var hierachy = Split(path); + + // Find all records where the path is equal, removing the file name + return Records.Where(record => record.Name != null && Split(record.Name).SkipLast(1).SequenceEqual(hierachy)).ToList(); + } + + /// + /// Gets the file name of a archive part + /// + /// The part + /// The extension + /// The formatted file name + protected string GetFilePart(int part, string extension = "") + { + var path = Options.Path[..^extension.Length]; + + var name = Path.GetFileNameWithoutExtension(path); + var directory = Path.GetDirectoryName(path); + + return Path.Combine(directory, name + "." + part.ToString("000") + extension); + } + + private static string[] Split(string path) + { + return path.Split('\\', StringSplitOptions.RemoveEmptyEntries); + } + } +} diff --git a/Yura.Shared/Archive/ArchiveOptions.cs b/Yura.Shared/Archive/ArchiveOptions.cs new file mode 100644 index 0000000..ae4878d --- /dev/null +++ b/Yura.Shared/Archive/ArchiveOptions.cs @@ -0,0 +1,28 @@ +using Yura.Shared.IO; +using Yura.Shared.Util; + +namespace Yura.Shared.Archive +{ + public class ArchiveOptions + { + /// + /// Gets or sets the path to the archive + /// + public string Path { get; set; } + + /// + /// Gets or sets the archive endianness + /// + public Endianness Endianness { get; set; } + + /// + /// Gets or sets the archive alignment + /// + public int Alignment { get; set; } + + /// + /// Gets or sets the file list + /// + public FileList? FileList { get; set; } + } +} diff --git a/Yura.Shared/Archive/ArchiveRecord.cs b/Yura.Shared/Archive/ArchiveRecord.cs new file mode 100644 index 0000000..f990ef4 --- /dev/null +++ b/Yura.Shared/Archive/ArchiveRecord.cs @@ -0,0 +1,25 @@ +namespace Yura.Shared.Archive +{ + public class ArchiveRecord + { + /// + /// Gets or sets the hash of the file name + /// + public ulong Hash { get; internal set; } + + /// + /// Gets or sets the file name + /// + public string? Name { get; internal set; } + + /// + /// Gets or sets the file size + /// + public uint Size { get; internal set; } + + /// + /// Gets the file specialisation mask + /// + public ulong Specialisation { get; internal set; } + } +} diff --git a/Yura.Shared/Archive/DefianceArchive.cs b/Yura.Shared/Archive/DefianceArchive.cs new file mode 100644 index 0000000..22957b3 --- /dev/null +++ b/Yura.Shared/Archive/DefianceArchive.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Yura.Shared.Archive +{ + public class DefianceArchive : ArchiveFile + { + public DefianceArchive(ArchiveOptions options) : base(options) + { + } + + public override void Open() + { + throw new NotImplementedException(); + } + + public override byte[] Read(ArchiveRecord record) + { + throw new NotImplementedException(); + } + } +} diff --git a/Yura.Shared/Archive/DeusExArchive.cs b/Yura.Shared/Archive/DeusExArchive.cs new file mode 100644 index 0000000..556ca28 --- /dev/null +++ b/Yura.Shared/Archive/DeusExArchive.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Yura.Shared.Archive +{ + public class DeusExArchive : ArchiveFile + { + public DeusExArchive(ArchiveOptions options) : base(options) + { + } + + public override void Open() + { + throw new NotImplementedException(); + } + + public override byte[] Read(ArchiveRecord record) + { + throw new NotImplementedException(); + } + } +} diff --git a/Yura.Shared/Archive/LegendArchive.cs b/Yura.Shared/Archive/LegendArchive.cs new file mode 100644 index 0000000..b4ffe02 --- /dev/null +++ b/Yura.Shared/Archive/LegendArchive.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Yura.Shared.IO; + +namespace Yura.Shared.Archive +{ + public class LegendArchive : ArchiveFile + { + public LegendArchive(ArchiveOptions options) : base(options) + { + } + + public override void Open() + { + var stream = File.OpenRead(Options.Path); + var reader = new DataReader(stream, Options.Endianness); + + // Read the number of files + var numRecords = reader.ReadUInt32(); + + if (numRecords > 1_000_000) + { + throw new ArgumentException("Bigfile has more than a million files, did you select the right endianness?"); + } + + // Read the file hashes + var hashes = new uint[numRecords]; + + for (var i = 0; i < numRecords; i++) + { + hashes[i] = reader.ReadUInt32(); + } + + // Read the file records + for (var i = 0; i < numRecords; i++) + { + var record = new Record + { + Hash = hashes[i], + + Size = reader.ReadUInt32(), + Offset = reader.ReadUInt32(), + Specialisation = reader.ReadUInt32(), + CompressedSize = reader.ReadUInt32(), + }; + + Records.Add(record); + } + + stream.Close(); + } + + public override byte[] Read(ArchiveRecord record) + { + var file = record as Record; + + // Calculate the location of the file + var offset = (long)file.Offset << 11; + var part = offset / Options.Alignment; + + var path = GetFilePart((int)part); + + // Read the file + var stream = File.OpenRead(path); + var data = new byte[file.Size]; + + stream.Position = offset % Options.Alignment; + stream.ReadExactly(data); + + stream.Close(); + + return data; + } + + private class Record : ArchiveRecord + { + public uint Offset { get; set; } + public uint CompressedSize { get; set; } + } + } +} diff --git a/Yura.Shared/Archive/TigerArchive.cs b/Yura.Shared/Archive/TigerArchive.cs new file mode 100644 index 0000000..6b9b15b --- /dev/null +++ b/Yura.Shared/Archive/TigerArchive.cs @@ -0,0 +1,125 @@ +using System; +using System.IO; +using Yura.Shared.IO; +using Yura.Shared.Util; + +namespace Yura.Shared.Archive +{ + public class TigerArchive : ArchiveFile + { + public TigerArchive(ArchiveOptions options) : base(options) + { + } + + public override void Open() + { + var stream = File.OpenRead(Options.Path); + var reader = new DataReader(stream, Options.Endianness); + + // Read the magic + var magic = reader.ReadUInt32(); + + if (magic != 0x53464154 && magic != 0x54414653) + { + throw new Exception("File is not a tiger archive file"); + } + + var version = reader.ReadUInt32(); + + // Check for version 3, 4 or 5 + if (version < 3 || version > 5) + { + throw new NotImplementedException($"Tiger archive version {version} is not supported"); + } + + // Load the file list with the correct hashing algorithm + Options.FileList?.Load(version < 5 ? HashAlgorithm.Crc32 : HashAlgorithm.Fnv1a); + + var numArchives = reader.ReadUInt32(); + var numRecords = reader.ReadUInt32(); + + reader.Position += 4; + + // Skip over the config name + reader.Position += 32; + + // Read the file records + for (var i = 0; i < numRecords; i++) + { + var record = new Record(); + + if (version < 5) + { + record.Hash = reader.ReadUInt32(); + record.Specialisation = reader.ReadUInt32(); + record.Size = reader.ReadUInt32(); + } + else + { + record.Hash = reader.ReadUInt64(); + record.Specialisation = reader.ReadUInt64(); + record.Size = reader.ReadUInt32(); + } + + // Skip in TR2 and later + if (version >= 4) + { + reader.Position += 4; + } + + // TRAS + if (version == 3) + { + var packedOffset = reader.ReadUInt32(); + + record.Index = packedOffset & 0xF; + record.Offset = packedOffset & 0xFFFFF800; + } + // TR2 + else if (version == 4) + { + var packedOffset = reader.ReadUInt64(); + + record.Index = packedOffset & 0xFFFF; + record.Offset = (packedOffset >> 32) & 0xFFFFFFFF; + } + // TR11 + else if (version == 5) + { + var packedOffset = reader.ReadUInt64(); + + record.Index = packedOffset & 0xFFFF; + record.Offset = (packedOffset >> 32) & 0xFFFFFFFF; + } + + Records.Add(record); + } + + stream.Close(); + } + + public override byte[] Read(ArchiveRecord record) + { + var file = record as Record; + + var path = GetFilePart((int)file.Index, ".tiger"); + + // Read the file + var stream = File.OpenRead(path); + var data = new byte[file.Size]; + + stream.Position = (long)file.Offset; + stream.ReadExactly(data); + + stream.Close(); + + return data; + } + + private class Record : ArchiveRecord + { + public ulong Index { get; set; } + public ulong Offset { get; set; } + } + } +} diff --git a/Yura.Shared/IO/DataReader.cs b/Yura.Shared/IO/DataReader.cs new file mode 100644 index 0000000..f7a26fc --- /dev/null +++ b/Yura.Shared/IO/DataReader.cs @@ -0,0 +1,128 @@ +using System; +using System.IO; +using System.Linq; + +namespace Yura.Shared.IO +{ + /// + /// Reads data types from a binary stream + /// + public class DataReader + { + private readonly Stream _stream; + private readonly Endianness _endianness; + + /// + /// Initializes a new data reader with the the specified stream and endianness + /// + /// The input stream + /// The endianness of the data + public DataReader(Stream stream, Endianness endianness) + { + ArgumentNullException.ThrowIfNull(stream); + + _stream = stream; + _endianness = endianness; + } + + /// + /// Gets the underlying stream + /// + public Stream BaseStream => _stream; + + /// + /// Gets the endianness + /// + public Endianness Endianness => _endianness; + + /// + /// Gets or sets the position within the stream + /// + public long Position + { + get => _stream.Position; + set => _stream.Position = value; + } + + /// + /// Reads a number of bytes from the stream into the buffer + /// + /// The buffer to read into + /// The buffer + private ReadOnlySpan InternalRead(Span data) + { + _stream.ReadExactly(data); + + // Reverse the buffer if the data is big endian + if (_endianness == Endianness.BigEndian) + { + data.Reverse(); + } + + return data; + } + + /// + /// Reads a signed 16-bit integer from the stream + /// + /// The read integer + public short ReadInt16() + { + return BitConverter.ToInt16(InternalRead(stackalloc byte[2])); + } + + /// + /// Reads a signed 32-bit integer from the stream + /// + /// The read integer + public int ReadInt32() + { + return BitConverter.ToInt32(InternalRead(stackalloc byte[4])); + } + + /// + /// Reads a signed 64-bit integer from the stream + /// + /// The read integer + public long ReadInt64() + { + return BitConverter.ToInt64(InternalRead(stackalloc byte[8])); + } + + /// + /// Reads an unsigned 16-bit integer from the stream + /// + /// The read integer + public ushort ReadUInt16() + { + return BitConverter.ToUInt16(InternalRead(stackalloc byte[2])); + } + + /// + /// Reads an unsigned 32-bit integer from the stream + /// + /// The read integer + public uint ReadUInt32() + { + return BitConverter.ToUInt32(InternalRead(stackalloc byte[4])); + } + + /// + /// Reads an unsigned 64-bit integer from the stream + /// + /// The read integer + public ulong ReadUInt64() + { + return BitConverter.ToUInt64(InternalRead(stackalloc byte[8])); + } + + /// + /// Reads a 32-bit floating point value from the stream + /// + /// The read value + public float ReadSingle() + { + return BitConverter.ToSingle(InternalRead(stackalloc byte[4])); + } + } +} diff --git a/Yura.Shared/IO/DataWriter.cs b/Yura.Shared/IO/DataWriter.cs new file mode 100644 index 0000000..9c24b0c --- /dev/null +++ b/Yura.Shared/IO/DataWriter.cs @@ -0,0 +1,125 @@ +using System; +using System.IO; +using System.Linq; + +namespace Yura.Shared.IO +{ + /// + /// Writes data types to a binary stream + /// + public class DataWriter + { + private readonly Stream _stream; + private readonly Endianness _endianness; + + /// + /// Initializes a new data writer with the the specified stream and endianness + /// + /// The output stream + /// The endianness of the data + public DataWriter(Stream stream, Endianness endianness) + { + ArgumentNullException.ThrowIfNull(stream); + + _stream = stream; + _endianness = endianness; + } + + /// + /// Gets the underlying stream + /// + public Stream BaseStream => _stream; + + /// + /// Gets the endianness + /// + public Endianness Endianness => _endianness; + + /// + /// Gets or sets the position within the stream + /// + public long Position + { + get => _stream.Position; + set => _stream.Position = value; + } + + /// + /// Writes a number of bytes to the stream + /// + /// The data to write + private void InternalWrite(Span data) + { + // Reverse the buffer if the data is big endian + if (_endianness == Endianness.BigEndian) + { + data.Reverse(); + } + + _stream.Write(data); + } + + /// + /// Writes a signed 16-bit integer to the stream + /// + /// The integer to write + public void Write(short value) + { + InternalWrite(BitConverter.GetBytes(value)); + } + + /// + /// Writes a signed 32-bit integer to the stream + /// + /// The integer to write + public void Write(int value) + { + InternalWrite(BitConverter.GetBytes(value)); + } + + /// + /// Writes a signed 64-bit integer to the stream + /// + /// The integer to write + public void Write(long value) + { + InternalWrite(BitConverter.GetBytes(value)); + } + + /// + /// Writes an unsigned 16-bit integer to the stream + /// + /// The integer to write + public void Write(ushort value) + { + InternalWrite(BitConverter.GetBytes(value)); + } + + /// + /// Writes an unsigned 32-bit integer to the stream + /// + /// The integer to write + public void Write(uint value) + { + InternalWrite(BitConverter.GetBytes(value)); + } + + /// + /// Writes an unsigned 64-bit integer to the stream + /// + /// The integer to write + public void Write(ulong value) + { + InternalWrite(BitConverter.GetBytes(value)); + } + + /// + /// Writes a 32-bit floating point value to the stream + /// + /// The value to write + public void Write(float value) + { + InternalWrite(BitConverter.GetBytes(value)); + } + } +} diff --git a/Yura.Shared/IO/Endianness.cs b/Yura.Shared/IO/Endianness.cs new file mode 100644 index 0000000..bf180d3 --- /dev/null +++ b/Yura.Shared/IO/Endianness.cs @@ -0,0 +1,8 @@ +namespace Yura.Shared.IO +{ + public enum Endianness + { + LittleEndian, + BigEndian + } +} diff --git a/Yura.Shared/Util/FileList.cs b/Yura.Shared/Util/FileList.cs new file mode 100644 index 0000000..ca9178f --- /dev/null +++ b/Yura.Shared/Util/FileList.cs @@ -0,0 +1,116 @@ +using System.Collections.Generic; +using System.IO; +using Yura.Shared.Archive; + +namespace Yura.Shared.Util +{ + public class FileList + { + private readonly Dictionary _values; + private readonly string _path; + + public FileList(string path, bool load) + { + _values = []; + _path = path; + + if (load) + { + Load(HashAlgorithm.Crc32); + } + } + + /// + /// Loads the file list with the specified hashing algorithm + /// + /// The hashing algorithm + public void Load(HashAlgorithm algorithm) + { + foreach (var line in File.ReadLines(_path)) + { + var hash = CalculateHash(line, algorithm); + + _values.TryAdd(hash, line); + } + } + + /// + /// Resolves a hash + /// + /// The hash to resolve + /// The resolved hash or null + public string? Resolve(ulong hash) + { + if (_values.TryGetValue(hash, out var value)) + { + return value; + } + + return null; + } + + /// + /// Resolves the hashes of all records + /// + /// The records to resolve the hashes for + public void Resolve(List records) + { + foreach (var record in records) + { + record.Name = Resolve(record.Hash); + } + } + + /// + /// Calculates the hash of the value with the provided algorithm + /// + /// The value to calculate the hash of + /// The hashing algorithm + /// The computed result + public static ulong CalculateHash(string value, HashAlgorithm algorithm) + { + value = value.ToLower(); + + return algorithm == HashAlgorithm.Crc32 ? CalculateHash32(value) : CalculateHash64(value); + } + + private static uint CalculateHash32(string value) + { + uint hash = 0xffffffff; + + foreach (var c in value) + { + hash ^= (uint)c << 24; + + for (int i = 0; i < 8; i++) + { + if ((hash & 0x80000000) != 0) + hash = (hash << 1) ^ 0x4c11db7; + else + hash <<= 1; + } + } + + return ~hash; + } + + private static ulong CalculateHash64(string value) + { + ulong hash = 0xcbf29ce484222325; + + foreach (var c in value) + { + hash ^= c; + hash *= 0x100000001b3; + } + + return hash; + } + } + + public enum HashAlgorithm + { + Crc32, + Fnv1a + } +} diff --git a/Yura.Shared/Yura.Shared.csproj b/Yura.Shared/Yura.Shared.csproj new file mode 100644 index 0000000..0672ddb --- /dev/null +++ b/Yura.Shared/Yura.Shared.csproj @@ -0,0 +1,8 @@ + + + + net8.0 + enable + + + diff --git a/Yura.sln b/Yura.sln index 3945a6f..aa7d925 100644 --- a/Yura.sln +++ b/Yura.sln @@ -1,10 +1,12 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31911.196 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35506.116 d17.12 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yura", "Yura\Yura.csproj", "{7D73C719-E756-4019-9C62-B318F1B61776}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yura.Shared", "Yura.Shared\Yura.Shared.csproj", "{67F16917-AD1D-485A-B419-A3360766365D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {7D73C719-E756-4019-9C62-B318F1B61776}.Debug|Any CPU.Build.0 = Debug|Any CPU {7D73C719-E756-4019-9C62-B318F1B61776}.Release|Any CPU.ActiveCfg = Release|Any CPU {7D73C719-E756-4019-9C62-B318F1B61776}.Release|Any CPU.Build.0 = Release|Any CPU + {67F16917-AD1D-485A-B419-A3360766365D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67F16917-AD1D-485A-B419-A3360766365D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67F16917-AD1D-485A-B419-A3360766365D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67F16917-AD1D-485A-B419-A3360766365D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Yura/CommandLineOptions.cs b/Yura/CommandLineOptions.cs index 93ca787..4144eb4 100644 --- a/Yura/CommandLineOptions.cs +++ b/Yura/CommandLineOptions.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using Yura.Shared.IO; namespace Yura { @@ -52,11 +53,11 @@ public Game Game } } - public bool LittleEndian + public Endianness Endianness { get { - return GetOption("-endianness") != "big"; + return GetOption("-endianness") != "big" ? Endianness.LittleEndian : Endianness.BigEndian; } } diff --git a/Yura/IFileSettings.cs b/Yura/IFileSettings.cs index 0954813..e7a1177 100644 --- a/Yura/IFileSettings.cs +++ b/Yura/IFileSettings.cs @@ -1,9 +1,11 @@ -namespace Yura +using Yura.Shared.IO; + +namespace Yura { public interface IFileSettings { public Game Game { get; } - public bool LittleEndian { get; } + public Endianness Endianness { get; } public int Alignment { get; } public string FileList { get; } public TextureFormat TextureFormat { get; } diff --git a/Yura/MainWindow.xaml.cs b/Yura/MainWindow.xaml.cs index 77cc852..3f11b0c 100644 --- a/Yura/MainWindow.xaml.cs +++ b/Yura/MainWindow.xaml.cs @@ -11,8 +11,10 @@ using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media.Imaging; -using Yura.Archive; using Yura.Formats; +using Yura.Shared.Archive; +using Yura.Shared.IO; +using Yura.Shared.Util; namespace Yura { @@ -133,31 +135,38 @@ public void OpenBigfile(string bigfile, IFileSettings settings) { var list = (settings.FileList == null) ? null : new FileList(settings.FileList, settings.Game != Game.Tiger); - _littleEndian = settings.LittleEndian; + _littleEndian = settings.Endianness == Endianness.LittleEndian; _textureFormat = settings.TextureFormat; + _currentGame = settings.Game; + + // Open the archive with the following options + var options = new ArchiveOptions + { + Path = bigfile, + Endianness = settings.Endianness, + Alignment = settings.Alignment, + FileList = list + }; switch (settings.Game) { case Game.Legend: - _bigfile = new LegendArchive(bigfile, settings.Alignment, _littleEndian); + _bigfile = new LegendArchive(options); break; case Game.DeusEx: - _bigfile = new DeusExArchive(bigfile, _littleEndian); + _bigfile = new DeusExArchive(options); break; case Game.Defiance: - _bigfile = new DefianceArchive(bigfile, settings.TextureFormat, _littleEndian); + _bigfile = new DefianceArchive(options); break; case Game.Tiger: - _bigfile = new TigerArchive(bigfile, settings.TextureFormat, _littleEndian); + _bigfile = new TigerArchive(options); break; default: MessageBox.Show(this, Properties.Resources.NoGameSelectedMessage, Properties.Resources.NoGameSelected, MessageBoxButton.OK, MessageBoxImage.Exclamation); return; } - _currentGame = settings.Game; - _bigfile.FileList = list; - PathBox.Text = Path.GetFileName(bigfile); try @@ -170,6 +179,9 @@ public void OpenBigfile(string bigfile, IFileSettings settings) return; } + // Resolve all file names + list?.Resolve(_bigfile.Records); + UpdateTree(); } @@ -216,8 +228,8 @@ private void DirectoryView_SelectedItemChanged(object sender, RoutedPropertyChan public void SwitchDirectory(string path, string selectedFile = null) { - var files = _bigfile.GetFolder(path); - var bigfile = Path.GetFileName(_bigfile.Filename); + var files = _bigfile.GetFiles(path); + var bigfile = Path.GetFileName(_bigfile.Name); if (path[0] == '\\') { @@ -285,7 +297,7 @@ private void ShowFiles(List files, string selectedFile = null) private string GetSpecMask(ArchiveRecord record) { - var specMask = _bigfile.GetSpecialisationMask(record); + var specMask = (uint)record.Specialisation; switch ((SpecMaskView) Properties.Settings.Default.SpecMaskView) { @@ -546,7 +558,7 @@ private void CopyFileList_Click(object sender, RoutedEventArgs e) foreach (var file in _bigfile.Records) { - output.AppendLine($"{file.Hash:X8}\t{file.Size}\t{_bigfile.GetSpecialisationMask(file):X}\t{file.Name}"); + output.AppendLine($"{file.Hash:X8}\t{file.Size}\t{(uint)file.Specialisation:X}\t{file.Name}"); } Clipboard.SetText(output.ToString()); diff --git a/Yura/OpenDialog.xaml.cs b/Yura/OpenDialog.xaml.cs index c494d74..279bec3 100644 --- a/Yura/OpenDialog.xaml.cs +++ b/Yura/OpenDialog.xaml.cs @@ -3,6 +3,7 @@ using System.IO; using System.Windows; using System.Windows.Controls; +using Yura.Shared.IO; namespace Yura { @@ -41,11 +42,11 @@ private void OkButton_Click(object sender, RoutedEventArgs e) DialogResult = true; } - public bool LittleEndian + public Endianness Endianness { get { - return (EndiannessSelect.SelectedItem as ComboBoxItem).Content.ToString() == "Little-endian"; + return (Endianness)EndiannessSelect.SelectedIndex; } } diff --git a/Yura/Windows/SearchWindow.xaml.cs b/Yura/Windows/SearchWindow.xaml.cs index 041f9c4..64ae72d 100644 --- a/Yura/Windows/SearchWindow.xaml.cs +++ b/Yura/Windows/SearchWindow.xaml.cs @@ -2,11 +2,10 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using System.Threading.Tasks; using System.Windows; -using Yura.Archive; using Yura.Formats; +using Yura.Shared.Archive; namespace Yura { diff --git a/Yura/Yura.csproj b/Yura/Yura.csproj index 35af785..f5ca3d9 100644 --- a/Yura/Yura.csproj +++ b/Yura/Yura.csproj @@ -26,6 +26,10 @@ + + + + From 9071a5ea0610108f860248566e636a2ec6b47d37 Mon Sep 17 00:00:00 2001 From: TheIndra55 Date: Sat, 25 Jan 2025 01:48:51 +0100 Subject: [PATCH 2/9] Move all archive implementations to shared --- Yura.Shared/Archive/DefianceArchive.cs | 59 +++++++-- Yura.Shared/Archive/DeusExArchive.cs | 76 +++++++++++- Yura.Shared/Archive/LegendArchive.cs | 6 +- Yura.Shared/Archive/TigerArchive.cs | 3 +- Yura/App.xaml.cs | 2 +- Yura/Archive/ArchiveFile.cs | 94 -------------- Yura/Archive/ArchiveRecord.cs | 22 ---- Yura/Archive/DefianceArchive.cs | 117 ------------------ Yura/Archive/DeusExArchive.cs | 122 ------------------ Yura/Archive/FileList.cs | 105 ---------------- Yura/Archive/LegendArchive.cs | 157 ----------------------- Yura/Archive/TigerArchive.cs | 164 ------------------------- 12 files changed, 126 insertions(+), 801 deletions(-) delete mode 100644 Yura/Archive/ArchiveFile.cs delete mode 100644 Yura/Archive/ArchiveRecord.cs delete mode 100644 Yura/Archive/DefianceArchive.cs delete mode 100644 Yura/Archive/DeusExArchive.cs delete mode 100644 Yura/Archive/FileList.cs delete mode 100644 Yura/Archive/LegendArchive.cs delete mode 100644 Yura/Archive/TigerArchive.cs diff --git a/Yura.Shared/Archive/DefianceArchive.cs b/Yura.Shared/Archive/DefianceArchive.cs index 22957b3..1e051f8 100644 --- a/Yura.Shared/Archive/DefianceArchive.cs +++ b/Yura.Shared/Archive/DefianceArchive.cs @@ -1,8 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.IO; +using Yura.Shared.IO; namespace Yura.Shared.Archive { @@ -14,12 +11,60 @@ public DefianceArchive(ArchiveOptions options) : base(options) public override void Open() { - throw new NotImplementedException(); + var stream = File.OpenRead(Options.Path); + var reader = new DataReader(stream, Options.Endianness); + + // Read the number of files + var numFiles = reader.ReadUInt16(); + + stream.Position += 2; + + // Read the file name hashes + var hashes = new uint[numFiles]; + + for (var i = 0; i < numFiles; i++) + { + hashes[i] = reader.ReadUInt32(); + } + + // Read the file records + for (var i = 0; i < numFiles; i++) + { + var record = new Record + { + Hash = hashes[i], + + Size = reader.ReadUInt32(), + Offset = reader.ReadUInt32() + }; + + reader.Position += 4; + + Records.Add(record); + } + + stream.Close(); } public override byte[] Read(ArchiveRecord record) { - throw new NotImplementedException(); + var file = record as Record; + + // Read the file + var stream = File.OpenRead(Options.Path); + var data = new byte[file.Size]; + + stream.Position = file.Offset; + stream.ReadExactly(data); + + stream.Close(); + + return data; + } + + private class Record : ArchiveRecord + { + public uint Offset { get; set; } } } } diff --git a/Yura.Shared/Archive/DeusExArchive.cs b/Yura.Shared/Archive/DeusExArchive.cs index 556ca28..5f87763 100644 --- a/Yura.Shared/Archive/DeusExArchive.cs +++ b/Yura.Shared/Archive/DeusExArchive.cs @@ -1,25 +1,89 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.IO; +using Yura.Shared.IO; namespace Yura.Shared.Archive { public class DeusExArchive : ArchiveFile { + private uint _alignment; + public DeusExArchive(ArchiveOptions options) : base(options) { } public override void Open() { - throw new NotImplementedException(); + var stream = File.OpenRead(Options.Path); + var reader = new DataReader(stream, Options.Endianness); + + // Read the header + _alignment = reader.ReadUInt32(); + + // Skip over the config name + reader.Position += 64; + + // Read the number of files + var numRecords = reader.ReadUInt32(); + + if (numRecords > 1_000_000) + { + throw new ArgumentException("Bigfile has more than a million files, did you select the right endianness?"); + } + + // Read the file name hashes + var hashes = new uint[numRecords]; + + for (var i = 0; i < numRecords; i++) + { + hashes[i] = reader.ReadUInt32(); + } + + // Read the file records + for (var i = 0; i < numRecords; i++) + { + var record = new Record + { + Hash = hashes[i], + + Size = reader.ReadUInt32(), + Offset = reader.ReadUInt32(), + Specialisation = reader.ReadUInt32(), + CompressedSize = reader.ReadUInt32(), + }; + + Records.Add(record); + } + + stream.Close(); } public override byte[] Read(ArchiveRecord record) { - throw new NotImplementedException(); + var file = record as Record; + + // Calculate the location of the file + var offset = (long)file.Offset << 11; + var part = offset / _alignment; + + var path = GetFilePart((int)part); + + // Read the file + var stream = File.OpenRead(path); + var data = new byte[file.Size]; + + stream.Position = offset % _alignment; + stream.ReadExactly(data); + + stream.Close(); + + return data; + } + + private class Record : ArchiveRecord + { + public uint Offset { get; set; } + public uint CompressedSize { get; set; } } } } diff --git a/Yura.Shared/Archive/LegendArchive.cs b/Yura.Shared/Archive/LegendArchive.cs index b4ffe02..29eb798 100644 --- a/Yura.Shared/Archive/LegendArchive.cs +++ b/Yura.Shared/Archive/LegendArchive.cs @@ -1,9 +1,5 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Yura.Shared.IO; namespace Yura.Shared.Archive @@ -27,7 +23,7 @@ public override void Open() throw new ArgumentException("Bigfile has more than a million files, did you select the right endianness?"); } - // Read the file hashes + // Read the file name hashes var hashes = new uint[numRecords]; for (var i = 0; i < numRecords; i++) diff --git a/Yura.Shared/Archive/TigerArchive.cs b/Yura.Shared/Archive/TigerArchive.cs index 6b9b15b..491949c 100644 --- a/Yura.Shared/Archive/TigerArchive.cs +++ b/Yura.Shared/Archive/TigerArchive.cs @@ -38,7 +38,8 @@ public override void Open() var numArchives = reader.ReadUInt32(); var numRecords = reader.ReadUInt32(); - reader.Position += 4; + // Skip 4 bytes, or 8 in version 5 or later + reader.Position += version < 5 ? 4 : 8; // Skip over the config name reader.Position += 32; diff --git a/Yura/App.xaml.cs b/Yura/App.xaml.cs index ef2f3ae..da0ed0f 100644 --- a/Yura/App.xaml.cs +++ b/Yura/App.xaml.cs @@ -29,7 +29,7 @@ public App() // add information about open bigfile if (_window != null && _window.Bigfile != null) { - sentryEvent.SetExtra("bigfile", _window.Bigfile.Filename); + sentryEvent.SetExtra("bigfile", _window.Bigfile.Name); sentryEvent.SetExtra("game", _window.Game); } diff --git a/Yura/Archive/ArchiveFile.cs b/Yura/Archive/ArchiveFile.cs deleted file mode 100644 index 128eb2d..0000000 --- a/Yura/Archive/ArchiveFile.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace Yura.Archive -{ - public abstract class ArchiveFile - { - public ArchiveFile(string path) - { - Filename = path; - } - - /// - /// Gets the records in the archive - /// - public abstract IReadOnlyList Records { get; } - - /// - /// Opens the archive - /// - public abstract void Open(); - - /// - /// Reads a file from the archive and returns the content - /// - /// The file record - /// The content of the file - public abstract byte[] Read(ArchiveRecord record); - - /// - /// Gets the specialisation mask of a record - /// - /// The record to get the specialisation of - /// The specialisation mask - public abstract uint GetSpecialisationMask(ArchiveRecord record); - - /// - /// Gets or sets the file list - /// - public FileList FileList { get; set; } - - /// - /// Gets the underlying file - /// - public string Filename { get; private set; } - - /// - /// Gets all records in a folder - /// - /// The folder path - /// All records in the folder - public List GetFolder(string path) - { - // if root return all unknown files too - if (path == "\\") - { - return Records.Where( - x => x.Name == null || x.Name.Split("\\", StringSplitOptions.RemoveEmptyEntries).Length == 1).ToList(); - } - - var hierarchy = path.Split("\\", StringSplitOptions.RemoveEmptyEntries); - - return Records.Where(x => - { - if (x.Name == null) return false; - - var split = x.Name.Split("\\", StringSplitOptions.RemoveEmptyEntries); - - if (split.Length - 1 != hierarchy.Length) return false; - - // compare both paths - for (int i = 0; i < split.Length - 1; i++) - { - if (split[i] != hierarchy[i]) - { - return false; - } - } - - return true; - }).ToList(); - } - - protected string FormatBigfile(string path, int part) - { - var name = Path.GetFileNameWithoutExtension(path); - var directory = Path.GetDirectoryName(path); - - return Path.Combine(directory, name + "." + part.ToString("000")); - } - } -} diff --git a/Yura/Archive/ArchiveRecord.cs b/Yura/Archive/ArchiveRecord.cs deleted file mode 100644 index 0805d61..0000000 --- a/Yura/Archive/ArchiveRecord.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Yura.Archive -{ - public class ArchiveRecord - { - /// - /// Gets the filename hash - /// - public ulong Hash { get; set; } - - /// - /// Gets the name of the file - /// -#nullable enable - public string? Name { get; set; } -#nullable restore - - /// - /// Gets the size of the file - /// - public uint Size { get; set; } - } -} diff --git a/Yura/Archive/DefianceArchive.cs b/Yura/Archive/DefianceArchive.cs deleted file mode 100644 index ebfcd8d..0000000 --- a/Yura/Archive/DefianceArchive.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using StreamReader = Yura.IO.StreamReader; - -namespace Yura.Archive -{ - class DefianceArchive : ArchiveFile - { - private const int XboxAlignment = 0xFA00000; - - private string _file; - private TextureFormat _platform; - private bool _littleEndian; - - private List _files; - - public DefianceArchive(string path, TextureFormat platform, bool littleEndian = true) - : base(path) - { - _file = path; - _platform = platform; - _littleEndian = littleEndian; - - _files = new List(); - } - - public override void Open() - { - var stream = File.OpenRead(_file); - var reader = new StreamReader(stream, _littleEndian); - - // extract number of files - var numFiles = reader.ReadUInt16(); - reader.BaseStream.Position += 2; - - var hashes = new uint[numFiles]; - - // read all filename hashes - for (var i = 0; i < numFiles; i++) - { - hashes[i] = reader.ReadUInt32(); - } - - // read all records - for (var i = 0; i < numFiles; i++) - { - var file = new DefianceRecord - { - Hash = hashes[i] - }; - - file.Size = reader.ReadUInt32(); - file.Offset = reader.ReadUInt32(); - - // check if hash exist in file list - if (FileList != null && FileList.Files.TryGetValue(file.Hash, out string name)) - { - file.Name = name; - } - - _files.Add(file); - - reader.BaseStream.Position += 4; - } - - stream.Close(); - } - - public override IReadOnlyList Records - { - get - { - return _files; - } - } - - public override byte[] Read(ArchiveRecord record) - { - var file = record as DefianceRecord; - - var filename = _file; - var offset = file.Offset; - - if (_platform == TextureFormat.Xbox) - { - // xbox bigfiles in this game are spread over multiple physical files - // calculate the bigfile this file is in - var bigfile = offset / XboxAlignment; - offset = offset % XboxAlignment; - - var name = Path.GetFileNameWithoutExtension(_file); - filename = Path.GetDirectoryName(_file) + Path.DirectorySeparatorChar + name + "." + bigfile.ToString("000"); - } - - var stream = File.OpenRead(filename); - var bytes = new byte[file.Size]; - - stream.Position = offset; - stream.Read(bytes, 0, (int)file.Size); - - stream.Close(); - - return bytes; - } - - public override uint GetSpecialisationMask(ArchiveRecord record) - { - // defiance does not use specialisation - return 0; - } - } - - class DefianceRecord : ArchiveRecord - { - public uint Offset { get; set; } - } -} diff --git a/Yura/Archive/DeusExArchive.cs b/Yura/Archive/DeusExArchive.cs deleted file mode 100644 index 8196343..0000000 --- a/Yura/Archive/DeusExArchive.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using StreamReader = Yura.IO.StreamReader; - -namespace Yura.Archive -{ - class DeusExArchive : ArchiveFile - { - private string _file; - private uint _alignment; - private bool _littleEndian; - - private List _files; - - public DeusExArchive(string path, bool littleEndian = true) - : base(path) - { - _file = path; - _littleEndian = littleEndian; - - _files = new List(); - } - - public override void Open() - { - var stream = File.OpenRead(_file); - var reader = new StreamReader(stream, _littleEndian); - - _alignment = reader.ReadUInt32(); - - // skip over config name - reader.BaseStream.Position += 64; - - // same as legend - var numRecords = reader.ReadUInt32(); - - if (numRecords > 1_000_000) - { - throw new ArgumentException("Bigfile has more than a million files, did you select the wrong endianness?"); - } - - var hashes = new uint[numRecords]; - - // read all filename hashes - for (var i = 0; i < numRecords; i++) - { - hashes[i] = reader.ReadUInt32(); - } - - // read all records - for (var i = 0; i < numRecords; i++) - { - var file = new DeusExRecord() - { - Hash = hashes[i] - }; - - file.Size = reader.ReadUInt32(); - file.Offset = reader.ReadUInt32(); - file.Specialisation = reader.ReadUInt32(); - - // check if hash exist in file list - if (FileList != null && FileList.Files.TryGetValue(file.Hash, out string name)) - { - file.Name = name; - } - - _files.Add(file); - - reader.BaseStream.Position += 4; - } - - stream.Close(); - } - - public override IReadOnlyList Records - { - get - { - return _files; - } - } - - public override byte[] Read(ArchiveRecord record) - { - var file = record as DeusExRecord; - - // calculate in which bigfile the data is - var offset = (long)file.Offset << 11; - var bigfile = offset / _alignment; - - // get the right bigfile filename - var name = Path.GetFileNameWithoutExtension(_file); - var filename = Path.GetDirectoryName(_file) + Path.DirectorySeparatorChar + name + "." + bigfile.ToString("000"); - - // read data - var stream = File.OpenRead(filename); - var bytes = new byte[file.Size]; - - stream.Position = offset % _alignment; - stream.Read(bytes, 0, (int)file.Size); - - stream.Close(); - - return bytes; - } - - public override uint GetSpecialisationMask(ArchiveRecord record) - { - var file = record as DeusExRecord; - - return file.Specialisation; - } - } - - class DeusExRecord : ArchiveRecord - { - public uint Offset { get; set; } - public uint Specialisation { get; set; } - } -} diff --git a/Yura/Archive/FileList.cs b/Yura/Archive/FileList.cs deleted file mode 100644 index 7079b78..0000000 --- a/Yura/Archive/FileList.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; - -namespace Yura.Archive -{ - public class FileList - { - public Dictionary Files { get; private set; } - public string Path { get; private set; } - - /// - /// Constructs a new file list with the path as input file - /// - /// The input file - /// Whether to already load the file, can be false if hashing algorithm is not yet known - public FileList([NotNull] string path, bool load) - { - if (!File.Exists(path)) - { - throw new FileNotFoundException("File list not found", path); - } - - Path = path; - Files = new Dictionary(); - - if (load) - { - Load(HashingAlgorithm.Crc32); - } - } - - /// - /// Loads the file list from disk - /// - public void Load(HashingAlgorithm algorithm) - { - foreach (var line in File.ReadAllLines(Path)) - { - ulong hash = 0; - - if (algorithm == HashingAlgorithm.Crc32) - { - hash = CalculateHash32(line); - } - else if (algorithm == HashingAlgorithm.Fnv1a) - { - hash = CalculateHash64(line); - } - - // either a hash collision or double file entry - if (!Files.ContainsKey(hash)) - { - Files.Add(hash, line); - } - } - } - - // CRC32 - public uint CalculateHash32(string name) - { - // all paths are lowercase - name = name.ToLower(); - - uint hash = 0xFFFFFFFF; - - foreach(var rune in name) - { - hash ^= (uint)rune << 24; - - for(int i = 0; i < 8; i++) - { - if ((hash & 0x80000000) != 0) - hash = (hash << 1) ^ 0x4C11DB7; - else - hash <<= 1; - } - } - - return ~hash; - } - - // FNV1A - public ulong CalculateHash64(string name) - { - name = name.ToLower(); - - ulong hash = 0xcbf29ce484222325; - - foreach (var rune in name) - { - hash = hash ^ rune; - hash *= 0x100000001b3; - } - - return hash; - } - } - - public enum HashingAlgorithm - { - Crc32, - Fnv1a - } -} diff --git a/Yura/Archive/LegendArchive.cs b/Yura/Archive/LegendArchive.cs deleted file mode 100644 index 1ba64bf..0000000 --- a/Yura/Archive/LegendArchive.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using StreamReader = Yura.IO.StreamReader; - -namespace Yura.Archive -{ - class LegendArchive : ArchiveFile - { - private string _file; - private int _alignment; - private bool _littleEndian; - - private List _files; - - public LegendArchive(string path, int alignment, bool littleEndian = true) - : base(path) - { - _file = path; - _littleEndian = littleEndian; - _alignment = alignment; - - _files = new List(); - } - - public override void Open() - { - var stream = File.OpenRead(_file); - var reader = new StreamReader(stream, _littleEndian); - - // extract number of files - var numRecords = reader.ReadUInt32(); - - if (numRecords > 1_000_000) - { - throw new ArgumentException("Bigfile has more than a million files, did you select the wrong endianness?"); - } - - var hashes = new uint[numRecords]; - - // read all filename hashes - for (var i = 0; i < numRecords; i++) - { - hashes[i] = reader.ReadUInt32(); - } - - // read all records - for (var i = 0; i < numRecords; i++) - { - var file = new LegendRecord() - { - Hash = hashes[i] - }; - - file.Size = reader.ReadUInt32(); - file.Offset = reader.ReadUInt32(); - file.Specialisation = reader.ReadUInt32(); - file.CompressedSize = reader.ReadUInt32(); - - // check if hash exist in file list - if (FileList != null && FileList.Files.TryGetValue(file.Hash, out string name)) - { - file.Name = name; - } - - _files.Add(file); - } - - stream.Close(); - } - - public override IReadOnlyList Records - { - get - { - return _files; - } - } - - public override byte[] Read(ArchiveRecord record) - { - var file = record as LegendRecord; - - var offset = (long)file.Offset << 11; - string path = null; - - // check whether the bigfile is split over multiple files - if (_file.EndsWith(".000")) - { - // calculate which bigfile the file is in, and get the file offset - var bigfile = (int)(offset / _alignment); - offset = offset % _alignment; - - path = FormatBigfile(_file, bigfile); - } - else - { - path = _file; - } - - // read the file - byte[] data; - if (file.CompressedSize == 0) - { - data = new byte[file.Size]; - Read(path, offset, file.Size, data); - - return data; - } - else - { - data = new byte[file.Size]; - var compressedData = new byte[file.CompressedSize]; - - Read(path, offset, file.CompressedSize, compressedData); - Decompress(compressedData, data); - } - - return data; - } - - private void Read(string path, long offset, uint size, byte[] data) - { - var stream = File.OpenRead(path); - - stream.Position = offset; - stream.Read(data, 0, (int)size); - - stream.Close(); - } - - private void Decompress(byte[] compressedData, byte[] data) - { - var stream = new MemoryStream(compressedData); - var decompressed = new MemoryStream(data); - - var zlib = new ZLibStream(stream, CompressionMode.Decompress); - - zlib.CopyTo(decompressed); - } - - public override uint GetSpecialisationMask(ArchiveRecord record) - { - var file = record as LegendRecord; - - return file.Specialisation; - } - } - - class LegendRecord : ArchiveRecord - { - public uint Offset { get; set; } - public uint Specialisation { get; set; } - public uint CompressedSize { get; set; } - } -} diff --git a/Yura/Archive/TigerArchive.cs b/Yura/Archive/TigerArchive.cs deleted file mode 100644 index ef1a259..0000000 --- a/Yura/Archive/TigerArchive.cs +++ /dev/null @@ -1,164 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using StreamReader = Yura.IO.StreamReader; - -namespace Yura.Archive -{ - class TigerArchive : ArchiveFile - { - private string _file; - private TextureFormat _platform; - private bool _littleEndian; - - private List _files; - - public TigerArchive(string path, TextureFormat platform, bool littleEndian = true) - : base(path) - { - _file = path; - _platform = platform; - _littleEndian = littleEndian; - - _files = new List(); - } - - public override void Open() - { - var stream = File.OpenRead(_file); - var reader = new StreamReader(stream, _littleEndian); - - var magic = reader.ReadUInt32(); - - if (magic != 0x53464154 && magic != 0x54414653) - { - throw new Exception("File is not a tiger archive file"); - } - - var version = reader.ReadUInt32(); - - // check if version 3, 4 or 5 - if (version < 3 || version > 5) - { - throw new Exception($"Tiger archive version {version} is not supported"); - } - - // load file list - FileList?.Load(version < 5 ? HashingAlgorithm.Crc32 : HashingAlgorithm.Fnv1a); - - var numArchives = reader.ReadUInt32(); - var numRecords = reader.ReadUInt32(); - - // skip 4 bytes, or 8 in version 5 or later - // also dont skip on PS4 since some idiot at CD or Eidos decided this field does not exist on PS4 - reader.BaseStream.Position += version >= 5 && _platform != TextureFormat.Orbis ? 8 : 4; - - // skip over config name - reader.BaseStream.Position += 32; - - // read all records - for (var i = 0; i < numRecords; i++) - { - var file = new TigerRecord(); - - if (version < 5) - { - file.Hash = reader.ReadUInt32(); - file.Specialisation = reader.ReadUInt32(); - file.Size = reader.ReadUInt32(); - } - else - { - file.Hash = reader.ReadUInt64(); - file.Specialisation = reader.ReadUInt64(); - file.Size = reader.ReadUInt32(); - } - - // 2013 - if (version == 3) - { - var packedOffset = reader.ReadUInt32(); - - file.Index = packedOffset & 0xF; - file.Offset = packedOffset & 0xFFFFF800; - } - // rise/tr2 - else if (version == 4) - { - reader.BaseStream.Position += 4; // padding - - var packedOffset = reader.ReadUInt64(); - - // not sure about this, need to check LOWORD/HIDWORD - file.Index = packedOffset & 0xffff; - file.Offset = (packedOffset >> 32) & 0xFFFFFFFF; - } - // shadow - else if (version == 5) - { - reader.BaseStream.Position += 4; - - var packedOffset = reader.ReadUInt64(); - file.Index = packedOffset & 0xffff; - file.Offset = (packedOffset >> 32) & 0xFFFFFFFF; - } - - // check if hash exist in file list - if (FileList != null && FileList.Files.TryGetValue(file.Hash, out string name)) - { - file.Name = name; - } - - _files.Add(file); - } - - stream.Close(); - } - - public override IReadOnlyList Records - { - get - { - return _files; - } - } - - public override byte[] Read(ArchiveRecord record) - { - var file = record as TigerRecord; - - // get right bigfile - var name = Path.GetFileNameWithoutExtension(_file); - name = name.Substring(0, name.Length - 4); - - var filename = Path.GetDirectoryName(_file) + Path.DirectorySeparatorChar + name + "." + file.Index.ToString("000") + ".tiger"; - - // read the file - var stream = File.OpenRead(filename); - var bytes = new byte[file.Size]; - - stream.Position = (long)file.Offset; - stream.Read(bytes, 0, (int)file.Size); - - stream.Close(); - - return bytes; - } - - public override uint GetSpecialisationMask(ArchiveRecord record) - { - var file = record as TigerRecord; - - // TODO this will lose 32 bits in Shadow - return (uint)file.Specialisation; - } - } - - class TigerRecord : ArchiveRecord - { - public ulong Index { get; set; } - public ulong Offset { get; set; } - - public ulong Specialisation { get; set; } - } -} From f098fc137a0c52d4e679ebf3ec6c027e990821a0 Mon Sep 17 00:00:00 2001 From: TheIndra55 Date: Sat, 25 Jan 2025 02:03:34 +0100 Subject: [PATCH 3/9] Fix viewing unknown files --- Yura/MainWindow.xaml.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Yura/MainWindow.xaml.cs b/Yura/MainWindow.xaml.cs index 3f11b0c..9723b8d 100644 --- a/Yura/MainWindow.xaml.cs +++ b/Yura/MainWindow.xaml.cs @@ -223,15 +223,15 @@ private void DirectoryView_SelectedItemChanged(object sender, RoutedPropertyChan var folder = e.NewValue as DirectoryViewFolder; // if path is null then get root - SwitchDirectory(folder.Path == null ? "\\" : folder.Path + "\\"); + SwitchDirectory(folder.Path); } public void SwitchDirectory(string path, string selectedFile = null) { - var files = _bigfile.GetFiles(path); + var files = GetFiles(path); var bigfile = Path.GetFileName(_bigfile.Name); - if (path[0] == '\\') + if (path == null) { PathBox.Text = bigfile; } @@ -243,6 +243,16 @@ public void SwitchDirectory(string path, string selectedFile = null) ShowFiles(files, selectedFile); } + private List GetFiles(string path) + { + if (path == null) + { + return _bigfile.Records.Where(x => x.Name == null).ToList(); + } + + return _bigfile.GetFiles(path); + } + private void ShowFiles(List files, string selectedFile = null) { var filesview = new List(); From 4f101026e504f29e20865d2113ac2a0dd67bd20d Mon Sep 17 00:00:00 2001 From: TheIndra55 Date: Sat, 25 Jan 2025 02:37:25 +0100 Subject: [PATCH 4/9] Replace binary reader with shared implementation --- Yura.Shared/IO/DataReader.cs | 47 +++++++++++- Yura.Shared/IO/DataWriter.cs | 2 +- Yura/Formats/CMPRTexture.cs | 4 +- Yura/Formats/DrmFile.cs | 9 +-- Yura/Formats/LocaleFile.cs | 9 +-- Yura/IO/StreamReader.cs | 111 ----------------------------- Yura/MainWindow.xaml.cs | 10 +-- Yura/Windows/LocaleViewer.xaml.cs | 5 +- Yura/Windows/SearchWindow.xaml.cs | 5 +- Yura/Windows/TextureViewer.xaml.cs | 8 +-- 10 files changed, 72 insertions(+), 138 deletions(-) delete mode 100644 Yura/IO/StreamReader.cs diff --git a/Yura.Shared/IO/DataReader.cs b/Yura.Shared/IO/DataReader.cs index f7a26fc..37e9ea4 100644 --- a/Yura.Shared/IO/DataReader.cs +++ b/Yura.Shared/IO/DataReader.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; namespace Yura.Shared.IO { @@ -13,7 +15,7 @@ public class DataReader private readonly Endianness _endianness; /// - /// Initializes a new data reader with the the specified stream and endianness + /// Initializes a new data reader with the specified stream and endianness /// /// The input stream /// The endianness of the data @@ -25,6 +27,15 @@ public DataReader(Stream stream, Endianness endianness) _endianness = endianness; } + /// + /// Initializes a new data reader from a buffer + /// + /// The input buffer + /// The endianness of the data + public DataReader(byte[] data, Endianness endianness) : this(new MemoryStream(data), endianness) + { + } + /// /// Gets the underlying stream /// @@ -62,6 +73,22 @@ private ReadOnlySpan InternalRead(Span data) return data; } + /// + /// Reads a byte from the stream + /// + /// The read byte + public byte ReadByte() + { + var value = _stream.ReadByte(); + + if (value == -1) + { + throw new EndOfStreamException(); + } + + return (byte)value; + } + /// /// Reads a signed 16-bit integer from the stream /// @@ -124,5 +151,23 @@ public float ReadSingle() { return BitConverter.ToSingle(InternalRead(stackalloc byte[4])); } + + /// + /// Reads a null-terminated string from the stream + /// + /// The encoding of the string + /// The read string + public string ReadString(Encoding encoding) + { + List data = []; + + byte value; + while ((value = ReadByte()) != 0) + { + data.Add(value); + } + + return encoding.GetString(data.ToArray()); + } } } diff --git a/Yura.Shared/IO/DataWriter.cs b/Yura.Shared/IO/DataWriter.cs index 9c24b0c..5db7264 100644 --- a/Yura.Shared/IO/DataWriter.cs +++ b/Yura.Shared/IO/DataWriter.cs @@ -13,7 +13,7 @@ public class DataWriter private readonly Endianness _endianness; /// - /// Initializes a new data writer with the the specified stream and endianness + /// Initializes a new data writer with the specified stream and endianness /// /// The output stream /// The endianness of the data diff --git a/Yura/Formats/CMPRTexture.cs b/Yura/Formats/CMPRTexture.cs index f4040d1..e5317a7 100644 --- a/Yura/Formats/CMPRTexture.cs +++ b/Yura/Formats/CMPRTexture.cs @@ -2,7 +2,7 @@ using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; -using Yura.IO; +using Yura.Shared.IO; namespace Yura.Formats { @@ -29,7 +29,7 @@ protected override Freezable CreateInstanceCore() // most of the code below for CMPR algo is copied from there public override void CopyPixels(Int32Rect sourceRect, Array pixels, int stride, int _) { - var reader = new StreamReader(_buffer, false); + var reader = new DataReader(_buffer, Endianness.LittleEndian); for (int y = 0; y < _height; y += 8) { diff --git a/Yura/Formats/DrmFile.cs b/Yura/Formats/DrmFile.cs index 511186f..f3ac03a 100644 --- a/Yura/Formats/DrmFile.cs +++ b/Yura/Formats/DrmFile.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Yura.IO; +using Yura.Shared.IO; namespace Yura.Formats { @@ -18,11 +15,11 @@ class DrmFile /// The data of the file /// Whether the file should be read as little endian /// Whether to relocate relocations, currently unsupported - public DrmFile(byte[] data, bool litteEndian, bool relocate = false) + public DrmFile(byte[] data, Endianness endianness, bool relocate = false) { _sections = new List
(); - var reader = new StreamReader(data, litteEndian); + var reader = new DataReader(data, endianness); // read file version if (reader.ReadInt32() != 14) diff --git a/Yura/Formats/LocaleFile.cs b/Yura/Formats/LocaleFile.cs index e810628..7359292 100644 --- a/Yura/Formats/LocaleFile.cs +++ b/Yura/Formats/LocaleFile.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; -using StreamReader = Yura.IO.StreamReader; +using System.Text; +using Yura.Shared.IO; namespace Yura.Formats { @@ -8,9 +9,9 @@ class LocaleFile public Language Language { get; private set; } public List Entries { get; private set; } - public LocaleFile(byte[] data, bool litteEndian) + public LocaleFile(byte[] data, Endianness endianness) { - var reader = new StreamReader(data, litteEndian); + var reader = new DataReader(data, endianness); Entries = new List(); Language = (Language)reader.ReadUInt32(); @@ -28,7 +29,7 @@ public LocaleFile(byte[] data, bool litteEndian) reader.BaseStream.Position = offset; // read the string - var str = reader.ReadString(); + var str = reader.ReadString(Encoding.UTF8); Entries.Add(str); reader.BaseStream.Position = cursor; diff --git a/Yura/IO/StreamReader.cs b/Yura/IO/StreamReader.cs deleted file mode 100644 index e1bcf43..0000000 --- a/Yura/IO/StreamReader.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace Yura.IO -{ - /// - /// BinaryReader-like class with support for different endianness - /// - class StreamReader - { - private Stream _stream; - private bool _littleEndian; - - /// - /// Creates a new StreamReader from a stream - /// - /// The stream to read from - /// Whether the data should be read little endian - public StreamReader(Stream stream, bool littleEndian = true) - { - _stream = stream; - _littleEndian = littleEndian; - } - - /// - /// Creates a new StreamReader from a buffer - /// - /// The buffer to read from - /// Whether the data should be read little endian - public StreamReader(byte[] buffer, bool littleEndian = true) - { - _stream = new MemoryStream(buffer); - _littleEndian = littleEndian; - } - - private byte[] Read(int bytes) - { - var data = new byte[bytes]; - _stream.Read(data, 0, bytes); - - // i guess this kinda assumes the platform is little endian - if (!_littleEndian) - { - Array.Reverse(data); - } - - return data; - } - - public byte ReadByte() - { - return (byte)_stream.ReadByte(); - } - - public short ReadInt16() - { - var data = Read(2); - - return BitConverter.ToInt16(data); - } - - public ushort ReadUInt16() - { - var data = Read(2); - - return BitConverter.ToUInt16(data); - } - - public int ReadInt32() - { - var data = Read(4); - - return BitConverter.ToInt32(data); - } - - public uint ReadUInt32() - { - var data = Read(4); - - return BitConverter.ToUInt32(data); - } - - public ulong ReadUInt64() - { - var data = Read(8); - - return BitConverter.ToUInt64(data); - } - - /// - /// Reads a null-terminated string from the stream - /// - /// The readed string - public string ReadString() - { - var chars = new List(); - - while(_stream.ReadByte() != 0) - { - _stream.Position--; - chars.Add((byte)_stream.ReadByte()); - } - - return Encoding.UTF8.GetString(chars.ToArray()); - } - - public Stream BaseStream => _stream; - } -} diff --git a/Yura/MainWindow.xaml.cs b/Yura/MainWindow.xaml.cs index 9723b8d..ab376f0 100644 --- a/Yura/MainWindow.xaml.cs +++ b/Yura/MainWindow.xaml.cs @@ -36,7 +36,7 @@ public partial class MainWindow : Window // dictionary of ext,file type for names and icons private Dictionary> _fileTypes; - private bool _littleEndian; + private Endianness _endianness; private TextureFormat _textureFormat; private Game _currentGame; @@ -135,7 +135,7 @@ public void OpenBigfile(string bigfile, IFileSettings settings) { var list = (settings.FileList == null) ? null : new FileList(settings.FileList, settings.Game != Game.Tiger); - _littleEndian = settings.Endianness == Endianness.LittleEndian; + _endianness = settings.Endianness; _textureFormat = settings.TextureFormat; _currentGame = settings.Game; @@ -385,7 +385,7 @@ private void FileView_MouseDoubleClick(object sender, MouseButtonEventArgs e) { var viewer = new TextureViewer(); viewer.TextureFormat = _textureFormat; - viewer.LittleEndian = _littleEndian; + viewer.Endianness = _endianness; viewer.Texture = file; viewer.Title = item.Name; @@ -398,7 +398,7 @@ private void FileView_MouseDoubleClick(object sender, MouseButtonEventArgs e) if (item.Name == "locals.bin") { var viewer = new LocaleViewer(); - viewer.LittleEndian = _littleEndian; + viewer.Endianness = _endianness; viewer.Data = file; viewer.Show(); @@ -552,7 +552,7 @@ private void SearchCommand_Executed(object sender, ExecutedRoutedEventArgs e) { var searchWindow = new SearchWindow() { Owner = this }; searchWindow.Archive = _bigfile; - searchWindow.LittleEndian = _littleEndian; + searchWindow.Endianness = _endianness; searchWindow.Show(); } diff --git a/Yura/Windows/LocaleViewer.xaml.cs b/Yura/Windows/LocaleViewer.xaml.cs index 7d0a753..72b3be4 100644 --- a/Yura/Windows/LocaleViewer.xaml.cs +++ b/Yura/Windows/LocaleViewer.xaml.cs @@ -4,6 +4,7 @@ using System.Windows; using System.Windows.Input; using Yura.Formats; +using Yura.Shared.IO; namespace Yura { @@ -19,13 +20,13 @@ public LocaleViewer() InitializeComponent(); } - public bool LittleEndian { get; set; } + public Endianness Endianness { get; set; } public byte[] Data { set { - _locale = new LocaleFile(value, LittleEndian); + _locale = new LocaleFile(value, Endianness); // append current language to title Title += " - " + _locale.Language.ToString(); diff --git a/Yura/Windows/SearchWindow.xaml.cs b/Yura/Windows/SearchWindow.xaml.cs index 64ae72d..d066846 100644 --- a/Yura/Windows/SearchWindow.xaml.cs +++ b/Yura/Windows/SearchWindow.xaml.cs @@ -6,6 +6,7 @@ using System.Windows; using Yura.Formats; using Yura.Shared.Archive; +using Yura.Shared.IO; namespace Yura { @@ -16,7 +17,7 @@ public partial class SearchWindow : Window { public ArchiveFile Archive { get; set; } - public bool LittleEndian { get; set; } + public Endianness Endianness { get; set; } public SearchWindow() { @@ -98,7 +99,7 @@ private void SearchTask(IEnumerable files, int id, SectionType ty } // read drm file - var drm = new DrmFile(content, LittleEndian); + var drm = new DrmFile(content, Endianness); // check sections foreach (var section in drm.Sections) diff --git a/Yura/Windows/TextureViewer.xaml.cs b/Yura/Windows/TextureViewer.xaml.cs index db4011e..f311825 100644 --- a/Yura/Windows/TextureViewer.xaml.cs +++ b/Yura/Windows/TextureViewer.xaml.cs @@ -2,7 +2,7 @@ using System.Windows.Media; using System.Windows.Media.Imaging; using Yura.Formats; -using StreamReader = Yura.IO.StreamReader; +using Yura.Shared.IO; namespace Yura { @@ -16,7 +16,7 @@ public TextureViewer() InitializeComponent(); } - private void CreateImage(int width, int height, StreamReader reader) + private void CreateImage(int width, int height, DataReader reader) { int stride = width * 4 + (width % 4); @@ -43,7 +43,7 @@ private void CreateImage(int width, int height, StreamReader reader) TextureImage.Source = image; } - public bool LittleEndian { get; set; } + public Endianness Endianness { get; set; } public TextureFormat TextureFormat { get; set; } @@ -51,7 +51,7 @@ public byte[] Texture { set { - var reader = new StreamReader(value, LittleEndian); + var reader = new DataReader(value, Endianness); var magic = reader.ReadInt32(); var start = reader.ReadInt32(); From 7af3f7d3de1c581de7f5f7f52ce904c2bb6c2520 Mon Sep 17 00:00:00 2001 From: TheIndra55 Date: Sat, 25 Jan 2025 02:56:33 +0100 Subject: [PATCH 5/9] Add tests --- Yura.Test/ReaderTests.cs | 47 ++++++++++++++++++++++++++++++++++++++ Yura.Test/WriterTests.cs | 41 +++++++++++++++++++++++++++++++++ Yura.Test/Yura.Test.csproj | 27 ++++++++++++++++++++++ Yura.sln | 8 ++++++- 4 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 Yura.Test/ReaderTests.cs create mode 100644 Yura.Test/WriterTests.cs create mode 100644 Yura.Test/Yura.Test.csproj diff --git a/Yura.Test/ReaderTests.cs b/Yura.Test/ReaderTests.cs new file mode 100644 index 0000000..37fd3f0 --- /dev/null +++ b/Yura.Test/ReaderTests.cs @@ -0,0 +1,47 @@ +using System.IO; +using System.Text; +using Yura.Shared.IO; + +namespace Yura.Test +{ + internal class ReaderTests + { + [Test] + public void TestSeek() + { + var stream = new MemoryStream(); + var reader = new DataReader(stream, Endianness.LittleEndian); + + reader.Position = 8; + + Assert.That(reader.Position, Is.EqualTo(8)); + } + + [Test] + public void TestLittleEndian() + { + var stream = new MemoryStream([0x7B, 0x00, 0x00, 0x00]); + var reader = new DataReader(stream, Endianness.LittleEndian); + + Assert.That(reader.ReadUInt32(), Is.EqualTo(123)); + } + + [Test] + public void TestBigEndian() + { + var stream = new MemoryStream([0x00, 0x00, 0x00, 0x7B]); + var reader = new DataReader(stream, Endianness.BigEndian); + + Assert.That(reader.ReadUInt32(), Is.EqualTo(123)); + } + + [Test] + public void TestString() + { + var stream = new MemoryStream([0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x00]); + var reader = new DataReader(stream, Endianness.BigEndian); + + Assert.That(reader.ReadString(Encoding.ASCII), Is.EqualTo("Hello")); + } + } +} diff --git a/Yura.Test/WriterTests.cs b/Yura.Test/WriterTests.cs new file mode 100644 index 0000000..6559c0a --- /dev/null +++ b/Yura.Test/WriterTests.cs @@ -0,0 +1,41 @@ +using System.IO; +using Yura.Shared.IO; + +namespace Yura.Test +{ + internal class WriterTests + { + [Test] + public void TestSeek() + { + var stream = new MemoryStream(); + var writer = new DataWriter(stream, Endianness.LittleEndian); + + writer.Position = 8; + + Assert.That(writer.Position, Is.EqualTo(8)); + } + + [Test] + public void TestLittleEndian() + { + var stream = new MemoryStream(); + var writer = new DataWriter(stream, Endianness.LittleEndian); + + writer.Write(123); + + Assert.That(stream.ToArray(), Is.EquivalentTo((byte[])[0x7B, 0x00, 0x00, 0x00])); + } + + [Test] + public void TestBigEndian() + { + var stream = new MemoryStream(); + var writer = new DataWriter(stream, Endianness.BigEndian); + + writer.Write(123); + + Assert.That(stream.ToArray(), Is.EquivalentTo((byte[])[0x00, 0x00, 0x00, 0x7B])); + } + } +} diff --git a/Yura.Test/Yura.Test.csproj b/Yura.Test/Yura.Test.csproj new file mode 100644 index 0000000..20ee77b --- /dev/null +++ b/Yura.Test/Yura.Test.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + + false + true + + + + + + + + + + + + + + + + + + + diff --git a/Yura.sln b/Yura.sln index aa7d925..8280276 100644 --- a/Yura.sln +++ b/Yura.sln @@ -1,12 +1,14 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.12.35506.116 d17.12 +VisualStudioVersion = 17.12.35506.116 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yura", "Yura\Yura.csproj", "{7D73C719-E756-4019-9C62-B318F1B61776}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yura.Shared", "Yura.Shared\Yura.Shared.csproj", "{67F16917-AD1D-485A-B419-A3360766365D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yura.Test", "Yura.Test\Yura.Test.csproj", "{AD8EDAE9-3B48-4437-AAA3-AEAD7127FDA0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {67F16917-AD1D-485A-B419-A3360766365D}.Debug|Any CPU.Build.0 = Debug|Any CPU {67F16917-AD1D-485A-B419-A3360766365D}.Release|Any CPU.ActiveCfg = Release|Any CPU {67F16917-AD1D-485A-B419-A3360766365D}.Release|Any CPU.Build.0 = Release|Any CPU + {AD8EDAE9-3B48-4437-AAA3-AEAD7127FDA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD8EDAE9-3B48-4437-AAA3-AEAD7127FDA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD8EDAE9-3B48-4437-AAA3-AEAD7127FDA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AD8EDAE9-3B48-4437-AAA3-AEAD7127FDA0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From c0153ee5231c3d5fadd7c60571ba1f01e930a594 Mon Sep 17 00:00:00 2001 From: TheIndra55 Date: Sat, 25 Jan 2025 03:09:54 +0100 Subject: [PATCH 6/9] Don't publish tests --- Yura.Test/Yura.Test.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/Yura.Test/Yura.Test.csproj b/Yura.Test/Yura.Test.csproj index 20ee77b..a6f7620 100644 --- a/Yura.Test/Yura.Test.csproj +++ b/Yura.Test/Yura.Test.csproj @@ -6,6 +6,7 @@ false true + false From 3621b2abcbf554b657bb8e54ae24c02efaa191a8 Mon Sep 17 00:00:00 2001 From: TheIndra55 Date: Sat, 25 Jan 2025 03:40:21 +0100 Subject: [PATCH 7/9] Rename TextureFormat to Platform --- Yura.Shared/Util/Platform.cs | 23 +++++++++++++++++++++++ Yura.Shared/Yura.Shared.csproj | 2 ++ Yura/CommandLineOptions.cs | 9 +++++---- Yura/IFileSettings.cs | 3 ++- Yura/MainWindow.xaml.cs | 6 +++--- Yura/OpenDialog.xaml | 9 +++++---- Yura/OpenDialog.xaml.cs | 26 +++----------------------- Yura/Windows/TextureViewer.xaml.cs | 9 +++++---- Yura/Yura.csproj | 2 +- 9 files changed, 49 insertions(+), 40 deletions(-) create mode 100644 Yura.Shared/Util/Platform.cs diff --git a/Yura.Shared/Util/Platform.cs b/Yura.Shared/Util/Platform.cs new file mode 100644 index 0000000..2265d2b --- /dev/null +++ b/Yura.Shared/Util/Platform.cs @@ -0,0 +1,23 @@ +namespace Yura.Shared.Util +{ + public enum Platform + { + Pc, + + // PlayStation + Ps2, + Psp, + Ps3, + Orbis, + + // Xbox + Xbox, + Xenon, + Durango, + Scarlett, + + // Nintendo + Wii, + GameCube + } +} diff --git a/Yura.Shared/Yura.Shared.csproj b/Yura.Shared/Yura.Shared.csproj index 0672ddb..8696845 100644 --- a/Yura.Shared/Yura.Shared.csproj +++ b/Yura.Shared/Yura.Shared.csproj @@ -3,6 +3,8 @@ net8.0 enable + + 1.7.0 diff --git a/Yura/CommandLineOptions.cs b/Yura/CommandLineOptions.cs index 4144eb4..16d26ad 100644 --- a/Yura/CommandLineOptions.cs +++ b/Yura/CommandLineOptions.cs @@ -1,6 +1,7 @@ using System; using System.IO; using Yura.Shared.IO; +using Yura.Shared.Util; namespace Yura { @@ -90,16 +91,16 @@ public string FileList } } - public TextureFormat TextureFormat + public Platform Platform { get { - if (Enum.TryParse(typeof(TextureFormat), GetOption("-platform"), true, out var game)) + if (Enum.TryParse(typeof(Platform), GetOption("-platform"), true, out var game)) { - return (TextureFormat)game; + return (Platform)game; } - return TextureFormat.Pc; + return Platform.Pc; } } } diff --git a/Yura/IFileSettings.cs b/Yura/IFileSettings.cs index e7a1177..4eb532a 100644 --- a/Yura/IFileSettings.cs +++ b/Yura/IFileSettings.cs @@ -1,4 +1,5 @@ using Yura.Shared.IO; +using Yura.Shared.Util; namespace Yura { @@ -8,6 +9,6 @@ public interface IFileSettings public Endianness Endianness { get; } public int Alignment { get; } public string FileList { get; } - public TextureFormat TextureFormat { get; } + public Platform Platform { get; } } } diff --git a/Yura/MainWindow.xaml.cs b/Yura/MainWindow.xaml.cs index ab376f0..a4a9b37 100644 --- a/Yura/MainWindow.xaml.cs +++ b/Yura/MainWindow.xaml.cs @@ -37,7 +37,7 @@ public partial class MainWindow : Window private Dictionary> _fileTypes; private Endianness _endianness; - private TextureFormat _textureFormat; + private Platform _platform; private Game _currentGame; // the current open bigfile @@ -136,7 +136,7 @@ public void OpenBigfile(string bigfile, IFileSettings settings) var list = (settings.FileList == null) ? null : new FileList(settings.FileList, settings.Game != Game.Tiger); _endianness = settings.Endianness; - _textureFormat = settings.TextureFormat; + _platform = settings.Platform; _currentGame = settings.Game; // Open the archive with the following options @@ -384,7 +384,7 @@ private void FileView_MouseDoubleClick(object sender, MouseButtonEventArgs e) if (size > 4 && file[0] == 33 && file[1] == 'W' && file[2] == 'A' && file[3] == 'R') { var viewer = new TextureViewer(); - viewer.TextureFormat = _textureFormat; + viewer.Platform = _platform; viewer.Endianness = _endianness; viewer.Texture = file; diff --git a/Yura/OpenDialog.xaml b/Yura/OpenDialog.xaml index 5a33669..d518ee8 100644 --- a/Yura/OpenDialog.xaml +++ b/Yura/OpenDialog.xaml @@ -53,15 +53,16 @@ PC - PS2 - PSP - PS3 - PS4 + PlayStation 2 + PlayStation Portable + PlayStation 3 + PlayStation 4 Xbox Xbox 360 Xbox One Xbox Series X Wii + GameCube diff --git a/Yura/OpenDialog.xaml.cs b/Yura/OpenDialog.xaml.cs index 279bec3..1d28f05 100644 --- a/Yura/OpenDialog.xaml.cs +++ b/Yura/OpenDialog.xaml.cs @@ -4,6 +4,7 @@ using System.Windows; using System.Windows.Controls; using Yura.Shared.IO; +using Yura.Shared.Util; namespace Yura { @@ -72,11 +73,11 @@ public Game Game } } - public TextureFormat TextureFormat + public Platform Platform { get { - return (TextureFormat)TextureFormatSelect.SelectedIndex; + return (Platform)TextureFormatSelect.SelectedIndex; } } @@ -100,27 +101,6 @@ private void GameSelect_SelectionChanged(object sender, SelectionChangedEventArg } } - public enum TextureFormat - { - Pc, - - // PlayStation - Ps2, - Psp, - Ps3, - Orbis, - - // Xbox - Xbox, - Xenon, - Durango, - Scarlett, - - // Nintendo - Wii, - GameCube - } - public enum Game { Defiance, diff --git a/Yura/Windows/TextureViewer.xaml.cs b/Yura/Windows/TextureViewer.xaml.cs index f311825..97fab17 100644 --- a/Yura/Windows/TextureViewer.xaml.cs +++ b/Yura/Windows/TextureViewer.xaml.cs @@ -3,6 +3,7 @@ using System.Windows.Media.Imaging; using Yura.Formats; using Yura.Shared.IO; +using Yura.Shared.Util; namespace Yura { @@ -25,13 +26,13 @@ private void CreateImage(int width, int height, DataReader reader) BitmapSource image; - switch (TextureFormat) + switch (Platform) { - case TextureFormat.Wii: + case Platform.Wii: image = new CMPRTexture(width, height, textureData); break; - case TextureFormat.Ps3: + case Platform.Ps3: image = new CMPRTexture(width, height, textureData); break; @@ -45,7 +46,7 @@ private void CreateImage(int width, int height, DataReader reader) public Endianness Endianness { get; set; } - public TextureFormat TextureFormat { get; set; } + public Platform Platform { get; set; } public byte[] Texture { diff --git a/Yura/Yura.csproj b/Yura/Yura.csproj index f5ca3d9..f598549 100644 --- a/Yura/Yura.csproj +++ b/Yura/Yura.csproj @@ -6,7 +6,7 @@ true true Yura.ico - 1.6 + 1.7.0 From 80be47cbdbc8176d7f927b0c3da416b934eae4ff Mon Sep 17 00:00:00 2001 From: TheIndra55 Date: Sat, 25 Jan 2025 03:45:58 +0100 Subject: [PATCH 8/9] Fix TR11 archive from PS4 not opening --- Yura.Shared/Archive/ArchiveOptions.cs | 5 +++++ Yura.Shared/Archive/TigerArchive.cs | 4 ++-- Yura/MainWindow.xaml.cs | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Yura.Shared/Archive/ArchiveOptions.cs b/Yura.Shared/Archive/ArchiveOptions.cs index ae4878d..ab30203 100644 --- a/Yura.Shared/Archive/ArchiveOptions.cs +++ b/Yura.Shared/Archive/ArchiveOptions.cs @@ -15,6 +15,11 @@ public class ArchiveOptions /// public Endianness Endianness { get; set; } + /// + /// Gets or sets the archive platform + /// + public Platform Platform { get; set; } + /// /// Gets or sets the archive alignment /// diff --git a/Yura.Shared/Archive/TigerArchive.cs b/Yura.Shared/Archive/TigerArchive.cs index 491949c..9ddf265 100644 --- a/Yura.Shared/Archive/TigerArchive.cs +++ b/Yura.Shared/Archive/TigerArchive.cs @@ -38,8 +38,8 @@ public override void Open() var numArchives = reader.ReadUInt32(); var numRecords = reader.ReadUInt32(); - // Skip 4 bytes, or 8 in version 5 or later - reader.Position += version < 5 ? 4 : 8; + // Skip 4 bytes, or 8 in version 5 or later (unless Orbis) + reader.Position += version >= 5 && Options.Platform != Platform.Orbis ? 8 : 4; // Skip over the config name reader.Position += 32; diff --git a/Yura/MainWindow.xaml.cs b/Yura/MainWindow.xaml.cs index a4a9b37..750b14e 100644 --- a/Yura/MainWindow.xaml.cs +++ b/Yura/MainWindow.xaml.cs @@ -144,6 +144,7 @@ public void OpenBigfile(string bigfile, IFileSettings settings) { Path = bigfile, Endianness = settings.Endianness, + Platform = settings.Platform, Alignment = settings.Alignment, FileList = list }; From 316770f60b36e0fc9b1f1ca057dcf8812c996493 Mon Sep 17 00:00:00 2001 From: TheIndra55 Date: Sat, 25 Jan 2025 17:13:16 +0100 Subject: [PATCH 9/9] Restore support for all platforms --- Yura.Shared/Archive/ArchiveFile.cs | 19 +++++++++++++++ Yura.Shared/Archive/DefianceArchive.cs | 6 +++-- Yura.Shared/Archive/LegendArchive.cs | 33 +++++++++++++++++++++----- Yura/OpenDialog.xaml | 1 + Yura/OpenDialog.xaml.cs | 2 +- 5 files changed, 52 insertions(+), 9 deletions(-) diff --git a/Yura.Shared/Archive/ArchiveFile.cs b/Yura.Shared/Archive/ArchiveFile.cs index 199a5f1..5de206d 100644 --- a/Yura.Shared/Archive/ArchiveFile.cs +++ b/Yura.Shared/Archive/ArchiveFile.cs @@ -68,6 +68,25 @@ protected string GetFilePart(int part, string extension = "") return Path.Combine(directory, name + "." + part.ToString("000") + extension); } + /// + /// From an offset and the alignment get the physical file and offset + /// + /// The offset of the file + /// The file and offset + protected (string, long) GetFileAndOffset(long offset) + { + // Check whether the archive is split over multiple files + if (Path.GetExtension(Name) == ".000") + { + var part = offset / Options.Alignment; + var path = GetFilePart((int)part); + + return (path, offset % Options.Alignment); + } + + return (Name, offset); + } + private static string[] Split(string path) { return path.Split('\\', StringSplitOptions.RemoveEmptyEntries); diff --git a/Yura.Shared/Archive/DefianceArchive.cs b/Yura.Shared/Archive/DefianceArchive.cs index 1e051f8..39e4bde 100644 --- a/Yura.Shared/Archive/DefianceArchive.cs +++ b/Yura.Shared/Archive/DefianceArchive.cs @@ -50,11 +50,13 @@ public override byte[] Read(ArchiveRecord record) { var file = record as Record; + var (path, offset) = GetFileAndOffset(file.Offset); + // Read the file - var stream = File.OpenRead(Options.Path); + var stream = File.OpenRead(path); var data = new byte[file.Size]; - stream.Position = file.Offset; + stream.Position = offset; stream.ReadExactly(data); stream.Close(); diff --git a/Yura.Shared/Archive/LegendArchive.cs b/Yura.Shared/Archive/LegendArchive.cs index 29eb798..38d265d 100644 --- a/Yura.Shared/Archive/LegendArchive.cs +++ b/Yura.Shared/Archive/LegendArchive.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.IO.Compression; using Yura.Shared.IO; namespace Yura.Shared.Archive @@ -54,24 +55,44 @@ public override byte[] Read(ArchiveRecord record) { var file = record as Record; - // Calculate the location of the file - var offset = (long)file.Offset << 11; - var part = offset / Options.Alignment; + // Convert sectors to file position + var position = (long)file.Offset << 11; - var path = GetFilePart((int)part); + var (path, offset) = GetFileAndOffset(position); // Read the file var stream = File.OpenRead(path); var data = new byte[file.Size]; - stream.Position = offset % Options.Alignment; - stream.ReadExactly(data); + if (file.CompressedSize == 0) + { + stream.Position = offset; + stream.ReadExactly(data); + } + else + { + ReadCompressed(stream, offset, file.CompressedSize, data); + } stream.Close(); return data; } + private static void ReadCompressed(Stream stream, long offset, uint size, Span buffer) + { + // Read the compressed data + var data = new byte[size]; + + stream.Position = offset; + stream.ReadExactly(data); + + // Decompress the data + var zlib = new ZLibStream(new MemoryStream(data), CompressionMode.Decompress); + + zlib.ReadExactly(buffer); + } + private class Record : ArchiveRecord { public uint Offset { get; set; } diff --git a/Yura/OpenDialog.xaml b/Yura/OpenDialog.xaml index d518ee8..4777217 100644 --- a/Yura/OpenDialog.xaml +++ b/Yura/OpenDialog.xaml @@ -69,6 +69,7 @@ 0x9600000 0x7FF00000 + 0xFA00000