diff options
Diffstat (limited to 'src/Ryujinx.Horizon/Sdk/Ngc/Detail/ContentsReader.cs')
| -rw-r--r-- | src/Ryujinx.Horizon/Sdk/Ngc/Detail/ContentsReader.cs | 404 |
1 files changed, 404 insertions, 0 deletions
diff --git a/src/Ryujinx.Horizon/Sdk/Ngc/Detail/ContentsReader.cs b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/ContentsReader.cs new file mode 100644 index 00000000..cb865fa0 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ngc/Detail/ContentsReader.cs @@ -0,0 +1,404 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Fs; +using System; +using System.IO; +using System.IO.Compression; + +namespace Ryujinx.Horizon.Sdk.Ngc.Detail +{ + class ContentsReader : IDisposable + { + private const string MountName = "NgWord"; + private const string VersionFilePath = $"{MountName}:/version.dat"; + private const ulong DataId = 0x100000000000823UL; + + private enum AcType + { + AcNotB, + AcB1, + AcB2, + AcSimilarForm, + TableSimilarForm, + } + + private readonly IFsClient _fsClient; + private readonly object _lock; + private bool _intialized; + private ulong _cacheSize; + + public ContentsReader(IFsClient fsClient) + { + _lock = new(); + _fsClient = fsClient; + } + + private static void MakeMountPoint(out string path, AcType type, int regionIndex) + { + path = null; + + switch (type) + { + case AcType.AcNotB: + if (regionIndex < 0) + { + path = $"{MountName}:/ac_common_not_b_nx"; + } + else + { + path = $"{MountName}:/ac_{regionIndex}_not_b_nx"; + } + break; + case AcType.AcB1: + if (regionIndex < 0) + { + path = $"{MountName}:/ac_common_b1_nx"; + } + else + { + path = $"{MountName}:/ac_{regionIndex}_b1_nx"; + } + break; + case AcType.AcB2: + if (regionIndex < 0) + { + path = $"{MountName}:/ac_common_b2_nx"; + } + else + { + path = $"{MountName}:/ac_{regionIndex}_b2_nx"; + } + break; + case AcType.AcSimilarForm: + path = $"{MountName}:/ac_similar_form_nx"; + break; + case AcType.TableSimilarForm: + path = $"{MountName}:/table_similar_form_nx"; + break; + } + } + + public Result Initialize(ulong cacheSize) + { + lock (_lock) + { + if (_intialized) + { + return Result.Success; + } + + Result result = _fsClient.QueryMountSystemDataCacheSize(out long dataCacheSize, DataId); + if (result.IsFailure) + { + return result; + } + + if (cacheSize < (ulong)dataCacheSize) + { + return NgcResult.InvalidSize; + } + + result = _fsClient.MountSystemData(MountName, DataId); + if (result.IsFailure) + { + // Official firmware would return the result here, + // we don't to support older firmware where the archive didn't exist yet. + return Result.Success; + } + + _cacheSize = cacheSize; + _intialized = true; + + return Result.Success; + } + } + + public Result Reload() + { + lock (_lock) + { + if (!_intialized) + { + return Result.Success; + } + + _fsClient.Unmount(MountName); + + Result result = Result.Success; + + try + { + result = _fsClient.QueryMountSystemDataCacheSize(out long cacheSize, DataId); + if (result.IsFailure) + { + return result; + } + + if (_cacheSize < (ulong)cacheSize) + { + result = NgcResult.InvalidSize; + return NgcResult.InvalidSize; + } + + result = _fsClient.MountSystemData(MountName, DataId); + if (result.IsFailure) + { + return result; + } + } + finally + { + if (result.IsFailure) + { + _intialized = false; + _cacheSize = 0; + } + } + } + + return Result.Success; + } + + private Result GetFileSize(out long size, string filePath) + { + size = 0; + + lock (_lock) + { + Result result = _fsClient.OpenFile(out FileHandle handle, filePath, OpenMode.Read); + if (result.IsFailure) + { + return result; + } + + try + { + result = _fsClient.GetFileSize(out size, handle); + if (result.IsFailure) + { + return result; + } + } + finally + { + _fsClient.CloseFile(handle); + } + } + + return Result.Success; + } + + private Result GetFileContent(Span<byte> destination, string filePath) + { + lock (_lock) + { + Result result = _fsClient.OpenFile(out FileHandle handle, filePath, OpenMode.Read); + if (result.IsFailure) + { + return result; + } + + try + { + result = _fsClient.ReadFile(handle, 0, destination); + if (result.IsFailure) + { + return result; + } + } + finally + { + _fsClient.CloseFile(handle); + } + } + + return Result.Success; + } + + public Result GetVersionDataSize(out long size) + { + return GetFileSize(out size, VersionFilePath); + } + + public Result GetVersionData(Span<byte> destination) + { + return GetFileContent(destination, VersionFilePath); + } + + public Result ReadDictionaries(out AhoCorasick partialWordsTrie, out AhoCorasick completeWordsTrie, out AhoCorasick delimitedWordsTrie, int regionIndex) + { + completeWordsTrie = null; + delimitedWordsTrie = null; + + MakeMountPoint(out string partialWordsTriePath, AcType.AcNotB, regionIndex); + MakeMountPoint(out string completeWordsTriePath, AcType.AcB1, regionIndex); + MakeMountPoint(out string delimitedWordsTriePath, AcType.AcB2, regionIndex); + + Result result = ReadDictionary(out partialWordsTrie, partialWordsTriePath); + if (result.IsFailure) + { + return NgcResult.DataAccessError; + } + + result = ReadDictionary(out completeWordsTrie, completeWordsTriePath); + if (result.IsFailure) + { + return NgcResult.DataAccessError; + } + + return ReadDictionary(out delimitedWordsTrie, delimitedWordsTriePath); + } + + public Result ReadSimilarFormDictionary(out AhoCorasick similarFormTrie) + { + MakeMountPoint(out string similarFormTriePath, AcType.AcSimilarForm, 0); + + return ReadDictionary(out similarFormTrie, similarFormTriePath); + } + + public Result ReadSimilarFormTable(out SimilarFormTable similarFormTable) + { + similarFormTable = null; + + MakeMountPoint(out string similarFormTablePath, AcType.TableSimilarForm, 0); + + Result result = ReadGZipCompressedArchive(out byte[] data, similarFormTablePath); + if (result.IsFailure) + { + return result; + } + + BinaryReader reader = new(data); + SimilarFormTable table = new(); + + if (!table.Import(ref reader)) + { + // Official firmware doesn't return an error here and just assumes the import was successful. + return NgcResult.DataAccessError; + } + + similarFormTable = table; + + return Result.Success; + } + + public static Result ReadNotSeparatorDictionary(out AhoCorasick notSeparatorTrie) + { + notSeparatorTrie = null; + + BinaryReader reader = new(EmbeddedTries.NotSeparatorTrie); + AhoCorasick ac = new(); + + if (!ac.Import(ref reader)) + { + // Official firmware doesn't return an error here and just assumes the import was successful. + return NgcResult.DataAccessError; + } + + notSeparatorTrie = ac; + + return Result.Success; + } + + private Result ReadDictionary(out AhoCorasick trie, string path) + { + trie = null; + + Result result = ReadGZipCompressedArchive(out byte[] data, path); + if (result.IsFailure) + { + return result; + } + + BinaryReader reader = new(data); + AhoCorasick ac = new(); + + if (!ac.Import(ref reader)) + { + // Official firmware doesn't return an error here and just assumes the import was successful. + return NgcResult.DataAccessError; + } + + trie = ac; + + return Result.Success; + } + + private Result ReadGZipCompressedArchive(out byte[] data, string filePath) + { + data = null; + + Result result = _fsClient.OpenFile(out FileHandle handle, filePath, OpenMode.Read); + if (result.IsFailure) + { + return result; + } + + try + { + result = _fsClient.GetFileSize(out long fileSize, handle); + if (result.IsFailure) + { + return result; + } + + data = new byte[fileSize]; + + result = _fsClient.ReadFile(handle, 0, data.AsSpan()); + if (result.IsFailure) + { + return result; + } + } + finally + { + _fsClient.CloseFile(handle); + } + + try + { + data = DecompressGZipCompressedStream(data); + } + catch (InvalidDataException) + { + // Official firmware returns a different error, but it is translated to this error on the caller. + return NgcResult.DataAccessError; + } + + return Result.Success; + } + + private static byte[] DecompressGZipCompressedStream(byte[] data) + { + using MemoryStream input = new(data); + using GZipStream gZipStream = new(input, CompressionMode.Decompress); + using MemoryStream output = new(); + + gZipStream.CopyTo(output); + + return output.ToArray(); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + lock (_lock) + { + if (!_intialized) + { + return; + } + + _fsClient.Unmount(MountName); + _intialized = false; + } + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} |
