diff --git a/pkNX.Containers/Container.cs b/pkNX.Containers/Container.cs
index 559b00f3..33e328a6 100644
--- a/pkNX.Containers/Container.cs
+++ b/pkNX.Containers/Container.cs
@@ -1,73 +1,72 @@
-using System;
+using System;
using System.IO;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+public static class Container
{
- public static class Container
+ ///
+ /// Gets a new for the provided and type.
+ ///
+ /// File location
+ /// File type
+ public static IFileContainer GetContainer(string path, ContainerType t)
{
- ///
- /// Gets a new for the provided and type.
- ///
- /// File location
- /// File type
- public static IFileContainer GetContainer(string path, ContainerType t)
+ return t switch
{
- return t switch
- {
- ContainerType.GARC => new GARC(path),
- ContainerType.Mini => MiniUtil.GetMini(path),
- ContainerType.SARC => new SARC(path),
- ContainerType.Folder => new FolderContainer(path),
- ContainerType.SingleFile => new SingleFileContainer(path),
- ContainerType.GFPack => new GFPack(path),
- _ => throw new ArgumentOutOfRangeException(nameof(t), t, null)
- };
- }
+ ContainerType.GARC => new GARC(path),
+ ContainerType.Mini => MiniUtil.GetMini(path),
+ ContainerType.SARC => new SARC(path),
+ ContainerType.Folder => new FolderContainer(path),
+ ContainerType.SingleFile => new SingleFileContainer(path),
+ ContainerType.GFPack => new GFPack(path),
+ _ => throw new ArgumentOutOfRangeException(nameof(t), t, null)
+ };
+ }
- ///
- /// Gets a for the stream.
- ///
- /// Path to the binary data
- public static IFileContainer? GetContainer(string path)
- {
- var fs = new FileStream(path, FileMode.Open);
- var container = GetContainer(fs);
- if (container is not LargeContainer) // not kept
- fs.Dispose();
- if (container == null)
- return null;
-
- container.FilePath = path;
- return container;
- }
-
- ///
- /// Gets a for the stream.
- ///
- /// Stream for the binary data
- public static IFileContainer? GetContainer(Stream stream)
- {
- var br = new BinaryReader(stream);
- var container = GetContainer(br);
- if (container is not LargeContainer) // not kept
- br.Dispose();
- return container;
- }
-
- ///
- /// Gets a for the stream within the .
- ///
- /// Reader for the binary data
- public static IFileContainer? GetContainer(BinaryReader br)
- {
- IFileContainer? container;
- if ((container = GARC.GetGARC(br)) != null)
- return container;
- if ((container = MiniUtil.GetMini(br)) != null)
- return container;
- if ((container = SARC.GetSARC(br)) != null)
- return container;
+ ///
+ /// Gets a for the stream.
+ ///
+ /// Path to the binary data
+ public static IFileContainer? GetContainer(string path)
+ {
+ var fs = new FileStream(path, FileMode.Open);
+ var container = GetContainer(fs);
+ if (container is not LargeContainer) // not kept
+ fs.Dispose();
+ if (container == null)
return null;
- }
+
+ container.FilePath = path;
+ return container;
+ }
+
+ ///
+ /// Gets a for the stream.
+ ///
+ /// Stream for the binary data
+ public static IFileContainer? GetContainer(Stream stream)
+ {
+ var br = new BinaryReader(stream);
+ var container = GetContainer(br);
+ if (container is not LargeContainer) // not kept
+ br.Dispose();
+ return container;
+ }
+
+ ///
+ /// Gets a for the stream within the .
+ ///
+ /// Reader for the binary data
+ public static IFileContainer? GetContainer(BinaryReader br)
+ {
+ IFileContainer? container;
+ if ((container = GARC.GetGARC(br)) != null)
+ return container;
+ if ((container = MiniUtil.GetMini(br)) != null)
+ return container;
+ if ((container = SARC.GetSARC(br)) != null)
+ return container;
+ return null;
}
}
diff --git a/pkNX.Containers/ContainerHandler/ContainerHandler.cs b/pkNX.Containers/ContainerHandler/ContainerHandler.cs
index 7a69ff3e..8ee15a04 100644
--- a/pkNX.Containers/ContainerHandler/ContainerHandler.cs
+++ b/pkNX.Containers/ContainerHandler/ContainerHandler.cs
@@ -2,28 +2,27 @@
// ReSharper disable ClassNeverInstantiated.Global
// ReSharper disable EventNeverSubscribedTo.Global
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+public sealed class ContainerHandler
{
- public sealed class ContainerHandler
+ public event EventHandler? FileCountDetermined;
+ public event EventHandler? FileProgressed;
+
+ private int count;
+
+ public void Initialize(int total)
{
- public event EventHandler? FileCountDetermined;
- public event EventHandler? FileProgressed;
-
- private int count;
-
- public void Initialize(int total)
- {
- count = total;
- var args = new FileCountDeterminedEventArgs {Total = total};
- FileCountDetermined?.Invoke(null, args);
- }
-
- public void StepFile(int ctr, int total = -1, string? fileName = null)
- {
- if (total < 0)
- total = count;
- var args = new FileProgressedEventArgs {Current = ctr, Total = total, CurrentFile = fileName ?? string.Empty};
- FileProgressed?.Invoke(null, args);
- }
+ count = total;
+ var args = new FileCountDeterminedEventArgs {Total = total};
+ FileCountDetermined?.Invoke(null, args);
}
-}
\ No newline at end of file
+
+ public void StepFile(int ctr, int total = -1, string? fileName = null)
+ {
+ if (total < 0)
+ total = count;
+ var args = new FileProgressedEventArgs {Current = ctr, Total = total, CurrentFile = fileName ?? string.Empty};
+ FileProgressed?.Invoke(null, args);
+ }
+}
diff --git a/pkNX.Containers/ContainerHandler/FileCountDeterminedEventArgs.cs b/pkNX.Containers/ContainerHandler/FileCountDeterminedEventArgs.cs
index 359a4f9b..f056caa0 100644
--- a/pkNX.Containers/ContainerHandler/FileCountDeterminedEventArgs.cs
+++ b/pkNX.Containers/ContainerHandler/FileCountDeterminedEventArgs.cs
@@ -1,9 +1,8 @@
using System;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+public class FileCountDeterminedEventArgs : EventArgs
{
- public class FileCountDeterminedEventArgs : EventArgs
- {
- public int Total { get; set; }
- }
-}
\ No newline at end of file
+ public int Total { get; set; }
+}
diff --git a/pkNX.Containers/ContainerHandler/FileProgressedEventArgs.cs b/pkNX.Containers/ContainerHandler/FileProgressedEventArgs.cs
index 2a7a2aba..502b782a 100644
--- a/pkNX.Containers/ContainerHandler/FileProgressedEventArgs.cs
+++ b/pkNX.Containers/ContainerHandler/FileProgressedEventArgs.cs
@@ -1,11 +1,10 @@
-using System;
+using System;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+public class FileProgressedEventArgs : EventArgs
{
- public class FileProgressedEventArgs : EventArgs
- {
- public int Current { get; set; }
- public int Total { get; set; }
- public string? CurrentFile { get; set; }
- }
-}
\ No newline at end of file
+ public int Current { get; set; }
+ public int Total { get; set; }
+ public string? CurrentFile { get; set; }
+}
diff --git a/pkNX.Containers/ContainerParent.cs b/pkNX.Containers/ContainerParent.cs
index 87b2d6be..1490d5fd 100644
--- a/pkNX.Containers/ContainerParent.cs
+++ b/pkNX.Containers/ContainerParent.cs
@@ -1,15 +1,14 @@
-namespace pkNX.Containers
-{
- public enum ContainerParent
- {
- ///
- /// File is located in the RomFS.
- ///
- RomFS,
+namespace pkNX.Containers;
- ///
- /// File is located in the ExeFS.
- ///
- ExeFS,
- }
-}
\ No newline at end of file
+public enum ContainerParent
+{
+ ///
+ /// File is located in the RomFS.
+ ///
+ RomFS,
+
+ ///
+ /// File is located in the ExeFS.
+ ///
+ ExeFS,
+}
diff --git a/pkNX.Containers/ContainerType.cs b/pkNX.Containers/ContainerType.cs
index 9b08d11b..b9aa0123 100644
--- a/pkNX.Containers/ContainerType.cs
+++ b/pkNX.Containers/ContainerType.cs
@@ -1,12 +1,11 @@
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+public enum ContainerType
{
- public enum ContainerType
- {
- GARC,
- Mini,
- SARC,
- Folder,
- SingleFile,
- GFPack,
- }
+ GARC,
+ Mini,
+ SARC,
+ Folder,
+ SingleFile,
+ GFPack,
}
diff --git a/pkNX.Containers/FakeContainer.cs b/pkNX.Containers/FakeContainer.cs
index 6d20bf20..09fcca01 100644
--- a/pkNX.Containers/FakeContainer.cs
+++ b/pkNX.Containers/FakeContainer.cs
@@ -1,55 +1,54 @@
-using System.IO;
+using System.IO;
using System.Threading;
using System.Threading.Tasks;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+public class FakeContainer : IFileContainer
{
- public class FakeContainer : IFileContainer
+ public readonly byte[][] Files;
+ private readonly byte[][] Backup;
+
+ public FakeContainer(byte[][] files)
{
- public readonly byte[][] Files;
- private readonly byte[][] Backup;
+ Files = files;
+ Backup = new byte[files.Length][];
+ for (int i = 0; i < Backup.Length; i++)
+ Backup[i] = (byte[])files[i].Clone();
+ }
- public FakeContainer(byte[][] files)
+ public string? FilePath { get; set; } = string.Empty;
+ public bool Modified { get; set; }
+ public int Count => Files.Length;
+
+ public byte[] this[int index]
+ {
+ get => Files[index];
+ set => Files[index] = value;
+ }
+
+ public Task GetFiles() => Task.FromResult(Files);
+ public Task GetFile(int file, int subFile = 0) => Task.FromResult(this[file]);
+ public Task SetFile(int file, byte[] value, int subFile = 0) => Task.FromResult(this[file] = value);
+ public Task SaveAs(string path, ContainerHandler handler, CancellationToken token) => new(() => SaveAll(path, handler, token), token);
+
+ public void SaveAll(string path, ContainerHandler handler, CancellationToken token)
+ {
+ handler.Initialize(Files.Length);
+ for (int i = 0; i < Files.Length; i++)
{
- Files = files;
- Backup = new byte[files.Length][];
- for (int i = 0; i < Backup.Length; i++)
- Backup[i] = (byte[])files[i].Clone();
- }
-
- public string? FilePath { get; set; } = string.Empty;
- public bool Modified { get; set; }
- public int Count => Files.Length;
-
- public byte[] this[int index]
- {
- get => Files[index];
- set => Files[index] = value;
- }
-
- public Task GetFiles() => Task.FromResult(Files);
- public Task GetFile(int file, int subFile = 0) => Task.FromResult(this[file]);
- public Task SetFile(int file, byte[] value, int subFile = 0) => Task.FromResult(this[file] = value);
- public Task SaveAs(string path, ContainerHandler handler, CancellationToken token) => new(() => SaveAll(path, handler, token), token);
-
- public void SaveAll(string path, ContainerHandler handler, CancellationToken token)
- {
- handler.Initialize(Files.Length);
- for (int i = 0; i < Files.Length; i++)
- {
- if (token.IsCancellationRequested)
- return;
- File.WriteAllBytes(Path.Combine(path, $"{i}.bin"), Files[i]);
- handler.StepFile(i);
- }
- }
-
- public void Dump(string path, ContainerHandler handler) => SaveAs(path, handler, CancellationToken.None);
-
- public void CancelEdits()
- {
- for (int i = 0; i < Files.Length; i++)
- Files[i] = (byte[])Backup[i].Clone();
+ if (token.IsCancellationRequested)
+ return;
+ File.WriteAllBytes(Path.Combine(path, $"{i}.bin"), Files[i]);
+ handler.StepFile(i);
}
}
-}
\ No newline at end of file
+
+ public void Dump(string path, ContainerHandler handler) => SaveAs(path, handler, CancellationToken.None);
+
+ public void CancelEdits()
+ {
+ for (int i = 0; i < Files.Length; i++)
+ Files[i] = (byte[])Backup[i].Clone();
+ }
+}
diff --git a/pkNX.Containers/FileMitm.cs b/pkNX.Containers/FileMitm.cs
index dd62a41e..b5d99f96 100644
--- a/pkNX.Containers/FileMitm.cs
+++ b/pkNX.Containers/FileMitm.cs
@@ -1,59 +1,65 @@
-using System.IO;
+using System;
+using System.IO;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+///
+/// Man in the middle redirection of file r/w requests
+///
+public static class FileMitm
{
- ///
- /// Man in the middle redirection of file r/w requests
- ///
- public static class FileMitm
+ public static bool Enabled { get; private set; }
+
+ public static void EnableIfSetup() => Enabled = PathOriginal != null;
+ public static void Disable() => Enabled = false;
+
+ private static string? PathOriginal;
+ private static string? PathRedirect;
+
+ public static byte[] ReadAllBytes(string path)
{
- public static bool Enabled { get; private set; }
-
- public static void EnableIfSetup() => Enabled = PathOriginal != null;
- public static void Disable() => Enabled = false;
-
- private static string? PathOriginal;
- private static string? PathRedirect;
-
- public static byte[] ReadAllBytes(string path)
- {
- path = GetRedirectedReadPath(path);
- return File.ReadAllBytes(path);
- }
-
- public static void WriteAllBytes(string path, byte[] data)
- {
- if (string.IsNullOrWhiteSpace(path))
- throw new FileNotFoundException("Invalid filename.");
- path = GetRedirectedWritePath(path);
- File.WriteAllBytes(path, data);
- }
-
- public static string GetRedirectedReadPath(string path)
- {
- if (!Enabled)
- return path;
- var newDest = path.Replace(PathOriginal, PathRedirect);
- if (!File.Exists(newDest))
- return path;
- return newDest;
- }
-
- public static string GetRedirectedWritePath(string path)
- {
- if (!Enabled)
- return path;
- var newDest = path.Replace(PathOriginal, PathRedirect);
- var parent = Path.GetDirectoryName(newDest);
- Directory.CreateDirectory(parent);
- return newDest;
- }
-
- public static void SetRedirect(string original, string dest)
- {
- PathOriginal = original;
- PathRedirect = dest;
- Enabled = true;
- }
+ path = GetRedirectedReadPath(path);
+ return File.ReadAllBytes(path);
}
-}
\ No newline at end of file
+
+ public static void WriteAllBytes(string path, byte[] data)
+ {
+ if (string.IsNullOrWhiteSpace(path))
+ throw new FileNotFoundException("Invalid filename.");
+ path = GetRedirectedWritePath(path);
+ File.WriteAllBytes(path, data);
+ }
+
+ public static string GetRedirectedReadPath(string path)
+ {
+ if (!Enabled)
+ return path;
+ if (PathOriginal is null)
+ throw new ArgumentException("No original path specified.");
+ var newDest = path.Replace(PathOriginal, PathRedirect);
+ if (!File.Exists(newDest))
+ return path;
+ return newDest;
+ }
+
+ public static string GetRedirectedWritePath(string path)
+ {
+ if (!Enabled)
+ return path;
+ if (PathOriginal is null)
+ throw new ArgumentException("No original path specified.");
+ var newDest = path.Replace(PathOriginal, PathRedirect);
+ var parent = Path.GetDirectoryName(newDest);
+ if (parent is null)
+ throw new ArgumentException("Invalid path specified.");
+ Directory.CreateDirectory(parent);
+ return newDest;
+ }
+
+ public static void SetRedirect(string original, string dest)
+ {
+ PathOriginal = original;
+ PathRedirect = dest;
+ Enabled = true;
+ }
+}
diff --git a/pkNX.Containers/FolderContainer.cs b/pkNX.Containers/FolderContainer.cs
index bdba1751..8eac08dc 100644
--- a/pkNX.Containers/FolderContainer.cs
+++ b/pkNX.Containers/FolderContainer.cs
@@ -1,126 +1,127 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+public class FolderContainer : IFileContainer
{
- public class FolderContainer : IFileContainer
+ private readonly List Paths = new();
+ private readonly List Data = new();
+ private readonly List TrackModify = new();
+
+ public string? FilePath { get; set; }
+
+ public FolderContainer() { }
+ public FolderContainer(IEnumerable files) => AddFiles(files);
+
+ public FolderContainer(string path) => FilePath = path;
+ public FolderContainer(string path, Func filter) : this(path) => Initialize(filter);
+
+ public IReadOnlyList GetPaths() => Paths;
+
+ public void Initialize(Func? filter = null)
{
- private readonly List Paths = new();
- private readonly List Data = new();
- private readonly List TrackModify = new();
+ if (Paths.Count > 0)
+ return; // already initialized
+ if (FilePath is null)
+ throw new ArgumentException("No path specified to initialize from.");
+ IEnumerable files = Directory.GetFiles(FilePath, "*", SearchOption.AllDirectories);
+ if (filter != null)
+ files = files.Where(filter);
+ files = files.OrderBy(z => Path.GetFileName(z).Length); // alphabetical sorting doesn't play nice with 100 & 1000
+ AddFiles(files);
+ }
- public string? FilePath { get; set; }
+ public void AddFile(string file, byte[]? data = null)
+ {
+ Paths.Add(file);
+ Data.Add(data);
+ TrackModify.Add(false);
+ }
- public FolderContainer() { }
- public FolderContainer(IEnumerable files) => AddFiles(files);
+ public void AddFiles(IEnumerable files)
+ {
+ foreach (var f in files)
+ AddFile(f);
+ }
- public FolderContainer(string path) => FilePath = path;
- public FolderContainer(string path, Func filter) : this(path) => Initialize(filter);
+ public byte[]? GetFileData(string file)
+ {
+ var index = Paths.FindIndex(z => Path.GetFileName(z) == file);
+ if (index < 0)
+ return null;
+ string path = Paths[index];
+ var data = Data[index] ??= FileMitm.ReadAllBytes(path);
+ return (byte[])data.Clone();
+ }
- public IReadOnlyList GetPaths() => Paths;
+ public byte[] GetFileData(int index)
+ {
+ var data = Data[index] ??= FileMitm.ReadAllBytes(Paths[index]);
+ return (byte[])data.Clone();
+ }
- public void Initialize(Func? filter = null)
+ public byte[] this[int index]
+ {
+ get => GetFileData(index);
+ set
{
- if (Paths.Count > 0)
- return; // already initialized
- IEnumerable files = Directory.GetFiles(FilePath, "*", SearchOption.AllDirectories);
- if (filter != null)
- files = files.Where(filter);
- files = files.OrderBy(z => Path.GetFileName(z).Length); // alphabetical sorting doesn't play nice with 100 & 1000
- AddFiles(files);
- }
+ var current = Data[index] ??= GetFileData(index);
+ TrackModify[index] = !value.SequenceEqual(current);
- public void AddFile(string file, byte[]? data = null)
- {
- Paths.Add(file);
- Data.Add(data);
- TrackModify.Add(false);
- }
-
- public void AddFiles(IEnumerable files)
- {
- foreach (var f in files)
- AddFile(f);
- }
-
- public byte[]? GetFileData(string file)
- {
- var index = Paths.FindIndex(z => Path.GetFileName(z) == file);
- if (index < 0)
- return null;
- string path = Paths[index];
- var data = Data[index] ??= FileMitm.ReadAllBytes(path);
- return (byte[])data.Clone();
- }
-
- public byte[] GetFileData(int index)
- {
- var data = Data[index] ??= FileMitm.ReadAllBytes(Paths[index]);
- return (byte[])data.Clone();
- }
-
- public byte[] this[int index]
- {
- get => GetFileData(index);
- set
- {
- var current = Data[index] ??= GetFileData(index);
- TrackModify[index] = !value.SequenceEqual(current);
-
- Data[index] = value;
- }
- }
-
- public void ResetIndex(int index)
- {
- Data[index] = null;
- TrackModify[index] = false;
- }
-
- public string GetFileName(int index) => Paths[index];
-
- public bool Modified
- {
- get => TrackModify.Count(z => z) != 0;
- set => CancelEdits();
- }
-
- public int Count => Paths.Count;
-
- public Task GetFiles() => Task.FromResult(Paths.Select(FileMitm.ReadAllBytes).ToArray());
- public Task GetFile(int file, int subFile = 0) => Task.FromResult(this[file]);
- public Task SetFile(int file, byte[] value, int subFile = 0) => Task.FromResult(this[file] = value);
- public Task SaveAs(string path, ContainerHandler handler, CancellationToken token) => new(SaveAll, token);
-
- private void SaveAll()
- {
- for (int i = 0; i < Paths.Count; i++)
- {
- if (!TrackModify[i])
- continue;
- var data = Data[i];
- if (data == null)
- continue;
- FileMitm.WriteAllBytes(Paths[i], data);
- }
- }
-
- public void CancelEdits()
- {
- for (int i = 0; i < TrackModify.Count; i++)
- {
- TrackModify[i] = false;
- Data[i] = null;
- }
- }
-
- public void Dump(string path, ContainerHandler handler)
- {
- SaveAll(); // there's really nothing to dump, just save any modified
+ Data[index] = value;
}
}
+
+ public void ResetIndex(int index)
+ {
+ Data[index] = null;
+ TrackModify[index] = false;
+ }
+
+ public string GetFileName(int index) => Paths[index];
+
+ public bool Modified
+ {
+ get => TrackModify.Contains(true);
+ set => CancelEdits();
+ }
+
+ public int Count => Paths.Count;
+
+ public Task GetFiles() => Task.FromResult(Paths.Select(FileMitm.ReadAllBytes).ToArray());
+ public Task GetFile(int file, int subFile = 0) => Task.FromResult(this[file]);
+ public Task SetFile(int file, byte[] value, int subFile = 0) => Task.FromResult(this[file] = value);
+ public Task SaveAs(string path, ContainerHandler handler, CancellationToken token) => new(SaveAll, token);
+
+ private void SaveAll()
+ {
+ for (int i = 0; i < Paths.Count; i++)
+ {
+ if (!TrackModify[i])
+ continue;
+ var data = Data[i];
+ if (data == null)
+ continue;
+ FileMitm.WriteAllBytes(Paths[i], data);
+ }
+ }
+
+ public void CancelEdits()
+ {
+ for (int i = 0; i < TrackModify.Count; i++)
+ {
+ TrackModify[i] = false;
+ Data[i] = null;
+ }
+ }
+
+ public void Dump(string path, ContainerHandler handler)
+ {
+ SaveAll(); // there's really nothing to dump, just save any modified
+ }
}
diff --git a/pkNX.Containers/GARC/FATB.cs b/pkNX.Containers/GARC/FATB.cs
index 9e624d33..36558284 100644
--- a/pkNX.Containers/GARC/FATB.cs
+++ b/pkNX.Containers/GARC/FATB.cs
@@ -1,54 +1,53 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.IO;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+internal class FATB
{
- internal class FATB
+ private const uint MAGIC = 0x46415442;
+
+ private readonly uint Magic = MAGIC;
+ private readonly int HeaderSize = 0xC;
+ private readonly ushort EntryCount;
+ private readonly short Padding = -1;
+
+ private FATBEntry[] Entries { get; }
+ public FATBEntry this[int index] => Entries[index];
+
+ public FATB(BinaryReader br, int DataOffset)
{
- private const uint MAGIC = 0x46415442;
+ Magic = br.ReadUInt32();
+ HeaderSize = br.ReadInt32();
+ EntryCount = br.ReadUInt16();
+ Padding = br.ReadInt16();
- private readonly uint Magic = MAGIC;
- private readonly int HeaderSize = 0xC;
- private readonly ushort EntryCount;
- private readonly short Padding = -1;
+ Entries = new FATBEntry[EntryCount];
+ for (int i = 0; i < EntryCount; i++)
+ Entries[i] = new FATBEntry(br, DataOffset);
+ }
- private FATBEntry[] Entries { get; }
- public FATBEntry this[int index] => Entries[index];
-
- public FATB(BinaryReader br, int DataOffset)
+ public FATB(IReadOnlyList files)
+ {
+ EntryCount = (ushort)files.Count;
+ Entries = new FATBEntry[EntryCount];
+ for (int i = 0; i < EntryCount; i++)
{
- Magic = br.ReadUInt32();
- HeaderSize = br.ReadInt32();
- EntryCount = br.ReadUInt16();
- Padding = br.ReadInt16();
-
- Entries = new FATBEntry[EntryCount];
- for (int i = 0; i < EntryCount; i++)
- Entries[i] = new FATBEntry(br, DataOffset);
- }
-
- public FATB(IReadOnlyList files)
- {
- EntryCount = (ushort)files.Count;
- Entries = new FATBEntry[EntryCount];
- for (int i = 0; i < EntryCount; i++)
- {
- if (!Directory.Exists(files[i]))
- Entries[i] = new FATBEntry(files[i]);
- else
- Entries[i] = new FATBEntry(Directory.GetFiles(files[i]));
- }
- }
-
- public void Write(BinaryWriter bw)
- {
- bw.Write(Magic);
- bw.Write(HeaderSize);
- bw.Write(EntryCount);
- bw.Write(Padding);
- foreach (var entry in Entries)
- entry.Write(bw);
- // sub entry writing handled separately
+ if (!Directory.Exists(files[i]))
+ Entries[i] = new FATBEntry(files[i]);
+ else
+ Entries[i] = new FATBEntry(Directory.GetFiles(files[i]));
}
}
-}
\ No newline at end of file
+
+ public void Write(BinaryWriter bw)
+ {
+ bw.Write(Magic);
+ bw.Write(HeaderSize);
+ bw.Write(EntryCount);
+ bw.Write(Padding);
+ foreach (var entry in Entries)
+ entry.Write(bw);
+ // sub entry writing handled separately
+ }
+}
diff --git a/pkNX.Containers/GARC/FATBEntry.cs b/pkNX.Containers/GARC/FATBEntry.cs
index 06068fb9..e9f89096 100644
--- a/pkNX.Containers/GARC/FATBEntry.cs
+++ b/pkNX.Containers/GARC/FATBEntry.cs
@@ -1,127 +1,126 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+internal class FATBEntry
{
- internal class FATBEntry
+ private readonly uint Vector;
+ public readonly bool IsFolder;
+ public readonly FATBSubEntry[] SubEntries;
+
+ private FATBEntry()
{
- private readonly uint Vector;
- public readonly bool IsFolder;
- public readonly FATBSubEntry[] SubEntries;
+ var sub = SubEntries = new FATBSubEntry[32];
+ for (int i = 0; i < sub.Length; i++)
+ sub[i] = new FATBSubEntry();
+ }
- private FATBEntry()
+ public FATBEntry(string file) : this()
+ {
+ IsFolder = false;
+ Vector = 1;
+ SubEntries[0].Exists = true;
+ SubEntries[0].File = file;
+ }
+
+ public FATBEntry(IEnumerable files) : this()
+ {
+ IsFolder = true;
+ Vector = 0;
+ foreach (var f in files)
{
- var sub = SubEntries = new FATBSubEntry[32];
- for (int i = 0; i < sub.Length; i++)
- sub[i] = new FATBSubEntry();
- }
+ var fn = Path.GetFileNameWithoutExtension(f);
+ if (!int.TryParse(fn, out var val) || val >= SubEntries.Length)
+ continue;
+ if (FATBSubEntry.GetFileNumber(val) != fn)
+ continue;
- public FATBEntry(string file) : this()
- {
- IsFolder = false;
- Vector = 1;
- SubEntries[0].Exists = true;
- SubEntries[0].File = file;
- }
+ Vector |= (uint)(1 << val);
- public FATBEntry(IEnumerable files) : this()
- {
- IsFolder = true;
- Vector = 0;
- foreach (var f in files)
- {
- var fn = Path.GetFileNameWithoutExtension(f);
- if (!int.TryParse(fn, out var val) || val >= SubEntries.Length)
- continue;
- if (FATBSubEntry.GetFileNumber(val) != fn)
- continue;
-
- Vector |= (uint)(1 << val);
-
- SubEntries[val].Exists = true;
- SubEntries[val].File = f;
- }
- }
-
- public FATBEntry(BinaryReader br, int DataOffset) : this()
- {
- Vector = br.ReadUInt32();
-
- int ctr = 0;
- for (int b = 0; b < 32; b++)
- {
- SubEntries[b].Exists = (Vector & 1 << b) != 0;
- if (!SubEntries[b].Exists)
- continue;
- SubEntries[b].Start = br.ReadInt32();
- SubEntries[b].End = br.ReadInt32();
- SubEntries[b].Length = br.ReadInt32();
- SubEntries[b].ParentDataPosition = DataOffset;
- ctr++;
- }
- IsFolder = ctr > 1;
- }
-
- public void Write(BinaryWriter bw)
- {
- bw.Write(Vector);
- foreach (var s in SubEntries.Where(s => s.Exists))
- {
- bw.Write(s.Start);
- bw.Write(s.End);
- bw.Write(s.Length);
- }
- }
-
- public void WriteEntries(BinaryWriter bw, Stream originalStream, int padToNearest, int DataOffset, ref int max, ref int maxPad)
- {
- var dest = bw.BaseStream;
-
- bw.Write(Vector);
- foreach (var entry in SubEntries.Where(z => z.Exists))
- {
- entry.Write(originalStream, dest, DataOffset);
-
- int padding = GetPadding(entry.Length, padToNearest);
- if (max < entry.Length)
- {
- max = entry.Length;
- maxPad = entry.Length + padding;
- }
-
- while (padding-- > 0)
- bw.Write((byte)0xFF);
- }
- }
-
- private static int GetPadding(int length, int padTo)
- {
- var remain = length % padTo;
- return remain == 0 ? 0 : padTo - remain;
- }
-
- public void Dump(string path, int index, string format, Stream parent, int DataOffset)
- {
- var fn = index.ToString(format);
- if (!IsFolder)
- {
- var loc = Path.Combine(path, fn + ".bin");
- SubEntries[0].Dump(parent, loc, DataOffset);
- return;
- }
-
- path = Path.Combine(path, fn);
- Directory.CreateDirectory(path);
- for (var i = 0; i < SubEntries.Length; i++)
- {
- var entry = SubEntries[i];
- if (!entry.Exists)
- continue;
- fn = i.ToString("D2");
- var loc = Path.Combine(path, fn + ".bin");
- entry.Dump(parent, loc, DataOffset);
- }
+ SubEntries[val].Exists = true;
+ SubEntries[val].File = f;
}
}
-}
\ No newline at end of file
+
+ public FATBEntry(BinaryReader br, int DataOffset) : this()
+ {
+ Vector = br.ReadUInt32();
+
+ int ctr = 0;
+ for (int b = 0; b < 32; b++)
+ {
+ SubEntries[b].Exists = (Vector & 1 << b) != 0;
+ if (!SubEntries[b].Exists)
+ continue;
+ SubEntries[b].Start = br.ReadInt32();
+ SubEntries[b].End = br.ReadInt32();
+ SubEntries[b].Length = br.ReadInt32();
+ SubEntries[b].ParentDataPosition = DataOffset;
+ ctr++;
+ }
+ IsFolder = ctr > 1;
+ }
+
+ public void Write(BinaryWriter bw)
+ {
+ bw.Write(Vector);
+ foreach (var s in SubEntries.Where(s => s.Exists))
+ {
+ bw.Write(s.Start);
+ bw.Write(s.End);
+ bw.Write(s.Length);
+ }
+ }
+
+ public void WriteEntries(BinaryWriter bw, Stream originalStream, int padToNearest, int DataOffset, ref int max, ref int maxPad)
+ {
+ var dest = bw.BaseStream;
+
+ bw.Write(Vector);
+ foreach (var entry in SubEntries.Where(z => z.Exists))
+ {
+ entry.Write(originalStream, dest, DataOffset);
+
+ int padding = GetPadding(entry.Length, padToNearest);
+ if (max < entry.Length)
+ {
+ max = entry.Length;
+ maxPad = entry.Length + padding;
+ }
+
+ while (padding-- > 0)
+ bw.Write((byte)0xFF);
+ }
+ }
+
+ private static int GetPadding(int length, int padTo)
+ {
+ var remain = length % padTo;
+ return remain == 0 ? 0 : padTo - remain;
+ }
+
+ public void Dump(string path, int index, string format, Stream parent, int DataOffset)
+ {
+ var fn = index.ToString(format);
+ if (!IsFolder)
+ {
+ var loc = Path.Combine(path, fn + ".bin");
+ SubEntries[0].Dump(parent, loc, DataOffset);
+ return;
+ }
+
+ path = Path.Combine(path, fn);
+ Directory.CreateDirectory(path);
+ for (var i = 0; i < SubEntries.Length; i++)
+ {
+ var entry = SubEntries[i];
+ if (!entry.Exists)
+ continue;
+ fn = i.ToString("D2");
+ var loc = Path.Combine(path, fn + ".bin");
+ entry.Dump(parent, loc, DataOffset);
+ }
+ }
+}
diff --git a/pkNX.Containers/GARC/FATBSubEntry.cs b/pkNX.Containers/GARC/FATBSubEntry.cs
index 0c97dc02..b7ca57d4 100644
--- a/pkNX.Containers/GARC/FATBSubEntry.cs
+++ b/pkNX.Containers/GARC/FATBSubEntry.cs
@@ -1,8 +1,7 @@
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+public class FATBSubEntry : LargeContainerEntry
{
- public class FATBSubEntry : LargeContainerEntry
- {
- public bool Exists;
- public static string GetFileNumber(int index) => $"{index:00}";
- }
-}
\ No newline at end of file
+ public bool Exists;
+ public static string GetFileNumber(int index) => $"{index:00}";
+}
diff --git a/pkNX.Containers/GARC/FATO.cs b/pkNX.Containers/GARC/FATO.cs
index 9b166b84..9fd9e484 100644
--- a/pkNX.Containers/GARC/FATO.cs
+++ b/pkNX.Containers/GARC/FATO.cs
@@ -1,47 +1,46 @@
-using System.IO;
+using System.IO;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+internal class FATO
{
- internal class FATO
+ private const uint MAGIC = 0x4641544F;
+
+ private readonly uint Magic = MAGIC;
+ private readonly int HeaderSize = 0xC;
+ public readonly ushort EntryCount;
+ private readonly short Padding = -1;
+
+ private FATOEntry[] Entries { get; }
+ public FATOEntry this[int index] => Entries[index];
+
+ public FATO(BinaryReader br)
{
- private const uint MAGIC = 0x4641544F;
+ Magic = br.ReadUInt32();
+ HeaderSize = br.ReadInt32();
+ EntryCount = br.ReadUInt16();
+ Padding = br.ReadInt16();
- private readonly uint Magic = MAGIC;
- private readonly int HeaderSize = 0xC;
- public readonly ushort EntryCount;
- private readonly short Padding = -1;
-
- private FATOEntry[] Entries { get; }
- public FATOEntry this[int index] => Entries[index];
-
- public FATO(BinaryReader br)
- {
- Magic = br.ReadUInt32();
- HeaderSize = br.ReadInt32();
- EntryCount = br.ReadUInt16();
- Padding = br.ReadInt16();
-
- Entries = new FATOEntry[EntryCount];
- for (int i = 0; i < EntryCount; i++)
- Entries[i] = new FATOEntry(br);
- }
-
- public FATO(int count)
- {
- EntryCount = (ushort)count;
- Entries = new FATOEntry[EntryCount];
- for (int i = 0; i < EntryCount; i++)
- Entries[i] = new FATOEntry();
- }
-
- public void Write(BinaryWriter bw)
- {
- bw.Write(Magic);
- bw.Write(HeaderSize);
- bw.Write(EntryCount);
- bw.Write(Padding);
- foreach (var entry in Entries)
- entry.Write(bw);
- }
+ Entries = new FATOEntry[EntryCount];
+ for (int i = 0; i < EntryCount; i++)
+ Entries[i] = new FATOEntry(br);
}
-}
\ No newline at end of file
+
+ public FATO(int count)
+ {
+ EntryCount = (ushort)count;
+ Entries = new FATOEntry[EntryCount];
+ for (int i = 0; i < EntryCount; i++)
+ Entries[i] = new FATOEntry();
+ }
+
+ public void Write(BinaryWriter bw)
+ {
+ bw.Write(Magic);
+ bw.Write(HeaderSize);
+ bw.Write(EntryCount);
+ bw.Write(Padding);
+ foreach (var entry in Entries)
+ entry.Write(bw);
+ }
+}
diff --git a/pkNX.Containers/GARC/FATOEntry.cs b/pkNX.Containers/GARC/FATOEntry.cs
index baf5894e..7356b101 100644
--- a/pkNX.Containers/GARC/FATOEntry.cs
+++ b/pkNX.Containers/GARC/FATOEntry.cs
@@ -1,14 +1,13 @@
-using System.IO;
+using System.IO;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+internal class FATOEntry
{
- internal class FATOEntry
- {
- public uint Offset { get; set; }
+ public uint Offset { get; set; }
- internal FATOEntry(uint offset = 0) => Offset = offset;
- internal FATOEntry(BinaryReader br) => Offset = br.ReadUInt32();
+ internal FATOEntry(uint offset = 0) => Offset = offset;
+ internal FATOEntry(BinaryReader br) => Offset = br.ReadUInt32();
- internal void Write(BinaryWriter bw) => bw.Write(Offset);
- }
-}
\ No newline at end of file
+ internal void Write(BinaryWriter bw) => bw.Write(Offset);
+}
diff --git a/pkNX.Containers/GARC/GARC.cs b/pkNX.Containers/GARC/GARC.cs
index 8592f6c1..668c98f5 100644
--- a/pkNX.Containers/GARC/GARC.cs
+++ b/pkNX.Containers/GARC/GARC.cs
@@ -1,135 +1,134 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+public class GARC : LargeContainer
{
- public class GARC : LargeContainer
- {
- private GARCHeader Header;
- private FATO FATO;
- private FATB FATB;
+ private GARCHeader Header;
+ private FATO FATO;
+ private FATB FATB;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
- public GARC(BinaryReader br) => OpenRead(br);
- public GARC(string file) => OpenBinary(file);
+ public GARC(BinaryReader br) => OpenRead(br);
+ public GARC(string file) => OpenBinary(file);
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
- public GARC(IReadOnlyList files, GARCVersion version = GARCVersion.VER_6)
- {
- Header = new GARCHeader(version);
- FATO = new FATO(files.Count);
- FATB = new FATB(files);
- Files = new byte[]?[files.Count];
- }
+ public GARC(IReadOnlyList files, GARCVersion version = GARCVersion.VER_6)
+ {
+ Header = new GARCHeader(version);
+ FATO = new FATO(files.Count);
+ FATB = new FATB(files);
+ Files = new byte[]?[files.Count];
+ }
- protected override void Initialize()
- {
- Reader!.BaseStream.Position = 0;
- Header = new GARCHeader(Reader);
- FATO = new FATO(Reader);
- FATB = new FATB(Reader, Header.DataOffset);
- Files = new byte[]?[FATO.EntryCount];
- }
+ protected override void Initialize()
+ {
+ Reader!.BaseStream.Position = 0;
+ Header = new GARCHeader(Reader);
+ FATO = new FATO(Reader);
+ FATB = new FATB(Reader, Header.DataOffset);
+ Files = new byte[]?[FATO.EntryCount];
+ }
- protected override int GetFileOffset(int file, int subFile = 0)
- {
- var f = FATB[file].SubEntries[subFile];
- return f.Start;
- }
+ protected override int GetFileOffset(int file, int subFile = 0)
+ {
+ var f = FATB[file].SubEntries[subFile];
+ return f.Start;
+ }
- public override byte[] GetEntry(int index, int subFile)
- {
- var f = FATB[index].SubEntries[subFile];
- if (f.File is byte[] data)
- return data;
-
- data = f.GetFileData(Reader!.BaseStream);
- f.File = data; // cache for future fetches
+ public override byte[] GetEntry(int index, int subFile)
+ {
+ var f = FATB[index].SubEntries[subFile];
+ if (f.File is byte[] data)
return data;
- }
- public override void SetEntry(int index, byte[]? value, int subFile)
+ data = f.GetFileData(Reader!.BaseStream);
+ f.File = data; // cache for future fetches
+ return data;
+ }
+
+ public override void SetEntry(int index, byte[]? value, int subFile)
+ {
+ Modified |= value != null && !GetEntry(index, subFile).SequenceEqual(value);
+ var f = FATB[index].SubEntries[subFile];
+ f.File = value;
+ }
+
+ protected override Task Pack(BinaryWriter bw, ContainerHandler handler, CancellationToken token)
+ {
+ return Task.Run(() => PackGARC(bw, handler, token), token);
+ }
+
+ private void PackGARC(BinaryWriter bw, ContainerHandler handler, CancellationToken token)
+ {
+ StartPack();
+ handler.Initialize(Count);
+
+ WriteIntro(bw);
+ int dataOffset = (int)bw.BaseStream.Position;
+ for (int i = 0; i < Count; i++)
{
- Modified |= value != null && !GetEntry(index, subFile).SequenceEqual(value);
- var f = FATB[index].SubEntries[subFile];
- f.File = value;
+ if (token.IsCancellationRequested)
+ return;
+ WriteEntry(bw, i, dataOffset);
}
+ WriteIntro(bw, true);
+ bw.Flush();
+ }
- protected override Task Pack(BinaryWriter bw, ContainerHandler handler, CancellationToken token)
+ private void StartPack()
+ {
+ Header.ContentLargestUnpadded = 0;
+ Header.ContentLargestPadded = 0;
+ }
+
+ private void WriteIntro(BinaryWriter bw, bool lastPass = false)
+ {
+ Header.Write(bw);
+ FATO.Write(bw);
+ FATB.Write(bw);
+ if (lastPass)
+ Header.DataOffset = (int)bw.BaseStream.Position;
+ }
+
+ private void WriteEntry(BinaryWriter bw, int i, int DataOffset)
+ {
+ FATO[i].Offset = (uint)bw.BaseStream.Position;
+ FATB[i].WriteEntries(bw, Reader!.BaseStream, Header.ContentPadToNearest, DataOffset,
+ ref Header.ContentLargestUnpadded, ref Header.ContentLargestPadded);
+ }
+
+ public override void Dump(string path, ContainerHandler handler)
+ {
+ string format = this.GetFileFormatString();
+
+ if (!Directory.Exists(path))
+ Directory.CreateDirectory(path);
+
+ handler.Initialize(Count);
+ for (int i = 0; i < Count; i++)
{
- return Task.Run(() => PackGARC(bw, handler, token), token);
- }
-
- private void PackGARC(BinaryWriter bw, ContainerHandler handler, CancellationToken token)
- {
- StartPack();
- handler.Initialize(Count);
-
- WriteIntro(bw);
- int dataOffset = (int)bw.BaseStream.Position;
- for (int i = 0; i < Count; i++)
- {
- if (token.IsCancellationRequested)
- return;
- WriteEntry(bw, i, dataOffset);
- }
- WriteIntro(bw, true);
- bw.Flush();
- }
-
- private void StartPack()
- {
- Header.ContentLargestUnpadded = 0;
- Header.ContentLargestPadded = 0;
- }
-
- private void WriteIntro(BinaryWriter bw, bool lastPass = false)
- {
- Header.Write(bw);
- FATO.Write(bw);
- FATB.Write(bw);
- if (lastPass)
- Header.DataOffset = (int)bw.BaseStream.Position;
- }
-
- private void WriteEntry(BinaryWriter bw, int i, int DataOffset)
- {
- FATO[i].Offset = (uint)bw.BaseStream.Position;
- FATB[i].WriteEntries(bw, Reader!.BaseStream, Header.ContentPadToNearest, DataOffset,
- ref Header.ContentLargestUnpadded, ref Header.ContentLargestPadded);
- }
-
- public override void Dump(string path, ContainerHandler handler)
- {
- string format = this.GetFileFormatString();
-
- if (!Directory.Exists(path))
- Directory.CreateDirectory(path);
-
- handler.Initialize(Count);
- for (int i = 0; i < Count; i++)
- {
- FATB[i].Dump(path, i, format, Reader!.BaseStream, Header.DataOffset);
- handler.StepFile(i+1);
- }
- }
-
- public static GARC? GetGARC(BinaryReader br)
- {
- if (br.BaseStream.Length < 20)
- return null;
- br.BaseStream.Position = 0;
- if (br.ReadUInt32() != GARCHeader.MAGIC)
- return null;
- br.BaseStream.Position = 10;
- var ver = br.ReadUInt16();
-
- if (ver is 0x0600 or 0x0400)
- return new GARC(br);
- return null;
+ FATB[i].Dump(path, i, format, Reader!.BaseStream, Header.DataOffset);
+ handler.StepFile(i+1);
}
}
+
+ public static GARC? GetGARC(BinaryReader br)
+ {
+ if (br.BaseStream.Length < 20)
+ return null;
+ br.BaseStream.Position = 0;
+ if (br.ReadUInt32() != GARCHeader.MAGIC)
+ return null;
+ br.BaseStream.Position = 10;
+ var ver = br.ReadUInt16();
+
+ if (ver is 0x0600 or 0x0400)
+ return new GARC(br);
+ return null;
+ }
}
diff --git a/pkNX.Containers/GARC/GARCHeader.cs b/pkNX.Containers/GARC/GARCHeader.cs
index 376e15b1..8f4004b9 100644
--- a/pkNX.Containers/GARC/GARCHeader.cs
+++ b/pkNX.Containers/GARC/GARCHeader.cs
@@ -1,98 +1,97 @@
-using System;
+using System;
using System.IO;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+public class GARCHeader
{
- public class GARCHeader
+ internal const uint MAGIC = 0x4E415243; // CRAG
+ private const ushort VER_4 = (ushort)GARCVersion.VER_4;
+ private const ushort VER_6 = (ushort)GARCVersion.VER_6;
+
+ private readonly uint Magic = MAGIC;
+ private readonly int HeaderSize;
+ private readonly ushort Endianess = 0xFEFF;
+ private readonly ushort Version;
+ private readonly uint ChunkCount = 4;
+
+ public int DataOffset { get; set; }
+ public uint FileSize { get; }
+
+ public readonly int ContentPadToNearest = 4; // Format 6 Only (4 bytes is standard in VER_4, and is not stored)
+ public int ContentLargestUnpadded;
+ public int ContentLargestPadded; // Format 6 Only
+
+ public bool VER6 => Version == VER_6;
+ public bool VER4 => Version == VER_4;
+
+ public GARCHeader(GARCVersion version)
{
- internal const uint MAGIC = 0x4E415243; // CRAG
- private const ushort VER_4 = (ushort)GARCVersion.VER_4;
- private const ushort VER_6 = (ushort)GARCVersion.VER_6;
-
- private readonly uint Magic = MAGIC;
- private readonly int HeaderSize;
- private readonly ushort Endianess = 0xFEFF;
- private readonly ushort Version;
- private readonly uint ChunkCount = 4;
-
- public int DataOffset { get; set; }
- public uint FileSize { get; }
-
- public readonly int ContentPadToNearest = 4; // Format 6 Only (4 bytes is standard in VER_4, and is not stored)
- public int ContentLargestUnpadded;
- public int ContentLargestPadded; // Format 6 Only
-
- public bool VER6 => Version == VER_6;
- public bool VER4 => Version == VER_4;
-
- public GARCHeader(GARCVersion version)
+ Version = version switch
{
- Version = version switch
- {
- GARCVersion.VER_6 => VER_6,
- GARCVersion.VER_4 => VER_4,
- _ => (ushort)version,
- };
- HeaderSize = VER6 ? 0x24 : 0x1C;
- }
+ GARCVersion.VER_6 => VER_6,
+ GARCVersion.VER_4 => VER_4,
+ _ => (ushort)version,
+ };
+ HeaderSize = VER6 ? 0x24 : 0x1C;
+ }
- public GARCHeader(BinaryReader br)
+ public GARCHeader(BinaryReader br)
+ {
+ Magic = br.ReadUInt32();
+ HeaderSize = br.ReadInt32();
+ Endianess = br.ReadUInt16();
+ Version = br.ReadUInt16();
+ ChunkCount = br.ReadUInt32();
+
+ if (ChunkCount != 4)
+ throw new FormatException($"Invalid GARC Chunk Count: {ChunkCount}");
+
+ DataOffset = br.ReadInt32();
+ FileSize = br.ReadUInt32();
+
+ switch (Version)
{
- Magic = br.ReadUInt32();
- HeaderSize = br.ReadInt32();
- Endianess = br.ReadUInt16();
- Version = br.ReadUInt16();
- ChunkCount = br.ReadUInt32();
-
- if (ChunkCount != 4)
- throw new FormatException($"Invalid GARC Chunk Count: {ChunkCount}");
-
- DataOffset = br.ReadInt32();
- FileSize = br.ReadUInt32();
-
- switch (Version)
- {
- case VER_4:
- ContentLargestUnpadded = br.ReadInt32();
- ContentPadToNearest = 4;
- break;
- case VER_6:
- ContentLargestPadded = br.ReadInt32();
- ContentLargestUnpadded = br.ReadInt32();
- ContentPadToNearest = br.ReadInt32();
- break;
- default:
- throw new FormatException($"Invalid GARC Version: 0x{Version:X4}");
- }
- }
-
- public void Write(BinaryWriter bw)
- {
- bw.Write(Magic);
- bw.Write(HeaderSize);
- bw.Write(Endianess);
- bw.Write(Version);
- bw.Write(ChunkCount);
-
- if (ChunkCount != 4)
- throw new FormatException($"Invalid GARC Chunk Count: {ChunkCount}");
-
- bw.Write(DataOffset);
- bw.Write(FileSize);
-
- switch (Version)
- {
- case VER_4:
- bw.Write(ContentLargestUnpadded);
- break;
- case VER_6:
- bw.Write(ContentLargestPadded);
- bw.Write(ContentLargestUnpadded);
- bw.Write(ContentPadToNearest);
- break;
- default:
- throw new FormatException($"Invalid GARC Version: 0x{Version:X4}");
- }
+ case VER_4:
+ ContentLargestUnpadded = br.ReadInt32();
+ ContentPadToNearest = 4;
+ break;
+ case VER_6:
+ ContentLargestPadded = br.ReadInt32();
+ ContentLargestUnpadded = br.ReadInt32();
+ ContentPadToNearest = br.ReadInt32();
+ break;
+ default:
+ throw new FormatException($"Invalid GARC Version: 0x{Version:X4}");
}
}
-}
\ No newline at end of file
+
+ public void Write(BinaryWriter bw)
+ {
+ bw.Write(Magic);
+ bw.Write(HeaderSize);
+ bw.Write(Endianess);
+ bw.Write(Version);
+ bw.Write(ChunkCount);
+
+ if (ChunkCount != 4)
+ throw new FormatException($"Invalid GARC Chunk Count: {ChunkCount}");
+
+ bw.Write(DataOffset);
+ bw.Write(FileSize);
+
+ switch (Version)
+ {
+ case VER_4:
+ bw.Write(ContentLargestUnpadded);
+ break;
+ case VER_6:
+ bw.Write(ContentLargestPadded);
+ bw.Write(ContentLargestUnpadded);
+ bw.Write(ContentPadToNearest);
+ break;
+ default:
+ throw new FormatException($"Invalid GARC Version: 0x{Version:X4}");
+ }
+ }
+}
diff --git a/pkNX.Containers/GARC/GARCVersion.cs b/pkNX.Containers/GARC/GARCVersion.cs
index 403f7320..8087748a 100644
--- a/pkNX.Containers/GARC/GARCVersion.cs
+++ b/pkNX.Containers/GARC/GARCVersion.cs
@@ -1,8 +1,7 @@
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+public enum GARCVersion : ushort
{
- public enum GARCVersion : ushort
- {
- VER_4 = 0x0400,
- VER_6 = 0x0600,
- }
-}
\ No newline at end of file
+ VER_4 = 0x0400,
+ VER_6 = 0x0600,
+}
diff --git a/pkNX.Containers/IFileContainer.cs b/pkNX.Containers/IFileContainer.cs
index 65ed294c..518c79b5 100644
--- a/pkNX.Containers/IFileContainer.cs
+++ b/pkNX.Containers/IFileContainer.cs
@@ -1,47 +1,46 @@
-using System;
+using System;
using System.Threading;
using System.Threading.Tasks;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+///
+/// Array of Files
+///
+public interface IFileContainer
{
///
- /// Array of Files
+ /// Path the was loaded from.
///
- public interface IFileContainer
- {
- ///
- /// Path the was loaded from.
- ///
- string? FilePath { get; set; }
+ string? FilePath { get; set; }
- ///
- /// Indication if the contents of the have been modified.
- ///
- bool Modified { get; set; }
+ ///
+ /// Indication if the contents of the have been modified.
+ ///
+ bool Modified { get; set; }
- ///
- /// Count of files inside the .
- ///
- int Count { get; }
+ ///
+ /// Count of files inside the .
+ ///
+ int Count { get; }
- ///
- /// File access for individually indexed files.
- ///
- /// File number to fetch
- /// Data representing the file at the specified index.
- byte[] this[int index] { get; set; }
+ ///
+ /// File access for individually indexed files.
+ ///
+ /// File number to fetch
+ /// Data representing the file at the specified index.
+ byte[] this[int index] { get; set; }
- Task GetFiles();
- Task GetFile(int file, int subFile = 0);
- Task SetFile(int file, byte[] value, int subFile = 0);
- Task SaveAs(string path, ContainerHandler handler, CancellationToken token);
+ Task GetFiles();
+ Task GetFile(int file, int subFile = 0);
+ Task SetFile(int file, byte[] value, int subFile = 0);
+ Task SaveAs(string path, ContainerHandler handler, CancellationToken token);
- void Dump(string path, ContainerHandler handler);
- void CancelEdits();
- }
-
- public static class FileContainerExtensions
- {
- public static string GetFileFormatString(this IFileContainer c) => "D" + Math.Ceiling(Math.Log10(c.Count));
- }
+ void Dump(string path, ContainerHandler handler);
+ void CancelEdits();
+}
+
+public static class FileContainerExtensions
+{
+ public static string GetFileFormatString(this IFileContainer c) => "D" + Math.Ceiling(Math.Log10(c.Count));
}
diff --git a/pkNX.Containers/LargeContainer.cs b/pkNX.Containers/LargeContainer.cs
index a531d440..0b2048fb 100644
--- a/pkNX.Containers/LargeContainer.cs
+++ b/pkNX.Containers/LargeContainer.cs
@@ -1,156 +1,155 @@
-using System;
+using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using pkNX.Containers;
-namespace pkNX
+namespace pkNX;
+
+///
+/// More complex container format for large long lived archives.
+///
+public abstract class LargeContainer : IDisposable, IFileContainer
{
+ public virtual int Count => Files.Length;
+
+ public Task GetFiles() => new(() => { CacheAll(); return Files!; });
+ public Task GetFile(int file, int subFile = 0) => new(() => GetEntry(file, subFile));
+ public Task SetFile(int file, byte[] value, int subFile = 0) => new(() => SetEntry(file, value, subFile));
+
+ public string? Extension => Path.GetExtension(FilePath);
+ public string? FileName => Path.GetFileName(FilePath);
+ public string? FilePath { get; set; }
+ public bool Modified { get; set; }
+
+ protected byte[]?[] Files = Array.Empty();
+
///
- /// More complex container format for large long lived archives.
+ /// Packs the to the specified writing stream.
///
- public abstract class LargeContainer : IDisposable, IFileContainer
+ /// Stream Writer to write contents to.
+ /// Manager for monitoring progress.
+ /// Cancellation object
+ /// Awaitable Task
+ protected abstract Task Pack(BinaryWriter bw, ContainerHandler handler, CancellationToken token);
+
+ #region File Reading
+
+ protected BinaryReader? Reader { get; private set; }
+ private Stream? Stream;
+
+ protected void OpenBinary(string path)
{
- public virtual int Count => Files.Length;
+ path = FileMitm.GetRedirectedReadPath(path);
+ Stream = new FileStream(path, FileMode.Open);
+ Reader = new BinaryReader(Stream);
+ Initialize();
+ }
- public Task GetFiles() => new(() => { CacheAll(); return Files!; });
- public Task GetFile(int file, int subFile = 0) => new(() => GetEntry(file, subFile));
- public Task SetFile(int file, byte[] value, int subFile = 0) => new(() => SetEntry(file, value, subFile));
+ protected void OpenRead(BinaryReader br)
+ {
+ Reader = br;
+ Stream = br.BaseStream;
+ Initialize();
+ }
- public string? Extension => Path.GetExtension(FilePath);
- public string? FileName => Path.GetFileName(FilePath);
- public string? FilePath { get; set; }
- public bool Modified { get; set; }
+ protected abstract void Initialize();
- protected byte[]?[] Files = Array.Empty();
+ protected abstract int GetFileOffset(int file, int subFile = 0);
- ///
- /// Packs the to the specified writing stream.
- ///
- /// Stream Writer to write contents to.
- /// Manager for monitoring progress.
- /// Cancellation object
- /// Awaitable Task
- protected abstract Task Pack(BinaryWriter bw, ContainerHandler handler, CancellationToken token);
+ public BinaryReader Seek(int file, long offset = 0, int subFile = 0)
+ {
+ if (Reader == null)
+ throw new NullReferenceException("Reader is not initialized.");
+ offset += GetFileOffset(file, subFile);
+ Reader.BaseStream.Position = offset;
+ return Reader;
+ }
- #region File Reading
+ public abstract byte[] GetEntry(int index, int subFile);
- protected BinaryReader? Reader { get; private set; }
- private Stream? Stream;
+ public virtual void SetEntry(int index, byte[]? value, int subFile)
+ {
+ Files[index] = value;
+ Modified |= value != null && !this[index].SequenceEqual(value);
+ }
- protected void OpenBinary(string path)
+ private byte[] GetCachedValue(int i, int subFile)
+ {
+ return Files[i] ??= GetEntry(i, subFile);
+ }
+
+ #endregion
+
+ public byte[] this[int index]
+ {
+ get => (byte[]) GetCachedValue(index, 0).Clone();
+ set => SetEntry(index, value, 0);
+ }
+
+ public void CacheAll()
+ {
+ for (int i = 0; i < Files.Length; i++)
+ Files[i] ??= GetCachedValue(i, 0);
+
+ Reader = null;
+ Stream?.Close();
+ Stream = null;
+ }
+
+ public void CancelEdits()
+ {
+ if (Reader == null)
+ throw new ArgumentNullException(nameof(Reader));
+
+ for (int i = 0; i < Files.Length; i++)
+ Files[i] = null;
+
+ Modified = false;
+ }
+
+ public abstract void Dump(string path, ContainerHandler handler);
+
+ public async Task SaveAs(string path, ContainerHandler handler, CancellationToken token = new())
+ {
+ bool sameLocation = path == FilePath && Reader != null;
+ var writePath = sameLocation ? Path.GetTempFileName() : path;
+
+ path = FileMitm.GetRedirectedWritePath(path);
+ var stream = new FileStream(path, FileMode.CreateNew);
+ using (var bw = new BinaryWriter(stream))
+ await Pack(bw, handler, token).ConfigureAwait(false);
+
+ if (token.IsCancellationRequested)
{
- path = FileMitm.GetRedirectedReadPath(path);
- Stream = new FileStream(path, FileMode.Open);
- Reader = new BinaryReader(Stream);
- Initialize();
+ stream.Close();
+ File.Delete(path);
+ return;
}
- protected void OpenRead(BinaryReader br)
+ if (sameLocation && path != writePath)
{
- Reader = br;
- Stream = br.BaseStream;
- Initialize();
- }
-
- protected abstract void Initialize();
-
- protected abstract int GetFileOffset(int file, int subFile = 0);
-
- public BinaryReader Seek(int file, long offset = 0, int subFile = 0)
- {
- if (Reader == null)
- throw new NullReferenceException("Reader is not initialized.");
- offset += GetFileOffset(file, subFile);
- Reader.BaseStream.Position = offset;
- return Reader;
- }
-
- public abstract byte[] GetEntry(int index, int subFile);
-
- public virtual void SetEntry(int index, byte[]? value, int subFile)
- {
- Files[index] = value;
- Modified |= value != null && !this[index].SequenceEqual(value);
- }
-
- private byte[] GetCachedValue(int i, int subFile)
- {
- return Files[i] ??= GetEntry(i, subFile);
- }
-
- #endregion
-
- public byte[] this[int index]
- {
- get => (byte[]) GetCachedValue(index, 0).Clone();
- set => SetEntry(index, value, 0);
- }
-
- public void CacheAll()
- {
- for (int i = 0; i < Files.Length; i++)
- Files[i] ??= GetCachedValue(i, 0);
-
- Reader = null;
- Stream?.Close();
- Stream = null;
- }
-
- public void CancelEdits()
- {
- if (Reader == null)
- throw new ArgumentNullException(nameof(Reader));
-
- for (int i = 0; i < Files.Length; i++)
- Files[i] = null;
-
- Modified = false;
- }
-
- public abstract void Dump(string path, ContainerHandler handler);
-
- public async Task SaveAs(string path, ContainerHandler handler, CancellationToken token = new())
- {
- bool sameLocation = path == FilePath && Reader != null;
- var writePath = sameLocation ? Path.GetTempFileName() : path;
-
- path = FileMitm.GetRedirectedWritePath(path);
- var stream = new FileStream(path, FileMode.CreateNew);
- using (var bw = new BinaryWriter(stream))
- await Pack(bw, handler, token).ConfigureAwait(false);
-
- if (token.IsCancellationRequested)
- {
- stream.Close();
+ if (File.Exists(path))
File.Delete(path);
- return;
- }
-
- if (sameLocation && path != writePath)
- {
- if (File.Exists(path))
- File.Delete(path);
- File.Move(writePath, path);
- }
-
- Stream?.Dispose();
- Reader?.Dispose();
-
- Stream = stream;
- Reader = new BinaryReader(Stream);
+ File.Move(writePath, path);
}
- public void Dispose()
- {
- Dispose(true);
- }
+ Stream?.Dispose();
+ Reader?.Dispose();
- protected virtual void Dispose(bool disposing)
- {
- Stream?.Dispose();
- Reader?.Dispose();
- }
+ Stream = stream;
+ Reader = new BinaryReader(Stream);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ Stream?.Dispose();
+ Reader?.Dispose();
}
}
diff --git a/pkNX.Containers/LargeContainerEntry.cs b/pkNX.Containers/LargeContainerEntry.cs
index 55bbbbbe..ab1b3d68 100644
--- a/pkNX.Containers/LargeContainerEntry.cs
+++ b/pkNX.Containers/LargeContainerEntry.cs
@@ -1,74 +1,73 @@
-using System.IO;
+using System.IO;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+public class LargeContainerEntry
{
- public class LargeContainerEntry
+ public int Start { get; set; }
+ public int End { get; set; }
+ public virtual int Length { get; set; }
+ public object? File { get; set; }
+ public int ParentDataPosition { get; set; }
+
+ public byte[] GetFileData(Stream parent)
{
- public int Start { get; set; }
- public int End { get; set; }
- public virtual int Length { get; set; }
- public object? File { get; set; }
- public int ParentDataPosition { get; set; }
+ parent.Seek(Start + ParentDataPosition, SeekOrigin.Begin);
+ byte[] data = new byte[Length];
+ _ = parent.Read(data, 0, Length);
+ return data;
+ }
- public byte[] GetFileData(Stream parent)
+ public void Write(Stream parent, Stream dest, int DataOffset)
+ {
+ switch (File)
{
- parent.Seek(Start + ParentDataPosition, SeekOrigin.Begin);
- byte[] data = new byte[Length];
- _ = parent.Read(data, 0, Length);
- return data;
- }
-
- public void Write(Stream parent, Stream dest, int DataOffset)
- {
- switch (File)
- {
- case string f:
- WriteFrom(dest, DataOffset, f);
- break;
- case byte[] data:
- WriteFrom(dest, DataOffset, data);
- break;
- default:
- WriteFrom(dest, DataOffset, parent);
- break;
- }
- }
-
- private void WriteFrom(Stream dest, int DataOffset, string f)
- {
- Start = (int)dest.Position - DataOffset;
- using (var s = new FileStream(f, FileMode.Open, FileAccess.Read, FileShare.None))
- s.CopyTo(dest);
- End = (int)dest.Position - DataOffset;
- Length = End - Start;
- }
-
- private void WriteFrom(Stream dest, int DataOffset, byte[] data)
- {
- Start = (int)dest.Position - DataOffset;
- Length = data.Length;
- End = Start + Length;
-
- File = data;
- dest.Write(data, 0, data.Length);
- }
-
- private void WriteFrom(Stream dest, int DataOffset, Stream source)
- {
- Start = (int)dest.Position - DataOffset;
- End = Start + Length;
-
- source.Seek(Start + ParentDataPosition, SeekOrigin.Begin);
- source.CopyTo(dest, Length);
- }
-
- public void Dump(Stream parent, string path, int DataOffset)
- {
- if (File is string)
- return;
-
- using var file = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
- Write(parent, file, DataOffset);
+ case string f:
+ WriteFrom(dest, DataOffset, f);
+ break;
+ case byte[] data:
+ WriteFrom(dest, DataOffset, data);
+ break;
+ default:
+ WriteFrom(dest, DataOffset, parent);
+ break;
}
}
+
+ private void WriteFrom(Stream dest, int DataOffset, string f)
+ {
+ Start = (int)dest.Position - DataOffset;
+ using (var s = new FileStream(f, FileMode.Open, FileAccess.Read, FileShare.None))
+ s.CopyTo(dest);
+ End = (int)dest.Position - DataOffset;
+ Length = End - Start;
+ }
+
+ private void WriteFrom(Stream dest, int DataOffset, byte[] data)
+ {
+ Start = (int)dest.Position - DataOffset;
+ Length = data.Length;
+ End = Start + Length;
+
+ File = data;
+ dest.Write(data, 0, data.Length);
+ }
+
+ private void WriteFrom(Stream dest, int DataOffset, Stream source)
+ {
+ Start = (int)dest.Position - DataOffset;
+ End = Start + Length;
+
+ source.Seek(Start + ParentDataPosition, SeekOrigin.Begin);
+ source.CopyTo(dest, Length);
+ }
+
+ public void Dump(Stream parent, string path, int DataOffset)
+ {
+ if (File is string)
+ return;
+
+ using var file = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
+ Write(parent, file, DataOffset);
+ }
}
diff --git a/pkNX.Containers/Mini/Mini.cs b/pkNX.Containers/Mini/Mini.cs
index 71d561cf..bc7f34e5 100644
--- a/pkNX.Containers/Mini/Mini.cs
+++ b/pkNX.Containers/Mini/Mini.cs
@@ -1,70 +1,69 @@
-using System.IO;
+using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+public class Mini : IFileContainer
{
- public class Mini : IFileContainer
+ public string Identifier { get; }
+ public byte[][] Files { get; private set; }
+
+ public Mini(byte[][] data, string ident)
{
- public string Identifier { get; }
- public byte[][] Files { get; private set; }
+ Identifier = ident;
+ Files = data;
+ Backup = new byte[data.Length][];
+ for (int i = 0; i < Backup.Length; i++)
+ Backup[i] = (byte[])data[i].Clone();
+ }
- public Mini(byte[][] data, string ident)
+ public byte[] this[int index]
+ {
+ get => (byte[])Files[index].Clone();
+ set
{
- Identifier = ident;
- Files = data;
- Backup = new byte[data.Length][];
- for (int i = 0; i < Backup.Length; i++)
- Backup[i] = (byte[])data[i].Clone();
+ Modified |= !Files[index].SequenceEqual(value);
+ Files[index] = value;
}
+ }
- public byte[] this[int index]
+ public string? FilePath { get; set; }
+ private readonly byte[][] Backup;
+
+ public bool Modified { get; set; }
+ public int Count => Files.Length;
+
+ public Task GetFile(int file, int subFile = 0) => Task.FromResult(Files[file]);
+ public Task SetFile(int file, byte[] value, int subFile = 0) => Task.FromResult(Files[file] = value);
+ public Task GetFiles() => Task.FromResult(Files);
+
+ public void CancelEdits()
+ {
+ Modified = false;
+ Files = new byte[Backup.Length][];
+ for (int i = 0; i < Files.Length; i++)
+ Files[i] = (byte[]) Backup[i].Clone();
+ }
+
+ public Task SaveAs(string path, ContainerHandler handler, CancellationToken token) => new(() =>
+ {
+ byte[] data = MiniUtil.PackMini(Files, Identifier);
+ FileMitm.WriteAllBytes(path, data);
+ }, token);
+
+ public void Dump(string path, ContainerHandler handler)
+ {
+ string format = this.GetFileFormatString();
+ Directory.CreateDirectory(path);
+
+ handler.Initialize(Count);
+ for (int i = 0; i < Count; i++)
{
- get => (byte[])Files[index].Clone();
- set
- {
- Modified |= !Files[index].SequenceEqual(value);
- Files[index] = value;
- }
- }
-
- public string? FilePath { get; set; }
- private readonly byte[][] Backup;
-
- public bool Modified { get; set; }
- public int Count => Files.Length;
-
- public Task GetFile(int file, int subFile = 0) => Task.FromResult(Files[file]);
- public Task SetFile(int file, byte[] value, int subFile = 0) => Task.FromResult(Files[file] = value);
- public Task GetFiles() => Task.FromResult(Files);
-
- public void CancelEdits()
- {
- Modified = false;
- Files = new byte[Backup.Length][];
- for (int i = 0; i < Files.Length; i++)
- Files[i] = (byte[]) Backup[i].Clone();
- }
-
- public Task SaveAs(string path, ContainerHandler handler, CancellationToken token) => new(() =>
- {
- byte[] data = MiniUtil.PackMini(Files, Identifier);
- FileMitm.WriteAllBytes(path, data);
- }, token);
-
- public void Dump(string path, ContainerHandler handler)
- {
- string format = this.GetFileFormatString();
- Directory.CreateDirectory(path);
-
- handler.Initialize(Count);
- for (int i = 0; i < Count; i++)
- {
- var fn = Path.Combine(path, i.ToString(format) + ".bin");
- FileMitm.WriteAllBytes(fn, Files[i]);
- handler.StepFile(i + 1);
- }
+ var fn = Path.Combine(path, i.ToString(format) + ".bin");
+ FileMitm.WriteAllBytes(fn, Files[i]);
+ handler.StepFile(i + 1);
}
}
}
diff --git a/pkNX.Containers/Mini/MiniUtil.cs b/pkNX.Containers/Mini/MiniUtil.cs
index c22cb3f1..b07db2b9 100644
--- a/pkNX.Containers/Mini/MiniUtil.cs
+++ b/pkNX.Containers/Mini/MiniUtil.cs
@@ -1,159 +1,158 @@
-using System;
+using System;
using System.IO;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+///
+/// // Mini Packing Util
+///
+public static class MiniUtil
{
- ///
- /// // Mini Packing Util
- ///
- public static class MiniUtil
+ public static byte[] PackMini(string folder, string identifier) => PackMini(Directory.GetFiles(folder), identifier);
+
+ public static byte[] PackMini(string[] files, string identifier)
{
- public static byte[] PackMini(string folder, string identifier) => PackMini(Directory.GetFiles(folder), identifier);
+ byte[][] fileData = new byte[files.Length][];
+ for (int i = 0; i < fileData.Length; i++)
+ fileData[i] = FileMitm.ReadAllBytes(files[i]);
+ return PackMini(fileData, identifier);
+ }
- public static byte[] PackMini(string[] files, string identifier)
+ public static byte[] PackMini(byte[][] fileData, string identifier)
+ {
+ // Create new Binary with the relevant header bytes
+ byte[] data = new byte[4];
+ data[0] = (byte)identifier[0];
+ data[1] = (byte)identifier[1];
+ Array.Copy(BitConverter.GetBytes((ushort)fileData.Length), 0, data, 2, 2);
+
+ int count = fileData.Length;
+ int dataOffset = 4 + 4 + (count * 4);
+
+ // Start the data filling.
+ using MemoryStream dataout = new();
+ using MemoryStream offsetMap = new();
+ using BinaryWriter bd = new(dataout);
+ using BinaryWriter bo = new(offsetMap);
+ // For each file...
+ for (int i = 0; i < count; i++)
{
- byte[][] fileData = new byte[files.Length][];
- for (int i = 0; i < fileData.Length; i++)
- fileData[i] = FileMitm.ReadAllBytes(files[i]);
- return PackMini(fileData, identifier);
+ // Write File Offset
+ uint fileOffset = (uint)(dataout.Position + dataOffset);
+ bo.Write(fileOffset);
+
+ // Write File to Stream
+ bd.Write(fileData[i]);
+
+ // Pad the Data MemoryStream with Zeroes until len%4=0;
+ while (dataout.Length % 4 != 0)
+ bd.Write((byte)0);
+ // File Offset will be updated as the offset is based off of the Data length.
+ }
+ // Cap the File
+ bo.Write((uint)(dataout.Position + dataOffset));
+
+ using var newPack = new MemoryStream();
+ using var header = new MemoryStream(data);
+ header.WriteTo(newPack);
+ offsetMap.WriteTo(newPack);
+ dataout.WriteTo(newPack);
+ return newPack.ToArray();
+ }
+
+ public static byte[][] UnpackMini(string file, string identifier)
+ {
+ byte[] fileData = FileMitm.ReadAllBytes(file);
+ return UnpackMini(fileData, identifier);
+ }
+
+ public static byte[][] UnpackMini(byte[] fileData, string identifier)
+ {
+ if (fileData.Length < 4)
+ throw new ArgumentOutOfRangeException(nameof(fileData));
+
+ if (identifier.Length == 2)
+ {
+ if (identifier[0] != fileData[0] || identifier[1] != fileData[1])
+ throw new FormatException("Prefix does not match.");
}
- public static byte[] PackMini(byte[][] fileData, string identifier)
+ int count = BitConverter.ToUInt16(fileData, 2); int ctr = 4;
+ int start = BitConverter.ToInt32(fileData, ctr); ctr += 4;
+ byte[][] returnData = new byte[count][];
+ for (int i = 0; i < count; i++)
{
- // Create new Binary with the relevant header bytes
- byte[] data = new byte[4];
- data[0] = (byte)identifier[0];
- data[1] = (byte)identifier[1];
- Array.Copy(BitConverter.GetBytes((ushort)fileData.Length), 0, data, 2, 2);
-
- int count = fileData.Length;
- int dataOffset = 4 + 4 + (count * 4);
-
- // Start the data filling.
- using MemoryStream dataout = new();
- using MemoryStream offsetMap = new();
- using BinaryWriter bd = new(dataout);
- using BinaryWriter bo = new(offsetMap);
- // For each file...
- for (int i = 0; i < count; i++)
- {
- // Write File Offset
- uint fileOffset = (uint)(dataout.Position + dataOffset);
- bo.Write(fileOffset);
-
- // Write File to Stream
- bd.Write(fileData[i]);
-
- // Pad the Data MemoryStream with Zeroes until len%4=0;
- while (dataout.Length % 4 != 0)
- bd.Write((byte)0);
- // File Offset will be updated as the offset is based off of the Data length.
- }
- // Cap the File
- bo.Write((uint)(dataout.Position + dataOffset));
-
- using var newPack = new MemoryStream();
- using var header = new MemoryStream(data);
- header.WriteTo(newPack);
- offsetMap.WriteTo(newPack);
- dataout.WriteTo(newPack);
- return newPack.ToArray();
+ int end = BitConverter.ToInt32(fileData, ctr); ctr += 4;
+ int len = end - start;
+ byte[] data = new byte[len];
+ Buffer.BlockCopy(fileData, start, data, 0, len);
+ returnData[i] = data;
+ start = end;
}
+ return returnData;
+ }
- public static byte[][] UnpackMini(string file, string identifier)
+ public static Mini GetMini(string path)
+ {
+ path = FileMitm.GetRedirectedReadPath(path);
+ using var fs = new FileStream(path, FileMode.Open, FileAccess.Read);
+ using var br = new BinaryReader(fs);
+ var result = GetMini(br);
+ if (result is null)
+ throw new FormatException($"The file at {path} is not a {nameof(Mini)} file.");
+ return result;
+ }
+
+ public static Mini? GetMini(BinaryReader br)
+ {
+ var ident = GetIsMini(br);
+ if (string.IsNullOrEmpty(ident))
+ return null;
+
+ br.BaseStream.Position = 0;
+ var data = br.ReadBytes((int)br.BaseStream.Length);
+ var unpack = UnpackMini(data, ident);
+ return new Mini(unpack, ident);
+ }
+
+ public static string GetIsMini(BinaryReader br)
+ {
+ if (br.BaseStream.Length < 12)
+ return string.Empty;
+ br.BaseStream.Position = 0;
+ var ident = br.ReadBytes(2);
+ var count = br.ReadUInt16();
+
+ int finalLengthOfs = 4 + (count * 4);
+ if (br.BaseStream.Length < finalLengthOfs + 4)
+ return string.Empty;
+ br.BaseStream.Position = 4 + (count * 4);
+ var len = br.ReadUInt32();
+ if (len != br.BaseStream.Length)
+ return string.Empty;
+ return $"{(char)ident[0]}{(char)ident[1]}";
+ }
+
+ public static string GetIsMini(string path)
+ {
+ try
{
- byte[] fileData = FileMitm.ReadAllBytes(file);
- return UnpackMini(fileData, identifier);
- }
-
- public static byte[][] UnpackMini(byte[] fileData, string identifier)
- {
- if (fileData.Length < 4)
- throw new ArgumentOutOfRangeException(nameof(fileData));
-
- if (identifier.Length == 2)
- {
- if (identifier[0] != fileData[0] || identifier[1] != fileData[1])
- throw new FormatException("Prefix does not match.");
- }
-
- int count = BitConverter.ToUInt16(fileData, 2); int ctr = 4;
- int start = BitConverter.ToInt32(fileData, ctr); ctr += 4;
- byte[][] returnData = new byte[count][];
- for (int i = 0; i < count; i++)
- {
- int end = BitConverter.ToInt32(fileData, ctr); ctr += 4;
- int len = end - start;
- byte[] data = new byte[len];
- Buffer.BlockCopy(fileData, start, data, 0, len);
- returnData[i] = data;
- start = end;
- }
- return returnData;
- }
-
- public static Mini GetMini(string path)
- {
- path = FileMitm.GetRedirectedReadPath(path);
- using var fs = new FileStream(path, FileMode.Open, FileAccess.Read);
+ path = FileMitm.GetRedirectedWritePath(path);
+ using var fs = new FileStream(path, FileMode.Open);
using var br = new BinaryReader(fs);
- var result = GetMini(br);
- if (result is null)
- throw new FormatException($"The file at {path} is not a {nameof(Mini)} file.");
- return result;
+ return GetIsMini(br);
}
+ catch { return string.Empty; }
+ }
- public static Mini? GetMini(BinaryReader br)
+ public static string GetIsMini(byte[] data)
+ {
+ try
{
- var ident = GetIsMini(br);
- if (string.IsNullOrEmpty(ident))
- return null;
-
- br.BaseStream.Position = 0;
- var data = br.ReadBytes((int)br.BaseStream.Length);
- var unpack = UnpackMini(data, ident);
- return new Mini(unpack, ident);
- }
-
- public static string GetIsMini(BinaryReader br)
- {
- if (br.BaseStream.Length < 12)
- return string.Empty;
- br.BaseStream.Position = 0;
- var ident = br.ReadBytes(2);
- var count = br.ReadUInt16();
-
- int finalLengthOfs = 4 + (count * 4);
- if (br.BaseStream.Length < finalLengthOfs + 4)
- return string.Empty;
- br.BaseStream.Position = 4 + (count * 4);
- var len = br.ReadUInt32();
- if (len != br.BaseStream.Length)
- return string.Empty;
- return $"{(char)ident[0]}{(char)ident[1]}";
- }
-
- public static string GetIsMini(string path)
- {
- try
- {
- path = FileMitm.GetRedirectedWritePath(path);
- using var fs = new FileStream(path, FileMode.Open);
- using var br = new BinaryReader(fs);
- return GetIsMini(br);
- }
- catch { return string.Empty; }
- }
-
- public static string GetIsMini(byte[] data)
- {
- try
- {
- using var ms = new MemoryStream(data);
- using var br = new BinaryReader(ms);
- return GetIsMini(br);
- }
- catch { return string.Empty; }
+ using var ms = new MemoryStream(data);
+ using var br = new BinaryReader(ms);
+ return GetIsMini(br);
}
+ catch { return string.Empty; }
}
}
diff --git a/pkNX.Containers/Misc/AHTB.cs b/pkNX.Containers/Misc/AHTB.cs
index 2ff342a9..ed472175 100644
--- a/pkNX.Containers/Misc/AHTB.cs
+++ b/pkNX.Containers/Misc/AHTB.cs
@@ -1,64 +1,63 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+///
+/// Asynchronous Hash TaBle containing file names and their corresponding hashes.
+///
+/// gfl::container::HashTable
+[Serializable]
+public class AHTB : Dictionary
{
- ///
- /// Asynchronous Hash TaBle containing file names and their corresponding hashes.
- ///
- /// gfl::container::HashTable
- [Serializable]
- public class AHTB : Dictionary
+ public readonly AHTBEntry[] Entries;
+ public const uint Magic = 0x42544841; // AHTB
+ public static bool IsAHTB(byte[] data) => BitConverter.ToUInt32(data, 0) == Magic;
+
+ public AHTB(byte[] table)
{
- public readonly AHTBEntry[] Entries;
- public const uint Magic = 0x42544841; // AHTB
- public static bool IsAHTB(byte[] data) => BitConverter.ToUInt32(data, 0) == Magic;
+ using var ms = new MemoryStream(table);
+ using var br = new BinaryReader(ms);
+ var magic = br.ReadUInt32();
+ Debug.Assert(magic == Magic);
+ var count = br.ReadUInt32();
- public AHTB(byte[] table)
+ Entries = new AHTBEntry[count];
+ for (int i = 0; i < count; i++)
{
- using var ms = new MemoryStream(table);
- using var br = new BinaryReader(ms);
- var magic = br.ReadUInt32();
- Debug.Assert(magic == Magic);
- var count = br.ReadUInt32();
-
- Entries = new AHTBEntry[count];
- for (int i = 0; i < count; i++)
- {
- var e = new AHTBEntry(br);
- Entries[i] = e;
- if (!ContainsKey(e.Hash))
- Add(e.Hash, e.Name);
- }
- }
-
- public int GetIndex(ulong hash)
- {
- if (!TryGetValue(hash, out _))
- return -1;
- return Array.FindIndex(Entries, z => z.Hash == hash);
- }
-
- protected AHTB(SerializationInfo info, StreamingContext context)
- {
- Entries = this.Select(z => new AHTBEntry(z.Key, (ushort) z.Value.Length, z.Value)).ToArray();
- }
-
- public int GetIndex(string value) => GetIndex(FnvHash.HashFnv1a_64(value));
-
- public IEnumerable Summary => Entries.Select((z, i) => $"{i:0000}\t{z.Summary}");
- public IEnumerable ShortSummary => Entries.Select(z => z.Summary);
-
- public Dictionary ToDictionary()
- {
- var map = new Dictionary();
- foreach (var entry in Entries)
- map[entry.Hash] = entry.Name;
- return map;
+ var e = new AHTBEntry(br);
+ Entries[i] = e;
+ if (!ContainsKey(e.Hash))
+ Add(e.Hash, e.Name);
}
}
+
+ public int GetIndex(ulong hash)
+ {
+ if (!TryGetValue(hash, out _))
+ return -1;
+ return Array.FindIndex(Entries, z => z.Hash == hash);
+ }
+
+ protected AHTB(SerializationInfo info, StreamingContext context)
+ {
+ Entries = this.Select(z => new AHTBEntry(z.Key, (ushort) z.Value.Length, z.Value)).ToArray();
+ }
+
+ public int GetIndex(string value) => GetIndex(FnvHash.HashFnv1a_64(value));
+
+ public IEnumerable Summary => Entries.Select((z, i) => $"{i:0000}\t{z.Summary}");
+ public IEnumerable ShortSummary => Entries.Select(z => z.Summary);
+
+ public Dictionary ToDictionary()
+ {
+ var map = new Dictionary();
+ foreach (var entry in Entries)
+ map[entry.Hash] = entry.Name;
+ return map;
+ }
}
diff --git a/pkNX.Containers/Misc/AHTBEntry.cs b/pkNX.Containers/Misc/AHTBEntry.cs
index 985ed370..8745671b 100644
--- a/pkNX.Containers/Misc/AHTBEntry.cs
+++ b/pkNX.Containers/Misc/AHTBEntry.cs
@@ -1,37 +1,36 @@
-using System.IO;
+using System.IO;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+public class AHTBEntry
{
- public class AHTBEntry
+ public ulong Hash;
+ public ushort NameLength;
+ public string Name;
+
+ public AHTBEntry(ulong hash, ushort namelen, string name)
{
- public ulong Hash;
- public ushort NameLength;
- public string Name;
-
- public AHTBEntry(ulong hash, ushort namelen, string name)
- {
- Hash = hash;
- NameLength = namelen;
- Name = name;
- }
-
- public AHTBEntry(BinaryReader br)
- {
- Hash = br.ReadUInt64();
- NameLength = br.ReadUInt16();
- Name = br.ReadStringBytesUntil(0); // could use Length field, but they're always \0 terminated
- //Debug.Assert(FnvHash.HashFnv1a_64(Name) == Hash);
- //Debug.Assert(Name.Length + 1 == NameLength); // Always null terminated
- }
-
- public void Write(BinaryWriter bw)
- {
- bw.Write(Hash);
- bw.Write(Name.Length + 1);
- bw.Write(Name);
- bw.Write((byte)0); // \0 terminator
- }
-
- public string Summary => $"{Hash:X16}\t{Name}";
+ Hash = hash;
+ NameLength = namelen;
+ Name = name;
}
-}
\ No newline at end of file
+
+ public AHTBEntry(BinaryReader br)
+ {
+ Hash = br.ReadUInt64();
+ NameLength = br.ReadUInt16();
+ Name = br.ReadStringBytesUntil(0); // could use Length field, but they're always \0 terminated
+ //Debug.Assert(FnvHash.HashFnv1a_64(Name) == Hash);
+ //Debug.Assert(Name.Length + 1 == NameLength); // Always null terminated
+ }
+
+ public void Write(BinaryWriter bw)
+ {
+ bw.Write(Hash);
+ bw.Write(Name.Length + 1);
+ bw.Write(Name);
+ bw.Write((byte)0); // \0 terminator
+ }
+
+ public string Summary => $"{Hash:X16}\t{Name}";
+}
diff --git a/pkNX.Containers/Misc/BinaryRWExtensions.cs b/pkNX.Containers/Misc/BinaryRWExtensions.cs
index 3f69d733..5d8dd215 100644
--- a/pkNX.Containers/Misc/BinaryRWExtensions.cs
+++ b/pkNX.Containers/Misc/BinaryRWExtensions.cs
@@ -1,45 +1,44 @@
-using System.Diagnostics;
+using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+public static class BinaryRWExtensions
{
- public static class BinaryRWExtensions
+ public static T ReadStruct(this BinaryReader br) where T : struct
{
- public static T ReadStruct(this BinaryReader br) where T : struct
- {
- var bytes = br.ReadBytes(Marshal.SizeOf());
- return bytes.ToStructure();
- }
-
- public static T[] ReadStructArray(this BinaryReader br, uint count) where T : struct
- {
- Debug.Assert(count < 1000); // pls no
- var arr = new T[count];
- for (int i = 0; i < arr.Length; i++)
- arr[i] = br.ReadStruct();
- return arr;
- }
-
- public static string ReadNXString(this BinaryReader br)
- {
- var length = br.ReadUInt16();
- var bytes = br.ReadBytes(length);
- var str = Encoding.ASCII.GetString(bytes);
- br.ReadByte(); // \0
- if (br.BaseStream.Position % 2 != 0)
- br.ReadByte(); // fix align
- return str;
- }
-
- public static string ReadStringBytesUntil(this BinaryReader br, byte end = 0)
- {
- StringBuilder str = new();
- byte b;
- while ((b = br.ReadByte()) != end)
- str.Append((char)b);
- return str.ToString();
- }
+ var bytes = br.ReadBytes(Marshal.SizeOf());
+ return bytes.ToStructure();
}
-}
\ No newline at end of file
+
+ public static T[] ReadStructArray(this BinaryReader br, uint count) where T : struct
+ {
+ Debug.Assert(count < 1000); // pls no
+ var arr = new T[count];
+ for (int i = 0; i < arr.Length; i++)
+ arr[i] = br.ReadStruct();
+ return arr;
+ }
+
+ public static string ReadNXString(this BinaryReader br)
+ {
+ var length = br.ReadUInt16();
+ var bytes = br.ReadBytes(length);
+ var str = Encoding.ASCII.GetString(bytes);
+ br.ReadByte(); // \0
+ if (br.BaseStream.Position % 2 != 0)
+ br.ReadByte(); // fix align
+ return str;
+ }
+
+ public static string ReadStringBytesUntil(this BinaryReader br, byte end = 0)
+ {
+ StringBuilder str = new();
+ byte b;
+ while ((b = br.ReadByte()) != end)
+ str.Append((char)b);
+ return str.ToString();
+ }
+}
diff --git a/pkNX.Containers/Misc/DatEntry.cs b/pkNX.Containers/Misc/DatEntry.cs
index 0330a83a..3705b506 100644
--- a/pkNX.Containers/Misc/DatEntry.cs
+++ b/pkNX.Containers/Misc/DatEntry.cs
@@ -1,38 +1,37 @@
-using System.IO;
+using System.IO;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+public readonly struct DatEntry
{
- public readonly struct DatEntry
+ public readonly ulong Hash;
+ public readonly int Value;
+
+ public DatEntry(ulong hash, int value)
{
- public readonly ulong Hash;
- public readonly int Value;
-
- public DatEntry(ulong hash, int value)
- {
- Hash = hash;
- Value = value;
- }
-
- public DatEntry(BinaryReader br)
- {
- Hash = br.ReadUInt64();
- Value = br.ReadInt32();
- br.ReadInt32();
- }
-
- public void Write(BinaryWriter bw)
- {
- bw.Write(Hash);
- bw.Write(Value);
- bw.Write(0);
- }
-
- public string Summary => $"{Hash:X16}\t{Value}";
-
- public override bool Equals(object obj) => obj is DatEntry d && Equals(d);
- public bool Equals(DatEntry d) => d.Hash == Hash;
- public override int GetHashCode() => Hash.GetHashCode();
- public static bool operator ==(DatEntry left, DatEntry right) => left.Equals(right);
- public static bool operator !=(DatEntry left, DatEntry right) => !(left == right);
+ Hash = hash;
+ Value = value;
}
-}
\ No newline at end of file
+
+ public DatEntry(BinaryReader br)
+ {
+ Hash = br.ReadUInt64();
+ Value = br.ReadInt32();
+ br.ReadInt32();
+ }
+
+ public void Write(BinaryWriter bw)
+ {
+ bw.Write(Hash);
+ bw.Write(Value);
+ bw.Write(0);
+ }
+
+ public string Summary => $"{Hash:X16}\t{Value}";
+
+ public override bool Equals(object? obj) => obj is DatEntry d && Equals(d);
+ public bool Equals(DatEntry d) => d.Hash == Hash;
+ public override int GetHashCode() => Hash.GetHashCode();
+ public static bool operator ==(DatEntry left, DatEntry right) => left.Equals(right);
+ public static bool operator !=(DatEntry left, DatEntry right) => !(left == right);
+}
diff --git a/pkNX.Containers/Misc/DatTable.cs b/pkNX.Containers/Misc/DatTable.cs
index 0897caeb..2ce07441 100644
--- a/pkNX.Containers/Misc/DatTable.cs
+++ b/pkNX.Containers/Misc/DatTable.cs
@@ -1,66 +1,65 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+///
+/// Data Table for Hash->Value lookup
+///
+/// for similar structure.
+[Serializable]
+public class DatTable : Dictionary
{
- ///
- /// Data Table for Hash->Value lookup
- ///
- /// for similar structure.
- [Serializable]
- public class DatTable : Dictionary
+ // u32 count
+ // hash-val tuple[count]
+
+ // hash-val tuple: size=0x10
+ // - 8byte hash
+ // - u32(?) index
+ // - u32(?) unk
+
+ public static bool IsDatTable(byte[] bytes)
{
- // u32 count
- // hash-val tuple[count]
-
- // hash-val tuple: size=0x10
- // - 8byte hash
- // - u32(?) index
- // - u32(?) unk
-
- public static bool IsDatTable(byte[] bytes)
- {
- // pretty weak, don't call this if you aren't sure!
- var count = BitConverter.ToUInt32(bytes, 0);
- return 4 + (count * 0x10) == bytes.Length;
- }
-
- private readonly DatEntry[] Entries;
-
- public DatTable(byte[] data)
- {
- using var ms = new MemoryStream(data);
- using var br = new BinaryReader(ms);
- var count = br.ReadInt32();
-
- Entries = new DatEntry[count];
- for (int i = 0; i < Entries.Length; i++)
- {
- var e = new DatEntry(br);
- Entries[i] = e;
- if (!ContainsKey(e.Hash))
- Add(e.Hash, e.Value);
- }
- }
-
- public int GetIndex(ulong hash)
- {
- if (!TryGetValue(hash, out _))
- return -1;
- return Array.FindIndex(Entries, z => z.Hash == hash);
- }
-
- protected DatTable(SerializationInfo info, StreamingContext context)
- {
- Entries = this.Select(z => new DatEntry(z.Key, z.Value)).ToArray();
- }
-
- public int GetIndex(string value) => GetIndex(FnvHash.HashFnv1a_64(value));
-
- public IEnumerable Summary => Entries.Select((z, i) => $"{i:0000}\t{z.Summary}");
- public IEnumerable ShortSummary => Entries.Select(z => z.Summary);
+ // pretty weak, don't call this if you aren't sure!
+ var count = BitConverter.ToUInt32(bytes, 0);
+ return 4 + (count * 0x10) == bytes.Length;
}
-}
\ No newline at end of file
+
+ private readonly DatEntry[] Entries;
+
+ public DatTable(byte[] data)
+ {
+ using var ms = new MemoryStream(data);
+ using var br = new BinaryReader(ms);
+ var count = br.ReadInt32();
+
+ Entries = new DatEntry[count];
+ for (int i = 0; i < Entries.Length; i++)
+ {
+ var e = new DatEntry(br);
+ Entries[i] = e;
+ if (!ContainsKey(e.Hash))
+ Add(e.Hash, e.Value);
+ }
+ }
+
+ public int GetIndex(ulong hash)
+ {
+ if (!TryGetValue(hash, out _))
+ return -1;
+ return Array.FindIndex(Entries, z => z.Hash == hash);
+ }
+
+ protected DatTable(SerializationInfo info, StreamingContext context)
+ {
+ Entries = this.Select(z => new DatEntry(z.Key, z.Value)).ToArray();
+ }
+
+ public int GetIndex(string value) => GetIndex(FnvHash.HashFnv1a_64(value));
+
+ public IEnumerable Summary => Entries.Select((z, i) => $"{i:0000}\t{z.Summary}");
+ public IEnumerable ShortSummary => Entries.Select(z => z.Summary);
+}
diff --git a/pkNX.Containers/Misc/FnvHash.cs b/pkNX.Containers/Misc/FnvHash.cs
index 12cf267d..06a2ed0b 100644
--- a/pkNX.Containers/Misc/FnvHash.cs
+++ b/pkNX.Containers/Misc/FnvHash.cs
@@ -1,114 +1,113 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+///
+/// Fowler–Noll–Vo non-cryptographic hash
+///
+public static class FnvHash
{
+ // 64 bit implementation
+ private const ulong kFnvPrime_64 = 0x00000100000001b3;
+ private const ulong kOffsetBasis_64 = 0xCBF29CE484222645;
+
///
- /// Fowler–Noll–Vo non-cryptographic hash
+ /// Gets the hash code of the input sequence via the default Fnv1 method.
///
- public static class FnvHash
+ /// Input sequence
+ /// Initial hash value
+ /// Computed hash code
+ public static ulong HashFnv1_64(IEnumerable input, ulong hash = kOffsetBasis_64)
{
- // 64 bit implementation
- private const ulong kFnvPrime_64 = 0x00000100000001b3;
- private const ulong kOffsetBasis_64 = 0xCBF29CE484222645;
-
- ///
- /// Gets the hash code of the input sequence via the default Fnv1 method.
- ///
- /// Input sequence
- /// Initial hash value
- /// Computed hash code
- public static ulong HashFnv1_64(IEnumerable input, ulong hash = kOffsetBasis_64)
+ foreach (var c in input)
{
- foreach (var c in input)
- {
- hash *= kFnvPrime_64;
- hash ^= c;
- }
- return hash;
+ hash *= kFnvPrime_64;
+ hash ^= c;
}
+ return hash;
+ }
- ///
- /// Gets the hash code of the input sequence via the default Fnv1 method.
- ///
- /// Input sequence
- /// Initial hash value
- /// Computed hash code
- public static ulong HashFnv1_64(IEnumerable input, ulong hash = kOffsetBasis_64)
+ ///
+ /// Gets the hash code of the input sequence via the default Fnv1 method.
+ ///
+ /// Input sequence
+ /// Initial hash value
+ /// Computed hash code
+ public static ulong HashFnv1_64(IEnumerable input, ulong hash = kOffsetBasis_64)
+ {
+ foreach (var c in input)
{
- foreach (var c in input)
- {
- hash *= kFnvPrime_64;
- hash ^= c;
- }
- return hash;
+ hash *= kFnvPrime_64;
+ hash ^= c;
}
+ return hash;
+ }
- ///
- /// Gets the hash code of the input sequence via the alternative Fnv1 method.
- ///
- /// Input sequence
- /// Initial hash value
- /// Computed hash code
- public static ulong HashFnv1a_64(IEnumerable input, ulong hash = kOffsetBasis_64)
+ ///
+ /// Gets the hash code of the input sequence via the alternative Fnv1 method.
+ ///
+ /// Input sequence
+ /// Initial hash value
+ /// Computed hash code
+ public static ulong HashFnv1a_64(IEnumerable input, ulong hash = kOffsetBasis_64)
+ {
+ foreach (var c in input)
{
- foreach (var c in input)
- {
- hash ^= c;
- hash *= kFnvPrime_64;
- }
- return hash;
+ hash ^= c;
+ hash *= kFnvPrime_64;
}
+ return hash;
+ }
- ///
- /// Gets the hash code of the input sequence via the alternative Fnv1 method.
- ///
- /// Input sequence
- /// Initial hash value
- /// Computed hash code
- public static ulong HashFnv1a_64(IEnumerable input, ulong hash = kOffsetBasis_64)
+ ///
+ /// Gets the hash code of the input sequence via the alternative Fnv1 method.
+ ///
+ /// Input sequence
+ /// Initial hash value
+ /// Computed hash code
+ public static ulong HashFnv1a_64(IEnumerable input, ulong hash = kOffsetBasis_64)
+ {
+ foreach (var c in input)
{
- foreach (var c in input)
- {
- hash ^= c;
- hash *= kFnvPrime_64;
- }
- return hash;
+ hash ^= c;
+ hash *= kFnvPrime_64;
}
+ return hash;
+ }
- // 32 bit implementation
- private const uint kFnvPrime_32 = 0x01000193;
- private const uint kOffsetBasis_32 = 0x811C9DC5;
+ // 32 bit implementation
+ private const uint kFnvPrime_32 = 0x01000193;
+ private const uint kOffsetBasis_32 = 0x811C9DC5;
- ///
- /// Gets the hash code of the input sequence via the default Fnv1 method.
- ///
- /// Input sequence
- /// Initial hash value
- /// Computed hash code
- public static uint HashFnv1_32(IEnumerable input, uint hash = kOffsetBasis_32)
+ ///
+ /// Gets the hash code of the input sequence via the default Fnv1 method.
+ ///
+ /// Input sequence
+ /// Initial hash value
+ /// Computed hash code
+ public static uint HashFnv1_32(IEnumerable input, uint hash = kOffsetBasis_32)
+ {
+ foreach (var c in input)
{
- foreach (var c in input)
- {
- hash *= kFnvPrime_32;
- hash ^= c;
- }
- return hash;
+ hash *= kFnvPrime_32;
+ hash ^= c;
}
+ return hash;
+ }
- ///
- /// Gets the hash code of the input sequence via the alternative Fnv1 method.
- ///
- /// Input sequence
- /// Initial hash value
- /// Computed hash code
- public static uint HashFnv1a_32(IEnumerable input, uint hash = kOffsetBasis_32)
+ ///
+ /// Gets the hash code of the input sequence via the alternative Fnv1 method.
+ ///
+ /// Input sequence
+ /// Initial hash value
+ /// Computed hash code
+ public static uint HashFnv1a_32(IEnumerable input, uint hash = kOffsetBasis_32)
+ {
+ foreach (var c in input)
{
- foreach (var c in input)
- {
- hash ^= c;
- hash *= kFnvPrime_32;
- }
- return hash;
+ hash ^= c;
+ hash *= kFnvPrime_32;
}
+ return hash;
}
}
diff --git a/pkNX.Containers/Misc/GFPack.cs b/pkNX.Containers/Misc/GFPack.cs
index c4612db5..e52f2dc2 100644
--- a/pkNX.Containers/Misc/GFPack.cs
+++ b/pkNX.Containers/Misc/GFPack.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
@@ -8,442 +8,443 @@
using System.Threading;
using System.Threading.Tasks;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+public class GFPack : IEnumerable, IFileContainer
{
- public class GFPack : IEnumerable, IFileContainer
- {
- public const ulong Magic = 0x4B434150_584C4647; // GFLXPACK
+ public const ulong Magic = 0x4B434150_584C4647; // GFLXPACK
- // Overall structure: Header, metadata, and the raw compressed files
- public GFPackHeader Header { get; set; }
- public GFPackPointers Pointers { get; set; }
- public FileHashAbsolute[] HashAbsolute { get; set; }
- public FileHashFolder[] HashInFolder { get; set; }
- public FileData[] FileTable { get; set; }
+ // Overall structure: Header, metadata, and the raw compressed files
+ public GFPackHeader Header { get; set; }
+ public GFPackPointers Pointers { get; set; }
+ public FileHashAbsolute[] HashAbsolute { get; set; }
+ public FileHashFolder[] HashInFolder { get; set; }
+ public FileData[] FileTable { get; set; }
- public byte[][] CompressedFiles { get; set; }
- public byte[][] DecompressedFiles { get; set; }
+ public byte[][] CompressedFiles { get; set; }
+ public byte[][] DecompressedFiles { get; set; }
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
- public GFPack(string path) : this(FileMitm.ReadAllBytes(path)) => FilePath = path;
- public GFPack(string[] directories, string parent = @"\bin") => LoadFiles(directories, parent);
-
- ///
- /// Initializes a packed object, and unpacks the data to accessible properties.
- ///
- /// Packed file
- public GFPack(byte[] data)
- {
- using var ms = new MemoryStream(data);
- using var br = new BinaryReader(ms);
- ReadPack(br);
- }
-
- public GFPack(BinaryReader br) => ReadPack(br);
-#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
-
- private void ReadPack(BinaryReader br)
- {
- Header = br.ReadBytes(GFPackHeader.SIZE).ToClass();
- Debug.Assert(Header.MAGIC == Magic);
- Pointers = new GFPackPointers(br, Header.CountFolders);
-
- Debug.Assert(Pointers.PtrHashPaths == br.BaseStream.Position);
- HashAbsolute = new FileHashAbsolute[Header.CountFiles];
- for (int i = 0; i < HashAbsolute.Length; i++)
- HashAbsolute[i] = br.ReadBytes(FileHashAbsolute.SIZE).ToClass();
-
- HashInFolder = new FileHashFolder[Header.CountFolders];
- for (int f = 0; f < HashInFolder.Length; f++)
- {
- Debug.Assert(Pointers.PtrHashFolders[f] == br.BaseStream.Position);
- var table = HashInFolder[f] = new FileHashFolder
- {
- Folder = br.ReadBytes(FileHashIndex.SIZE).ToClass()
- };
- table.Files = new FileHashIndex[table.Folder.FileCount];
- for (int i = 0; i < table.Files.Length; i++)
- table.Files[i] = br.ReadBytes(FileHashIndex.SIZE).ToClass();
- }
-
- Debug.Assert(Pointers.PtrFileTable == br.BaseStream.Position);
- FileTable = new FileData[Header.CountFiles];
- for (int i = 0; i < FileTable.Length; i++)
- FileTable[i] = br.ReadBytes(FileData.SIZE).ToClass();
-
- CompressedFiles = new byte[Header.CountFiles][];
- for (int i = 0; i < CompressedFiles.Length; i++)
- {
- br.BaseStream.Position = FileTable[i].OffsetPacked;
- CompressedFiles[i] = br.ReadBytes(FileTable[i].SizeCompressed);
- }
-
- DecompressedFiles = new byte[Header.CountFiles][];
- for (int i = 0; i < DecompressedFiles.Length; i++)
- DecompressedFiles[i] = Decompress(CompressedFiles[i], FileTable[i].SizeDecompressed, FileTable[i].Type);
- }
-
- public IEnumerator GetEnumerator() => (IEnumerator)DecompressedFiles.GetEnumerator();
- IEnumerator IEnumerable.GetEnumerator() => DecompressedFiles.GetEnumerator();
- public int Count => Header.CountFiles;
-
- public byte[] this[int index]
- {
- get => (byte[])DecompressedFiles[index].Clone();
- set
- {
- Modified |= !DecompressedFiles[index].SequenceEqual(value);
- DecompressedFiles[index] = value;
- }
- }
-
- public int GetIndexFull(ulong hash) => Array.FindIndex(HashAbsolute, z => z.HashFnv1aPathFull == hash);
- public int GetIndexFull(string path) => GetIndexFull(FnvHash.HashFnv1a_64(path));
-
- public int GetIndexFileName(ulong hash)
- {
- foreach (var f in HashInFolder)
- {
- int index = f.GetIndexFileName(hash);
- if (index >= 0)
- return f.Files[index].Index;
- }
- return -1;
- }
-
- public int GetIndexFileName(string name)
- {
- foreach (var f in HashInFolder)
- {
- int index = f.GetIndexFileName(name);
- if (index >= 0)
- return f.Files[index].Index;
- }
- return -1;
- }
-
- public byte[] GetDataFileName(string name)
- {
- int index = GetIndexFileName(name);
- return DecompressedFiles[index];
- }
-
- public byte[] GetDataFull(ulong hash) => DecompressedFiles[GetIndexFull(hash)];
-
- public byte[] GetDataFullPath(string path) => GetDataFull(FnvHash.HashFnv1a_64(path));
-
- public void SetDataFileName(string name, byte[] data)
- {
- int index = GetIndexFileName(name);
- DecompressedFiles[index] = data;
- }
-
- public void SetDataFullPath(string path, byte[] data)
- {
- var hash = FnvHash.HashFnv1a_64(path);
- int index = GetIndexFull(hash);
- DecompressedFiles[index] = data;
- }
-
- public void LoadFiles(string[] directories, string parent, CompressionType type = CompressionType.Lz4)
- {
- var groups = directories.Select(Directory.GetFiles).ToArray();
- var files = groups.SelectMany(z => z).ToArray();
- Header = new GFPackHeader { CountFolders = directories.Length, CountFiles = files.Length };
-
- HashAbsolute = new FileHashAbsolute[files.Length];
- HashInFolder = new FileHashFolder[groups.Length];
- FileTable = new FileData[files.Length];
- DecompressedFiles = new byte[files.Length][];
- CompressedFiles = new byte[files.Length][];
-
- for (int f = 0; f < groups.Length; f++)
- {
- var folderFiles = groups[f];
- var folderName = Path.GetDirectoryName(directories[f]);
- var table = HashInFolder[f] = new FileHashFolder();
- table.Folder = new FileHashFolderInfo
- {
- FileCount = folderFiles.Length,
- HashFnv1aPathFolderName = FnvHash.HashFnv1a_64(folderName),
- };
- table.Files = new FileHashIndex[folderFiles.Length];
- for (int i = 0; i < folderFiles.Length; i++)
- {
- var file = folderFiles[i];
- int index = Array.IndexOf(files, folderFiles[i]);
- var nameshort = Path.GetFileName(file);
- ulong hashShort = FnvHash.HashFnv1a_64(nameshort);
- table.Files[i] = new FileHashIndex { HashFnv1aPathFileName = hashShort, Index = index };
- }
- }
-
- for (var i = 0; i < files.Length; i++)
- {
- var file = files[i];
- var namelong = file[file.IndexOf(parent, StringComparison.Ordinal)..];
- ulong hashFull = FnvHash.HashFnv1a_64(namelong);
-
- HashAbsolute[i] = new FileHashAbsolute { HashFnv1aPathFull = hashFull };
- FileTable[i] = new FileData { Type = type };
- DecompressedFiles[i] = FileMitm.ReadAllBytes(file);
- }
- Modified = true;
- }
-
- public byte[] Write()
- {
- using var ms = new MemoryStream();
- using var bw = new BinaryWriter(ms);
- WriteHeaderTableList(bw);
- for (var i = 0; i < DecompressedFiles.Length; i++)
- {
- var entry = FileTable[i];
- var f = DecompressedFiles[i];
- var c = Compress(f, entry.Type);
- CompressedFiles[i] = c;
-
- // update entry details
- entry.SizeDecompressed = f.Length;
- entry.SizeCompressed = c.Length;
- entry.OffsetPacked = (int)bw.BaseStream.Position;
-
- bw.Write(c);
- while (bw.BaseStream.Position % 0x10 != 0) // pad to nearest 0x10 alignment
- bw.Write((byte)0);
- }
- bw.BaseStream.Position = 0;
- WriteHeaderTableList(bw);
- return ms.ToArray();
- }
-
- private static byte[] Decompress(byte[] encryptedData, int decryptedLength, CompressionType type)
- {
- return type switch
- {
- CompressionType.None => encryptedData,
- CompressionType.Zlib => throw new NotSupportedException(nameof(CompressionType.Zlib)), // not implemented
- CompressionType.Lz4 => LZ4.Decode(encryptedData, decryptedLength),
- CompressionType.OodleKraken => Oodle.Decompress(encryptedData, decryptedLength)!,
- CompressionType.OodleLeviathan => Oodle.Decompress(encryptedData, decryptedLength)!,
- CompressionType.OodleMermaid => Oodle.Decompress(encryptedData, decryptedLength)!,
- CompressionType.OodleSelkie => Oodle.Decompress(encryptedData, decryptedLength)!,
- CompressionType.OodleHydra => Oodle.Decompress(encryptedData, decryptedLength)!,
- _ => throw new ArgumentOutOfRangeException(nameof(type)),
- };
- }
-
- private static byte[] Compress(byte[] decryptedData, CompressionType type)
- {
- return type switch
- {
- CompressionType.None => decryptedData,
- CompressionType.Zlib => throw new NotSupportedException(nameof(CompressionType.Zlib)), // not implemented
- CompressionType.Lz4 => LZ4.Encode(decryptedData),
- CompressionType.OodleKraken => Oodle.Compress(decryptedData, out _, OodleFormat.Kraken).ToArray(),
- CompressionType.OodleLeviathan => Oodle.Compress(decryptedData, out _, OodleFormat.Leviathan).ToArray(),
- CompressionType.OodleMermaid => Oodle.Compress(decryptedData, out _, OodleFormat.Mermaid).ToArray(),
- CompressionType.OodleSelkie => Oodle.Compress(decryptedData, out _, OodleFormat.Selkie).ToArray(),
- CompressionType.OodleHydra => Oodle.Compress(decryptedData, out _, OodleFormat.Hydra).ToArray(),
- _ => throw new ArgumentOutOfRangeException(nameof(type)),
- };
- }
-
- public void CancelEdits()
- {
- for (int i = 0; i < DecompressedFiles.Length; i++)
- DecompressedFiles[i] = Decompress(CompressedFiles[i], FileTable[i].SizeDecompressed, FileTable[i].Type);
- Modified = false;
- }
-
- private void WriteHeaderTableList(BinaryWriter bw)
- {
- bw.Write(Header.ToBytesClass());
- Pointers.Write(bw);
- Pointers.PtrHashPaths = bw.BaseStream.Position;
- foreach (var hp in HashAbsolute)
- bw.Write(hp.ToBytesClass());
-
- for (var f = 0; f < Pointers.PtrHashFolders.Length; f++)
- {
- Pointers.PtrHashFolders[f] = bw.BaseStream.Position;
-
- var folder = HashInFolder[f];
- folder.Folder.FileCount = folder.Files.Length;
- bw.Write(folder.Folder.ToBytesClass());
- foreach (var hi in folder.Files)
- bw.Write(hi.ToBytesClass());
- }
-
- Pointers.PtrFileTable = bw.BaseStream.Position;
- foreach (var ft in FileTable)
- bw.Write(ft.ToBytesClass());
- }
-
- public string? FilePath { get; set; }
- public bool Modified { get; set; }
- public Task GetFiles() => Task.FromResult(DecompressedFiles);
- public Task GetFile(int file, int subFile = 0) => Task.FromResult(this[file]);
- public Task SetFile(int file, byte[] value, int subFile = 0) => Task.FromResult(this[file] = value);
- public Task SaveAs(string path, ContainerHandler handler, CancellationToken token) => new(() => FileMitm.WriteAllBytes(path, Write()), token);
-
- public void Dump(string path, ContainerHandler handler)
- {
- handler.Initialize(FileTable.Length);
- string format = this.GetFileFormatString();
-
- foreach (var grp in HashInFolder)
- {
- var dirName = grp.Folder.HashFnv1aPathFolderName;
- foreach (var f in grp.Files)
- {
- var index = f.Index;
- var name = f.HashFnv1aPathFileName;
-
- var fn = $"{index.ToString(format)} {name:X16}.bin";
- var data = DecompressedFiles[f.Index];
-
- var subfolder = HashInFolder.Length == 1 ? fn : Path.Combine(dirName.ToString("X16"), fn);
- var loc = Path.Combine(path, subfolder);
- FileMitm.WriteAllBytes(loc, data);
- }
- }
- }
- }
+ public GFPack(string path) : this(FileMitm.ReadAllBytes(path)) => FilePath = path;
+ public GFPack(string[] directories, string parent = @"\bin") => LoadFiles(directories, parent);
///
- /// Intro bytes to the packed binary.
+ /// Initializes a packed object, and unpacks the data to accessible properties.
///
- [StructLayout(LayoutKind.Sequential)]
- public class GFPackHeader
+ /// Packed file
+ public GFPack(byte[] data)
{
- public const int SIZE = 0x18;
- public ulong MAGIC = GFPack.Magic;
- public uint Version = 0x1000;
- public uint IsRelocated; // bit0
-
- ///
- /// Count of Files packed into the binary.
- ///
- public int CountFiles;
-
- ///
- /// Count of Folders packed into the binary.
- ///
- public int CountFolders;
+ using var ms = new MemoryStream(data);
+ using var br = new BinaryReader(ms);
+ ReadPack(br);
}
- public class GFPackPointers
+ public GFPack(BinaryReader br) => ReadPack(br);
+#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+
+ private void ReadPack(BinaryReader br)
{
- ///
- /// Data offset for the table.
- ///
- public long PtrFileTable; // array stored at end
+ Header = br.ReadBytes(GFPackHeader.SIZE).ToClass();
+ Debug.Assert(Header.MAGIC == Magic);
+ Pointers = new GFPackPointers(br, Header.CountFolders);
- ///
- /// Data offset for the table.
- ///
- public long PtrHashPaths; // array stored first
+ Debug.Assert(Pointers.PtrHashPaths == br.BaseStream.Position);
+ HashAbsolute = new FileHashAbsolute[Header.CountFiles];
+ for (int i = 0; i < HashAbsolute.Length; i++)
+ HashAbsolute[i] = br.ReadBytes(FileHashAbsolute.SIZE).ToClass();
- ///
- /// Data offset for the table, which has a leading .
- ///
- public long[] PtrHashFolders; // array stored in middle
-
- // immediately after the pointers are the arrays
-
- public GFPackPointers(BinaryReader br, int folderCount)
+ HashInFolder = new FileHashFolder[Header.CountFolders];
+ for (int f = 0; f < HashInFolder.Length; f++)
{
- PtrFileTable = br.ReadInt64();
- PtrHashPaths = br.ReadInt64();
- PtrHashFolders = new long[folderCount];
- for (int i = 0; i < PtrHashFolders.Length; i++)
- PtrHashFolders[i] = br.ReadInt64();
+ Debug.Assert(Pointers.PtrHashFolders[f] == br.BaseStream.Position);
+ var table = HashInFolder[f] = new FileHashFolder
+ {
+ Folder = br.ReadBytes(FileHashIndex.SIZE).ToClass()
+ };
+ table.Files = new FileHashIndex[table.Folder.FileCount];
+ for (int i = 0; i < table.Files.Length; i++)
+ table.Files[i] = br.ReadBytes(FileHashIndex.SIZE).ToClass();
}
- public void Write(BinaryWriter bw)
+ Debug.Assert(Pointers.PtrFileTable == br.BaseStream.Position);
+ FileTable = new FileData[Header.CountFiles];
+ for (int i = 0; i < FileTable.Length; i++)
+ FileTable[i] = br.ReadBytes(FileData.SIZE).ToClass();
+
+ CompressedFiles = new byte[Header.CountFiles][];
+ for (int i = 0; i < CompressedFiles.Length; i++)
{
- bw.Write(PtrFileTable);
- bw.Write(PtrHashPaths);
- foreach (var table in PtrHashFolders)
- bw.Write(table);
+ br.BaseStream.Position = FileTable[i].OffsetPacked;
+ CompressedFiles[i] = br.ReadBytes(FileTable[i].SizeCompressed);
+ }
+
+ DecompressedFiles = new byte[Header.CountFiles][];
+ for (int i = 0; i < DecompressedFiles.Length; i++)
+ DecompressedFiles[i] = Decompress(CompressedFiles[i], FileTable[i].SizeDecompressed, FileTable[i].Type);
+ }
+
+ public IEnumerator GetEnumerator() => (IEnumerator)DecompressedFiles.GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => DecompressedFiles.GetEnumerator();
+ public int Count => Header.CountFiles;
+
+ public byte[] this[int index]
+ {
+ get => (byte[])DecompressedFiles[index].Clone();
+ set
+ {
+ Modified |= !DecompressedFiles[index].SequenceEqual(value);
+ DecompressedFiles[index] = value;
}
}
- [StructLayout(LayoutKind.Sequential)]
- public class FileHashAbsolute
+ public int GetIndexFull(ulong hash) => Array.FindIndex(HashAbsolute, z => z.HashFnv1aPathFull == hash);
+ public int GetIndexFull(string path) => GetIndexFull(FnvHash.HashFnv1a_64(path));
+
+ public int GetIndexFileName(ulong hash)
{
- public const int SIZE = 0x08;
-
- ///
- /// Filename (with directory details) hash.
- ///
- public ulong HashFnv1aPathFull;
-
- public bool IsMatch(string fileName) => FnvHash.HashFnv1a_64(fileName) == HashFnv1aPathFull;
+ foreach (var f in HashInFolder)
+ {
+ int index = f.GetIndexFileName(hash);
+ if (index >= 0)
+ return f.Files[index].Index;
+ }
+ return -1;
}
- public class FileHashFolder
+ public int GetIndexFileName(string name)
{
- public FileHashFolderInfo Folder = new();
- public FileHashIndex[] Files = Array.Empty();
- public int GetIndexFileName(ulong hash) => Array.FindIndex(Files, z => z.HashFnv1aPathFileName == hash);
- public int GetIndexFileName(string name) => Array.FindIndex(Files, z => z.IsMatch(name));
+ foreach (var f in HashInFolder)
+ {
+ int index = f.GetIndexFileName(name);
+ if (index >= 0)
+ return f.Files[index].Index;
+ }
+ return -1;
}
- [StructLayout(LayoutKind.Sequential)]
- public class FileHashFolderInfo
+ public byte[] GetDataFileName(string name)
{
- public const int SIZE = 0x10;
-
- ///
- /// Filename (without directory details) hash.
- ///
- public ulong HashFnv1aPathFolderName;
- public int FileCount;
- public uint Padding = 0xCC;
-
- public bool IsMatch(string fileName) => FnvHash.HashFnv1a_64(fileName) == HashFnv1aPathFolderName;
+ int index = GetIndexFileName(name);
+ return DecompressedFiles[index];
}
- [StructLayout(LayoutKind.Sequential)]
- public class FileHashIndex
+ public byte[] GetDataFull(ulong hash) => DecompressedFiles[GetIndexFull(hash)];
+
+ public byte[] GetDataFullPath(string path) => GetDataFull(FnvHash.HashFnv1a_64(path));
+
+ public void SetDataFileName(string name, byte[] data)
{
- public const int SIZE = 0x10;
-
- ///
- /// Filename (without directory details) hash.
- ///
- public ulong HashFnv1aPathFileName;
- public int Index;
- public uint Padding = 0xCC;
-
- public bool IsMatch(string fileName) => FnvHash.HashFnv1a_64(fileName) == HashFnv1aPathFileName;
+ int index = GetIndexFileName(name);
+ DecompressedFiles[index] = data;
}
- [StructLayout(LayoutKind.Sequential)]
- public class FileData
+ public void SetDataFullPath(string path, byte[] data)
{
- public const int SIZE = 0x18;
-
- public ushort Level = 9; // quality?
- public CompressionType Type;
- public int SizeDecompressed;
- public int SizeCompressed;
- public int Padding = 0xCC;
- public int OffsetPacked;
- public uint unused;
+ var hash = FnvHash.HashFnv1a_64(path);
+ int index = GetIndexFull(hash);
+ DecompressedFiles[index] = data;
}
- public enum CompressionType : ushort
+ public void LoadFiles(string[] directories, string parent, CompressionType type = CompressionType.Lz4)
{
- None = 0,
- Zlib = 1,
- Lz4 = 2,
- OodleKraken = 3,
- OodleLeviathan = 4,
- OodleMermaid = 5,
- OodleSelkie = 6,
- OodleHydra = 7,
+ var groups = directories.Select(Directory.GetFiles).ToArray();
+ var files = groups.SelectMany(z => z).ToArray();
+ Header = new GFPackHeader { CountFolders = directories.Length, CountFiles = files.Length };
+
+ HashAbsolute = new FileHashAbsolute[files.Length];
+ HashInFolder = new FileHashFolder[groups.Length];
+ FileTable = new FileData[files.Length];
+ DecompressedFiles = new byte[files.Length][];
+ CompressedFiles = new byte[files.Length][];
+
+ for (int f = 0; f < groups.Length; f++)
+ {
+ var folderFiles = groups[f];
+ var folderName = Path.GetDirectoryName(directories[f]);
+ if (folderName is null)
+ throw new Exception("Invalid folder name");
+ var table = HashInFolder[f] = new FileHashFolder();
+ table.Folder = new FileHashFolderInfo
+ {
+ FileCount = folderFiles.Length,
+ HashFnv1aPathFolderName = FnvHash.HashFnv1a_64(folderName),
+ };
+ table.Files = new FileHashIndex[folderFiles.Length];
+ for (int i = 0; i < folderFiles.Length; i++)
+ {
+ var file = folderFiles[i];
+ int index = Array.IndexOf(files, folderFiles[i]);
+ var nameshort = Path.GetFileName(file);
+ ulong hashShort = FnvHash.HashFnv1a_64(nameshort);
+ table.Files[i] = new FileHashIndex { HashFnv1aPathFileName = hashShort, Index = index };
+ }
+ }
+
+ for (var i = 0; i < files.Length; i++)
+ {
+ var file = files[i];
+ var namelong = file[file.IndexOf(parent, StringComparison.Ordinal)..];
+ ulong hashFull = FnvHash.HashFnv1a_64(namelong);
+
+ HashAbsolute[i] = new FileHashAbsolute { HashFnv1aPathFull = hashFull };
+ FileTable[i] = new FileData { Type = type };
+ DecompressedFiles[i] = FileMitm.ReadAllBytes(file);
+ }
+ Modified = true;
+ }
+
+ public byte[] Write()
+ {
+ using var ms = new MemoryStream();
+ using var bw = new BinaryWriter(ms);
+ WriteHeaderTableList(bw);
+ for (var i = 0; i < DecompressedFiles.Length; i++)
+ {
+ var entry = FileTable[i];
+ var f = DecompressedFiles[i];
+ var c = Compress(f, entry.Type);
+ CompressedFiles[i] = c;
+
+ // update entry details
+ entry.SizeDecompressed = f.Length;
+ entry.SizeCompressed = c.Length;
+ entry.OffsetPacked = (int)bw.BaseStream.Position;
+
+ bw.Write(c);
+ while (bw.BaseStream.Position % 0x10 != 0) // pad to nearest 0x10 alignment
+ bw.Write((byte)0);
+ }
+ bw.BaseStream.Position = 0;
+ WriteHeaderTableList(bw);
+ return ms.ToArray();
+ }
+
+ private static byte[] Decompress(byte[] encryptedData, int decryptedLength, CompressionType type)
+ {
+ return type switch
+ {
+ CompressionType.None => encryptedData,
+ CompressionType.Zlib => throw new NotSupportedException(nameof(CompressionType.Zlib)), // not implemented
+ CompressionType.Lz4 => LZ4.Decode(encryptedData, decryptedLength),
+ CompressionType.OodleKraken => Oodle.Decompress(encryptedData, decryptedLength)!,
+ CompressionType.OodleLeviathan => Oodle.Decompress(encryptedData, decryptedLength)!,
+ CompressionType.OodleMermaid => Oodle.Decompress(encryptedData, decryptedLength)!,
+ CompressionType.OodleSelkie => Oodle.Decompress(encryptedData, decryptedLength)!,
+ CompressionType.OodleHydra => Oodle.Decompress(encryptedData, decryptedLength)!,
+ _ => throw new ArgumentOutOfRangeException(nameof(type)),
+ };
+ }
+
+ private static byte[] Compress(byte[] decryptedData, CompressionType type)
+ {
+ return type switch
+ {
+ CompressionType.None => decryptedData,
+ CompressionType.Zlib => throw new NotSupportedException(nameof(CompressionType.Zlib)), // not implemented
+ CompressionType.Lz4 => LZ4.Encode(decryptedData),
+ CompressionType.OodleKraken => Oodle.Compress(decryptedData, out _, OodleFormat.Kraken).ToArray(),
+ CompressionType.OodleLeviathan => Oodle.Compress(decryptedData, out _, OodleFormat.Leviathan).ToArray(),
+ CompressionType.OodleMermaid => Oodle.Compress(decryptedData, out _, OodleFormat.Mermaid).ToArray(),
+ CompressionType.OodleSelkie => Oodle.Compress(decryptedData, out _, OodleFormat.Selkie).ToArray(),
+ CompressionType.OodleHydra => Oodle.Compress(decryptedData, out _, OodleFormat.Hydra).ToArray(),
+ _ => throw new ArgumentOutOfRangeException(nameof(type)),
+ };
+ }
+
+ public void CancelEdits()
+ {
+ for (int i = 0; i < DecompressedFiles.Length; i++)
+ DecompressedFiles[i] = Decompress(CompressedFiles[i], FileTable[i].SizeDecompressed, FileTable[i].Type);
+ Modified = false;
+ }
+
+ private void WriteHeaderTableList(BinaryWriter bw)
+ {
+ bw.Write(Header.ToBytesClass());
+ Pointers.Write(bw);
+ Pointers.PtrHashPaths = bw.BaseStream.Position;
+ foreach (var hp in HashAbsolute)
+ bw.Write(hp.ToBytesClass());
+
+ for (var f = 0; f < Pointers.PtrHashFolders.Length; f++)
+ {
+ Pointers.PtrHashFolders[f] = bw.BaseStream.Position;
+
+ var folder = HashInFolder[f];
+ folder.Folder.FileCount = folder.Files.Length;
+ bw.Write(folder.Folder.ToBytesClass());
+ foreach (var hi in folder.Files)
+ bw.Write(hi.ToBytesClass());
+ }
+
+ Pointers.PtrFileTable = bw.BaseStream.Position;
+ foreach (var ft in FileTable)
+ bw.Write(ft.ToBytesClass());
+ }
+
+ public string? FilePath { get; set; }
+ public bool Modified { get; set; }
+ public Task GetFiles() => Task.FromResult(DecompressedFiles);
+ public Task GetFile(int file, int subFile = 0) => Task.FromResult(this[file]);
+ public Task SetFile(int file, byte[] value, int subFile = 0) => Task.FromResult(this[file] = value);
+ public Task SaveAs(string path, ContainerHandler handler, CancellationToken token) => new(() => FileMitm.WriteAllBytes(path, Write()), token);
+
+ public void Dump(string path, ContainerHandler handler)
+ {
+ handler.Initialize(FileTable.Length);
+ string format = this.GetFileFormatString();
+
+ foreach (var grp in HashInFolder)
+ {
+ var dirName = grp.Folder.HashFnv1aPathFolderName;
+ foreach (var f in grp.Files)
+ {
+ var index = f.Index;
+ var name = f.HashFnv1aPathFileName;
+
+ var fn = $"{index.ToString(format)} {name:X16}.bin";
+ var data = DecompressedFiles[f.Index];
+
+ var subfolder = HashInFolder.Length == 1 ? fn : Path.Combine(dirName.ToString("X16"), fn);
+ var loc = Path.Combine(path, subfolder);
+ FileMitm.WriteAllBytes(loc, data);
+ }
+ }
}
}
+
+///
+/// Intro bytes to the packed binary.
+///
+[StructLayout(LayoutKind.Sequential)]
+public class GFPackHeader
+{
+ public const int SIZE = 0x18;
+ public ulong MAGIC = GFPack.Magic;
+ public uint Version = 0x1000;
+ public uint IsRelocated; // bit0
+
+ ///
+ /// Count of Files packed into the binary.
+ ///
+ public int CountFiles;
+
+ ///
+ /// Count of Folders packed into the binary.
+ ///
+ public int CountFolders;
+}
+
+public class GFPackPointers
+{
+ ///
+ /// Data offset for the table.
+ ///
+ public long PtrFileTable; // array stored at end
+
+ ///
+ /// Data offset for the table.
+ ///
+ public long PtrHashPaths; // array stored first
+
+ ///
+ /// Data offset for the table, which has a leading .
+ ///
+ public long[] PtrHashFolders; // array stored in middle
+
+ // immediately after the pointers are the arrays
+
+ public GFPackPointers(BinaryReader br, int folderCount)
+ {
+ PtrFileTable = br.ReadInt64();
+ PtrHashPaths = br.ReadInt64();
+ PtrHashFolders = new long[folderCount];
+ for (int i = 0; i < PtrHashFolders.Length; i++)
+ PtrHashFolders[i] = br.ReadInt64();
+ }
+
+ public void Write(BinaryWriter bw)
+ {
+ bw.Write(PtrFileTable);
+ bw.Write(PtrHashPaths);
+ foreach (var table in PtrHashFolders)
+ bw.Write(table);
+ }
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public class FileHashAbsolute
+{
+ public const int SIZE = 0x08;
+
+ ///
+ /// Filename (with directory details) hash.
+ ///
+ public ulong HashFnv1aPathFull;
+
+ public bool IsMatch(string fileName) => FnvHash.HashFnv1a_64(fileName) == HashFnv1aPathFull;
+}
+
+public class FileHashFolder
+{
+ public FileHashFolderInfo Folder = new();
+ public FileHashIndex[] Files = Array.Empty();
+ public int GetIndexFileName(ulong hash) => Array.FindIndex(Files, z => z.HashFnv1aPathFileName == hash);
+ public int GetIndexFileName(string name) => Array.FindIndex(Files, z => z.IsMatch(name));
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public class FileHashFolderInfo
+{
+ public const int SIZE = 0x10;
+
+ ///
+ /// Filename (without directory details) hash.
+ ///
+ public ulong HashFnv1aPathFolderName;
+ public int FileCount;
+ public uint Padding = 0xCC;
+
+ public bool IsMatch(string fileName) => FnvHash.HashFnv1a_64(fileName) == HashFnv1aPathFolderName;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public class FileHashIndex
+{
+ public const int SIZE = 0x10;
+
+ ///
+ /// Filename (without directory details) hash.
+ ///
+ public ulong HashFnv1aPathFileName;
+ public int Index;
+ public uint Padding = 0xCC;
+
+ public bool IsMatch(string fileName) => FnvHash.HashFnv1a_64(fileName) == HashFnv1aPathFileName;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public class FileData
+{
+ public const int SIZE = 0x18;
+
+ public ushort Level = 9; // quality?
+ public CompressionType Type;
+ public int SizeDecompressed;
+ public int SizeCompressed;
+ public int Padding = 0xCC;
+ public int OffsetPacked;
+ public uint unused;
+}
+
+public enum CompressionType : ushort
+{
+ None = 0,
+ Zlib = 1,
+ Lz4 = 2,
+ OodleKraken = 3,
+ OodleLeviathan = 4,
+ OodleMermaid = 5,
+ OodleSelkie = 6,
+ OodleHydra = 7,
+}
diff --git a/pkNX.Containers/Misc/LZ4.cs b/pkNX.Containers/Misc/LZ4.cs
index 28f1970f..c0aa05eb 100644
--- a/pkNX.Containers/Misc/LZ4.cs
+++ b/pkNX.Containers/Misc/LZ4.cs
@@ -1,10 +1,9 @@
-using LZ4;
+using LZ4;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+public static class LZ4
{
- public static class LZ4
- {
- public static byte[] Decode(byte[] data, int decLength) => LZ4Codec.Decode(data, 0, data.Length, decLength);
- public static byte[] Encode(byte[] data) => LZ4Codec.Encode(data, 0, data.Length);
- }
+ public static byte[] Decode(byte[] data, int decLength) => LZ4Codec.Decode(data, 0, data.Length, decLength);
+ public static byte[] Encode(byte[] data) => LZ4Codec.Encode(data, 0, data.Length);
}
diff --git a/pkNX.Containers/Misc/Oodle.cs b/pkNX.Containers/Misc/Oodle.cs
index 7e81b44c..f8a29ab9 100644
--- a/pkNX.Containers/Misc/Oodle.cs
+++ b/pkNX.Containers/Misc/Oodle.cs
@@ -1,156 +1,155 @@
-using System;
+using System;
using System.Runtime.InteropServices;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+///
+/// Oodle Compression and Decompression wrapper around the external dll.
+///
+///
+/// These methods are safely tuned for Span in order to minimize allocation.
+///
+public static class Oodle
{
///
- /// Oodle Compression and Decompression wrapper around the external dll.
+ /// Oodle Library Path
///
- ///
- /// These methods are safely tuned for Span in order to minimize allocation.
- ///
- public static class Oodle
+ public const string OodleLibraryPath = "oo2core_8_win64";
+
+ ///
+ /// Oodle64 Decompression Method
+ ///
+ [DllImport(OodleLibraryPath, CallingConvention = CallingConvention.Cdecl)]
+ private static extern long OodleLZ_Decompress(ref byte buffer, long bufferSize, ref byte result, long outputBufferSize,
+ OodleFuzzSafe fuzz = OodleFuzzSafe.Yes,
+ OodleCheckCrc crc = OodleCheckCrc.No,
+ OodleVerbosity verbosity = OodleVerbosity.None,
+ long context = 0, long e = 0, long callback = 0, long callback_ctx = 0, long scratch = 0, long scratch_size = 0,
+ OodleThreadPhase threadPhase = OodleThreadPhase.Unthreaded);
+
+ ///
+ /// Oodle64 Compression Method
+ ///
+ [DllImport(OodleLibraryPath)]
+ private static extern long OodleLZ_Compress(OodleFormat format, ref byte buffer, long bufferSize, ref byte result, OodleCompressionLevel level,
+ long opts = 0, long context = 0, long unused = 0, long scratch = 0, long scratch_size = 0);
+
+ ///
+ /// Decompresses a span of Oodle Compressed bytes (Requires Oodle DLL)
+ ///
+ /// Input Compressed Data
+ /// Decompressed Size
+ /// Resulting Array if success, otherwise null.
+ public static byte[]? Decompress(ReadOnlySpan input, long decompressedLength)
{
- ///
- /// Oodle Library Path
- ///
- public const string OodleLibraryPath = "oo2core_8_win64";
-
- ///
- /// Oodle64 Decompression Method
- ///
- [DllImport(OodleLibraryPath, CallingConvention = CallingConvention.Cdecl)]
- private static extern long OodleLZ_Decompress(ref byte buffer, long bufferSize, ref byte result, long outputBufferSize,
- OodleFuzzSafe fuzz = OodleFuzzSafe.Yes,
- OodleCheckCrc crc = OodleCheckCrc.No,
- OodleVerbosity verbosity = OodleVerbosity.None,
- long context = 0, long e = 0, long callback = 0, long callback_ctx = 0, long scratch = 0, long scratch_size = 0,
- OodleThreadPhase threadPhase = OodleThreadPhase.Unthreaded);
-
- ///
- /// Oodle64 Compression Method
- ///
- [DllImport(OodleLibraryPath)]
- private static extern long OodleLZ_Compress(OodleFormat format, ref byte buffer, long bufferSize, ref byte result, OodleCompressionLevel level,
- long opts = 0, long context = 0, long unused = 0, long scratch = 0, long scratch_size = 0);
-
- ///
- /// Decompresses a span of Oodle Compressed bytes (Requires Oodle DLL)
- ///
- /// Input Compressed Data
- /// Decompressed Size
- /// Resulting Array if success, otherwise null.
- public static byte[]? Decompress(ReadOnlySpan input, long decompressedLength)
- {
- var result = new byte[decompressedLength];
- return Decompress(input, result);
- }
-
- private static byte[]? Decompress(ReadOnlySpan input, byte[] result)
- {
- var dest = result.AsSpan();
- long decodedSize = OodleLZ_Decompress(ref MemoryMarshal.GetReference(input), input.Length, ref MemoryMarshal.GetReference(dest), result.Length);
- if (decodedSize == 0)
- return null; // failed
- return result;
- }
-
- ///
- /// Compresses a span of bytes to Oodle Compressed bytes (Requires Oodle DLL)
- ///
- /// Input Decompressed Data
- /// Actual Compressed Data size
- /// Compression format to use
- /// Compression setting to use
- /// Span of compressed data with remainder aligned working bytes.
- public static Span Compress(ReadOnlySpan input, out int compressedSize,
- OodleFormat format = OodleFormat.Kraken, OodleCompressionLevel level = OodleCompressionLevel.Optimal2)
- {
- var maxSize = GetCompressedBufferSizeNeeded(input.Length);
- var result = new byte[maxSize].AsSpan();
- return Compress(input, result, out compressedSize, format, level);
- }
-
- private static Span Compress(ReadOnlySpan input, Span result, out int compressedSize, OodleFormat format, OodleCompressionLevel level)
- {
- var encodedSize = OodleLZ_Compress(format, ref MemoryMarshal.GetReference(input), input.Length, ref MemoryMarshal.GetReference(result), level);
-
- // Oodle's compressed result leaves data after the "compressed length" return index.
- // Return an aligned span (ensuring length is a multiple of 4).
- // Retaining these unused bytes matches the behavior observed in New Pokémon Snap DRPF files.
- compressedSize = (int)encodedSize;
- var align = (compressedSize + 3) & ~3;
- return result[..align];
- }
-
- ///
- /// Gets the dimension required to compress the data.
- ///
- ///
- ///
- private static long GetCompressedBufferSizeNeeded(long inputSize)
- {
- return inputSize + (274 * ((inputSize + 0x3FFFF) / 0x40000));
- }
+ var result = new byte[decompressedLength];
+ return Decompress(input, result);
}
- public enum OodleFormat : uint
+ private static byte[]? Decompress(ReadOnlySpan input, byte[] result)
{
- LZH = 0,
- LZHLW = 1,
- LZNIB = 2,
- None = 3,
- LZB16 = 4,
- LZBLW = 5,
- LZA = 6,
- LZNA = 7,
- Kraken = 8,
- Mermaid = 9,
- BitKnit = 10,
- Selkie = 11,
- Hydra = 12,
- Leviathan = 13,
+ var dest = result.AsSpan();
+ long decodedSize = OodleLZ_Decompress(ref MemoryMarshal.GetReference(input), input.Length, ref MemoryMarshal.GetReference(dest), result.Length);
+ if (decodedSize == 0)
+ return null; // failed
+ return result;
}
- public enum OodleCompressionLevel : ulong
+ ///
+ /// Compresses a span of bytes to Oodle Compressed bytes (Requires Oodle DLL)
+ ///
+ /// Input Decompressed Data
+ /// Actual Compressed Data size
+ /// Compression format to use
+ /// Compression setting to use
+ /// Span of compressed data with remainder aligned working bytes.
+ public static Span Compress(ReadOnlySpan input, out int compressedSize,
+ OodleFormat format = OodleFormat.Kraken, OodleCompressionLevel level = OodleCompressionLevel.Optimal2)
{
- None = 0,
- SuperFast = 1,
- VeryFast = 2,
- Fast = 3,
- Normal = 4,
- Optimal1 = 5,
- Optimal2 = 6,
- Optimal3 = 7,
- Optimal4 = 8,
- Optimal5 = 9,
+ var maxSize = GetCompressedBufferSizeNeeded(input.Length);
+ var result = new byte[maxSize].AsSpan();
+ return Compress(input, result, out compressedSize, format, level);
}
- public enum OodleFuzzSafe
+ private static Span Compress(ReadOnlySpan input, Span result, out int compressedSize, OodleFormat format, OodleCompressionLevel level)
{
- No = 0,
- Yes = 1,
+ var encodedSize = OodleLZ_Compress(format, ref MemoryMarshal.GetReference(input), input.Length, ref MemoryMarshal.GetReference(result), level);
+
+ // Oodle's compressed result leaves data after the "compressed length" return index.
+ // Return an aligned span (ensuring length is a multiple of 4).
+ // Retaining these unused bytes matches the behavior observed in New Pokémon Snap DRPF files.
+ compressedSize = (int)encodedSize;
+ var align = (compressedSize + 3) & ~3;
+ return result[..align];
}
- public enum OodleCheckCrc
+ ///
+ /// Gets the dimension required to compress the data.
+ ///
+ ///
+ ///
+ private static long GetCompressedBufferSizeNeeded(long inputSize)
{
- No = 0,
- Yes = 1,
- }
-
- public enum OodleVerbosity
- {
- None = 0,
- Max = 3,
- }
-
- [Flags]
- public enum OodleThreadPhase
- {
- Invalid = 0,
- ThreadPhase1 = 1,
- ThreadPhase2 = 2,
-
- Unthreaded = ThreadPhase1 | ThreadPhase2, // 3
+ return inputSize + (274 * ((inputSize + 0x3FFFF) / 0x40000));
}
}
+
+public enum OodleFormat : uint
+{
+ LZH = 0,
+ LZHLW = 1,
+ LZNIB = 2,
+ None = 3,
+ LZB16 = 4,
+ LZBLW = 5,
+ LZA = 6,
+ LZNA = 7,
+ Kraken = 8,
+ Mermaid = 9,
+ BitKnit = 10,
+ Selkie = 11,
+ Hydra = 12,
+ Leviathan = 13,
+}
+
+public enum OodleCompressionLevel : ulong
+{
+ None = 0,
+ SuperFast = 1,
+ VeryFast = 2,
+ Fast = 3,
+ Normal = 4,
+ Optimal1 = 5,
+ Optimal2 = 6,
+ Optimal3 = 7,
+ Optimal4 = 8,
+ Optimal5 = 9,
+}
+
+public enum OodleFuzzSafe
+{
+ No = 0,
+ Yes = 1,
+}
+
+public enum OodleCheckCrc
+{
+ No = 0,
+ Yes = 1,
+}
+
+public enum OodleVerbosity
+{
+ None = 0,
+ Max = 3,
+}
+
+[Flags]
+public enum OodleThreadPhase
+{
+ Invalid = 0,
+ ThreadPhase1 = 1,
+ ThreadPhase2 = 2,
+
+ Unthreaded = ThreadPhase1 | ThreadPhase2, // 3
+}
diff --git a/pkNX.Containers/Misc/StructConverter.cs b/pkNX.Containers/Misc/StructConverter.cs
index ad98436d..e5059ea5 100644
--- a/pkNX.Containers/Misc/StructConverter.cs
+++ b/pkNX.Containers/Misc/StructConverter.cs
@@ -1,46 +1,45 @@
-using System;
+using System;
using System.Runtime.InteropServices;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+internal static class StructConverter
{
- internal static class StructConverter
+ public static T ToStructure(this byte[] bytes) where T : struct
{
- public static T ToStructure(this byte[] bytes) where T : struct
- {
- var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
- try { return (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); }
- finally { handle.Free(); }
- }
+ var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
+ try { return (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T))!; }
+ finally { handle.Free(); }
+ }
- public static T ToClass(this byte[] bytes) where T : class
- {
- var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
- try { return (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); }
- finally { handle.Free(); }
- }
+ public static T ToClass(this byte[] bytes) where T : class
+ {
+ var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
+ try { return (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T))!; }
+ finally { handle.Free(); }
+ }
- public static byte[] ToBytesClass(this T obj) where T : class
- {
- int size = Marshal.SizeOf(obj);
- byte[] arr = new byte[size];
+ public static byte[] ToBytesClass(this T obj) where T : class
+ {
+ int size = Marshal.SizeOf(obj);
+ byte[] arr = new byte[size];
- IntPtr ptr = Marshal.AllocHGlobal(size);
- Marshal.StructureToPtr(obj, ptr, true);
- Marshal.Copy(ptr, arr, 0, size);
- Marshal.FreeHGlobal(ptr);
- return arr;
- }
+ IntPtr ptr = Marshal.AllocHGlobal(size);
+ Marshal.StructureToPtr(obj, ptr, true);
+ Marshal.Copy(ptr, arr, 0, size);
+ Marshal.FreeHGlobal(ptr);
+ return arr;
+ }
- public static byte[] ToBytes(this T obj) where T : struct
- {
- int size = Marshal.SizeOf(obj);
- byte[] arr = new byte[size];
+ public static byte[] ToBytes(this T obj) where T : struct
+ {
+ int size = Marshal.SizeOf(obj);
+ byte[] arr = new byte[size];
- IntPtr ptr = Marshal.AllocHGlobal(size);
- Marshal.StructureToPtr(obj, ptr, true);
- Marshal.Copy(ptr, arr, 0, size);
- Marshal.FreeHGlobal(ptr);
- return arr;
- }
+ IntPtr ptr = Marshal.AllocHGlobal(size);
+ Marshal.StructureToPtr(obj, ptr, true);
+ Marshal.Copy(ptr, arr, 0, size);
+ Marshal.FreeHGlobal(ptr);
+ return arr;
}
}
diff --git a/pkNX.Containers/NX/NSO.cs b/pkNX.Containers/NX/NSO.cs
index 0ecf9845..a93c7d4b 100644
--- a/pkNX.Containers/NX/NSO.cs
+++ b/pkNX.Containers/NX/NSO.cs
@@ -1,134 +1,133 @@
-using System.IO;
+using System.IO;
using System.Linq;
using System.Security.Cryptography;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+public class NSO
{
- public class NSO
- {
- public NSOHeader Header { get; private set; }
+ public NSOHeader Header { get; private set; }
- public byte[] CompressedText { get; set; }
- public byte[] CompressedRO { get; set; }
- public byte[] CompressedData { get; set; }
+ public byte[] CompressedText { get; set; }
+ public byte[] CompressedRO { get; set; }
+ public byte[] CompressedData { get; set; }
- public byte[] DecompressedText { get; set; }
- public byte[] DecompressedRO { get; set; }
- public byte[] DecompressedData { get; set; }
+ public byte[] DecompressedText { get; set; }
+ public byte[] DecompressedRO { get; set; }
+ public byte[] DecompressedData { get; set; }
- public bool ValidText => Hash(DecompressedText).SequenceEqual(Header.HashText);
- public bool ValidRO => Hash(DecompressedText).SequenceEqual(Header.HashText);
- public bool ValidData => Hash(DecompressedText).SequenceEqual(Header.HashText);
+ public bool ValidText => Hash(DecompressedText).SequenceEqual(Header.HashText);
+ public bool ValidRO => Hash(DecompressedText).SequenceEqual(Header.HashText);
+ public bool ValidData => Hash(DecompressedText).SequenceEqual(Header.HashText);
- public decimal CompressionRatioText => (decimal)DecompressedText.Length / CompressedText.Length;
- public decimal CompressionRatioRO => (decimal)DecompressedRO.Length / CompressedRO.Length;
- public decimal CompressionRatioData => (decimal)DecompressedData.Length / CompressedData.Length;
+ public decimal CompressionRatioText => (decimal)DecompressedText.Length / CompressedText.Length;
+ public decimal CompressionRatioRO => (decimal)DecompressedRO.Length / CompressedRO.Length;
+ public decimal CompressionRatioData => (decimal)DecompressedData.Length / CompressedData.Length;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
- public NSO(BinaryReader br) => ReadHeader(br);
+ public NSO(BinaryReader br) => ReadHeader(br);
- public NSO(byte[] data)
- {
- using var ms = new MemoryStream(data);
- using var br = new BinaryReader(ms);
- ReadHeader(br);
- }
+ public NSO(byte[] data)
+ {
+ using var ms = new MemoryStream(data);
+ using var br = new BinaryReader(ms);
+ ReadHeader(br);
+ }
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
- private void ReadHeader(BinaryReader br)
- {
- if (br.BaseStream.Length < NSOHeader.SIZE)
- return;
- Header = br.ReadBytes(NSOHeader.SIZE).ToClass();
+ private void ReadHeader(BinaryReader br)
+ {
+ if (br.BaseStream.Length < NSOHeader.SIZE)
+ return;
+ Header = br.ReadBytes(NSOHeader.SIZE).ToClass();
- // seek around to decode
- CompressedText = GetCompressedSegment(br, Header.HeaderText, Header.SizeCompressedText);
- CompressedRO = GetCompressedSegment(br, Header.HeaderRO, Header.SizeCompressedRO);
- CompressedData = GetCompressedSegment(br, Header.HeaderData, Header.SizeCompressedData);
+ // seek around to decode
+ CompressedText = GetCompressedSegment(br, Header.HeaderText, Header.SizeCompressedText);
+ CompressedRO = GetCompressedSegment(br, Header.HeaderRO, Header.SizeCompressedRO);
+ CompressedData = GetCompressedSegment(br, Header.HeaderData, Header.SizeCompressedData);
- Decompress();
- }
+ Decompress();
+ }
- public static byte[] GetCompressedSegment(BinaryReader br, SegmentHeader h, int sizeCompressed)
- {
- br.BaseStream.Position = h.FileOffset;
- return br.ReadBytes(sizeCompressed);
- }
+ public static byte[] GetCompressedSegment(BinaryReader br, SegmentHeader h, int sizeCompressed)
+ {
+ br.BaseStream.Position = h.FileOffset;
+ return br.ReadBytes(sizeCompressed);
+ }
- public static byte[] GetDecompressedSegment(BinaryReader br, SegmentHeader h, int sizeCompressed)
- {
- byte[] data = GetCompressedSegment(br, h, sizeCompressed);
- return LZ4.Decode(data, h.DecompressedSize);
- }
+ public static byte[] GetDecompressedSegment(BinaryReader br, SegmentHeader h, int sizeCompressed)
+ {
+ byte[] data = GetCompressedSegment(br, h, sizeCompressed);
+ return LZ4.Decode(data, h.DecompressedSize);
+ }
- public static byte[] Hash(byte[] data)
- {
- using var method = SHA256.Create();
- return method.ComputeHash(data);
- }
+ public static byte[] Hash(byte[] data)
+ {
+ using var method = SHA256.Create();
+ return method.ComputeHash(data);
+ }
- private void Decompress()
- {
- DecompressedText = Header.Flags.HasFlagFast(NSOFlag.CompressedText)
- ? LZ4.Decode(CompressedText, Header.HeaderText.DecompressedSize)
- : CompressedText;
- DecompressedRO = Header.Flags.HasFlagFast(NSOFlag.CompressedRO)
- ? LZ4.Decode(CompressedRO, Header.HeaderRO.DecompressedSize)
- : CompressedRO;
- DecompressedData = Header.Flags.HasFlagFast(NSOFlag.CompressedData)
- ? LZ4.Decode(CompressedData, Header.HeaderData.DecompressedSize)
- : CompressedData;
- }
+ private void Decompress()
+ {
+ DecompressedText = Header.Flags.HasFlagFast(NSOFlag.CompressedText)
+ ? LZ4.Decode(CompressedText, Header.HeaderText.DecompressedSize)
+ : CompressedText;
+ DecompressedRO = Header.Flags.HasFlagFast(NSOFlag.CompressedRO)
+ ? LZ4.Decode(CompressedRO, Header.HeaderRO.DecompressedSize)
+ : CompressedRO;
+ DecompressedData = Header.Flags.HasFlagFast(NSOFlag.CompressedData)
+ ? LZ4.Decode(CompressedData, Header.HeaderData.DecompressedSize)
+ : CompressedData;
+ }
- private void Compress()
- {
- CompressedText = Header.Flags.HasFlagFast(NSOFlag.CompressedText)
- ? LZ4.Encode(DecompressedText)
- : DecompressedText;
- Header.SizeCompressedText = CompressedText.Length;
- Header.HeaderText.DecompressedSize = DecompressedText.Length;
- Header.HashText = Hash(DecompressedText);
+ private void Compress()
+ {
+ CompressedText = Header.Flags.HasFlagFast(NSOFlag.CompressedText)
+ ? LZ4.Encode(DecompressedText)
+ : DecompressedText;
+ Header.SizeCompressedText = CompressedText.Length;
+ Header.HeaderText.DecompressedSize = DecompressedText.Length;
+ Header.HashText = Hash(DecompressedText);
- CompressedRO = Header.Flags.HasFlagFast(NSOFlag.CompressedRO)
- ? LZ4.Encode(DecompressedRO)
- : DecompressedRO;
- Header.SizeCompressedRO = CompressedRO.Length;
- Header.HeaderRO.DecompressedSize = DecompressedRO.Length;
- Header.HashRO = Hash(DecompressedRO);
+ CompressedRO = Header.Flags.HasFlagFast(NSOFlag.CompressedRO)
+ ? LZ4.Encode(DecompressedRO)
+ : DecompressedRO;
+ Header.SizeCompressedRO = CompressedRO.Length;
+ Header.HeaderRO.DecompressedSize = DecompressedRO.Length;
+ Header.HashRO = Hash(DecompressedRO);
- CompressedData = Header.Flags.HasFlagFast(NSOFlag.CompressedData)
- ? LZ4.Encode(DecompressedData)
- : DecompressedData;
- Header.SizeCompressedData = CompressedData.Length;
- Header.HeaderData.DecompressedSize = DecompressedData.Length;
- Header.HashData = Hash(DecompressedData);
- }
+ CompressedData = Header.Flags.HasFlagFast(NSOFlag.CompressedData)
+ ? LZ4.Encode(DecompressedData)
+ : DecompressedData;
+ Header.SizeCompressedData = CompressedData.Length;
+ Header.HeaderData.DecompressedSize = DecompressedData.Length;
+ Header.HashData = Hash(DecompressedData);
+ }
- public byte[] Write()
- {
- Compress();
- using var ms = new MemoryStream();
- using var bw = new BinaryWriter(ms);
- bw.Write(Header.ToBytesClass());
- while (bw.BaseStream.Position != Header.HeaderText.FileOffset)
- bw.Write((byte)0); // match layout for previous example
+ public byte[] Write()
+ {
+ Compress();
+ using var ms = new MemoryStream();
+ using var bw = new BinaryWriter(ms);
+ bw.Write(Header.ToBytesClass());
+ while (bw.BaseStream.Position != Header.HeaderText.FileOffset)
+ bw.Write((byte)0); // match layout for previous example
- // text
- Header.HeaderText.FileOffset = (int)bw.BaseStream.Position;
- bw.Write(CompressedText);
+ // text
+ Header.HeaderText.FileOffset = (int)bw.BaseStream.Position;
+ bw.Write(CompressedText);
- // ro
- Header.HeaderRO.FileOffset = (int)bw.BaseStream.Position;
- bw.Write(CompressedRO);
+ // ro
+ Header.HeaderRO.FileOffset = (int)bw.BaseStream.Position;
+ bw.Write(CompressedRO);
- // data
- Header.HeaderData.FileOffset = (int)bw.BaseStream.Position;
- bw.Write(CompressedData);
+ // data
+ Header.HeaderData.FileOffset = (int)bw.BaseStream.Position;
+ bw.Write(CompressedData);
- bw.BaseStream.Position = 0;
- bw.Write(Header.ToBytesClass());
+ bw.BaseStream.Position = 0;
+ bw.Write(Header.ToBytesClass());
- return ms.ToArray();
- }
+ return ms.ToArray();
}
}
diff --git a/pkNX.Containers/NX/NSOFlag.cs b/pkNX.Containers/NX/NSOFlag.cs
index d10f1219..9f9154da 100644
--- a/pkNX.Containers/NX/NSOFlag.cs
+++ b/pkNX.Containers/NX/NSOFlag.cs
@@ -1,23 +1,22 @@
using System;
-namespace pkNX.Containers
-{
- [Flags]
- public enum NSOFlag : uint
- {
- None = 0,
- CompressedText = 1 << 0,
- CompressedRO = 1 << 1,
- CompressedData = 1 << 2,
- CheckHashText = 1 << 3,
- CheckHashRO = 1 << 4,
- CheckHashData = 1 << 5,
- Unused6 = 1 << 6,
- Unused7 = 1 << 7,
- }
+namespace pkNX.Containers;
- public static class NsoFlagExtensions
- {
- public static bool HasFlagFast(this NSOFlag value, NSOFlag flag) => (value & flag) != 0;
- }
-}
\ No newline at end of file
+[Flags]
+public enum NSOFlag : uint
+{
+ None = 0,
+ CompressedText = 1 << 0,
+ CompressedRO = 1 << 1,
+ CompressedData = 1 << 2,
+ CheckHashText = 1 << 3,
+ CheckHashRO = 1 << 4,
+ CheckHashData = 1 << 5,
+ Unused6 = 1 << 6,
+ Unused7 = 1 << 7,
+}
+
+public static class NsoFlagExtensions
+{
+ public static bool HasFlagFast(this NSOFlag value, NSOFlag flag) => (value & flag) != 0;
+}
diff --git a/pkNX.Containers/NX/NSOHeader.cs b/pkNX.Containers/NX/NSOHeader.cs
index d8d5fa28..ac3cf9c5 100644
--- a/pkNX.Containers/NX/NSOHeader.cs
+++ b/pkNX.Containers/NX/NSOHeader.cs
@@ -1,51 +1,50 @@
using System.Runtime.InteropServices;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+[StructLayout(LayoutKind.Sequential)]
+public class NSOHeader
{
- [StructLayout(LayoutKind.Sequential)]
- public class NSOHeader
- {
- public const int SIZE = 0x100;
- public const uint ExpectedMagic = 0x304F534E; // NSO0
- public bool Valid => Magic == ExpectedMagic;
+ public const int SIZE = 0x100;
+ public const uint ExpectedMagic = 0x304F534E; // NSO0
+ public bool Valid => Magic == ExpectedMagic;
- // Structure below
+ // Structure below
- public uint Magic;
- public uint Version;
- public uint Reserved;
- public NSOFlag Flags;
+ public uint Magic;
+ public uint Version;
+ public uint Reserved;
+ public NSOFlag Flags;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
- public SegmentHeader HeaderText;
- public int ModuleOffset;
- public SegmentHeader HeaderRO;
- public int ModuleFileSize;
- public SegmentHeader HeaderData;
- public int BssSize;
+ public SegmentHeader HeaderText;
+ public int ModuleOffset;
+ public SegmentHeader HeaderRO;
+ public int ModuleFileSize;
+ public SegmentHeader HeaderData;
+ public int BssSize;
- [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)]
- public byte[] DigestBuildID;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)]
+ public byte[] DigestBuildID;
- public int SizeCompressedText;
- public int SizeCompressedRO;
- public int SizeCompressedData;
+ public int SizeCompressedText;
+ public int SizeCompressedRO;
+ public int SizeCompressedData;
- [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x1C)]
- public byte[] Padding;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x1C)]
+ public byte[] Padding;
- public RelativeExtent APIInfo;
- public RelativeExtent DynStr;
- public RelativeExtent DynSym;
+ public RelativeExtent APIInfo;
+ public RelativeExtent DynStr;
+ public RelativeExtent DynSym;
- [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)]
- public byte[] HashText;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)]
+ public byte[] HashText;
- [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)]
- public byte[] HashRO;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)]
+ public byte[] HashRO;
- [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)]
- public byte[] HashData;
- }
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)]
+ public byte[] HashData;
+}
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
-}
\ No newline at end of file
diff --git a/pkNX.Containers/NX/RelativeExtent.cs b/pkNX.Containers/NX/RelativeExtent.cs
index 42fae8b3..c3548a7c 100644
--- a/pkNX.Containers/NX/RelativeExtent.cs
+++ b/pkNX.Containers/NX/RelativeExtent.cs
@@ -1,11 +1,10 @@
using System.Runtime.InteropServices;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+[StructLayout(LayoutKind.Sequential)]
+public class RelativeExtent
{
- [StructLayout(LayoutKind.Sequential)]
- public class RelativeExtent
- {
- public int RegionRODataOffset;
- public int RegionSize;
- }
-}
\ No newline at end of file
+ public int RegionRODataOffset;
+ public int RegionSize;
+}
diff --git a/pkNX.Containers/NX/SegmentHeader.cs b/pkNX.Containers/NX/SegmentHeader.cs
index 250aaacc..298bc4f5 100644
--- a/pkNX.Containers/NX/SegmentHeader.cs
+++ b/pkNX.Containers/NX/SegmentHeader.cs
@@ -1,12 +1,11 @@
using System.Runtime.InteropServices;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+[StructLayout(LayoutKind.Sequential)]
+public class SegmentHeader
{
- [StructLayout(LayoutKind.Sequential)]
- public class SegmentHeader
- {
- public int FileOffset;
- public int MemoryOffset;
- public int DecompressedSize;
- }
-}
\ No newline at end of file
+ public int FileOffset;
+ public int MemoryOffset;
+ public int DecompressedSize;
+}
diff --git a/pkNX.Containers/SARC/SARC.cs b/pkNX.Containers/SARC/SARC.cs
index 19b2f3a0..fcdf8f9e 100644
--- a/pkNX.Containers/SARC/SARC.cs
+++ b/pkNX.Containers/SARC/SARC.cs
@@ -1,173 +1,172 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+///
+/// Simple (?) ARChive
+///
+public sealed class SARC : LargeContainer
{
+ private const string Identifier = nameof(SARC);
+
+ public SARCHeader Header { get; set; }
+ public SFAT SFAT;
+ public SFNT SFNT;
+
+ public override int Count => SFAT.Entries.Count;
+
///
- /// Simple (?) ARChive
+ /// The required matches the first 4 bytes of the file data.
///
- public sealed class SARC : LargeContainer
+ public bool SigMatches => Header.Magic == Identifier;
+
+ ///
+ /// Initializes an empty .
+ ///
+ /// Files inside the .
+ /// Root location of the archive
+ public SARC(IReadOnlyList files, string baseFolder)
{
- private const string Identifier = nameof(SARC);
-
- public SARCHeader Header { get; set; }
- public SFAT SFAT;
- public SFNT SFNT;
-
- public override int Count => SFAT.Entries.Count;
-
- ///
- /// The required matches the first 4 bytes of the file data.
- ///
- public bool SigMatches => Header.Magic == Identifier;
-
- ///
- /// Initializes an empty .
- ///
- /// Files inside the .
- /// Root location of the archive
- public SARC(IReadOnlyList files, string baseFolder)
- {
- Header = new SARCHeader();
- SFAT = new SFAT(baseFolder, files);
- SFNT = new SFNT();
- Files = new byte[]?[SFAT.Entries.Count];
- }
+ Header = new SARCHeader();
+ SFAT = new SFAT(baseFolder, files);
+ SFNT = new SFNT();
+ Files = new byte[]?[SFAT.Entries.Count];
+ }
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
- ///
- /// Initializes a from a file location.
- ///
- ///
- public SARC(string path) => OpenBinary(path);
+ ///
+ /// Initializes a from a file location.
+ ///
+ ///
+ public SARC(string path) => OpenBinary(path);
- ///
- /// Initializes a from a provided stream.
- ///
- ///
- public SARC(Stream fs) => OpenRead(new BinaryReader(fs));
+ ///
+ /// Initializes a from a provided stream.
+ ///
+ ///
+ public SARC(Stream fs) => OpenRead(new BinaryReader(fs));
- ///
- /// Initializes a from a provided reader.
- ///
- ///
- public SARC(BinaryReader br) => OpenRead(br);
+ ///
+ /// Initializes a from a provided reader.
+ ///
+ ///
+ public SARC(BinaryReader br) => OpenRead(br);
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
- ///
- /// Reads the contents of the header and file info tables.
- ///
- protected override void Initialize()
+ ///
+ /// Reads the contents of the header and file info tables.
+ ///
+ protected override void Initialize()
+ {
+ if (Reader is null)
+ throw new NullReferenceException(nameof(Reader));
+ Header = new SARCHeader(Reader);
+ if (!SigMatches)
+ return;
+ SFAT = new SFAT(Reader, Header.DataOffset);
+ SFNT = new SFNT(Reader);
+ }
+
+ protected override Task Pack(BinaryWriter bw, ContainerHandler handler, CancellationToken token)
+ {
+ return Task.Run(() => PackSARC(bw, handler, token), token);
+ }
+
+ private void PackSARC(BinaryWriter bw, ContainerHandler handler, CancellationToken token)
+ {
+ StartPack();
+ var count = SFAT.Entries.Count;
+ handler.Initialize(count);
+ var currentStringOffset = SFNT.StringOffset;
+
+ WriteIntro(bw);
+ for (int i = 0; i < count; i++)
{
- if (Reader is null)
- throw new NullReferenceException(nameof(Reader));
- Header = new SARCHeader(Reader);
- if (!SigMatches)
+ var entr = SFAT.Entries[i];
+ if (entr.FileName == null)
+ entr.GetFileName(Reader!.BaseStream, (int)currentStringOffset);
+ entr.WriteFileName(Reader!.BaseStream, (int)SFNT.StringOffset);
+ while (Reader.BaseStream.Position % 4 != 0)
+ bw.Write((byte)0);
+ }
+ for (int i = 0; i < count; i++)
+ {
+ if (token.IsCancellationRequested)
return;
- SFAT = new SFAT(Reader, Header.DataOffset);
- SFNT = new SFNT(Reader);
+ WriteEntry(bw, i);
}
+ WriteIntro(bw, true);
+ bw.Flush();
+ }
- protected override Task Pack(BinaryWriter bw, ContainerHandler handler, CancellationToken token)
- {
- return Task.Run(() => PackSARC(bw, handler, token), token);
- }
+ private void StartPack() { }
- private void PackSARC(BinaryWriter bw, ContainerHandler handler, CancellationToken token)
- {
- StartPack();
- var count = SFAT.Entries.Count;
- handler.Initialize(count);
- var currentStringOffset = SFNT.StringOffset;
+ private void WriteIntro(BinaryWriter bw, bool finalPass = false)
+ {
+ Header.Write(bw);
+ SFAT.Write(bw);
+ SFNT.Write(bw);
+ if (finalPass)
+ Header.DataOffset = (int)bw.BaseStream.Position;
+ }
- WriteIntro(bw);
- for (int i = 0; i < count; i++)
- {
- var entr = SFAT.Entries[i];
- if (entr.FileName == null)
- entr.GetFileName(Reader!.BaseStream, (int)currentStringOffset);
- entr.WriteFileName(Reader!.BaseStream, (int)SFNT.StringOffset);
- while (Reader.BaseStream.Position % 4 != 0)
- bw.Write((byte)0);
- }
- for (int i = 0; i < count; i++)
- {
- if (token.IsCancellationRequested)
- return;
- WriteEntry(bw, i);
- }
- WriteIntro(bw, true);
- bw.Flush();
- }
+ private void WriteEntry(BinaryWriter bw, int i)
+ {
+ throw new NotImplementedException();
+ }
- private void StartPack() { }
+ protected override int GetFileOffset(int file, int subFile = 0)
+ {
+ var f = SFAT[file];
+ return Header.DataOffset + f.Start;
+ }
- private void WriteIntro(BinaryWriter bw, bool finalPass = false)
- {
- Header.Write(bw);
- SFAT.Write(bw);
- SFNT.Write(bw);
- if (finalPass)
- Header.DataOffset = (int)bw.BaseStream.Position;
- }
-
- private void WriteEntry(BinaryWriter bw, int i)
- {
- throw new NotImplementedException();
- }
-
- protected override int GetFileOffset(int file, int subFile = 0)
- {
- var f = SFAT[file];
- return Header.DataOffset + f.Start;
- }
-
- public override byte[] GetEntry(int index, int subFile)
- {
- var f = SFAT[index];
- if (f.File is byte[] data)
- return data;
-
- data = f.GetFileData(Reader!.BaseStream);
- f.File = data; // cache for future fetches
+ public override byte[] GetEntry(int index, int subFile)
+ {
+ var f = SFAT[index];
+ if (f.File is byte[] data)
return data;
- }
- public override void Dump(string? path, ContainerHandler handler)
+ data = f.GetFileData(Reader!.BaseStream);
+ f.File = data; // cache for future fetches
+ return data;
+ }
+
+ public override void Dump(string? path, ContainerHandler handler)
+ {
+ path ??= FilePath;
+ if (path == null)
+ throw new ArgumentNullException(nameof(path));
+ if (File.Exists(path))
+ path = Path.GetDirectoryName(path);
+ if (path == null)
+ throw new ArgumentNullException(nameof(path));
+
+ var folder = FileName ?? "sarc";
+ string dir = Path.Combine(path, folder);
+
+ Directory.CreateDirectory(dir);
+ var count = SFAT.Entries.Count;
+ handler.Initialize(count);
+ for (int i = 0; i < count; i++)
{
- path ??= FilePath;
- if (path == null)
- throw new ArgumentNullException(nameof(path));
- if (File.Exists(path))
- path = Path.GetDirectoryName(path);
- if (path == null)
- throw new ArgumentNullException(nameof(path));
-
- var folder = FileName ?? "sarc";
- string dir = Path.Combine(path, folder);
-
- Directory.CreateDirectory(dir);
- var count = SFAT.Entries.Count;
- handler.Initialize(count);
- for (int i = 0; i < count; i++)
- {
- SFAT.Entries[i].Dump(Reader!.BaseStream, path, Header.DataOffset);
- handler.StepFile(i + 1);
- }
- }
-
- public static SARC? GetSARC(BinaryReader br)
- {
- if (br.BaseStream.Length < 20)
- return null;
- br.BaseStream.Position = 0;
- var ident = new string(br.ReadChars(4));
- if (ident != SARCHeader.Identifier)
- return null;
- return new SARC(br);
+ SFAT.Entries[i].Dump(Reader!.BaseStream, path, Header.DataOffset);
+ handler.StepFile(i + 1);
}
}
-}
\ No newline at end of file
+
+ public static SARC? GetSARC(BinaryReader br)
+ {
+ if (br.BaseStream.Length < 20)
+ return null;
+ br.BaseStream.Position = 0;
+ var ident = new string(br.ReadChars(4));
+ if (ident != SARCHeader.Identifier)
+ return null;
+ return new SARC(br);
+ }
+}
diff --git a/pkNX.Containers/SARC/SARCHeader.cs b/pkNX.Containers/SARC/SARCHeader.cs
index a2d346d1..c4893c7f 100644
--- a/pkNX.Containers/SARC/SARCHeader.cs
+++ b/pkNX.Containers/SARC/SARCHeader.cs
@@ -1,47 +1,46 @@
-using System.IO;
+using System.IO;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+public class SARCHeader
{
- public class SARCHeader
+ public const string Identifier = nameof(SARC);
+
+ ///
+ /// The required matches the first 4 bytes of the file data.
+ ///
+ public bool SigMatches => Magic == Identifier;
+
+ public string Magic = Identifier;
+ public ushort HeaderSize = 0x14;
+ public ushort Endianness = 0xFFFE;
+ public uint FileSize;
+ public int DataOffset;
+ public ushort Version = 0x0100;
+ public ushort Reserved;
+
+ public SARCHeader() { }
+
+ public SARCHeader(BinaryReader Reader)
{
- public const string Identifier = nameof(SARC);
+ Magic = new string(Reader.ReadChars(4));
+ HeaderSize = Reader.ReadUInt16();
+ Endianness = Reader.ReadUInt16();
+ FileSize = Reader.ReadUInt32();
+ DataOffset = Reader.ReadInt32();
+ Version = Reader.ReadUInt16();
+ Reserved = Reader.ReadUInt16();
+ }
- ///
- /// The required matches the first 4 bytes of the file data.
- ///
- public bool SigMatches => Magic == Identifier;
-
- public string Magic = Identifier;
- public ushort HeaderSize = 0x14;
- public ushort Endianness = 0xFFFE;
- public uint FileSize;
- public int DataOffset;
- public ushort Version = 0x0100;
- public ushort Reserved;
-
- public SARCHeader() { }
-
- public SARCHeader(BinaryReader Reader)
- {
- Magic = new string(Reader.ReadChars(4));
- HeaderSize = Reader.ReadUInt16();
- Endianness = Reader.ReadUInt16();
- FileSize = Reader.ReadUInt32();
- DataOffset = Reader.ReadInt32();
- Version = Reader.ReadUInt16();
- Reserved = Reader.ReadUInt16();
- }
-
- public void Write(BinaryWriter bw)
- {
- foreach (var c in Magic)
- bw.Write((byte)c);
- bw.Write(HeaderSize);
- bw.Write(FileSize);
- bw.Write(FileSize);
- bw.Write(DataOffset);
- bw.Write(Version);
- bw.Write(Reserved);
- }
+ public void Write(BinaryWriter bw)
+ {
+ foreach (var c in Magic)
+ bw.Write((byte)c);
+ bw.Write(HeaderSize);
+ bw.Write(FileSize);
+ bw.Write(FileSize);
+ bw.Write(DataOffset);
+ bw.Write(Version);
+ bw.Write(Reserved);
}
}
diff --git a/pkNX.Containers/SARC/SFAT.cs b/pkNX.Containers/SARC/SFAT.cs
index f538ea59..313533db 100644
--- a/pkNX.Containers/SARC/SFAT.cs
+++ b/pkNX.Containers/SARC/SFAT.cs
@@ -1,78 +1,77 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+///
+/// File Access Table
+///
+public class SFAT
{
+ public const string Identifier = nameof(SFAT);
+
///
- /// File Access Table
+ /// The required matches the first 4 bytes of the file data.
///
- public class SFAT
+ public bool SigMatches => Magic == Identifier;
+
+ private readonly string Magic = Identifier;
+ private readonly ushort HeaderSize = 0xC;
+ private readonly ushort EntryCount;
+ private readonly uint HashMult = 0x65;
+ public readonly List Entries;
+
+ public SFATEntry this[int index]
{
- public const string Identifier = nameof(SFAT);
+ get => Entries[index];
+ set => Entries[index] = value;
+ }
- ///
- /// The required matches the first 4 bytes of the file data.
- ///
- public bool SigMatches => Magic == Identifier;
-
- private readonly string Magic = Identifier;
- private readonly ushort HeaderSize = 0xC;
- private readonly ushort EntryCount;
- private readonly uint HashMult = 0x65;
- public readonly List Entries;
-
- public SFATEntry this[int index]
+ public SFAT(string baseFolder, IReadOnlyList files)
+ {
+ EntryCount = (ushort)files.Count;
+ Entries = new List(EntryCount);
+ for (int i = 0; i < EntryCount; i++)
{
- get => Entries[index];
- set => Entries[index] = value;
- }
+ Entries[i] = new SFATEntry {File = files[i]};
- public SFAT(string baseFolder, IReadOnlyList files)
- {
- EntryCount = (ushort)files.Count;
- Entries = new List(EntryCount);
- for (int i = 0; i < EntryCount; i++)
- {
- Entries[i] = new SFATEntry {File = files[i]};
-
- var fn = files[i].Remove(baseFolder.Length);
- Entries[i].SetFileName(fn, HashMult);
- }
- }
-
- public SFAT(BinaryReader br, int DataOffset)
- {
- Magic = new string(br.ReadChars(4));
- if (!SigMatches)
- throw new FormatException(nameof(SFAT));
-
- HeaderSize = br.ReadUInt16();
- EntryCount = br.ReadUInt16();
- HashMult = br.ReadUInt32();
- Entries = new List(EntryCount);
-
- for (int i = 0; i < EntryCount; i++)
- Entries.Add(new SFATEntry(br, DataOffset));
- }
-
- public string GetFileName(int index, Stream parent, int StringOffset) => this[index].GetFileName(parent, StringOffset);
- public void SetFileName(int index, string value) => this[index].SetFileName(value, HashMult);
-
- public void Write(BinaryWriter bw)
- {
- foreach (var c in Magic)
- bw.Write((byte)c);
- bw.Write(HeaderSize);
- bw.Write(EntryCount);
- bw.Write(HashMult);
- WriteEntries(bw);
- }
-
- public void WriteEntries(BinaryWriter bw)
- {
- foreach (var entry in Entries)
- entry.Write(bw);
+ var fn = files[i].Remove(baseFolder.Length);
+ Entries[i].SetFileName(fn, HashMult);
}
}
-}
\ No newline at end of file
+
+ public SFAT(BinaryReader br, int DataOffset)
+ {
+ Magic = new string(br.ReadChars(4));
+ if (!SigMatches)
+ throw new FormatException(nameof(SFAT));
+
+ HeaderSize = br.ReadUInt16();
+ EntryCount = br.ReadUInt16();
+ HashMult = br.ReadUInt32();
+ Entries = new List(EntryCount);
+
+ for (int i = 0; i < EntryCount; i++)
+ Entries.Add(new SFATEntry(br, DataOffset));
+ }
+
+ public string GetFileName(int index, Stream parent, int StringOffset) => this[index].GetFileName(parent, StringOffset);
+ public void SetFileName(int index, string value) => this[index].SetFileName(value, HashMult);
+
+ public void Write(BinaryWriter bw)
+ {
+ foreach (var c in Magic)
+ bw.Write((byte)c);
+ bw.Write(HeaderSize);
+ bw.Write(EntryCount);
+ bw.Write(HashMult);
+ WriteEntries(bw);
+ }
+
+ public void WriteEntries(BinaryWriter bw)
+ {
+ foreach (var entry in Entries)
+ entry.Write(bw);
+ }
+}
diff --git a/pkNX.Containers/SARC/SFATEntry.cs b/pkNX.Containers/SARC/SFATEntry.cs
index 1f46f32d..40a6a6ef 100644
--- a/pkNX.Containers/SARC/SFATEntry.cs
+++ b/pkNX.Containers/SARC/SFATEntry.cs
@@ -1,81 +1,80 @@
-using System.IO;
+using System.IO;
using System.Text;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+///
+/// File Access Table () Entry
+///
+public class SFATEntry : LargeContainerEntry
{
- ///
- /// File Access Table () Entry
- ///
- public class SFATEntry : LargeContainerEntry
+ public uint FileNameHash;
+ public int FileNameOffset;
+
+ public string? FileName { get; private set; }
+
+ private static uint GetHash(string name, int length, uint multiplier)
{
- public uint FileNameHash;
- public int FileNameOffset;
-
- public string? FileName { get; private set; }
-
- private static uint GetHash(string name, int length, uint multiplier)
- {
- uint result = 0;
- for (int i = 0; i < length; i++)
- result = name[i] + (result * multiplier);
- return result;
- }
-
- public override int Length
- {
- get => End - Start;
- set { }
- }
-
- public SFATEntry() { }
-
- public SFATEntry(BinaryReader br, int DataOffset)
- {
- FileNameHash = br.ReadUInt32();
- FileNameOffset = br.ReadInt32();
- Start = br.ReadInt32();
- End = br.ReadInt32();
- ParentDataPosition = DataOffset;
- }
-
- public void Write(BinaryWriter bw)
- {
- bw.Write(FileNameHash);
- bw.Write(FileNameOffset);
- bw.Write(Start);
- bw.Write(End);
- }
-
- public string GetFileName(Stream parent, int StringOffset)
- {
- if (FileName != null)
- return FileName;
-
- var ofs = ((FileNameOffset & 0x00FFFFFF) * 4) + StringOffset;
- parent.Seek(ofs, SeekOrigin.Begin);
- var sb = new StringBuilder();
-
- for (char c = (char)parent.ReadByte(); c != 0; c = (char)parent.ReadByte())
- sb.Append(c);
-
- FileName = sb.Replace('/', Path.DirectorySeparatorChar).ToString();
- return FileName;
- }
-
- public void WriteFileName(Stream parent, int StringOffset)
- {
- FileNameOffset = (int)(parent.Position - StringOffset) / 4;
-
- var str = FileName?.Replace(Path.DirectorySeparatorChar, '/') ?? string.Empty;
- foreach (var b in str)
- parent.WriteByte((byte)b);
- parent.WriteByte(0); // \0
- }
-
- public void SetFileName(string value, uint hashMult)
- {
- FileName = value;
- FileNameHash = GetHash(value, value.Length, hashMult);
- }
+ uint result = 0;
+ for (int i = 0; i < length; i++)
+ result = name[i] + (result * multiplier);
+ return result;
}
-}
\ No newline at end of file
+
+ public override int Length
+ {
+ get => End - Start;
+ set { }
+ }
+
+ public SFATEntry() { }
+
+ public SFATEntry(BinaryReader br, int DataOffset)
+ {
+ FileNameHash = br.ReadUInt32();
+ FileNameOffset = br.ReadInt32();
+ Start = br.ReadInt32();
+ End = br.ReadInt32();
+ ParentDataPosition = DataOffset;
+ }
+
+ public void Write(BinaryWriter bw)
+ {
+ bw.Write(FileNameHash);
+ bw.Write(FileNameOffset);
+ bw.Write(Start);
+ bw.Write(End);
+ }
+
+ public string GetFileName(Stream parent, int StringOffset)
+ {
+ if (FileName != null)
+ return FileName;
+
+ var ofs = ((FileNameOffset & 0x00FFFFFF) * 4) + StringOffset;
+ parent.Seek(ofs, SeekOrigin.Begin);
+ var sb = new StringBuilder();
+
+ for (char c = (char)parent.ReadByte(); c != 0; c = (char)parent.ReadByte())
+ sb.Append(c);
+
+ FileName = sb.Replace('/', Path.DirectorySeparatorChar).ToString();
+ return FileName;
+ }
+
+ public void WriteFileName(Stream parent, int StringOffset)
+ {
+ FileNameOffset = (int)(parent.Position - StringOffset) / 4;
+
+ var str = FileName?.Replace(Path.DirectorySeparatorChar, '/') ?? string.Empty;
+ foreach (var b in str)
+ parent.WriteByte((byte)b);
+ parent.WriteByte(0); // \0
+ }
+
+ public void SetFileName(string value, uint hashMult)
+ {
+ FileName = value;
+ FileNameHash = GetHash(value, value.Length, hashMult);
+ }
+}
diff --git a/pkNX.Containers/SARC/SFNT.cs b/pkNX.Containers/SARC/SFNT.cs
index a956b49d..b0b7234b 100644
--- a/pkNX.Containers/SARC/SFNT.cs
+++ b/pkNX.Containers/SARC/SFNT.cs
@@ -1,45 +1,44 @@
using System;
using System.IO;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+///
+/// File Name Table
+///
+public class SFNT
{
+ public const string Identifier = nameof(SFNT);
+
///
- /// File Name Table
+ /// The required matches the first 4 bytes of the file data.
///
- public class SFNT
+ public bool SigMatches => Magic == Identifier;
+
+ public string Magic = Identifier;
+ public ushort HeaderSize;
+ public ushort Reserved;
+ public uint StringOffset;
+
+ public SFNT() { }
+
+ public SFNT(BinaryReader br)
{
- public const string Identifier = nameof(SFNT);
+ Magic = new string(br.ReadChars(4));
+ if (!SigMatches)
+ throw new FormatException(nameof(SFNT));
- ///
- /// The required matches the first 4 bytes of the file data.
- ///
- public bool SigMatches => Magic == Identifier;
-
- public string Magic = Identifier;
- public ushort HeaderSize;
- public ushort Reserved;
- public uint StringOffset;
-
- public SFNT() { }
-
- public SFNT(BinaryReader br)
- {
- Magic = new string(br.ReadChars(4));
- if (!SigMatches)
- throw new FormatException(nameof(SFNT));
-
- HeaderSize = br.ReadUInt16();
- Reserved = br.ReadUInt16();
- StringOffset = (uint)br.BaseStream.Position;
- }
-
- public void Write(BinaryWriter bw)
- {
- foreach (var c in Magic)
- bw.Write((byte)c);
- bw.Write(HeaderSize);
- bw.Write(Reserved);
- StringOffset = (uint)bw.BaseStream.Position;
- }
+ HeaderSize = br.ReadUInt16();
+ Reserved = br.ReadUInt16();
+ StringOffset = (uint)br.BaseStream.Position;
}
-}
\ No newline at end of file
+
+ public void Write(BinaryWriter bw)
+ {
+ foreach (var c in Magic)
+ bw.Write((byte)c);
+ bw.Write(HeaderSize);
+ bw.Write(Reserved);
+ StringOffset = (uint)bw.BaseStream.Position;
+ }
+}
diff --git a/pkNX.Containers/SingleFileContainer.cs b/pkNX.Containers/SingleFileContainer.cs
index d137b560..af35a17a 100644
--- a/pkNX.Containers/SingleFileContainer.cs
+++ b/pkNX.Containers/SingleFileContainer.cs
@@ -1,45 +1,44 @@
-using System;
+using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-namespace pkNX.Containers
+namespace pkNX.Containers;
+
+public class SingleFileContainer : IFileContainer
{
- public class SingleFileContainer : IFileContainer
+ public string? FilePath { get; set; }
+ public bool Modified { get; set; }
+ public int Count => 1;
+
+ public byte[] Data = Array.Empty();
+ private byte[] Backup = Array.Empty();
+ public SingleFileContainer(byte[] data) => LoadData(data);
+ public SingleFileContainer(BinaryReader br) => LoadData(br.ReadBytes((int) br.BaseStream.Length));
+ public SingleFileContainer(string path) => LoadData(FileMitm.ReadAllBytes(FilePath = path));
+
+ private void LoadData(byte[] data) => Backup = (byte[]) (Data = data).Clone();
+
+ public void CancelEdits()
{
- public string? FilePath { get; set; }
- public bool Modified { get; set; }
- public int Count => 1;
-
- public byte[] Data = Array.Empty();
- private byte[] Backup = Array.Empty();
- public SingleFileContainer(byte[] data) => LoadData(data);
- public SingleFileContainer(BinaryReader br) => LoadData(br.ReadBytes((int) br.BaseStream.Length));
- public SingleFileContainer(string path) => LoadData(FileMitm.ReadAllBytes(FilePath = path));
-
- private void LoadData(byte[] data) => Backup = (byte[]) (Data = data).Clone();
-
- public void CancelEdits()
- {
- Modified = false;
- Data = (byte[]) Backup.Clone();
- }
-
- public byte[] this[int index]
- {
- get => (byte[])Data.Clone();
- set
- {
- Modified |= !Data.SequenceEqual(value);
- Data = value;
- }
- }
-
- public Task GetFiles() => Task.FromResult(new[] {this[0]});
- public Task GetFile(int file, int subFile = 0) => Task.FromResult(this[0]);
- public Task SetFile(int file, byte[] value, int subFile = 0) => Task.FromResult(Data = value);
- public Task SaveAs(string path, ContainerHandler handler, CancellationToken token) => new(() => Dump(path, handler), token);
- public void Dump(string? path, ContainerHandler handler) => FileMitm.WriteAllBytes(path ?? FilePath!, Data);
+ Modified = false;
+ Data = (byte[]) Backup.Clone();
}
+
+ public byte[] this[int index]
+ {
+ get => (byte[])Data.Clone();
+ set
+ {
+ Modified |= !Data.SequenceEqual(value);
+ Data = value;
+ }
+ }
+
+ public Task GetFiles() => Task.FromResult(new[] {this[0]});
+ public Task GetFile(int file, int subFile = 0) => Task.FromResult(this[0]);
+ public Task SetFile(int file, byte[] value, int subFile = 0) => Task.FromResult(Data = value);
+ public Task SaveAs(string path, ContainerHandler handler, CancellationToken token) => new(() => Dump(path, handler), token);
+ public void Dump(string? path, ContainerHandler handler) => FileMitm.WriteAllBytes(path ?? FilePath!, Data);
}
diff --git a/pkNX.Containers/pkNX.Containers.csproj b/pkNX.Containers/pkNX.Containers.csproj
index ff322c46..025142bd 100644
--- a/pkNX.Containers/pkNX.Containers.csproj
+++ b/pkNX.Containers/pkNX.Containers.csproj
@@ -1,16 +1,14 @@
- netstandard2.0;net461
+ net6.0
Packing & Unpacking
10
enable
-
-
diff --git a/pkNX.Game/Editors/DataCache.cs b/pkNX.Game/Editors/DataCache.cs
index ad58dab9..161b8152 100644
--- a/pkNX.Game/Editors/DataCache.cs
+++ b/pkNX.Game/Editors/DataCache.cs
@@ -2,114 +2,113 @@
using pkNX.Containers;
using pkNX.Structures.FlatBuffers;
-namespace pkNX.Game
+namespace pkNX.Game;
+
+public class DataCache : IDataEditor where T : class
{
- public class DataCache : IDataEditor where T : class
+ public IFileContainer Data { protected get; set; }
+ public Func Create { private get; set; }
+ public Func Write { protected get; set; }
+
+ public DataCache(T[] cache) => Cache = cache;
+ public DataCache(IFileContainer f) : this(new T[f.Count]) => Data = f;
+
+ protected readonly T[] Cache;
+ private bool Cached;
+
+ public int Length => Cache.Length;
+
+ public T this[int index]
{
- public IFileContainer Data { protected get; set; }
- public Func Create { private get; set; }
- public Func Write { protected get; set; }
+ get => Cache[index] ??= Create(Data[index]);
+ set => Cache[index] = value;
+ }
- public DataCache(T[] cache) => Cache = cache;
- public DataCache(IFileContainer f) : this(new T[f.Count]) => Data = f;
+ public void CancelEdits()
+ {
+ for (int i = 0; i < Cache.Length; i++)
+ Cache[i] = default;
+ }
- protected readonly T[] Cache;
- private bool Cached;
+ public void Initialize() { }
- public int Length => Cache.Length;
-
- public T this[int index]
- {
- get => Cache[index] ??= Create(Data[index]);
- set => Cache[index] = value;
- }
-
- public void CancelEdits()
- {
- for (int i = 0; i < Cache.Length; i++)
- Cache[i] = default;
- }
-
- public void Initialize() { }
-
- public T[] LoadAll()
- {
- if (Cached)
- return Cache;
- for (int i = 0; i < Length; i++)
- {
- // ReSharper disable once AssignmentIsFullyDiscarded
- _ = this[i]; // force load cache
- }
-
- Cached = true;
+ public T[] LoadAll()
+ {
+ if (Cached)
return Cache;
+ for (int i = 0; i < Length; i++)
+ {
+ // ReSharper disable once AssignmentIsFullyDiscarded
+ _ = this[i]; // force load cache
}
- public void ClearAll()
- {
- Cached = false;
- for (int i = 0; i < Cache.Length; i++)
- Cache[i] = null;
- }
+ Cached = true;
+ return Cache;
+ }
- ///
- /// Pushes changes back to the .
- ///
- public virtual void Save()
- {
- for (int i = 0; i < Cache.Length; i++)
- {
- var val = Cache[i];
- if (val == null)
- continue;
- Data[i] = Write(val);
- }
- }
+ public void ClearAll()
+ {
+ Cached = false;
+ for (int i = 0; i < Cache.Length; i++)
+ Cache[i] = null;
}
///
- /// Data with already known contents.
+ /// Pushes changes back to the .
///
- ///
- public class DirectCache : DataCache where T : class
+ public virtual void Save()
{
- public DirectCache(T[] cache) : base(cache) { }
- public override void Save() { }
+ for (int i = 0; i < Cache.Length; i++)
+ {
+ var val = Cache[i];
+ if (val == null)
+ continue;
+ Data[i] = Write(val);
+ }
+ }
+}
+
+///
+/// Data with already known contents.
+///
+///
+public class DirectCache : DataCache where T : class
+{
+ public DirectCache(T[] cache) : base(cache) { }
+ public override void Save() { }
+}
+
+///
+/// Data 'from a flatbuffer table
+///
+/// The type of table
+/// The type of data inside the table
+public class TableCache
+ where TTable : class, IFlatBufferArchive
+ where TData : class
+{
+ public IFileContainer File { get; private set; }
+ public TTable Root { get; private set; }
+ public TData[] Table => Root.Table;
+ public DataCache Cache { get; private set; }
+
+ public TableCache(IFileContainer f)
+ {
+ File = f;
+ Root = FlatBufferConverter.DeserializeFrom(f[0]);
+ Cache = new DirectCache(Root.Table);
}
- ///
- /// Data 'from a flatbuffer table
- ///
- /// The type of table
- /// The type of data inside the table
- public class TableCache
- where TTable : class, IFlatBufferArchive
- where TData : class
+ public void Save()
{
- public IFileContainer File { get; private set; }
- public TTable Root { get; private set; }
- public TData[] Table => Root.Table;
- public DataCache Cache { get; private set; }
-
- public TableCache(IFileContainer f)
- {
- File = f;
- Root = FlatBufferConverter.DeserializeFrom(f[0]);
- Cache = new DirectCache(Root.Table);
- }
-
- public void Save()
- {
- File[0] = FlatBufferConverter.SerializeFrom(Root);
- }
-
- public TData this[int index]
- {
- get => Cache[index];
- set => Cache[index] = value;
- }
-
- public int Length => Cache.Length;
+ File[0] = FlatBufferConverter.SerializeFrom(Root);
}
+
+ public TData this[int index]
+ {
+ get => Cache[index];
+ set => Cache[index] = value;
+ }
+
+ public int Length => Cache.Length;
}
\ No newline at end of file
diff --git a/pkNX.Game/Editors/EditorFactory.cs b/pkNX.Game/Editors/EditorFactory.cs
index 20bdf852..a8ff978c 100644
--- a/pkNX.Game/Editors/EditorFactory.cs
+++ b/pkNX.Game/Editors/EditorFactory.cs
@@ -1,6 +1,5 @@
-namespace pkNX.Game
+namespace pkNX.Game;
+
+public class EditorFactory
{
- public class EditorFactory
- {
- }
-}
+}
\ No newline at end of file
diff --git a/pkNX.Game/Editors/EditorUtil.cs b/pkNX.Game/Editors/EditorUtil.cs
index 297ca089..2dc6c334 100644
--- a/pkNX.Game/Editors/EditorUtil.cs
+++ b/pkNX.Game/Editors/EditorUtil.cs
@@ -1,16 +1,15 @@
-namespace pkNX.Game
+namespace pkNX.Game;
+
+public static class EditorUtil
{
- public static class EditorUtil
+ public static string[] SanitizeMoveList(string[] list)
{
- public static string[] SanitizeMoveList(string[] list)
- {
- var movelist = (string[])list.Clone();
- if (movelist.Length < 658)
- return movelist;
- string[] ps = { "P", "S" }; // Distinguish Physical/Special Z Moves
- for (int i = 622; i < 658; i++)
- movelist[i] += $" ({ps[i % 2]})";
+ var movelist = (string[])list.Clone();
+ if (movelist.Length < 658)
return movelist;
- }
+ string[] ps = { "P", "S" }; // Distinguish Physical/Special Z Moves
+ for (int i = 622; i < 658; i++)
+ movelist[i] += $" ({ps[i % 2]})";
+ return movelist;
}
-}
+}
\ No newline at end of file
diff --git a/pkNX.Game/Editors/IDataEditor.cs b/pkNX.Game/Editors/IDataEditor.cs
index b23e23ea..6dbcb3e5 100644
--- a/pkNX.Game/Editors/IDataEditor.cs
+++ b/pkNX.Game/Editors/IDataEditor.cs
@@ -1,9 +1,8 @@
-namespace pkNX.Game
+namespace pkNX.Game;
+
+public interface IDataEditor
{
- public interface IDataEditor
- {
- void CancelEdits();
- void Initialize();
- void Save();
- }
+ void CancelEdits();
+ void Initialize();
+ void Save();
}
\ No newline at end of file
diff --git a/pkNX.Game/Editors/Poke/PokeEditor.cs b/pkNX.Game/Editors/Poke/PokeEditor.cs
index be3873e5..280955e3 100644
--- a/pkNX.Game/Editors/Poke/PokeEditor.cs
+++ b/pkNX.Game/Editors/Poke/PokeEditor.cs
@@ -1,30 +1,29 @@
using System.Collections.Generic;
using pkNX.Structures;
-namespace pkNX.Game
+namespace pkNX.Game;
+
+public class PokeEditor : IDataEditor
{
- public class PokeEditor : IDataEditor
+ public IPersonalTable Personal { get; set; }
+ public DataCache Learn { get; set; }
+ public DataCache Evolve { get; set; }
+ public DataCache Mega { get; set; }
+ public IReadOnlyList TMHM { get; set; }
+
+ public void CancelEdits()
{
- public IPersonalTable Personal { get; set; }
- public DataCache Learn { get; set; }
- public DataCache Evolve { get; set; }
- public DataCache Mega { get; set; }
- public IReadOnlyList TMHM { get; set; }
-
- public void CancelEdits()
- {
- Learn.CancelEdits();
- Evolve.CancelEdits();
- Mega?.CancelEdits();
- }
-
- public void Initialize() { }
-
- public void Save()
- {
- Learn.Save();
- Evolve.Save();
- Mega?.Save();
- }
+ Learn.CancelEdits();
+ Evolve.CancelEdits();
+ Mega?.CancelEdits();
}
-}
+
+ public void Initialize() { }
+
+ public void Save()
+ {
+ Learn.Save();
+ Evolve.Save();
+ Mega?.Save();
+ }
+}
\ No newline at end of file
diff --git a/pkNX.Game/Editors/Poke/PokeEditor8a.cs b/pkNX.Game/Editors/Poke/PokeEditor8a.cs
index b8d17f53..e0da64f8 100644
--- a/pkNX.Game/Editors/Poke/PokeEditor8a.cs
+++ b/pkNX.Game/Editors/Poke/PokeEditor8a.cs
@@ -1,32 +1,30 @@
-using System.Collections.Generic;
using pkNX.Structures;
using pkNX.Structures.FlatBuffers;
-namespace pkNX.Game
+namespace pkNX.Game;
+
+public class PokeEditor8a : IDataEditor
{
- public class PokeEditor8a : IDataEditor
+ public IPersonalTable Personal { get; set; }
+ public TableCache PokeMisc { get; set; }
+ public TableCache Evolve { get; set; }
+ public TableCache Learn { get; set; }
+ public TableCache FieldDropTables { get; set; }
+ public TableCache BattleDropTabels { get; set; }
+ public TableCache DexResearch { get; set; }
+
+ public void CancelEdits() { }
+
+ public void Initialize() { }
+
+ public void Save()
{
- public IPersonalTable Personal { get; set; }
- public TableCache PokeMisc { get; set; }
- public TableCache Evolve { get; set; }
- public TableCache Learn { get; set; }
- public TableCache FieldDropTables { get; set; }
- public TableCache BattleDropTabels { get; set; }
- public TableCache DexResearch { get; set; }
-
- public void CancelEdits() { }
-
- public void Initialize() { }
-
- public void Save()
- {
- Personal.Save();
- PokeMisc.Save();
- Learn.Save();
- Evolve.Save();
- FieldDropTables.Save();
- BattleDropTabels.Save();
- DexResearch.Save();
- }
+ Personal.Save();
+ PokeMisc.Save();
+ Learn.Save();
+ Evolve.Save();
+ FieldDropTables.Save();
+ BattleDropTabels.Save();
+ DexResearch.Save();
}
}
diff --git a/pkNX.Game/Editors/ShinyRate/ShinyRateGG.cs b/pkNX.Game/Editors/ShinyRate/ShinyRateGG.cs
index da33ea98..4ec6b1ec 100644
--- a/pkNX.Game/Editors/ShinyRate/ShinyRateGG.cs
+++ b/pkNX.Game/Editors/ShinyRate/ShinyRateGG.cs
@@ -1,88 +1,87 @@
using System;
using pkNX.Structures;
-namespace pkNX.Game
+namespace pkNX.Game;
+
+public sealed class ShinyRateGG : ShinyRateInfo
{
- public sealed class ShinyRateGG : ShinyRateInfo
+ /*
+ Shiny Rate Patch -- fix the loop counter regardless of input param value.
+ ARM64 disassembly @ sub_71007399D8
+ LDRB W8, [X19, #0x25] // load loop max
+ ADD W20, W20, #1 // increment counter
+ CMP W20, W8 // compare counter to max
+ BL -44 // branch if less (loop)
+ B [....] // branch (no loop), PID is done!
+
+ Patch creation: accept input for reroll count=1-4091
+ Get u32: ((count & 0xFFF) << 10) | 0b111000100_000000000000_1010011111
+ Convert to bytes, this is our new "CMP W20, XXX" instruction
+
+ Replace these bytes:
+ 68 96 40 39 94 06 00 11 9F 02 08 6B
+ With these bytes:
+ 68 96 40 39 94 06 00 11 [u32 bytes]
+
+ ***
+ Always Shiny Patch -- nop the bl
+ write 1F 20 03 D5 after the above 12 byte sequence
+
+ ***
+ Revert above patch -- restore the bl
+ write AB FE FF 54 after the above 12 byte sequence
+ */
+
+ private readonly int CodeOffset;
+ private static readonly byte[] Pattern = { 0x68, 0x96, 0x40, 0x39, 0x94, 0x06, 0x00, 0x11 };
+ private static readonly byte[] Default = { 0x9F, 0x02, 0x08, 0x6B }; // cmp W20, W8
+ private static readonly byte[] Always12 = { 0x1F, 0x20, 0x03, 0xD5 }; // nop
+ private static readonly byte[] Revert12 = { 0xAB, 0xFE, 0xFF, 0x54 }; // bl
+
+ public ShinyRateGG(byte[] data) : base(data) => CodeOffset = CodePattern.IndexOfBytes(data, Pattern, 0x500_000);
+
+ public override bool IsEditable => CodeOffset > 0;
+
+ public override bool IsDefault => !IsAlways && IsPresent(Data, Default, CodeOffset + 8);
+ public override bool IsFixed => !IsAlways && !IsDefault;
+ public override bool IsAlways => IsPresent(Data, Always12, CodeOffset + 12);
+
+ public override bool AllowAlways => false;
+
+ public override int GetFixedRate() // "CMP W20, {val}" instruction
{
- /*
- Shiny Rate Patch -- fix the loop counter regardless of input param value.
- ARM64 disassembly @ sub_71007399D8
- LDRB W8, [X19, #0x25] // load loop max
- ADD W20, W20, #1 // increment counter
- CMP W20, W8 // compare counter to max
- BL -44 // branch if less (loop)
- B [....] // branch (no loop), PID is done!
+ if (!IsFixed)
+ return -1;
+ var instr = BitConverter.ToUInt32(Data, CodeOffset + 8);
+ return (int)((instr >> 10) & 0xFFF);
+ }
- Patch creation: accept input for reroll count=1-4091
- Get u32: ((count & 0xFFF) << 10) | 0b111000100_000000000000_1010011111
- Convert to bytes, this is our new "CMP W20, XXX" instruction
+ public override void SetDefault()
+ {
+ Default.CopyTo(Data, CodeOffset + 8);
+ Revert12.CopyTo(Data, CodeOffset + 12);
+ }
- Replace these bytes:
- 68 96 40 39 94 06 00 11 9F 02 08 6B
- With these bytes:
- 68 96 40 39 94 06 00 11 [u32 bytes]
+ public override void SetFixedRate(int rerollCount)
+ {
+ var instr = GetFixedInstruction(rerollCount);
+ instr.CopyTo(Data, CodeOffset + 8);
+ Revert12.CopyTo(Data, CodeOffset + 12);
+ }
- ***
- Always Shiny Patch -- nop the bl
- write 1F 20 03 D5 after the above 12 byte sequence
+ public override void SetAlwaysShiny()
+ {
+ Default.CopyTo(Data, CodeOffset + 8);
+ Always12.CopyTo(Data, CodeOffset + 12);
+ }
- ***
- Revert above patch -- restore the bl
- write AB FE FF 54 after the above 12 byte sequence
- */
-
- private readonly int CodeOffset;
- private static readonly byte[] Pattern = { 0x68, 0x96, 0x40, 0x39, 0x94, 0x06, 0x00, 0x11 };
- private static readonly byte[] Default = { 0x9F, 0x02, 0x08, 0x6B }; // cmp W20, W8
- private static readonly byte[] Always12 = { 0x1F, 0x20, 0x03, 0xD5 }; // nop
- private static readonly byte[] Revert12 = { 0xAB, 0xFE, 0xFF, 0x54 }; // bl
-
- public ShinyRateGG(byte[] data) : base(data) => CodeOffset = CodePattern.IndexOfBytes(data, Pattern, 0x500_000);
-
- public override bool IsEditable => CodeOffset > 0;
-
- public override bool IsDefault => !IsAlways && IsPresent(Data, Default, CodeOffset + 8);
- public override bool IsFixed => !IsAlways && !IsDefault;
- public override bool IsAlways => IsPresent(Data, Always12, CodeOffset + 12);
-
- public override bool AllowAlways => false;
-
- public override int GetFixedRate() // "CMP W20, {val}" instruction
- {
- if (!IsFixed)
- return -1;
- var instr = BitConverter.ToUInt32(Data, CodeOffset + 8);
- return (int)((instr >> 10) & 0xFFF);
- }
-
- public override void SetDefault()
- {
- Default.CopyTo(Data, CodeOffset + 8);
- Revert12.CopyTo(Data, CodeOffset + 12);
- }
-
- public override void SetFixedRate(int rerollCount)
- {
- var instr = GetFixedInstruction(rerollCount);
- instr.CopyTo(Data, CodeOffset + 8);
- Revert12.CopyTo(Data, CodeOffset + 12);
- }
-
- public override void SetAlwaysShiny()
- {
- Default.CopyTo(Data, CodeOffset + 8);
- Always12.CopyTo(Data, CodeOffset + 12);
- }
-
- public static byte[] GetFixedInstruction(int count)
- {
- if (count <= 0)
- count = 1;
- else if (count >= 4092)
- count = 4091;
- var val = ((count & 0xFFF) << 10) | 0b111000100_000000000000_1010011111;
- return BitConverter.GetBytes((uint)val);
- }
+ public static byte[] GetFixedInstruction(int count)
+ {
+ if (count <= 0)
+ count = 1;
+ else if (count >= 4092)
+ count = 4091;
+ var val = ((count & 0xFFF) << 10) | 0b111000100_000000000000_1010011111;
+ return BitConverter.GetBytes((uint)val);
}
}
\ No newline at end of file
diff --git a/pkNX.Game/Editors/ShinyRate/ShinyRateInfo.cs b/pkNX.Game/Editors/ShinyRate/ShinyRateInfo.cs
index 89f3c192..ea29dfe9 100644
--- a/pkNX.Game/Editors/ShinyRate/ShinyRateInfo.cs
+++ b/pkNX.Game/Editors/ShinyRate/ShinyRateInfo.cs
@@ -1,30 +1,29 @@
-namespace pkNX.Game
+namespace pkNX.Game;
+
+public abstract class ShinyRateInfo
{
- public abstract class ShinyRateInfo
+ public byte[] Data { get; }
+ protected ShinyRateInfo(byte[] data) => Data = data;
+
+ public abstract bool IsEditable { get; }
+
+ public abstract bool IsDefault { get; }
+ public abstract bool IsFixed { get; }
+ public abstract bool IsAlways { get; }
+ public abstract bool AllowAlways { get; }
+
+ public abstract int GetFixedRate();
+ public abstract void SetDefault();
+ public abstract void SetFixedRate(int rerollCount);
+ public abstract void SetAlwaysShiny();
+
+ protected static bool IsPresent(byte[] source, byte[] pattern, int offset)
{
- public byte[] Data { get; }
- protected ShinyRateInfo(byte[] data) => Data = data;
-
- public abstract bool IsEditable { get; }
-
- public abstract bool IsDefault { get; }
- public abstract bool IsFixed { get; }
- public abstract bool IsAlways { get; }
- public abstract bool AllowAlways { get; }
-
- public abstract int GetFixedRate();
- public abstract void SetDefault();
- public abstract void SetFixedRate(int rerollCount);
- public abstract void SetAlwaysShiny();
-
- protected static bool IsPresent(byte[] source, byte[] pattern, int offset)
+ for (int i = 0; i < pattern.Length; i++)
{
- for (int i = 0; i < pattern.Length; i++)
- {
- if (source[i + offset] != pattern[i])
- return false;
- }
- return true;
+ if (source[i + offset] != pattern[i])
+ return false;
}
+ return true;
}
-}
+}
\ No newline at end of file
diff --git a/pkNX.Game/Editors/ShinyRate/ShinyRateSWSH.cs b/pkNX.Game/Editors/ShinyRate/ShinyRateSWSH.cs
index fb078d5c..06eef824 100644
--- a/pkNX.Game/Editors/ShinyRate/ShinyRateSWSH.cs
+++ b/pkNX.Game/Editors/ShinyRate/ShinyRateSWSH.cs
@@ -1,96 +1,94 @@
using System;
-using System.Diagnostics;
using pkNX.Structures;
-namespace pkNX.Game
+namespace pkNX.Game;
+
+public sealed class ShinyRateSWSH : ShinyRateInfo
{
- public sealed class ShinyRateSWSH : ShinyRateInfo
+ /*
+ Shiny Rate Patch -- fix the overworld loop counter regardless of shiny rate factors.
+ ARM64 disassembly @ FUN_7100d311f0 (Shield), FUN_7100d311c0 (Sword)
+
+ orr w24,w24,w0 // set flag for shiny if found
+ cmp w25,w23 // check loop counter
+ b.cs LAB_7100d314c8 // break if maximum loop reached
+
+
+ Patch creation: accept input for reroll count=0-4095
+ Get u32: ((count & 0xFFF) << 10) | 0b0111000100_000000000000_11001_11111
+ Convert to bytes, this is our new "CMP W25, XXX" instruction
+
+ Replace these bytes:
+ 18 03 00 2a 3f 03 17 6b 62 00 00 54
+ With these bytes:
+ 18 03 00 2a [u32 bytes] 62 00 00 54
+
+ ***
+ Always Shiny Patch -- nop the b.cs
+ write 1f 20 03 d5 to the last 4 bytes of the above sequence
+
+ ***
+ Revert above patch -- restore the b.cs
+ write 62 00 00 54 after the above 12 byte sequence.
+ */
+
+ private readonly int FunctionOffset; // loop counter and break
+ private static readonly byte[] FunctionPrelude = { 0xff, 0x03, 0x06, 0xd1, 0xfc, 0x6f, 0x12, 0xa9, 0xfa, 0x67, 0x13, 0xa9, 0xf8, 0x5f, 0x14, 0xa9, 0xf6, 0x57, 0x15, 0xa9, 0xf4, 0x4f, 0x16, 0xa9, 0xfd, 0x7b, 0x17, 0xa9, 0xfd, 0xc3, 0x05, 0x91, 0xfa, 0xc6, 0x00, 0xf0 };
+
+ private static readonly int RerollCountCheckOffset = 0x2C8;
+ private static readonly byte[] RerollCountCheckDefault = { 0x3f, 0x03, 0x17, 0x6b};
+
+ private static readonly int RerollCountBreakOffset = 0x2CC;
+ private static readonly byte[] RerollCountBreakDefault = { 0x62, 0x00, 0x00, 0x54 }; // b.cs $pc + 12
+ private static readonly byte[] RerollCountBreakNop = { 0x1F, 0x20, 0x03, 0xD5 }; // nop
+
+ public ShinyRateSWSH(byte[] data, int offset = 0x700_000) : base(data)
{
- /*
- Shiny Rate Patch -- fix the overworld loop counter regardless of shiny rate factors.
- ARM64 disassembly @ FUN_7100d311f0 (Shield), FUN_7100d311c0 (Sword)
+ FunctionOffset = CodePattern.IndexOfBytes(data, FunctionPrelude, offset);
+ }
- orr w24,w24,w0 // set flag for shiny if found
- cmp w25,w23 // check loop counter
- b.cs LAB_7100d314c8 // break if maximum loop reached
+ public override bool IsEditable => FunctionOffset > 0;
+ public override bool IsDefault => !IsAlways && !IsAlways;
+ public override bool IsFixed => !IsPresent(Data, RerollCountCheckDefault, FunctionOffset + RerollCountCheckOffset);
+ public override bool IsAlways => IsPresent(Data, RerollCountBreakNop, FunctionOffset + RerollCountBreakOffset);
- Patch creation: accept input for reroll count=0-4095
- Get u32: ((count & 0xFFF) << 10) | 0b0111000100_000000000000_11001_11111
- Convert to bytes, this is our new "CMP W25, XXX" instruction
+ public override bool AllowAlways => true;
- Replace these bytes:
- 18 03 00 2a 3f 03 17 6b 62 00 00 54
- With these bytes:
- 18 03 00 2a [u32 bytes] 62 00 00 54
+ public override int GetFixedRate() // "CMP W25, {val}" instruction
+ {
+ if (!IsFixed)
+ return -1;
+ var instr = BitConverter.ToUInt32(Data, FunctionOffset + RerollCountCheckOffset);
+ return (int)((instr >> 10) & 0xFFF);
+ }
- ***
- Always Shiny Patch -- nop the b.cs
- write 1f 20 03 d5 to the last 4 bytes of the above sequence
+ public override void SetDefault()
+ {
+ RerollCountCheckDefault.CopyTo(Data, FunctionOffset + RerollCountCheckOffset);
+ RerollCountBreakDefault.CopyTo(Data, FunctionOffset + RerollCountBreakOffset);
+ }
- ***
- Revert above patch -- restore the b.cs
- write 62 00 00 54 after the above 12 byte sequence.
- */
+ public override void SetFixedRate(int rerollCount)
+ {
+ SetDefault();
+ var instr = GetFixedInstruction(rerollCount);
+ instr.CopyTo(Data, FunctionOffset + RerollCountCheckOffset);
+ }
- private readonly int FunctionOffset; // loop counter and break
- private static readonly byte[] FunctionPrelude = { 0xff, 0x03, 0x06, 0xd1, 0xfc, 0x6f, 0x12, 0xa9, 0xfa, 0x67, 0x13, 0xa9, 0xf8, 0x5f, 0x14, 0xa9, 0xf6, 0x57, 0x15, 0xa9, 0xf4, 0x4f, 0x16, 0xa9, 0xfd, 0x7b, 0x17, 0xa9, 0xfd, 0xc3, 0x05, 0x91, 0xfa, 0xc6, 0x00, 0xf0 };
+ public override void SetAlwaysShiny()
+ {
+ SetDefault();
+ RerollCountBreakNop.CopyTo(Data, FunctionOffset + RerollCountBreakOffset);
+ }
- private static readonly int RerollCountCheckOffset = 0x2C8;
- private static readonly byte[] RerollCountCheckDefault = { 0x3f, 0x03, 0x17, 0x6b};
-
- private static readonly int RerollCountBreakOffset = 0x2CC;
- private static readonly byte[] RerollCountBreakDefault = { 0x62, 0x00, 0x00, 0x54 }; // b.cs $pc + 12
- private static readonly byte[] RerollCountBreakNop = { 0x1F, 0x20, 0x03, 0xD5 }; // nop
-
- public ShinyRateSWSH(byte[] data, int offset = 0x700_000) : base(data)
- {
- FunctionOffset = CodePattern.IndexOfBytes(data, FunctionPrelude, offset);
- }
-
- public override bool IsEditable => FunctionOffset > 0;
-
- public override bool IsDefault => !IsAlways && !IsAlways;
- public override bool IsFixed => !IsPresent(Data, RerollCountCheckDefault, FunctionOffset + RerollCountCheckOffset);
- public override bool IsAlways => IsPresent(Data, RerollCountBreakNop, FunctionOffset + RerollCountBreakOffset);
-
- public override bool AllowAlways => true;
-
- public override int GetFixedRate() // "CMP W25, {val}" instruction
- {
- if (!IsFixed)
- return -1;
- var instr = BitConverter.ToUInt32(Data, FunctionOffset + RerollCountCheckOffset);
- return (int)((instr >> 10) & 0xFFF);
- }
-
- public override void SetDefault()
- {
- RerollCountCheckDefault.CopyTo(Data, FunctionOffset + RerollCountCheckOffset);
- RerollCountBreakDefault.CopyTo(Data, FunctionOffset + RerollCountBreakOffset);
- }
-
- public override void SetFixedRate(int rerollCount)
- {
- SetDefault();
- var instr = GetFixedInstruction(rerollCount);
- instr.CopyTo(Data, FunctionOffset + RerollCountCheckOffset);
- }
-
- public override void SetAlwaysShiny()
- {
- SetDefault();
- RerollCountBreakNop.CopyTo(Data, FunctionOffset + RerollCountBreakOffset);
- }
-
- public static byte[] GetFixedInstruction(int count)
- {
- if (count <= 0)
- count = 1;
- else if (count >= 4092)
- count = 4091;
- var val = ((count & 0xFFF) << 10) | 0b0111000100_000000000000_11001_11111;
- return BitConverter.GetBytes((uint)val);
- }
+ public static byte[] GetFixedInstruction(int count)
+ {
+ if (count <= 0)
+ count = 1;
+ else if (count >= 4092)
+ count = 4091;
+ var val = ((count & 0xFFF) << 10) | 0b0111000100_000000000000_11001_11111;
+ return BitConverter.GetBytes((uint)val);
}
}
diff --git a/pkNX.Game/Editors/TMEditorGG.cs b/pkNX.Game/Editors/TMEditorGG.cs
index 395791f4..ca903da2 100644
--- a/pkNX.Game/Editors/TMEditorGG.cs
+++ b/pkNX.Game/Editors/TMEditorGG.cs
@@ -3,43 +3,42 @@
using pkNX.Containers;
using pkNX.Structures;
-namespace pkNX.Game
+namespace pkNX.Game;
+
+public class TMEditorGG
{
- public class TMEditorGG
+ private readonly int Offset;
+ private readonly NSO NSO;
+ private readonly byte[] Data;
+
+ private const int count = 60;
+
+ public TMEditorGG(byte[] data)
{
- private readonly int Offset;
- private readonly NSO NSO;
- private readonly byte[] Data;
+ NSO = new NSO(data);
+ Data = NSO.DecompressedRO;
- private const int count = 60;
-
- public TMEditorGG(byte[] data)
- {
- NSO = new NSO(data);
- Data = NSO.DecompressedRO;
-
- // tm list is stored immediately after TM item index list
- var pattern = CodePattern.TMHM_GG;
- Offset = CodePattern.IndexOfBytes(Data, pattern, 0x200_000);
- if (Valid)
- Offset += pattern.Length;
- }
-
- public ushort[] GetMoves()
- {
- var moves = new ushort[count];
- for (int i = 0; i < moves.Length; i++)
- moves[i] = BitConverter.ToUInt16(Data, Offset + (2 * i));
- return moves;
- }
-
- public void SetMoves(ushort[] finalMoves)
- {
- var result = finalMoves.SelectMany(BitConverter.GetBytes).ToArray();
- result.CopyTo(Data, Offset);
- }
-
- public bool Valid => Offset > 0;
- public byte[] Write() => NSO.Write();
+ // tm list is stored immediately after TM item index list
+ var pattern = CodePattern.TMHM_GG;
+ Offset = CodePattern.IndexOfBytes(Data, pattern, 0x200_000);
+ if (Valid)
+ Offset += pattern.Length;
}
-}
+
+ public ushort[] GetMoves()
+ {
+ var moves = new ushort[count];
+ for (int i = 0; i < moves.Length; i++)
+ moves[i] = BitConverter.ToUInt16(Data, Offset + (2 * i));
+ return moves;
+ }
+
+ public void SetMoves(ushort[] finalMoves)
+ {
+ var result = finalMoves.SelectMany(BitConverter.GetBytes).ToArray();
+ result.CopyTo(Data, Offset);
+ }
+
+ public bool Valid => Offset > 0;
+ public byte[] Write() => NSO.Write();
+}
\ No newline at end of file
diff --git a/pkNX.Game/Editors/TrainerEditor.cs b/pkNX.Game/Editors/TrainerEditor.cs
index 643f19fe..960b1c65 100644
--- a/pkNX.Game/Editors/TrainerEditor.cs
+++ b/pkNX.Game/Editors/TrainerEditor.cs
@@ -3,81 +3,80 @@
using pkNX.Containers;
using pkNX.Structures;
-namespace pkNX.Game
+namespace pkNX.Game;
+
+public class TrainerEditor : IDataEditor
{
- public class TrainerEditor : IDataEditor
+ public IFileContainer TrainerData;
+ public IFileContainer TrainerPoke;
+ public IFileContainer TrainerClass;
+ public IFileContainer TrainerMsg;
+
+ public Func ReadTrainer;
+ public Func ReadPoke;
+ public Func ReadTeam;
+ public Func WriteTeam;
+ public Func ReadClass;
+
+ private VsTrainer[] Cache;
+ private TrainerClass[] CacheClass;
+
+ public int Length => Cache.Length;
+
+ public void Initialize()
{
- public IFileContainer TrainerData;
- public IFileContainer TrainerPoke;
- public IFileContainer TrainerClass;
- public IFileContainer TrainerMsg;
+ Cache = new VsTrainer[TrainerData.Count];
+ CacheClass = new TrainerClass[TrainerData.Count];
+ }
- public Func ReadTrainer;
- public Func ReadPoke;
- public Func ReadTeam;
- public Func WriteTeam;
- public Func ReadClass;
+ public VsTrainer this[int index]
+ {
+ get => Cache[index] ??= LoadTrainer(index);
+ set => Cache[index] = value;
+ }
- private VsTrainer[] Cache;
- private TrainerClass[] CacheClass;
+ public TrainerClass GetClass(int index) => CacheClass[index] ??= ReadClass(TrainerClass[index]);
- public int Length => Cache.Length;
-
- public void Initialize()
+ private VsTrainer LoadTrainer(int index)
+ {
+ var tr = ReadTrainer(TrainerData[index]);
+ var poke = ReadTeam(TrainerPoke[index], tr);
+ var data = new VsTrainer
{
- Cache = new VsTrainer[TrainerData.Count];
- CacheClass = new TrainerClass[TrainerData.Count];
- }
+ ID = index,
+ Self = tr,
+ };
+ data.Team.AddRange(poke);
+ return data;
+ }
- public VsTrainer this[int index]
+ public void Save()
+ {
+ for (int i = 0; i < Length; i++)
{
- get => Cache[index] ??= LoadTrainer(index);
- set => Cache[index] = value;
- }
-
- public TrainerClass GetClass(int index) => CacheClass[index] ??= ReadClass(TrainerClass[index]);
-
- private VsTrainer LoadTrainer(int index)
- {
- var tr = ReadTrainer(TrainerData[index]);
- var poke = ReadTeam(TrainerPoke[index], tr);
- var data = new VsTrainer
- {
- ID = index,
- Self = tr,
- };
- data.Team.AddRange(poke);
- return data;
- }
-
- public void Save()
- {
- for (int i = 0; i < Length; i++)
- {
- var data = Cache[i];
- if (data == null)
- continue;
- data.Self.NumPokemon = data.Team.Count;
- TrainerData[i] = data.Self.Write();
- TrainerPoke[i] = data.Team.SelectMany(z => z.Write()).ToArray();
- }
- }
-
- public VsTrainer[] LoadAll()
- {
- for (int i = 0; i < Length; i++)
- {
- // ReSharper disable once AssignmentIsFullyDiscarded
- _ = this[i]; // force load cache
- }
-
- return Cache;
- }
-
- public void CancelEdits()
- {
- TrainerData.CancelEdits();
- TrainerPoke.CancelEdits();
+ var data = Cache[i];
+ if (data == null)
+ continue;
+ data.Self.NumPokemon = data.Team.Count;
+ TrainerData[i] = data.Self.Write();
+ TrainerPoke[i] = data.Team.SelectMany(z => z.Write()).ToArray();
}
}
+
+ public VsTrainer[] LoadAll()
+ {
+ for (int i = 0; i < Length; i++)
+ {
+ // ReSharper disable once AssignmentIsFullyDiscarded
+ _ = this[i]; // force load cache
+ }
+
+ return Cache;
+ }
+
+ public void CancelEdits()
+ {
+ TrainerData.CancelEdits();
+ TrainerPoke.CancelEdits();
+ }
}
\ No newline at end of file
diff --git a/pkNX.Game/Editors/TypeChartEditor.cs b/pkNX.Game/Editors/TypeChartEditor.cs
index 19b7b7d9..21ec2839 100644
--- a/pkNX.Game/Editors/TypeChartEditor.cs
+++ b/pkNX.Game/Editors/TypeChartEditor.cs
@@ -2,80 +2,79 @@
using pkNX.Structures;
using Util = pkNX.Randomization.Util;
-namespace pkNX.Game
+namespace pkNX.Game;
+
+public class TypeChartEditor
{
- public class TypeChartEditor
+ public byte[] Data;
+ public int Width => (int)Math.Sqrt(Data.Length);
+ public int Height => (int)Math.Sqrt(Data.Length);
+
+ public TypeChartEditor(byte[] data) => Data = data;
+
+ public void Randomize()
{
- public byte[] Data;
- public int Width => (int)Math.Sqrt(Data.Length);
- public int Height => (int)Math.Sqrt(Data.Length);
-
- public TypeChartEditor(byte[] data) => Data = data;
-
- public void Randomize()
+ var rnd = Util.Random;
+ for (int i = 0; i < Data.Length; i++)
{
- var rnd = Util.Random;
- for (int i = 0; i < Data.Length; i++)
- {
- var rv = rnd.Next(100);
- Data[i] = GetEffectiveness(rv);
- }
- }
-
- private static byte GetEffectiveness(int rv)
- {
- return rv switch
- {
- < 2 => (byte)TypeEffectiveness.Immune, // 2%
- < 19 => (byte)TypeEffectiveness.NotVery, // 17%
- < 36 => (byte)TypeEffectiveness.Super, // 17%
- _ => (byte)TypeEffectiveness.Normal,
- };
- }
-
- private static readonly uint[] Colors =
- {
- 0xFF000000,
- 0, // unused
- 0xFFFF0000,
- 0, // unused
- 0xFFFFFFFF,
- 0, 0, 0, // unused
- 0xFF008000,
- };
-
- public static byte[] GetTypeChartImageData(int itemsize, int itemsPerRow, byte[] vals, out int width, out int height)
- {
- width = itemsize * itemsPerRow;
- height = itemsize * vals.Length / itemsPerRow;
- var bmpData = new byte[4 * width * height];
-
- // loop over area
- for (int i = 0; i < vals.Length; i++)
- {
- int X = i % itemsPerRow;
- int Y = i / itemsPerRow;
-
- // Plop into image
- byte[] itemColor = BitConverter.GetBytes(Colors[vals[i]]);
- for (int x = 0; x < itemsize * itemsize; x++)
- {
- var ofs = (((Y * itemsize) + (x % itemsize)) * width * 4) + (((X * itemsize) + (x / itemsize)) * 4);
- Buffer.BlockCopy(itemColor, 0, bmpData, ofs, 4);
- }
- }
- // slap on a grid
- byte[] gridColor = BitConverter.GetBytes(0x17000000);
- for (int i = 0; i < width * height; i++)
- {
- if (i % itemsize == 0 || i / (itemsize * itemsPerRow) % itemsize == 0)
- {
- var ofs = (i / (itemsize * itemsPerRow) * width * 4) + (i % (itemsize * itemsPerRow) * 4);
- Buffer.BlockCopy(gridColor, 0, bmpData, ofs, 4);
- }
- }
-
- return bmpData;
+ var rv = rnd.Next(100);
+ Data[i] = GetEffectiveness(rv);
}
}
-}
+
+ private static byte GetEffectiveness(int rv)
+ {
+ return rv switch
+ {
+ < 2 => (byte)TypeEffectiveness.Immune, // 2%
+ < 19 => (byte)TypeEffectiveness.NotVery, // 17%
+ < 36 => (byte)TypeEffectiveness.Super, // 17%
+ _ => (byte)TypeEffectiveness.Normal,
+ };
+ }
+
+ private static readonly uint[] Colors =
+ {
+ 0xFF000000,
+ 0, // unused
+ 0xFFFF0000,
+ 0, // unused
+ 0xFFFFFFFF,
+ 0, 0, 0, // unused
+ 0xFF008000,
+ };
+
+ public static byte[] GetTypeChartImageData(int itemsize, int itemsPerRow, byte[] vals, out int width, out int height)
+ {
+ width = itemsize * itemsPerRow;
+ height = itemsize * vals.Length / itemsPerRow;
+ var bmpData = new byte[4 * width * height];
+
+ // loop over area
+ for (int i = 0; i < vals.Length; i++)
+ {
+ int X = i % itemsPerRow;
+ int Y = i / itemsPerRow;
+
+ // Plop into image
+ byte[] itemColor = BitConverter.GetBytes(Colors[vals[i]]);
+ for (int x = 0; x < itemsize * itemsize; x++)
+ {
+ var ofs = (((Y * itemsize) + (x % itemsize)) * width * 4) + (((X * itemsize) + (x / itemsize)) * 4);
+ Buffer.BlockCopy(itemColor, 0, bmpData, ofs, 4);
+ }
+ }
+ // slap on a grid
+ byte[] gridColor = BitConverter.GetBytes(0x17000000);
+ for (int i = 0; i < width * height; i++)
+ {
+ if (i % itemsize == 0 || i / (itemsize * itemsPerRow) % itemsize == 0)
+ {
+ var ofs = (i / (itemsize * itemsPerRow) * width * 4) + (i % (itemsize * itemsPerRow) * 4);
+ Buffer.BlockCopy(gridColor, 0, bmpData, ofs, 4);
+ }
+ }
+
+ return bmpData;
+ }
+}
\ No newline at end of file
diff --git a/pkNX.Game/File/GameFile.cs b/pkNX.Game/File/GameFile.cs
index 6a1cbfaa..2138e805 100644
--- a/pkNX.Game/File/GameFile.cs
+++ b/pkNX.Game/File/GameFile.cs
@@ -1,229 +1,228 @@
-namespace pkNX.Game
+namespace pkNX.Game;
+
+///
+/// Simple descriptor related to what purpose the game data serves.
+///
+public enum GameFile
{
- ///
- /// Simple descriptor related to what purpose the game data serves.
- ///
- public enum GameFile
- {
- /// Contains the game text that is commonly re-used, not related to the storyline or general overworld content.
- GameText,
+ /// Contains the game text that is commonly re-used, not related to the storyline or general overworld content.
+ GameText,
- /// Localized Game Text for .
- GameText0,
+ /// Localized Game Text for .
+ GameText0,
- /// Localized Game Text for .
- GameText1,
+ /// Localized Game Text for .
+ GameText1,
- /// Localized Game Text for .
- GameText2,
+ /// Localized Game Text for .
+ GameText2,
- /// Localized Game Text for .
- GameText3,
+ /// Localized Game Text for .
+ GameText3,
- /// Localized Game Text for .
- GameText4,
+ /// Localized Game Text for .
+ GameText4,
- /// Localized Game Text for .
- GameText5,
+ /// Localized Game Text for .
+ GameText5,
- /// Localized Game Text for .
- GameText6,
+ /// Localized Game Text for .
+ GameText6,
- /// Localized Game Text for .
- GameText7,
+ /// Localized Game Text for .
+ GameText7,
- /// Localized Game Text for .
- GameText8,
+ /// Localized Game Text for .
+ GameText8,
- /// Localized Game Text for .
- GameText9,
+ /// Localized Game Text for .
+ GameText9,
- /// Contains the story text that is used to tell the story via overworld events and interactions.
- StoryText,
+ /// Contains the story text that is used to tell the story via overworld events and interactions.
+ StoryText,
- /// Localized Story Text for .
- StoryText0,
+ /// Localized Story Text for .
+ StoryText0,
- /// Localized Story Text for .
- StoryText1,
+ /// Localized Story Text for .
+ StoryText1,
- /// Localized Story Text for .
- StoryText2,
+ /// Localized Story Text for .
+ StoryText2,
- /// Localized Story Text for .
- StoryText3,
+ /// Localized Story Text for .
+ StoryText3,
- /// Localized Story Text for .
- StoryText4,
+ /// Localized Story Text for .
+ StoryText4,
- /// Localized Story Text for .
- StoryText5,
+ /// Localized Story Text for .
+ StoryText5,
- /// Localized Story Text for .
- StoryText6,
+ /// Localized Story Text for .
+ StoryText6,
- /// Localized Story Text for .
- StoryText7,
+ /// Localized Story Text for .
+ StoryText7,
- /// Localized Story Text for .
- StoryText8,
+ /// Localized Story Text for .
+ StoryText8,
- /// Localized Story Text for .
- StoryText9,
+ /// Localized Story Text for .
+ StoryText9,
- /// Overworld grass/etc encounterable species data.
- Encounters,
+ /// Overworld grass/etc encounterable species data.
+ Encounters,
- /// Trainer Data related to Trainers of a shared type.
- TrainerClass,
+ /// Trainer Data related to Trainers of a shared type.
+ TrainerClass,
- /// Trainer Data for individual Trainers that can be battled.
- TrainerData,
+ /// Trainer Data for individual Trainers that can be battled.
+ TrainerData,
- /// Trainer PKM template data for regular battles.
- TrainerPoke,
+ /// Trainer PKM template data for regular battles.
+ TrainerPoke,
- /// Move data that defines the properties of in-game moves.
- MoveStats,
+ /// Move data that defines the properties of in-game moves.
+ MoveStats,
- /// Egg Moves a species can learn when bred.
- EggMoves,
+ /// Egg Moves a species can learn when bred.
+ EggMoves,
- /// Moves a species can learn via level up.
- Learnsets,
+ /// Moves a species can learn via level up.
+ Learnsets,
- /// Evolutions a species can have under specified conditions.
- Evolutions,
+ /// Evolutions a species can have under specified conditions.
+ Evolutions,
- /// Mega Evolutions a species can have under specified conditions.
- MegaEvolutions,
+ /// Mega Evolutions a species can have under specified conditions.
+ MegaEvolutions,
- /// In-game stats a species can have.
- PersonalStats,
+ /// In-game stats a species can have.
+ PersonalStats,
- /// Properties of in-game posessible items.
- ItemStats,
+ /// Properties of in-game posessible items.
+ ItemStats,
- /// Static (fixed position/condition) encounter table.
- EncounterStatic,
+ /// Static (fixed position/condition) encounter table.
+ EncounterStatic,
- /// Post-game roulette trainer data with normal difficulty.
- FacilityTrainerNormal,
+ /// Post-game roulette trainer data with normal difficulty.
+ FacilityTrainerNormal,
- /// Post-game roulette trainer data with heightened difficulty.
- FacilityTrainerSuper,
+ /// Post-game roulette trainer data with heightened difficulty.
+ FacilityTrainerSuper,
- /// Post-game roulette PKM template data for normal difficulty trainers.
- FacilityPokeNormal,
+ /// Post-game roulette PKM template data for normal difficulty trainers.
+ FacilityPokeNormal,
- /// Post-game roulette PKM template data for heightened difficulty trainers.
- FacilityPokeSuper,
+ /// Post-game roulette PKM template data for heightened difficulty trainers.
+ FacilityPokeSuper,
- /// Title Screen staging data.
- TitleScreen,
+ /// Title Screen staging data.
+ TitleScreen,
- /// Box Interface wallpapers.
- Wallpaper,
+ /// Box Interface wallpapers.
+ Wallpaper,
- /// Walk/Collision data for individual Maps.
- MapMatrix,
+ /// Walk/Collision data for individual Maps.
+ MapMatrix,
- /// Zone assembling information to build large maps from individual small zones.
- MapGameRegion,
+ /// Zone assembling information to build large maps from individual small zones.
+ MapGameRegion,
- /// Area settings and permissives related to in-game areas the player travels to.
- ZoneData,
+ /// Area settings and permissives related to in-game areas the player travels to.
+ ZoneData,
- /// Post-battle items that can be picked up.
- BattleDrops,
+ /// Post-battle items that can be picked up.
+ BattleDrops,
- /// Items that can be dropped by pokemon in the field.
- FieldDrops,
+ /// Items that can be dropped by pokemon in the field.
+ FieldDrops,
- /// Map data for individual zones.
- WorldData,
+ /// Map data for individual zones.
+ WorldData,
- /// UI Sprites for pretty in-game move descriptors.
- MoveSprites,
+ /// UI Sprites for pretty in-game move descriptors.
+ MoveSprites,
- /// Traded Pokémon swap data.
- EncounterTrade,
+ /// Traded Pokémon swap data.
+ EncounterTrade,
- /// Gift Pokémon data.
- EncounterGift,
+ /// Gift Pokémon data.
+ EncounterGift,
- /// Nest Data
- NestData,
+ /// Nest Data
+ NestData,
- /// Wild Data
- WildData,
+ /// Wild Data
+ WildData,
- /// Wild Data
- WildData1,
+ /// Wild Data
+ WildData1,
- /// Wild Data
- WildData2,
+ /// Wild Data
+ WildData2,
- /// Dynamax Adventure Dens
- DynamaxDens,
+ /// Dynamax Adventure Dens
+ DynamaxDens,
- /// Area Placement Archive
- Placement,
+ /// Area Placement Archive
+ Placement,
- /// Shop Inventory Lists
- Shops,
+ /// Shop Inventory Lists
+ Shops,
- /// Rental Team Pokémon
- Rentals,
+ /// Rental Team Pokémon
+ Rentals,
- /// Symbol Behavior Definition
- SymbolBehave,
+ /// Symbol Behavior Definition
+ SymbolBehave,
- /// Area Resident Archive
- Resident,
+ /// Area Resident Archive
+ Resident,
- /// "PokeEncount" Rate Multipler Archive
- EncounterRateTable,
+ /// "PokeEncount" Rate Multipler Archive
+ EncounterRateTable,
- /// huge_outbreak.bin
- Outbreak,
+ /// huge_outbreak.bin
+ Outbreak,
- /// wazashop_table.bin
- MoveShop,
+ /// wazashop_table.bin
+ MoveShop,
- /// "PokeMisc" Details about a given Species-Form not stored in
- PokeMisc,
+ /// "PokeMisc" Details about a given Species-Form not stored in
+ PokeMisc,
- /// All pokedex research tasks
- DexResearch,
+ /// All pokedex research tasks
+ DexResearch,
- ThrowableParam,
- ThrowParam,
- ThrowableResourceSet,
- ThrowableResource,
- ThrowPermissionSet,
+ ThrowableParam,
+ ThrowParam,
+ ThrowableResourceSet,
+ ThrowableResource,
+ ThrowPermissionSet,
- /// Gives bonus rolls based on met thresholds.
- ShinyRolls,
- WormholeConfig,
- CaptureConfig,
- BattleLogicConfig,
- PlayerConfig,
- EventFarmConfig,
- FieldLandmarkConfig,
- BattleViewConfig,
- AICommonConfig,
- FieldSpawnerConfig,
- OutbreakConfig,
- EvolutionConfig,
- BallThrowConfig,
- SizeScaleConfig,
+ /// Gives bonus rolls based on met thresholds.
+ ShinyRolls,
+ WormholeConfig,
+ CaptureConfig,
+ BattleLogicConfig,
+ PlayerConfig,
+ EventFarmConfig,
+ FieldLandmarkConfig,
+ BattleViewConfig,
+ AICommonConfig,
+ FieldSpawnerConfig,
+ OutbreakConfig,
+ EvolutionConfig,
+ BallThrowConfig,
+ SizeScaleConfig,
- AppConfigList,
- HaShop,
- NewHugeGroup,
- NewHugeGroupLottery,
- NewHugeLottery,
- NewHugeTimeLimit,
- }
-}
+ AppConfigList,
+ HaShop,
+ NewHugeGroup,
+ NewHugeGroupLottery,
+ NewHugeLottery,
+ NewHugeTimeLimit,
+}
\ No newline at end of file
diff --git a/pkNX.Game/File/GameFileMapping.cs b/pkNX.Game/File/GameFileMapping.cs
index cf71a5db..8d63b99c 100644
--- a/pkNX.Game/File/GameFileMapping.cs
+++ b/pkNX.Game/File/GameFileMapping.cs
@@ -5,267 +5,266 @@
using pkNX.Containers;
using pkNX.Structures;
-namespace pkNX.Game
+namespace pkNX.Game;
+
+///
+/// Handles file retrieval and lifetime management for a 's data.
+///
+public class GameFileMapping
{
- ///
- /// Handles file retrieval and lifetime management for a 's data.
- ///
- public class GameFileMapping
+ private readonly Dictionary Cache = new();
+ private readonly IReadOnlyCollection FileMap;
+
+ public readonly ContainerHandler ProgressTracker = new();
+ public readonly CancellationTokenSource TokenSource = new();
+
+ private readonly GameLocation ROM;
+ public GameFileMapping(GameLocation rom) => FileMap = GetMapping((ROM = rom).Game);
+
+ internal IFileContainer GetFile(GameFile file, int language)
{
- private readonly Dictionary Cache = new();
- private readonly IReadOnlyCollection FileMap;
+ if (file is GameFile.GameText or GameFile.StoryText)
+ file += language + 1; // shift to localized language
- public readonly ContainerHandler ProgressTracker = new();
- public readonly CancellationTokenSource TokenSource = new();
-
- private readonly GameLocation ROM;
- public GameFileMapping(GameLocation rom) => FileMap = GetMapping((ROM = rom).Game);
-
- internal IFileContainer GetFile(GameFile file, int language)
- {
- if (file is GameFile.GameText or GameFile.StoryText)
- file += language + 1; // shift to localized language
-
- if (Cache.TryGetValue(file, out var container))
- return container;
-
- var info = FileMap.FirstOrDefault(f => f.File == file);
- if (info == null)
- throw new ArgumentException($"Unknown {nameof(GameFile)} provided.", file.ToString());
-
- var basePath = info.Parent == ContainerParent.ExeFS ? ROM.ExeFS : ROM.RomFS;
- container = info.Get(basePath);
- Cache.Add(file, container);
+ if (Cache.TryGetValue(file, out var container))
return container;
- }
- internal void SaveAll()
- {
- foreach (var container in Cache)
- {
- var c = container.Value;
- if (c.Modified)
- c.SaveAs(c.FilePath, ProgressTracker, TokenSource.Token).RunSynchronously();
- }
- var modified = Cache.Where(z => z.Value.Modified).ToArray();
- foreach (var m in modified)
- Cache.Remove(m.Key);
- }
+ var info = FileMap.FirstOrDefault(f => f.File == file);
+ if (info == null)
+ throw new ArgumentException($"Unknown {nameof(GameFile)} provided.", file.ToString());
- public static IReadOnlyCollection GetMapping(GameVersion game) => game switch
- {
- GameVersion.GP => GG,
- GameVersion.GE => GG,
- GameVersion.GG => GG,
- GameVersion.SW => SWSH,
- GameVersion.SH => SWSH,
- GameVersion.SWSH => SWSH,
- GameVersion.PLA => PLA,
- _ => null,
- };
-
- #region Gen7
-
- ///
- /// Let's Go Pikachu & Let's Go Eevee
- ///
- private static readonly GameFileReference[] GG =
- {
- new(GameFile.TrainerData, "bin", "trainer", "trainer_data"),
- new(GameFile.TrainerPoke, "bin", "trainer", "trainer_poke"),
- new(GameFile.TrainerClass, "bin", "trainer", "trainer_type"),
-
- new(GameFile.GameText0, 0, "bin", "message", "JPN", "common"),
- new(GameFile.GameText1, 1, "bin", "message", "JPN_KANJI", "common"),
- new(GameFile.GameText2, 2, "bin", "message", "English", "common"),
- new(GameFile.GameText3, 3, "bin", "message", "French", "common"),
- new(GameFile.GameText4, 4, "bin", "message", "Italian", "common"),
- new(GameFile.GameText5, 5, "bin", "message", "German", "common"),
- // 6 unused lang
- new(GameFile.GameText6, 7, "bin", "message", "Spanish", "common"),
- new(GameFile.GameText7, 8, "bin", "message", "Korean", "common"),
- new(GameFile.GameText8, 9, "bin", "message", "Simp_Chinese", "common"),
- new(GameFile.GameText9, 10, "bin", "message", "Trad_Chinese", "common"),
-
- new(GameFile.StoryText0, 0, "bin", "message", "JPN", "script"),
- new(GameFile.StoryText1, 1, "bin", "message", "JPN_KANJI", "script"),
- new(GameFile.StoryText2, 2, "bin", "message", "English", "script"),
- new(GameFile.StoryText3, 3, "bin", "message", "French", "script"),
- new(GameFile.StoryText4, 4, "bin", "message", "Italian", "script"),
- new(GameFile.StoryText5, 5, "bin", "message", "German", "script"),
- // 6 unused lang
- new(GameFile.StoryText6, 7, "bin", "message", "Spanish", "script"),
- new(GameFile.StoryText7, 8, "bin", "message", "Korean", "script"),
- new(GameFile.StoryText8, 9, "bin", "message", "Simp_Chinese", "script"),
- new(GameFile.StoryText9, 10, "bin", "message", "Trad_Chinese", "script"),
-
- new(GameFile.ItemStats, "bin", "pokelib", "item"),
- new(GameFile.Evolutions, "bin", "pokelib", "evolution"),
- new(GameFile.PersonalStats, "bin", "pokelib", "personal"),
- new(GameFile.MegaEvolutions, "bin", "pokelib", "mega_evolution"),
- new(GameFile.MoveStats, ContainerType.Mini, "bin", "pokelib", "waza", "waza_data.bin"),
- new(GameFile.EncounterStatic, ContainerType.SingleFile, "bin", "script_event_data", "event_encount.bin"),
- new(GameFile.EncounterTrade, ContainerType.SingleFile, "bin", "script_event_data", "field_trade_data.bin"),
- new(GameFile.EncounterGift, ContainerType.SingleFile, "bin", "script_event_data", "add_poke.bin"),
- new(GameFile.Learnsets, ContainerType.GFPack, "bin", "archive", "waza_oboe.gfpak"),
-
- new(GameFile.WildData1, ContainerType.SingleFile, "bin", "field", "param", "encount", "encount_data_p.bin"),
- new(GameFile.WildData2, ContainerType.SingleFile, "bin", "field", "param", "encount", "encount_data_e.bin"),
- new(GameFile.Shops, ContainerType.SingleFile, "bin", "app", "shop", "shop_data.bin"),
-
- // Cutscenes bin\demo
- // Models bin\archive\pokemon
- // pretty much everything is obviously named :)
- #endregion
- };
-
- #region Gen 8
- ///
- /// Sword
- ///
- private static readonly GameFileReference[] SWSH =
- {
- new(GameFile.TrainerData, "bin", "trainer", "trainer_data"),
- new(GameFile.TrainerPoke, "bin", "trainer", "trainer_poke"),
- new(GameFile.TrainerClass, "bin", "trainer", "trainer_type"),
-
- new(GameFile.GameText0, 0, "bin", "message", "JPN", "common"),
- new(GameFile.GameText1, 1, "bin", "message", "JPN_KANJI", "common"),
- new(GameFile.GameText2, 2, "bin", "message", "English", "common"),
- new(GameFile.GameText3, 3, "bin", "message", "French", "common"),
- new(GameFile.GameText4, 4, "bin", "message", "Italian", "common"),
- new(GameFile.GameText5, 5, "bin", "message", "German", "common"),
- // 6 unused lang
- new(GameFile.GameText6, 7, "bin", "message", "Spanish", "common"),
- new(GameFile.GameText7, 8, "bin", "message", "Korean", "common"),
- new(GameFile.GameText8, 9, "bin", "message", "Simp_Chinese", "common"),
- new(GameFile.GameText9, 10, "bin", "message", "Trad_Chinese", "common"),
-
- new(GameFile.StoryText0, 0, "bin", "message", "JPN", "script"),
- new(GameFile.StoryText1, 1, "bin", "message", "JPN_KANJI", "script"),
- new(GameFile.StoryText2, 2, "bin", "message", "English", "script"),
- new(GameFile.StoryText3, 3, "bin", "message", "French", "script"),
- new(GameFile.StoryText4, 4, "bin", "message", "Italian", "script"),
- new(GameFile.StoryText5, 5, "bin", "message", "German", "script"),
- // 6 unused lang
- new(GameFile.StoryText6, 7, "bin", "message", "Spanish", "script"),
- new(GameFile.StoryText7, 8, "bin", "message", "Korean", "script"),
- new(GameFile.StoryText8, 9, "bin", "message", "Simp_Chinese", "script"),
- new(GameFile.StoryText9, 10, "bin", "message", "Trad_Chinese", "script"),
-
- new(GameFile.ItemStats, ContainerType.SingleFile, "bin", "pml", "item", "item.dat"),
- new(GameFile.Evolutions, "bin", "pml", "evolution"),
- new(GameFile.EggMoves, "bin", "pml", "tamagowaza"),
- new(GameFile.PersonalStats, "bin", "pml", "personal"),
- new(GameFile.MoveStats, "bin", "pml", "waza"),
- new(GameFile.EncounterStatic, ContainerType.SingleFile, "bin", "script_event_data", "event_encount_data.bin"),
- new(GameFile.EncounterTrade, ContainerType.SingleFile, "bin", "script_event_data", "field_trade.bin"),
- new(GameFile.EncounterGift, ContainerType.SingleFile, "bin", "script_event_data", "add_poke.bin"),
- new(GameFile.Learnsets, ContainerType.SingleFile, "bin", "pml", "waza_oboe", "wazaoboe_total.bin"),
-
- new(GameFile.FacilityPokeNormal, ContainerType.SingleFile, "bin", "field", "param", "battle_tower", "battle_tower_poke_table.bin"),
- new(GameFile.FacilityTrainerNormal, ContainerType.SingleFile, "bin", "field", "param", "battle_tower", "battle_tower_trainer_table.bin"),
-
- new(GameFile.WildData, ContainerType.SingleFile, "bin", "archive", "field", "resident", "data_table.gfpak"),
- new(GameFile.NestData, ContainerType.SingleFile, "bin", "archive", "field", "resident", "data_table.gfpak"),
-
- new(GameFile.DynamaxDens, ContainerType.SingleFile, "bin", "appli", "chika", "data_table", "underground_exploration_poke.bin"),
-
- new(GameFile.Placement, ContainerType.SingleFile, "bin", "archive", "field", "resident", "placement.gfpak"),
- new(GameFile.Shops, ContainerType.SingleFile, "bin", "appli", "shop", "bin", "shop_data.bin"),
- new(GameFile.Rentals, ContainerType.SingleFile, "bin", "script_event_data", "rental.bin"),
- new(GameFile.SymbolBehave, ContainerType.SingleFile, "bin", "field", "param", "symbol_encount_mons_param", "symbol_encount_mons_param.bin")
-
- // Cutscenes bin\demo
- // Models bin\archive\pokemon
- // pretty much everything is obviously named :)
- };
-
- ///
- /// Sword
- ///
- private static readonly GameFileReference[] PLA =
- {
- new(GameFile.TrainerData, "bin", "trainer"),
-
- new(GameFile.GameText0, 0, "bin", "message", "JPN", "common"),
- new(GameFile.GameText1, 1, "bin", "message", "JPN_KANJI", "common"),
- new(GameFile.GameText2, 2, "bin", "message", "English", "common"),
- new(GameFile.GameText3, 3, "bin", "message", "French", "common"),
- new(GameFile.GameText4, 4, "bin", "message", "Italian", "common"),
- new(GameFile.GameText5, 5, "bin", "message", "German", "common"),
- // 6 unused lang
- new(GameFile.GameText6, 7, "bin", "message", "Spanish", "common"),
- new(GameFile.GameText7, 8, "bin", "message", "Korean", "common"),
- new(GameFile.GameText8, 9, "bin", "message", "Simp_Chinese", "common"),
- new(GameFile.GameText9, 10, "bin", "message", "Trad_Chinese", "common"),
-
- new(GameFile.StoryText0, 0, "bin", "message", "JPN", "script"),
- new(GameFile.StoryText1, 1, "bin", "message", "JPN_KANJI", "script"),
- new(GameFile.StoryText2, 2, "bin", "message", "English", "script"),
- new(GameFile.StoryText3, 3, "bin", "message", "French", "script"),
- new(GameFile.StoryText4, 4, "bin", "message", "Italian", "script"),
- new(GameFile.StoryText5, 5, "bin", "message", "German", "script"),
- // 6 unused lang
- new(GameFile.StoryText6, 7, "bin", "message", "Spanish", "script"),
- new(GameFile.StoryText7, 8, "bin", "message", "Korean", "script"),
- new(GameFile.StoryText8, 9, "bin", "message", "Simp_Chinese", "script"),
- new(GameFile.StoryText9, 10, "bin", "message", "Trad_Chinese", "script"),
-
- new(GameFile.ItemStats, ContainerType.SingleFile, "bin", "pml", "item", "item.dat"),
- new(GameFile.Evolutions, ContainerType.SingleFile, "bin", "pml", "evolution", "evolution_data_total.evobin"),
- new(GameFile.PersonalStats, ContainerType.SingleFile, "bin", "pml", "personal", "personal_data_total.perbin"),
- new(GameFile.MoveStats, "bin", "pml", "waza"),
- new(GameFile.EncounterStatic, ContainerType.SingleFile, "bin", "pokemon", "data", "poke_event_encount.bin"),
- new(GameFile.EncounterTrade, ContainerType.SingleFile, "bin", "script_event_data", "field_trade.bin"),
- new(GameFile.EncounterGift, ContainerType.SingleFile, "bin", "pokemon", "data", "poke_add.bin"),
- new(GameFile.Learnsets, ContainerType.SingleFile, "bin", "pml", "waza_oboe", "waza_oboe_total.wazaoboe"),
-
- new(GameFile.Resident, ContainerType.GFPack, "bin", "archive", "field", "resident_release.gfpak"),
-
- new(GameFile.FieldDrops, ContainerType.SingleFile, "bin", "pokemon", "data", "poke_drop_item.bin"),
- new(GameFile.BattleDrops, ContainerType.SingleFile, "bin", "pokemon", "data", "poke_drop_item_battle.bin"),
-
- new(GameFile.DexResearch, ContainerType.SingleFile, "bin", "appli", "pokedex", "res_table", "pokedex_research_task_table.bin"),
-
- new(GameFile.EncounterRateTable, ContainerType.SingleFile, "bin", "pokemon", "data", "poke_encount.bin"),
- new(GameFile.PokeMisc, ContainerType.SingleFile, "bin", "pokemon", "data", "poke_misc.bin"),
- new(GameFile.Outbreak, ContainerType.SingleFile, "bin", "field", "encount", "huge_outbreak.bin"),
- new(GameFile.MoveShop, ContainerType.SingleFile, "bin", "appli", "wazaremember", "bin", "wazashop_table.bin"),
-
- new(GameFile.ThrowableParam, ContainerType.SingleFile, "bin", "capture", "throwable_param_table.bin"),
- new(GameFile.ThrowParam, ContainerType.SingleFile, "bin", "capture", "throw_param_table.bin"),
- new(GameFile.ThrowableResourceSet, ContainerType.SingleFile, "bin", "capture", "throwable_resourceset_dictionary.bin"),
- new(GameFile.ThrowableResource, ContainerType.SingleFile, "bin", "capture", "throwable_resource_dictionary.bin"),
- new(GameFile.ThrowPermissionSet, ContainerType.SingleFile, "bin", "capture", "throw_permissionset_dictionary.bin"),
- new(GameFile.HaShop, ContainerType.SingleFile, "bin", "appli", "shop", "bin", "ha_shop_data.bin"),
-
- new(GameFile.ShinyRolls, ContainerType.SingleFile, "bin", "misc", "app_config", "pokemon_rare.bin"),
- new(GameFile.WormholeConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "field_wormhole_config.bin"),
- new(GameFile.CaptureConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "common_capture_config.bin"),
- new(GameFile.BattleLogicConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "battle_logic_config.bin"),
- new(GameFile.PlayerConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "player_config.bin"),
- new(GameFile.EventFarmConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "event_farm_config.bin"),
- new(GameFile.AppConfigList, ContainerType.SingleFile, "bin", "misc", "app_config", "app_config_list.bin"),
- new(GameFile.FieldLandmarkConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "field_landmark_config.bin"),
- new(GameFile.BattleViewConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "battle_view_config.bin"),
- new(GameFile.AICommonConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "ai_common_config.bin"),
- new(GameFile.FieldSpawnerConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "field_spawner_config.bin"),
- new(GameFile.OutbreakConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "field_huge_outbreak.bin"),
- new(GameFile.EvolutionConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "pokemon_evolution_config.bin"),
- new(GameFile.BallThrowConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "field_my_poke_ball_config.bin"),
- new(GameFile.SizeScaleConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "pokemon_size_category_adjust_scale_config.bin"),
- new(GameFile.SymbolBehave, ContainerType.SingleFile, "bin", "pokemon", "data", "poke_ai.bin"),
-
- new(GameFile.NewHugeGroup, ContainerType.SingleFile, "bin", "field", "encount", "new_huge_outbreak_group.bin"),
- new(GameFile.NewHugeGroupLottery, ContainerType.SingleFile, "bin", "field", "encount", "new_huge_outbreak_group_lottery.bin"),
- new(GameFile.NewHugeLottery, ContainerType.SingleFile, "bin", "field", "encount", "new_huge_outbreak_lottery.bin"),
- new(GameFile.NewHugeTimeLimit, ContainerType.SingleFile, "bin", "field", "encount", "new_huge_outbreak_time_limit.bin"),
-
- // Cutscenes bin\demo
- // Models bin\archive\pokemon
- // pretty much everything is obviously named :)
- };
- #endregion
+ var basePath = info.Parent == ContainerParent.ExeFS ? ROM.ExeFS : ROM.RomFS;
+ container = info.Get(basePath);
+ Cache.Add(file, container);
+ return container;
}
-}
+
+ internal void SaveAll()
+ {
+ foreach (var container in Cache)
+ {
+ var c = container.Value;
+ if (c.Modified)
+ c.SaveAs(c.FilePath, ProgressTracker, TokenSource.Token).RunSynchronously();
+ }
+ var modified = Cache.Where(z => z.Value.Modified).ToArray();
+ foreach (var m in modified)
+ Cache.Remove(m.Key);
+ }
+
+ public static IReadOnlyCollection GetMapping(GameVersion game) => game switch
+ {
+ GameVersion.GP => GG,
+ GameVersion.GE => GG,
+ GameVersion.GG => GG,
+ GameVersion.SW => SWSH,
+ GameVersion.SH => SWSH,
+ GameVersion.SWSH => SWSH,
+ GameVersion.PLA => PLA,
+ _ => null,
+ };
+
+ #region Gen7
+
+ ///
+ /// Let's Go Pikachu & Let's Go Eevee
+ ///
+ private static readonly GameFileReference[] GG =
+ {
+ new(GameFile.TrainerData, "bin", "trainer", "trainer_data"),
+ new(GameFile.TrainerPoke, "bin", "trainer", "trainer_poke"),
+ new(GameFile.TrainerClass, "bin", "trainer", "trainer_type"),
+
+ new(GameFile.GameText0, 0, "bin", "message", "JPN", "common"),
+ new(GameFile.GameText1, 1, "bin", "message", "JPN_KANJI", "common"),
+ new(GameFile.GameText2, 2, "bin", "message", "English", "common"),
+ new(GameFile.GameText3, 3, "bin", "message", "French", "common"),
+ new(GameFile.GameText4, 4, "bin", "message", "Italian", "common"),
+ new(GameFile.GameText5, 5, "bin", "message", "German", "common"),
+ // 6 unused lang
+ new(GameFile.GameText6, 7, "bin", "message", "Spanish", "common"),
+ new(GameFile.GameText7, 8, "bin", "message", "Korean", "common"),
+ new(GameFile.GameText8, 9, "bin", "message", "Simp_Chinese", "common"),
+ new(GameFile.GameText9, 10, "bin", "message", "Trad_Chinese", "common"),
+
+ new(GameFile.StoryText0, 0, "bin", "message", "JPN", "script"),
+ new(GameFile.StoryText1, 1, "bin", "message", "JPN_KANJI", "script"),
+ new(GameFile.StoryText2, 2, "bin", "message", "English", "script"),
+ new(GameFile.StoryText3, 3, "bin", "message", "French", "script"),
+ new(GameFile.StoryText4, 4, "bin", "message", "Italian", "script"),
+ new(GameFile.StoryText5, 5, "bin", "message", "German", "script"),
+ // 6 unused lang
+ new(GameFile.StoryText6, 7, "bin", "message", "Spanish", "script"),
+ new(GameFile.StoryText7, 8, "bin", "message", "Korean", "script"),
+ new(GameFile.StoryText8, 9, "bin", "message", "Simp_Chinese", "script"),
+ new(GameFile.StoryText9, 10, "bin", "message", "Trad_Chinese", "script"),
+
+ new(GameFile.ItemStats, "bin", "pokelib", "item"),
+ new(GameFile.Evolutions, "bin", "pokelib", "evolution"),
+ new(GameFile.PersonalStats, "bin", "pokelib", "personal"),
+ new(GameFile.MegaEvolutions, "bin", "pokelib", "mega_evolution"),
+ new(GameFile.MoveStats, ContainerType.Mini, "bin", "pokelib", "waza", "waza_data.bin"),
+ new(GameFile.EncounterStatic, ContainerType.SingleFile, "bin", "script_event_data", "event_encount.bin"),
+ new(GameFile.EncounterTrade, ContainerType.SingleFile, "bin", "script_event_data", "field_trade_data.bin"),
+ new(GameFile.EncounterGift, ContainerType.SingleFile, "bin", "script_event_data", "add_poke.bin"),
+ new(GameFile.Learnsets, ContainerType.GFPack, "bin", "archive", "waza_oboe.gfpak"),
+
+ new(GameFile.WildData1, ContainerType.SingleFile, "bin", "field", "param", "encount", "encount_data_p.bin"),
+ new(GameFile.WildData2, ContainerType.SingleFile, "bin", "field", "param", "encount", "encount_data_e.bin"),
+ new(GameFile.Shops, ContainerType.SingleFile, "bin", "app", "shop", "shop_data.bin"),
+
+ // Cutscenes bin\demo
+ // Models bin\archive\pokemon
+ // pretty much everything is obviously named :)
+ #endregion
+ };
+
+ #region Gen 8
+ ///
+ /// Sword
+ ///
+ private static readonly GameFileReference[] SWSH =
+ {
+ new(GameFile.TrainerData, "bin", "trainer", "trainer_data"),
+ new(GameFile.TrainerPoke, "bin", "trainer", "trainer_poke"),
+ new(GameFile.TrainerClass, "bin", "trainer", "trainer_type"),
+
+ new(GameFile.GameText0, 0, "bin", "message", "JPN", "common"),
+ new(GameFile.GameText1, 1, "bin", "message", "JPN_KANJI", "common"),
+ new(GameFile.GameText2, 2, "bin", "message", "English", "common"),
+ new(GameFile.GameText3, 3, "bin", "message", "French", "common"),
+ new(GameFile.GameText4, 4, "bin", "message", "Italian", "common"),
+ new(GameFile.GameText5, 5, "bin", "message", "German", "common"),
+ // 6 unused lang
+ new(GameFile.GameText6, 7, "bin", "message", "Spanish", "common"),
+ new(GameFile.GameText7, 8, "bin", "message", "Korean", "common"),
+ new(GameFile.GameText8, 9, "bin", "message", "Simp_Chinese", "common"),
+ new(GameFile.GameText9, 10, "bin", "message", "Trad_Chinese", "common"),
+
+ new(GameFile.StoryText0, 0, "bin", "message", "JPN", "script"),
+ new(GameFile.StoryText1, 1, "bin", "message", "JPN_KANJI", "script"),
+ new(GameFile.StoryText2, 2, "bin", "message", "English", "script"),
+ new(GameFile.StoryText3, 3, "bin", "message", "French", "script"),
+ new(GameFile.StoryText4, 4, "bin", "message", "Italian", "script"),
+ new(GameFile.StoryText5, 5, "bin", "message", "German", "script"),
+ // 6 unused lang
+ new(GameFile.StoryText6, 7, "bin", "message", "Spanish", "script"),
+ new(GameFile.StoryText7, 8, "bin", "message", "Korean", "script"),
+ new(GameFile.StoryText8, 9, "bin", "message", "Simp_Chinese", "script"),
+ new(GameFile.StoryText9, 10, "bin", "message", "Trad_Chinese", "script"),
+
+ new(GameFile.ItemStats, ContainerType.SingleFile, "bin", "pml", "item", "item.dat"),
+ new(GameFile.Evolutions, "bin", "pml", "evolution"),
+ new(GameFile.EggMoves, "bin", "pml", "tamagowaza"),
+ new(GameFile.PersonalStats, "bin", "pml", "personal"),
+ new(GameFile.MoveStats, "bin", "pml", "waza"),
+ new(GameFile.EncounterStatic, ContainerType.SingleFile, "bin", "script_event_data", "event_encount_data.bin"),
+ new(GameFile.EncounterTrade, ContainerType.SingleFile, "bin", "script_event_data", "field_trade.bin"),
+ new(GameFile.EncounterGift, ContainerType.SingleFile, "bin", "script_event_data", "add_poke.bin"),
+ new(GameFile.Learnsets, ContainerType.SingleFile, "bin", "pml", "waza_oboe", "wazaoboe_total.bin"),
+
+ new(GameFile.FacilityPokeNormal, ContainerType.SingleFile, "bin", "field", "param", "battle_tower", "battle_tower_poke_table.bin"),
+ new(GameFile.FacilityTrainerNormal, ContainerType.SingleFile, "bin", "field", "param", "battle_tower", "battle_tower_trainer_table.bin"),
+
+ new(GameFile.WildData, ContainerType.SingleFile, "bin", "archive", "field", "resident", "data_table.gfpak"),
+ new(GameFile.NestData, ContainerType.SingleFile, "bin", "archive", "field", "resident", "data_table.gfpak"),
+
+ new(GameFile.DynamaxDens, ContainerType.SingleFile, "bin", "appli", "chika", "data_table", "underground_exploration_poke.bin"),
+
+ new(GameFile.Placement, ContainerType.SingleFile, "bin", "archive", "field", "resident", "placement.gfpak"),
+ new(GameFile.Shops, ContainerType.SingleFile, "bin", "appli", "shop", "bin", "shop_data.bin"),
+ new(GameFile.Rentals, ContainerType.SingleFile, "bin", "script_event_data", "rental.bin"),
+ new(GameFile.SymbolBehave, ContainerType.SingleFile, "bin", "field", "param", "symbol_encount_mons_param", "symbol_encount_mons_param.bin")
+
+ // Cutscenes bin\demo
+ // Models bin\archive\pokemon
+ // pretty much everything is obviously named :)
+ };
+
+ ///
+ /// Sword
+ ///
+ private static readonly GameFileReference[] PLA =
+ {
+ new(GameFile.TrainerData, "bin", "trainer"),
+
+ new(GameFile.GameText0, 0, "bin", "message", "JPN", "common"),
+ new(GameFile.GameText1, 1, "bin", "message", "JPN_KANJI", "common"),
+ new(GameFile.GameText2, 2, "bin", "message", "English", "common"),
+ new(GameFile.GameText3, 3, "bin", "message", "French", "common"),
+ new(GameFile.GameText4, 4, "bin", "message", "Italian", "common"),
+ new(GameFile.GameText5, 5, "bin", "message", "German", "common"),
+ // 6 unused lang
+ new(GameFile.GameText6, 7, "bin", "message", "Spanish", "common"),
+ new(GameFile.GameText7, 8, "bin", "message", "Korean", "common"),
+ new(GameFile.GameText8, 9, "bin", "message", "Simp_Chinese", "common"),
+ new(GameFile.GameText9, 10, "bin", "message", "Trad_Chinese", "common"),
+
+ new(GameFile.StoryText0, 0, "bin", "message", "JPN", "script"),
+ new(GameFile.StoryText1, 1, "bin", "message", "JPN_KANJI", "script"),
+ new(GameFile.StoryText2, 2, "bin", "message", "English", "script"),
+ new(GameFile.StoryText3, 3, "bin", "message", "French", "script"),
+ new(GameFile.StoryText4, 4, "bin", "message", "Italian", "script"),
+ new(GameFile.StoryText5, 5, "bin", "message", "German", "script"),
+ // 6 unused lang
+ new(GameFile.StoryText6, 7, "bin", "message", "Spanish", "script"),
+ new(GameFile.StoryText7, 8, "bin", "message", "Korean", "script"),
+ new(GameFile.StoryText8, 9, "bin", "message", "Simp_Chinese", "script"),
+ new(GameFile.StoryText9, 10, "bin", "message", "Trad_Chinese", "script"),
+
+ new(GameFile.ItemStats, ContainerType.SingleFile, "bin", "pml", "item", "item.dat"),
+ new(GameFile.Evolutions, ContainerType.SingleFile, "bin", "pml", "evolution", "evolution_data_total.evobin"),
+ new(GameFile.PersonalStats, ContainerType.SingleFile, "bin", "pml", "personal", "personal_data_total.perbin"),
+ new(GameFile.MoveStats, "bin", "pml", "waza"),
+ new(GameFile.EncounterStatic, ContainerType.SingleFile, "bin", "pokemon", "data", "poke_event_encount.bin"),
+ new(GameFile.EncounterTrade, ContainerType.SingleFile, "bin", "script_event_data", "field_trade.bin"),
+ new(GameFile.EncounterGift, ContainerType.SingleFile, "bin", "pokemon", "data", "poke_add.bin"),
+ new(GameFile.Learnsets, ContainerType.SingleFile, "bin", "pml", "waza_oboe", "waza_oboe_total.wazaoboe"),
+
+ new(GameFile.Resident, ContainerType.GFPack, "bin", "archive", "field", "resident_release.gfpak"),
+
+ new(GameFile.FieldDrops, ContainerType.SingleFile, "bin", "pokemon", "data", "poke_drop_item.bin"),
+ new(GameFile.BattleDrops, ContainerType.SingleFile, "bin", "pokemon", "data", "poke_drop_item_battle.bin"),
+
+ new(GameFile.DexResearch, ContainerType.SingleFile, "bin", "appli", "pokedex", "res_table", "pokedex_research_task_table.bin"),
+
+ new(GameFile.EncounterRateTable, ContainerType.SingleFile, "bin", "pokemon", "data", "poke_encount.bin"),
+ new(GameFile.PokeMisc, ContainerType.SingleFile, "bin", "pokemon", "data", "poke_misc.bin"),
+ new(GameFile.Outbreak, ContainerType.SingleFile, "bin", "field", "encount", "huge_outbreak.bin"),
+ new(GameFile.MoveShop, ContainerType.SingleFile, "bin", "appli", "wazaremember", "bin", "wazashop_table.bin"),
+
+ new(GameFile.ThrowableParam, ContainerType.SingleFile, "bin", "capture", "throwable_param_table.bin"),
+ new(GameFile.ThrowParam, ContainerType.SingleFile, "bin", "capture", "throw_param_table.bin"),
+ new(GameFile.ThrowableResourceSet, ContainerType.SingleFile, "bin", "capture", "throwable_resourceset_dictionary.bin"),
+ new(GameFile.ThrowableResource, ContainerType.SingleFile, "bin", "capture", "throwable_resource_dictionary.bin"),
+ new(GameFile.ThrowPermissionSet, ContainerType.SingleFile, "bin", "capture", "throw_permissionset_dictionary.bin"),
+ new(GameFile.HaShop, ContainerType.SingleFile, "bin", "appli", "shop", "bin", "ha_shop_data.bin"),
+
+ new(GameFile.ShinyRolls, ContainerType.SingleFile, "bin", "misc", "app_config", "pokemon_rare.bin"),
+ new(GameFile.WormholeConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "field_wormhole_config.bin"),
+ new(GameFile.CaptureConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "common_capture_config.bin"),
+ new(GameFile.BattleLogicConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "battle_logic_config.bin"),
+ new(GameFile.PlayerConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "player_config.bin"),
+ new(GameFile.EventFarmConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "event_farm_config.bin"),
+ new(GameFile.AppConfigList, ContainerType.SingleFile, "bin", "misc", "app_config", "app_config_list.bin"),
+ new(GameFile.FieldLandmarkConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "field_landmark_config.bin"),
+ new(GameFile.BattleViewConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "battle_view_config.bin"),
+ new(GameFile.AICommonConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "ai_common_config.bin"),
+ new(GameFile.FieldSpawnerConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "field_spawner_config.bin"),
+ new(GameFile.OutbreakConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "field_huge_outbreak.bin"),
+ new(GameFile.EvolutionConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "pokemon_evolution_config.bin"),
+ new(GameFile.BallThrowConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "field_my_poke_ball_config.bin"),
+ new(GameFile.SizeScaleConfig, ContainerType.SingleFile, "bin", "misc", "app_config", "pokemon_size_category_adjust_scale_config.bin"),
+ new(GameFile.SymbolBehave, ContainerType.SingleFile, "bin", "pokemon", "data", "poke_ai.bin"),
+
+ new(GameFile.NewHugeGroup, ContainerType.SingleFile, "bin", "field", "encount", "new_huge_outbreak_group.bin"),
+ new(GameFile.NewHugeGroupLottery, ContainerType.SingleFile, "bin", "field", "encount", "new_huge_outbreak_group_lottery.bin"),
+ new(GameFile.NewHugeLottery, ContainerType.SingleFile, "bin", "field", "encount", "new_huge_outbreak_lottery.bin"),
+ new(GameFile.NewHugeTimeLimit, ContainerType.SingleFile, "bin", "field", "encount", "new_huge_outbreak_time_limit.bin"),
+
+ // Cutscenes bin\demo
+ // Models bin\archive\pokemon
+ // pretty much everything is obviously named :)
+ };
+ #endregion
+}
\ No newline at end of file
diff --git a/pkNX.Game/File/GameFileReference.cs b/pkNX.Game/File/GameFileReference.cs
index ac0f6a50..5cc998cd 100644
--- a/pkNX.Game/File/GameFileReference.cs
+++ b/pkNX.Game/File/GameFileReference.cs
@@ -1,92 +1,91 @@
using System.IO;
using pkNX.Containers;
-namespace pkNX.Game
+namespace pkNX.Game;
+
+///
+/// File reference pointing to the location of the data.
+///
+public class GameFileReference
{
///
- /// File reference pointing to the location of the data.
+ /// Type of data the file contains.
///
- public class GameFileReference
+ public GameFile File { get; }
+
+ ///
+ /// Location of the file in the user's environment.
+ ///
+ public string RelativePath { get; }
+
+ ///
+ /// Type of container the data is within.
+ ///
+ public ContainerType Type { get; }
+
+ ///
+ /// Toggle to indicate that the data is localized and should be shifted.
+ ///
+ public bool LanguageVariant { get; }
+
+ public int Language { get; }
+
+ ///
+ /// Indicates the parent of the data.
+ ///
+ public ContainerParent Parent { get; set; }
+
+ internal GameFileReference(int FileNumber, GameFile ident, ContainerType t = ContainerType.GARC)
{
- ///
- /// Type of data the file contains.
- ///
- public GameFile File { get; }
+ File = ident;
+ Type = t;
- ///
- /// Location of the file in the user's environment.
- ///
- public string RelativePath { get; }
+ int A = FileNumber / 100 % 10;
+ int B = FileNumber / 10 % 10;
+ int C = FileNumber / 1 % 10;
+ RelativePath = Path.Combine("a", A.ToString(), B.ToString(), C.ToString());
+ }
- ///
- /// Type of container the data is within.
- ///
- public ContainerType Type { get; }
+ internal GameFileReference(string relPath, ContainerType t, GameFile ident, bool variant = false)
+ {
+ File = ident;
+ Type = t;
+ LanguageVariant = variant;
- ///
- /// Toggle to indicate that the data is localized and should be shifted.
- ///
- public bool LanguageVariant { get; }
+ RelativePath = relPath;
+ }
- public int Language { get; }
+ internal GameFileReference(GameFile ident, int lang, params string[] relPath)
+ {
+ File = ident;
+ Type = ContainerType.Folder;
+ LanguageVariant = true;
+ Language = lang;
- ///
- /// Indicates the parent of the data.
- ///
- public ContainerParent Parent { get; set; }
+ RelativePath = Path.Combine(relPath);
+ }
- internal GameFileReference(int FileNumber, GameFile ident, ContainerType t = ContainerType.GARC)
- {
- File = ident;
- Type = t;
+ internal GameFileReference(GameFile ident, params string[] relPath)
+ {
+ File = ident;
+ Type = ContainerType.Folder;
- int A = FileNumber / 100 % 10;
- int B = FileNumber / 10 % 10;
- int C = FileNumber / 1 % 10;
- RelativePath = Path.Combine("a", A.ToString(), B.ToString(), C.ToString());
- }
+ RelativePath = Path.Combine(relPath);
+ }
- internal GameFileReference(string relPath, ContainerType t, GameFile ident, bool variant = false)
- {
- File = ident;
- Type = t;
- LanguageVariant = variant;
+ internal GameFileReference(GameFile ident, ContainerType t, params string[] relPath)
+ {
+ File = ident;
+ Type = t;
- RelativePath = relPath;
- }
+ RelativePath = Path.Combine(relPath);
+ }
- internal GameFileReference(GameFile ident, int lang, params string[] relPath)
- {
- File = ident;
- Type = ContainerType.Folder;
- LanguageVariant = true;
- Language = lang;
-
- RelativePath = Path.Combine(relPath);
- }
-
- internal GameFileReference(GameFile ident, params string[] relPath)
- {
- File = ident;
- Type = ContainerType.Folder;
-
- RelativePath = Path.Combine(relPath);
- }
-
- internal GameFileReference(GameFile ident, ContainerType t, params string[] relPath)
- {
- File = ident;
- Type = t;
-
- RelativePath = Path.Combine(relPath);
- }
-
- public IFileContainer Get(string basePath)
- {
- var path = Path.Combine(basePath, RelativePath);
- var container = Container.GetContainer(path, Type);
- container.FilePath = path;
- return container;
- }
+ public IFileContainer Get(string basePath)
+ {
+ var path = Path.Combine(basePath, RelativePath);
+ var container = Container.GetContainer(path, Type);
+ container.FilePath = path;
+ return container;
}
}
\ No newline at end of file
diff --git a/pkNX.Game/GameLanguage.cs b/pkNX.Game/GameLanguage.cs
index 5cd955be..cc0eea9b 100644
--- a/pkNX.Game/GameLanguage.cs
+++ b/pkNX.Game/GameLanguage.cs
@@ -1,58 +1,57 @@
-namespace pkNX.Game
+namespace pkNX.Game;
+
+///
+/// Game Language IDs to index consecutive localized files
+///
+public enum GameLanguage
{
///
- /// Game Language IDs to index consecutive localized files
+ /// Japanese (katakana)
///
- public enum GameLanguage
- {
- ///
- /// Japanese (katakana)
- ///
- カタカナ = 0,
+ カタカナ = 0,
- ///
- /// Japanese (hiragana)
- ///
- 漢字 = 1,
+ ///
+ /// Japanese (hiragana)
+ ///
+ 漢字 = 1,
- ///
- /// English
- ///
- English = 2,
+ ///
+ /// English
+ ///
+ English = 2,
- ///
- /// French
- ///
- Français = 3,
+ ///
+ /// French
+ ///
+ Français = 3,
- ///
- /// Italian
- ///
- Italiano = 4,
+ ///
+ /// Italian
+ ///
+ Italiano = 4,
- ///
- /// German
- ///
- Deutsch = 5,
+ ///
+ /// German
+ ///
+ Deutsch = 5,
- ///
- /// Spanish
- ///
- Español = 6,
+ ///
+ /// Spanish
+ ///
+ Español = 6,
- ///
- /// Korean
- ///
- 한국 = 7,
+ ///
+ /// Korean
+ ///
+ 한국 = 7,
- ///
- /// Chinese (Simplified)
- ///
- 汉字简化方案 = 8,
+ ///
+ /// Chinese (Simplified)
+ ///
+ 汉字简化方案 = 8,
- ///
- /// Chinese (Traditional)
- ///
- 漢字簡化方案 = 9,
- }
-}
+ ///
+ /// Chinese (Traditional)
+ ///
+ 漢字簡化方案 = 9,
+}
\ No newline at end of file
diff --git a/pkNX.Game/GameLocation.cs b/pkNX.Game/GameLocation.cs
index 0d16c1d3..598558de 100644
--- a/pkNX.Game/GameLocation.cs
+++ b/pkNX.Game/GameLocation.cs
@@ -2,136 +2,135 @@
using System.IO;
using pkNX.Structures;
-namespace pkNX.Game
+namespace pkNX.Game;
+
+///
+/// Environment Data where the Game's Data is located on the user's machine.
+///
+public sealed class GameLocation
{
///
- /// Environment Data where the Game's Data is located on the user's machine.
+ /// Location of the RomFS
///
- public sealed class GameLocation
+ public string RomFS { get; }
+
+ ///
+ /// Location of the ExeFS.
+ ///
+ public string ExeFS { get; }
+
+ ///
+ /// Game version the files belong to.
+ ///
+ public GameVersion Game { get; }
+
+ private GameLocation(string romfs, string exefs, GameVersion game)
{
- ///
- /// Location of the RomFS
- ///
- public string RomFS { get; }
+ Game = game;
+ RomFS = romfs;
+ ExeFS = exefs;
+ }
- ///
- /// Location of the ExeFS.
- ///
- public string ExeFS { get; }
+ ///
+ /// Determines the of the input directory and detects the location of files for editing.
+ ///
+ /// Directory the game data is in
+ /// Detected version
+ /// New object with references to file paths.
+ public static GameLocation GetGame(string dir, GameVersion gameOverride = GameVersion.Any)
+ {
+ if (dir == null || !Directory.Exists(dir))
+ return null;
- ///
- /// Game version the files belong to.
- ///
- public GameVersion Game { get; }
+ var dirs = Directory.GetDirectories(dir);
+ var romfs = Array.Find(dirs, z => Path.GetFileName(z).StartsWith("rom", StringComparison.CurrentCultureIgnoreCase));
+ var exefs = Array.Find(dirs, z => Path.GetFileName(z).StartsWith("exe", StringComparison.CurrentCultureIgnoreCase));
- private GameLocation(string romfs, string exefs, GameVersion game)
+ if (romfs == null && exefs == null)
+ return null;
+
+ var game = gameOverride != GameVersion.Any ? gameOverride : GetGameFromPath(romfs, exefs);
+ if (game == GameVersion.Invalid)
+ return null;
+ return new GameLocation(romfs, exefs, game);
+ }
+
+ private static GameVersion GetGameFromPath(string romfs, string exefs)
+ {
+ var files = Directory.GetFiles(romfs, "*", SearchOption.AllDirectories);
+ return GetGameFromCount(files.Length, romfs, exefs);
+ }
+
+ private const int FILECOUNT_XY = 271;
+ private const int FILECOUNT_ORASDEMO = 301;
+ private const int FILECOUNT_ORAS = 299;
+ private const int FILECOUNT_SMDEMO = 239;
+ private const int FILECOUNT_SM = 311;
+ private const int FILECOUNT_USUM = 333;
+ private const int FILECOUNT_GG = 27818;
+ private const int FILECOUNT_SWSH = 41702;
+ private const int FILECOUNT_SWSH_110 = 41951; // Ver. 1.1.0 (Galarian Slowpoke)
+ private const int FILECOUNT_SWSH_120 = 46867; // Ver. 1.2.0 (Isle of Armor)
+ private const int FILECOUNT_SWSH_130 = 50494; // Ver. 1.3.0 (Crown Tundra)
+ private const int FILECOUNT_LA = 18_370;
+ private const int FILECOUNT_LA_101 = 18_371; // Ver. 1.0.1 (Day 1 Patch)
+ private const int FILECOUNT_LA_110 = 19_095; // Ver. 1.1.0 (Daybreak)
+
+ private static GameVersion GetGameFromCount(int fileCount, string romfs, string exefs)
+ {
+ string GetTitleID() => BitConverter.ToUInt64(File.ReadAllBytes(Path.Combine(exefs, "main.npdm")), 0x290).ToString("X16");
+
+ switch (fileCount)
{
- Game = game;
- RomFS = romfs;
- ExeFS = exefs;
- }
-
- ///
- /// Determines the of the input directory and detects the location of files for editing.
- ///
- /// Directory the game data is in
- /// Detected version
- /// New object with references to file paths.
- public static GameLocation GetGame(string dir, GameVersion gameOverride = GameVersion.Any)
- {
- if (dir == null || !Directory.Exists(dir))
- return null;
-
- var dirs = Directory.GetDirectories(dir);
- var romfs = Array.Find(dirs, z => Path.GetFileName(z).StartsWith("rom", StringComparison.CurrentCultureIgnoreCase));
- var exefs = Array.Find(dirs, z => Path.GetFileName(z).StartsWith("exe", StringComparison.CurrentCultureIgnoreCase));
-
- if (romfs == null && exefs == null)
- return null;
-
- var game = gameOverride != GameVersion.Any ? gameOverride : GetGameFromPath(romfs, exefs);
- if (game == GameVersion.Invalid)
- return null;
- return new GameLocation(romfs, exefs, game);
- }
-
- private static GameVersion GetGameFromPath(string romfs, string exefs)
- {
- var files = Directory.GetFiles(romfs, "*", SearchOption.AllDirectories);
- return GetGameFromCount(files.Length, romfs, exefs);
- }
-
- private const int FILECOUNT_XY = 271;
- private const int FILECOUNT_ORASDEMO = 301;
- private const int FILECOUNT_ORAS = 299;
- private const int FILECOUNT_SMDEMO = 239;
- private const int FILECOUNT_SM = 311;
- private const int FILECOUNT_USUM = 333;
- private const int FILECOUNT_GG = 27818;
- private const int FILECOUNT_SWSH = 41702;
- private const int FILECOUNT_SWSH_110 = 41951; // Ver. 1.1.0 (Galarian Slowpoke)
- private const int FILECOUNT_SWSH_120 = 46867; // Ver. 1.2.0 (Isle of Armor)
- private const int FILECOUNT_SWSH_130 = 50494; // Ver. 1.3.0 (Crown Tundra)
- private const int FILECOUNT_LA = 18_370;
- private const int FILECOUNT_LA_101 = 18_371; // Ver. 1.0.1 (Day 1 Patch)
- private const int FILECOUNT_LA_110 = 19_095; // Ver. 1.1.0 (Daybreak)
-
- private static GameVersion GetGameFromCount(int fileCount, string romfs, string exefs)
- {
- string GetTitleID() => BitConverter.ToUInt64(File.ReadAllBytes(Path.Combine(exefs, "main.npdm")), 0x290).ToString("X16");
-
- switch (fileCount)
+ case FILECOUNT_XY: return GameVersion.XY;
+ case FILECOUNT_ORASDEMO: return GameVersion.ORASDEMO;
+ case FILECOUNT_ORAS: return GameVersion.ORAS;
+ case FILECOUNT_SMDEMO: return GameVersion.SMDEMO;
+ case FILECOUNT_SM:
{
- case FILECOUNT_XY: return GameVersion.XY;
- case FILECOUNT_ORASDEMO: return GameVersion.ORASDEMO;
- case FILECOUNT_ORAS: return GameVersion.ORAS;
- case FILECOUNT_SMDEMO: return GameVersion.SMDEMO;
- case FILECOUNT_SM:
- {
- var encdata = Path.Combine(romfs, "a", "0", "8", "2");
- if (File.Exists(encdata) && new FileInfo(encdata).Length != 0)
- return GameVersion.SN;
- return GameVersion.MN;
- }
-
- case FILECOUNT_USUM:
- {
- var encdata = Path.Combine(romfs, "a", "0", "8", "2");
- if (File.Exists(encdata) && new FileInfo(encdata).Length != 0)
- return GameVersion.US;
- return GameVersion.UM;
- }
-
- case FILECOUNT_GG:
- {
- bool eevee = Directory.Exists(Path.Combine(romfs, "bin", "movies", "EEVEE_GO"));
- if (eevee)
- return GameVersion.GE;
- return GameVersion.GP;
- }
-
- case FILECOUNT_SWSH:
- case FILECOUNT_SWSH_110:
- case FILECOUNT_SWSH_120:
- case FILECOUNT_SWSH_130:
- {
- if (exefs == null)
- return GameVersion.SWSH;
-
- return GetTitleID() switch
- {
- "0100ABF008968000" => GameVersion.SW,
- "01008DB008C2C000" => GameVersion.SH,
- _ => GameVersion.SWSH, // can't figure out Title ID, default to SWSH so that wild editor prompts for version selection
- };
- }
-
- case FILECOUNT_LA or FILECOUNT_LA_101 or FILECOUNT_LA_110:
- return GameVersion.PLA;
-
- default:
- return GameVersion.Invalid;
+ var encdata = Path.Combine(romfs, "a", "0", "8", "2");
+ if (File.Exists(encdata) && new FileInfo(encdata).Length != 0)
+ return GameVersion.SN;
+ return GameVersion.MN;
}
+
+ case FILECOUNT_USUM:
+ {
+ var encdata = Path.Combine(romfs, "a", "0", "8", "2");
+ if (File.Exists(encdata) && new FileInfo(encdata).Length != 0)
+ return GameVersion.US;
+ return GameVersion.UM;
+ }
+
+ case FILECOUNT_GG:
+ {
+ bool eevee = Directory.Exists(Path.Combine(romfs, "bin", "movies", "EEVEE_GO"));
+ if (eevee)
+ return GameVersion.GE;
+ return GameVersion.GP;
+ }
+
+ case FILECOUNT_SWSH:
+ case FILECOUNT_SWSH_110:
+ case FILECOUNT_SWSH_120:
+ case FILECOUNT_SWSH_130:
+ {
+ if (exefs == null)
+ return GameVersion.SWSH;
+
+ return GetTitleID() switch
+ {
+ "0100ABF008968000" => GameVersion.SW,
+ "01008DB008C2C000" => GameVersion.SH,
+ _ => GameVersion.SWSH, // can't figure out Title ID, default to SWSH so that wild editor prompts for version selection
+ };
+ }
+
+ case FILECOUNT_LA or FILECOUNT_LA_101 or FILECOUNT_LA_110:
+ return GameVersion.PLA;
+
+ default:
+ return GameVersion.Invalid;
}
}
-}
+}
\ No newline at end of file
diff --git a/pkNX.Game/GameManager.cs b/pkNX.Game/GameManager.cs
index 84f9044d..ebf4e5ca 100644
--- a/pkNX.Game/GameManager.cs
+++ b/pkNX.Game/GameManager.cs
@@ -2,119 +2,118 @@
using pkNX.Containers;
using pkNX.Structures;
-namespace pkNX.Game
+namespace pkNX.Game;
+
+///
+/// Manages fetching of game data.
+///
+public abstract class GameManager
{
+ protected readonly GameLocation ROM;
+ protected readonly TextManager Text; // GameText
+ protected readonly GameFileMapping FileMap;
+ public readonly GameInfo Info;
+ private int _language;
+
+ public string PathExeFS => ROM.ExeFS;
+ public string PathRomFS => ROM.RomFS;
+
///
- /// Manages fetching of game data.
+ /// Language to use when fetching string & graphic assets.
///
- public abstract class GameManager
+ public int Language
{
- protected readonly GameLocation ROM;
- protected readonly TextManager Text; // GameText
- protected readonly GameFileMapping FileMap;
- public readonly GameInfo Info;
- private int _language;
-
- public string PathExeFS => ROM.ExeFS;
- public string PathRomFS => ROM.RomFS;
-
- ///
- /// Language to use when fetching string & graphic assets.
- ///
- public int Language
+ get => _language;
+ set
{
- get => _language;
- set
- {
- if (value == _language)
- return;
- _language = value;
- Text?.ClearCache();
- }
- }
-
- ///
- /// Current the data represents.
- ///
- public GameVersion Game => ROM.Game;
-
- ///
- /// Initializes a new for the input with initial .
- ///
- ///
- ///
- protected GameManager(GameLocation rom, int language)
- {
- ROM = rom;
- Language = language;
- FileMap = new GameFileMapping(rom);
- Text = new TextManager(Game);
- Info = new GameInfo(Game);
- }
-
- ///
- /// Fetches a from the Game data.
- ///
- /// File type to fetch
- /// Container that contains the game data requested.
- /// Sugar for the other method.
- public IFileContainer this[GameFile file] => GetFile(file);
-
- ///
- /// Fetches a from the Game data.
- ///
- /// File type to fetch
- /// Container that contains the game data requested.
- public IFileContainer GetFile(GameFile file) => FileMap.GetFile(file, Language);
-
- ///
- /// Fetches strings for the input .
- ///
- /// Text file to fetch
- /// Array of strings from the requested text file.
- public string[] GetStrings(TextName text)
- {
- var arc = this[GameFile.GameText];
- var lines = Text.GetStrings(text, arc);
- return lines;
- }
-
- ///
- /// Saves all open files and finalizes the ROM data.
- ///
- /// Skip re-initialization of game data.
- public void SaveAll(bool closing)
- {
- Terminate();
- FileMap.SaveAll();
- if (!closing)
- Initialize();
- }
-
- public virtual void Initialize()
- {
- SetMitm();
- }
-
- protected abstract void Terminate();
- protected abstract void SetMitm();
-
- public FolderContainer GetFilteredFolder(GameFile type, Func filter = null)
- {
- var c = (FolderContainer)this[type];
- c.Initialize(filter);
- return c;
- }
-
- public static GameManager GetManager(GameLocation loc, int language)
- {
- return loc.Game switch
- {
- GameVersion.GP or GameVersion.GE or GameVersion.GG => new GameManagerGG(loc, language),
- GameVersion.SW or GameVersion.SH or GameVersion.SWSH => new GameManagerSWSH(loc, language),
- GameVersion.PLA => new GameManagerPLA(loc, language),
- _ => throw new ArgumentException(nameof(loc.Game))
- };
+ if (value == _language)
+ return;
+ _language = value;
+ Text?.ClearCache();
}
}
-}
+
+ ///
+ /// Current the data represents.
+ ///
+ public GameVersion Game => ROM.Game;
+
+ ///
+ /// Initializes a new for the input with initial .
+ ///
+ ///
+ ///
+ protected GameManager(GameLocation rom, int language)
+ {
+ ROM = rom;
+ Language = language;
+ FileMap = new GameFileMapping(rom);
+ Text = new TextManager(Game);
+ Info = new GameInfo(Game);
+ }
+
+ ///
+ /// Fetches a from the Game data.
+ ///
+ /// File type to fetch
+ /// Container that contains the game data requested.
+ /// Sugar for the other method.
+ public IFileContainer this[GameFile file] => GetFile(file);
+
+ ///
+ /// Fetches a from the Game data.
+ ///
+ /// File type to fetch
+ /// Container that contains the game data requested.
+ public IFileContainer GetFile(GameFile file) => FileMap.GetFile(file, Language);
+
+ ///
+ /// Fetches strings for the input .
+ ///
+ /// Text file to fetch
+ /// Array of strings from the requested text file.
+ public string[] GetStrings(TextName text)
+ {
+ var arc = this[GameFile.GameText];
+ var lines = Text.GetStrings(text, arc);
+ return lines;
+ }
+
+ ///
+ /// Saves all open files and finalizes the ROM data.
+ ///
+ /// Skip re-initialization of game data.
+ public void SaveAll(bool closing)
+ {
+ Terminate();
+ FileMap.SaveAll();
+ if (!closing)
+ Initialize();
+ }
+
+ public virtual void Initialize()
+ {
+ SetMitm();
+ }
+
+ protected abstract void Terminate();
+ protected abstract void SetMitm();
+
+ public FolderContainer GetFilteredFolder(GameFile type, Func filter = null)
+ {
+ var c = (FolderContainer)this[type];
+ c.Initialize(filter);
+ return c;
+ }
+
+ public static GameManager GetManager(GameLocation loc, int language)
+ {
+ return loc.Game switch
+ {
+ GameVersion.GP or GameVersion.GE or GameVersion.GG => new GameManagerGG(loc, language),
+ GameVersion.SW or GameVersion.SH or GameVersion.SWSH => new GameManagerSWSH(loc, language),
+ GameVersion.PLA => new GameManagerPLA(loc, language),
+ _ => throw new ArgumentException(nameof(loc.Game))
+ };
+ }
+}
\ No newline at end of file
diff --git a/pkNX.Game/GameManagerGG.cs b/pkNX.Game/GameManagerGG.cs
index e9a27afa..fbc80178 100644
--- a/pkNX.Game/GameManagerGG.cs
+++ b/pkNX.Game/GameManagerGG.cs
@@ -3,73 +3,72 @@
using pkNX.Containers;
using pkNX.Structures;
-namespace pkNX.Game
+namespace pkNX.Game;
+
+public class GameManagerGG : GameManager
{
- public class GameManagerGG : GameManager
+ public GameManagerGG(GameLocation rom, int language) : base(rom, language) { }
+ private GameVersion ActualGame;
+ private string TitleID => ActualGame == GameVersion.GP ? Pikachu : Eevee;
+ private const string Pikachu = "010003F003A34000";
+ private const string Eevee = "0100187003A36000";
+
+ ///
+ /// Generally useful game data that can be used by multiple editors.
+ ///
+ public GameData Data { get; protected set; }
+
+ protected override void SetMitm()
{
- public GameManagerGG(GameLocation rom, int language) : base(rom, language) { }
- private GameVersion ActualGame;
- private string TitleID => ActualGame == GameVersion.GP ? Pikachu : Eevee;
- private const string Pikachu = "010003F003A34000";
- private const string Eevee = "0100187003A36000";
+ var basePath = Path.GetDirectoryName(ROM.RomFS);
+ // unlike SWSH, LGPE has a unique opening movie in romfs to differentiate between versions
+ bool eevee = Directory.Exists(Path.Combine(PathRomFS, "bin", "movies", "EEVEE_GO"));
+ ActualGame = eevee ? GameVersion.GE : GameVersion.GP;
+ var redirect = Path.Combine(basePath, TitleID);
+ FileMitm.SetRedirect(basePath, redirect);
+ }
- ///
- /// Generally useful game data that can be used by multiple editors.
- ///
- public GameData Data { get; protected set; }
+ public override void Initialize()
+ {
+ base.Initialize();
- protected override void SetMitm()
+ // initialize gametext
+ GetFilteredFolder(GameFile.GameText, z => Path.GetExtension(z) == ".dat");
+
+ // initialize common structures
+ var personal = GetFilteredFolder(GameFile.PersonalStats, z => Path.GetFileNameWithoutExtension(z) == "personal_total");
+ Data = new GameData
{
- var basePath = Path.GetDirectoryName(ROM.RomFS);
- // unlike SWSH, LGPE has a unique opening movie in romfs to differentiate between versions
- bool eevee = Directory.Exists(Path.Combine(PathRomFS, "bin", "movies", "EEVEE_GO"));
- ActualGame = eevee ? GameVersion.GE : GameVersion.GP;
- var redirect = Path.Combine(basePath, TitleID);
- FileMitm.SetRedirect(basePath, redirect);
- }
-
- public override void Initialize()
- {
- base.Initialize();
-
- // initialize gametext
- GetFilteredFolder(GameFile.GameText, z => Path.GetExtension(z) == ".dat");
-
- // initialize common structures
- var personal = GetFilteredFolder(GameFile.PersonalStats, z => Path.GetFileNameWithoutExtension(z) == "personal_total");
- Data = new GameData
+ MoveData = new DataCache(this[GameFile.MoveStats]) // mini
{
- MoveData = new DataCache(this[GameFile.MoveStats]) // mini
- {
- Create = z => new Move7(z),
- Write = z => z.Write(),
- },
- LevelUpData = new DataCache(this[GameFile.Learnsets]) // gfpak
- {
- Create = z => new Learnset6(z),
- Write = z => z.Write(),
- },
+ Create = z => new Move7(z),
+ Write = z => z.Write(),
+ },
+ LevelUpData = new DataCache(this[GameFile.Learnsets]) // gfpak
+ {
+ Create = z => new Learnset6(z),
+ Write = z => z.Write(),
+ },
- // folders;
- PersonalData = new PersonalTable7GG(personal[0]),
- MegaEvolutionData = new DataCache(GetFilteredFolder(GameFile.MegaEvolutions))
- {
- Create = MegaEvolutionSet.ReadArray,
- Write = MegaEvolutionSet.WriteArray,
- },
- EvolutionData = new DataCache(GetFilteredFolder(GameFile.Evolutions))
- {
- Create = data => new EvolutionSet7(data),
- Write = evo => evo.Write(),
- },
- };
- }
+ // folders;
+ PersonalData = new PersonalTable7GG(personal[0]),
+ MegaEvolutionData = new DataCache(GetFilteredFolder(GameFile.MegaEvolutions))
+ {
+ Create = MegaEvolutionSet.ReadArray,
+ Write = MegaEvolutionSet.WriteArray,
+ },
+ EvolutionData = new DataCache(GetFilteredFolder(GameFile.Evolutions))
+ {
+ Create = data => new EvolutionSet7(data),
+ Write = evo => evo.Write(),
+ },
+ };
+ }
- protected override void Terminate()
- {
- // Store Personal Data back in the file. Let the container detect if it is modified.
- var personal = this[GameFile.PersonalStats];
- personal[0] = Data.PersonalData.Table.SelectMany(z => ((IPersonalInfoBin)z).Write()).ToArray();
- }
+ protected override void Terminate()
+ {
+ // Store Personal Data back in the file. Let the container detect if it is modified.
+ var personal = this[GameFile.PersonalStats];
+ personal[0] = Data.PersonalData.Table.SelectMany(z => ((IPersonalInfoBin)z).Write()).ToArray();
}
}
\ No newline at end of file
diff --git a/pkNX.Game/GameManagerPLA.cs b/pkNX.Game/GameManagerPLA.cs
index 59f2ad07..ef279c9d 100644
--- a/pkNX.Game/GameManagerPLA.cs
+++ b/pkNX.Game/GameManagerPLA.cs
@@ -5,81 +5,80 @@
using pkNX.Structures;
using pkNX.Structures.FlatBuffers;
-namespace pkNX.Game
+namespace pkNX.Game;
+
+public class GameManagerPLA : GameManager
{
- public class GameManagerPLA : GameManager
+ public GameManagerPLA(GameLocation rom, int language) : base(rom, language) { }
+ private string PathNPDM => Path.Combine(PathExeFS, "main.npdm");
+ private string TitleID => BitConverter.ToUInt64(File.ReadAllBytes(PathNPDM), 0x470).ToString("X16");
+
+ ///
+ /// Generally useful game data that can be used by multiple editors.
+ ///
+ public GameData8a Data { get; protected set; }
+
+ protected override void SetMitm()
{
- public GameManagerPLA(GameLocation rom, int language) : base(rom, language) { }
- private string PathNPDM => Path.Combine(PathExeFS, "main.npdm");
- private string TitleID => BitConverter.ToUInt64(File.ReadAllBytes(PathNPDM), 0x470).ToString("X16");
-
- ///
- /// Generally useful game data that can be used by multiple editors.
- ///
- public GameData8a Data { get; protected set; }
-
- protected override void SetMitm()
- {
- var basePath = Path.GetDirectoryName(ROM.RomFS);
- var tid = ROM.ExeFS != null ? TitleID : "arceus";
- var redirect = Path.Combine(basePath, tid);
- FileMitm.SetRedirect(basePath, redirect);
- }
-
- public override void Initialize()
- {
- base.Initialize();
-
- // initialize gametext
- ResetText();
-
- // initialize common structures
- ResetData();
-
- ItemConverter.ItemNames = GetStrings(TextName.ItemNames);
- }
-
- private void ResetData()
- {
- Data = new GameData8a
- {
- // Folders
- MoveData = GetMoves(),
-
- // Single Files
- PersonalData = new PersonalTable8LA(GetFile(GameFile.PersonalStats)),
- PokeMiscData = new(GetFile(GameFile.PokeMisc)),
- LevelUpData = new(GetFile(GameFile.Learnsets)),
- EvolutionData = new(GetFile(GameFile.Evolutions)),
-
- FieldDrops = new(GetFile(GameFile.FieldDrops)),
- BattleDrops = new(GetFile(GameFile.BattleDrops)),
- DexResearch = new(GetFile(GameFile.DexResearch)),
- };
-
- DropTableConverter.DropTableHashes = Data.FieldDrops.Table.Select(x => x.Hash).ToArray();
- }
-
- private DataCache GetMoves()
- {
- var move = this[GameFile.MoveStats];
- ((FolderContainer)move).Initialize();
- return new DataCache(move)
- {
- Create = FlatBufferConverter.DeserializeFrom,
- Write = FlatBufferConverter.SerializeFrom,
- };
- }
-
- public void ResetMoves() => Data.MoveData.ClearAll();
-
- public void ResetText()
- {
- GetFilteredFolder(GameFile.GameText, z => Path.GetExtension(z) == ".dat");
- }
-
- protected override void Terminate()
- {
- }
+ var basePath = Path.GetDirectoryName(ROM.RomFS);
+ var tid = ROM.ExeFS != null ? TitleID : "arceus";
+ var redirect = Path.Combine(basePath, tid);
+ FileMitm.SetRedirect(basePath, redirect);
}
-}
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ // initialize gametext
+ ResetText();
+
+ // initialize common structures
+ ResetData();
+
+ ItemConverter.ItemNames = GetStrings(TextName.ItemNames);
+ }
+
+ private void ResetData()
+ {
+ Data = new GameData8a
+ {
+ // Folders
+ MoveData = GetMoves(),
+
+ // Single Files
+ PersonalData = new PersonalTable8LA(GetFile(GameFile.PersonalStats)),
+ PokeMiscData = new(GetFile(GameFile.PokeMisc)),
+ LevelUpData = new(GetFile(GameFile.Learnsets)),
+ EvolutionData = new(GetFile(GameFile.Evolutions)),
+
+ FieldDrops = new(GetFile(GameFile.FieldDrops)),
+ BattleDrops = new(GetFile(GameFile.BattleDrops)),
+ DexResearch = new(GetFile(GameFile.DexResearch)),
+ };
+
+ DropTableConverter.DropTableHashes = Data.FieldDrops.Table.Select(x => x.Hash).ToArray();
+ }
+
+ private DataCache GetMoves()
+ {
+ var move = this[GameFile.MoveStats];
+ ((FolderContainer)move).Initialize();
+ return new DataCache(move)
+ {
+ Create = FlatBufferConverter.DeserializeFrom,
+ Write = FlatBufferConverter.SerializeFrom,
+ };
+ }
+
+ public void ResetMoves() => Data.MoveData.ClearAll();
+
+ public void ResetText()
+ {
+ GetFilteredFolder(GameFile.GameText, z => Path.GetExtension(z) == ".dat");
+ }
+
+ protected override void Terminate()
+ {
+ }
+}
\ No newline at end of file
diff --git a/pkNX.Game/GameManagerSWSH.cs b/pkNX.Game/GameManagerSWSH.cs
index d6fcf1b7..2ea6764d 100644
--- a/pkNX.Game/GameManagerSWSH.cs
+++ b/pkNX.Game/GameManagerSWSH.cs
@@ -5,81 +5,80 @@
using pkNX.Structures;
using pkNX.Structures.FlatBuffers;
-namespace pkNX.Game
+namespace pkNX.Game;
+
+public class GameManagerSWSH : GameManager
{
- public class GameManagerSWSH : GameManager
+ public GameManagerSWSH(GameLocation rom, int language) : base(rom, language) { }
+ private string npdmPath => Path.Combine(PathExeFS, "main.npdm");
+ private string TitleID => BitConverter.ToUInt64(File.ReadAllBytes(npdmPath), 0x290).ToString("X16");
+
+ ///
+ /// Generally useful game data that can be used by multiple editors.
+ ///
+ public GameData Data { get; protected set; }
+
+ protected override void SetMitm()
{
- public GameManagerSWSH(GameLocation rom, int language) : base(rom, language) { }
- private string npdmPath => Path.Combine(PathExeFS, "main.npdm");
- private string TitleID => BitConverter.ToUInt64(File.ReadAllBytes(npdmPath), 0x290).ToString("X16");
+ var basePath = Path.GetDirectoryName(ROM.RomFS);
+ var tid = ROM.ExeFS != null ? TitleID : "0100ABF008968000"; // no way to differentiate without exefs, so default to Sword
+ var redirect = Path.Combine(basePath, tid);
+ FileMitm.SetRedirect(basePath, redirect);
+ }
- ///
- /// Generally useful game data that can be used by multiple editors.
- ///
- public GameData Data { get; protected set; }
+ private FakeContainer Learn;
- protected override void SetMitm()
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ // initialize gametext
+ ResetText();
+
+ // initialize common structures
+ var personal = GetFilteredFolder(GameFile.PersonalStats, z => Path.GetFileNameWithoutExtension(z) == "personal_total");
+ var learn = this[GameFile.Learnsets][0];
+ var splitLearn = learn.Split(0x104);
+ Learn = new FakeContainer(splitLearn);
+
+ var move = this[GameFile.MoveStats];
+ ((FolderContainer)move).Initialize();
+ Data = new GameData
{
- var basePath = Path.GetDirectoryName(ROM.RomFS);
- var tid = ROM.ExeFS != null ? TitleID : "0100ABF008968000"; // no way to differentiate without exefs, so default to Sword
- var redirect = Path.Combine(basePath, tid);
- FileMitm.SetRedirect(basePath, redirect);
- }
-
- private FakeContainer Learn;
-
- public override void Initialize()
- {
- base.Initialize();
-
- // initialize gametext
- ResetText();
-
- // initialize common structures
- var personal = GetFilteredFolder(GameFile.PersonalStats, z => Path.GetFileNameWithoutExtension(z) == "personal_total");
- var learn = this[GameFile.Learnsets][0];
- var splitLearn = learn.Split(0x104);
- Learn = new FakeContainer(splitLearn);
-
- var move = this[GameFile.MoveStats];
- ((FolderContainer)move).Initialize();
- Data = new GameData
+ MoveData = new DataCache(move)
{
- MoveData = new DataCache(move)
- {
- Create = FlatBufferConverter.DeserializeFrom,
- Write = z => FlatBufferConverter.SerializeFrom((Waza8)z),
- },
- LevelUpData = new DataCache(Learn)
- {
- Create = z => new Learnset8(z),
- Write = z => z.Write(),
- },
+ Create = FlatBufferConverter.DeserializeFrom,
+ Write = z => FlatBufferConverter.SerializeFrom((Waza8)z),
+ },
+ LevelUpData = new DataCache(Learn)
+ {
+ Create = z => new Learnset8(z),
+ Write = z => z.Write(),
+ },
- // folders
- PersonalData = new PersonalTable8SWSH(personal[0]),
- EvolutionData = new DataCache(GetFilteredFolder(GameFile.Evolutions))
- {
- Create = data => new EvolutionSet8(data),
- Write = evo => evo.Write(),
- },
- };
- }
+ // folders
+ PersonalData = new PersonalTable8SWSH(personal[0]),
+ EvolutionData = new DataCache(GetFilteredFolder(GameFile.Evolutions))
+ {
+ Create = data => new EvolutionSet8(data),
+ Write = evo => evo.Write(),
+ },
+ };
+ }
- public void ResetMoves() => GetFilteredFolder(GameFile.MoveStats);
+ public void ResetMoves() => GetFilteredFolder(GameFile.MoveStats);
- public void ResetText()
- {
- GetFilteredFolder(GameFile.GameText, z => Path.GetExtension(z) == ".dat");
- }
+ public void ResetText()
+ {
+ GetFilteredFolder(GameFile.GameText, z => Path.GetExtension(z) == ".dat");
+ }
- protected override void Terminate()
- {
- // Store Personal Data back in the file. Let the container detect if it is modified.
- var personal = this[GameFile.PersonalStats];
- personal[0] = Data.PersonalData.Table.SelectMany(z => ((IPersonalInfoBin)z).Write()).ToArray();
- var learn = this[GameFile.Learnsets];
- learn[0] = Learn.Files.SelectMany(z => z).ToArray();
- }
+ protected override void Terminate()
+ {
+ // Store Personal Data back in the file. Let the container detect if it is modified.
+ var personal = this[GameFile.PersonalStats];
+ personal[0] = Data.PersonalData.Table.SelectMany(z => ((IPersonalInfoBin)z).Write()).ToArray();
+ var learn = this[GameFile.Learnsets];
+ learn[0] = Learn.Files.SelectMany(z => z).ToArray();
}
}
\ No newline at end of file
diff --git a/pkNX.Game/Misc/SWSHInfo.cs b/pkNX.Game/Misc/SWSHInfo.cs
index 1897e9d3..182cd02a 100644
--- a/pkNX.Game/Misc/SWSHInfo.cs
+++ b/pkNX.Game/Misc/SWSHInfo.cs
@@ -1,631 +1,630 @@
using System.Collections.Generic;
using static pkNX.Game.SWSHSlotType;
-namespace pkNX.Game
+namespace pkNX.Game;
+
+public static class SWSHInfo
{
- public static class SWSHInfo
+ public static readonly IReadOnlyDictionary ZoneLocations = new Dictionary
{
- public static readonly IReadOnlyDictionary ZoneLocations = new Dictionary
- {
- {0x078BC1FF1A657844, 012}, // on Route 1
- {0x10355EFF1F4DB0B5, 018}, // on Route 2
- {0x776776717EA4483E, 122}, // in the Rolling Fields (in a Wild Area)
- {0x776777717EA449F1, 124}, // in the Dappled Grove (in a Wild Area)
- {0x776778717EA44BA4, 126}, // at Watchtower Ruins (in a Wild Area)
- {0x776779717EA44D57, 128}, // at East Lake Axewell (in a Wild Area)
- {0x77677A717EA44F0A, 130}, // at West Lake Axewell (in a Wild Area)
- {0x77677B717EA450BD, 132}, // on Axew's Eye (in a Wild Area)
- {0x77676C717EA43740, 134}, // at South Lake Miloch (in a Wild Area)
- {0x77676D717EA438F3, 136}, // near the Giant's Seat (in a Wild Area)
- {0x776AFA717EA75E61, 138}, // at North Lake Miloch (in a Wild Area)
- {0x194B97FF2492111A, 028}, // on Route 3
- {0x776E81717EAA799D, 140}, // at the Motostoke Riverbank (in a Wild Area)
- {0x776E7E717EAA7484, 142}, // in Bridge Field (in a Wild Area)
- {0xDBCF5CFF0180B073, 032}, // on Route 4
- {0x8F67CD45F405D66E, 008}, // in the Slumbering Weald
- {0xE0D6E5E78C91F4A7, 020}, // in the city of Motostoke
- {0xE4E595FF06C510D8, 040}, // on Route 5
- {0x1C7150C0594994E5, 044}, // in the town of Hulbury
- {0x7D3B7A45E97D4A51, 054}, // in Galar Mine No. 2
- {0x75D83E45E5AA7953, 030}, // in Galar Mine
- {0x7D3B7745E97D4538, 052}, // in the Motostoke Outskirts
- {0xA88AC04602050B95, 076}, // in Glimwood Tangle
- {0xEDFC32FF0C0A1B29, 068}, // on Route 6
- {0xF55F6BFF0FDCE70E, 084}, // on Route 7
- {0x449AE0FF3D19D777, 086}, // on Route 8
- {0x4BFDF9FF40EC6CFC, 088}, // on Route 8 (on Steamdrift Way)
- {0x4BFDFCFF40EC7215, 090}, // on Route 9
- {0x4BFDF6FF40EC67E3, 092}, // on Route 9 (in Circhester Bay)
- {0x4BFDFBFF40EC7062, 094}, // on Route 9 (in Outer Spikemuth)
- {0xB332930807F9D48A, 106}, // on Route 10 // Near Station
- {0x7771E5717EAD5960, 144}, // in the Stony Wilderness (in a Wild Area)
- {0x7771E8717EAD5E79, 146}, // in Dusty Bowl (in a Wild Area)
- {0x7771E7717EAD5CC6, 148}, // around the Giant's Mirror (in a Wild Area)
- {0x7771EA717EAD61DF, 150}, // on the Hammerlocke Hills (in a Wild Area)
- {0x7771E9717EAD602C, 152}, // near the Giant's Cap (in a Wild Area)
- {0x7771EC717EAD6545, 154}, // at the Lake of Outrage (in a Wild Area)
- {0x10355BFF1F4DAB9C, 018}, // on Route 2
- {0xB332920807F9D2D7, 106}, // on Route 10
- {0x8F67CB45F405D308, 008}, // in the Slumbering Weald
+ {0x078BC1FF1A657844, 012}, // on Route 1
+ {0x10355EFF1F4DB0B5, 018}, // on Route 2
+ {0x776776717EA4483E, 122}, // in the Rolling Fields (in a Wild Area)
+ {0x776777717EA449F1, 124}, // in the Dappled Grove (in a Wild Area)
+ {0x776778717EA44BA4, 126}, // at Watchtower Ruins (in a Wild Area)
+ {0x776779717EA44D57, 128}, // at East Lake Axewell (in a Wild Area)
+ {0x77677A717EA44F0A, 130}, // at West Lake Axewell (in a Wild Area)
+ {0x77677B717EA450BD, 132}, // on Axew's Eye (in a Wild Area)
+ {0x77676C717EA43740, 134}, // at South Lake Miloch (in a Wild Area)
+ {0x77676D717EA438F3, 136}, // near the Giant's Seat (in a Wild Area)
+ {0x776AFA717EA75E61, 138}, // at North Lake Miloch (in a Wild Area)
+ {0x194B97FF2492111A, 028}, // on Route 3
+ {0x776E81717EAA799D, 140}, // at the Motostoke Riverbank (in a Wild Area)
+ {0x776E7E717EAA7484, 142}, // in Bridge Field (in a Wild Area)
+ {0xDBCF5CFF0180B073, 032}, // on Route 4
+ {0x8F67CD45F405D66E, 008}, // in the Slumbering Weald
+ {0xE0D6E5E78C91F4A7, 020}, // in the city of Motostoke
+ {0xE4E595FF06C510D8, 040}, // on Route 5
+ {0x1C7150C0594994E5, 044}, // in the town of Hulbury
+ {0x7D3B7A45E97D4A51, 054}, // in Galar Mine No. 2
+ {0x75D83E45E5AA7953, 030}, // in Galar Mine
+ {0x7D3B7745E97D4538, 052}, // in the Motostoke Outskirts
+ {0xA88AC04602050B95, 076}, // in Glimwood Tangle
+ {0xEDFC32FF0C0A1B29, 068}, // on Route 6
+ {0xF55F6BFF0FDCE70E, 084}, // on Route 7
+ {0x449AE0FF3D19D777, 086}, // on Route 8
+ {0x4BFDF9FF40EC6CFC, 088}, // on Route 8 (on Steamdrift Way)
+ {0x4BFDFCFF40EC7215, 090}, // on Route 9
+ {0x4BFDF6FF40EC67E3, 092}, // on Route 9 (in Circhester Bay)
+ {0x4BFDFBFF40EC7062, 094}, // on Route 9 (in Outer Spikemuth)
+ {0xB332930807F9D48A, 106}, // on Route 10 // Near Station
+ {0x7771E5717EAD5960, 144}, // in the Stony Wilderness (in a Wild Area)
+ {0x7771E8717EAD5E79, 146}, // in Dusty Bowl (in a Wild Area)
+ {0x7771E7717EAD5CC6, 148}, // around the Giant's Mirror (in a Wild Area)
+ {0x7771EA717EAD61DF, 150}, // on the Hammerlocke Hills (in a Wild Area)
+ {0x7771E9717EAD602C, 152}, // near the Giant's Cap (in a Wild Area)
+ {0x7771EC717EAD6545, 154}, // at the Lake of Outrage (in a Wild Area)
+ {0x10355BFF1F4DAB9C, 018}, // on Route 2
+ {0xB332920807F9D2D7, 106}, // on Route 10
+ {0x8F67CB45F405D308, 008}, // in the Slumbering Weald
- {0xCD6E4FBCE1466F32, 012}, // on Route 1
- {0xDF686EC613544BD1, 018}, // on Route 2
- {0xD602B2A66C268F7C, 122}, // in the Rolling Fields (in a Wild Area)
- {0x458C9CA2C0087385, 124}, // in the Dappled Grove (in a Wild Area)
- {0xE20E6AE30AAA57D2, 126}, // at Watchtower Ruins (in a Wild Area)
- {0xEEEEAC06BAC8D0B3, 128}, // at East Lake Axewell (in a Wild Area)
- {0xF8D1E527F7B21FA0, 130}, // at West Lake Axewell (in a Wild Area)
- {0xB6CFE90E0378FD79, 132}, // on Axew's Eye (in a Wild Area)
- {0x520D8DD522E9A4C6, 134}, // at South Lake Miloch (in a Wild Area)
- {0xBC7237A0392D8837, 136}, // near the Giant's Seat (in a Wild Area)
- {0xB67C706F5BAE9E35, 138}, // at North Lake Miloch (in a Wild Area)
- {0xDA910F69A1B92FED, 130}, // at West Lake Axewell (in a Wild Area) // Surfing
- {0x7C17DB1B430F9543, 134}, // at South Lake Miloch (in a Wild Area) // Surfing
- {0xCC0F8A437312B8AC, 128}, // at East Lake Axewell (in a Wild Area) // Surfing
- {0x8BE2F6160986FB8E, 138}, // at North Lake Miloch (in a Wild Area) // Surfing
- {0x0E8392C0A57D5830, 028}, // on Route 3
- {0x82A7A328A26B9057, 030}, // in Galar Mine
- {0x5B2BC38E044EC2B7, 032}, // on Route 4
- {0x8D68276C03A332BE, 040}, // on Route 5
- {0x16D2FC4840A658A5, 054}, // in Galar Mine No. 2
- {0x3D6D58A96894575E, 052}, // in the Motostoke Outskirts
- {0x6AA652641154B119, 140}, // at the Motostoke Riverbank (in a Wild Area)
- {0x36A5DC94335E1E72, 142}, // in Bridge Field (in a Wild Area)
- {0xE503416A1C05765D, 068}, // on Route 6
- {0x201EF8E9D2A32D71, 076}, // in Glimwood Tangle
- {0x42312695C904658C, 084}, // on Route 7
- {0x1B95A78295F6F213, 086}, // on Route 8
- {0xAADAC3CB6A1DFE8A, 088}, // on Route 8 (on Steamdrift Way)
- {0x9116B224702CDCF1, 090}, // on Route 9
- {0xCDD3B5660D2E5E67, 092}, // on Route 9 (in Circhester Bay)
- {0x5A3B8F8147272058, 094}, // on Route 9 (in Outer Spikemuth)
- {0xA93101EA38598995, 090}, // on Route 9 // Surfing
- {0x0181225223DE5420, 106}, // on Route 10 // Near Station
- {0x1F0F1AE1818C4326, 144}, // in the Stony Wilderness (in a Wild Area)
- {0xAD11B3F3B2AC662D, 146}, // in Dusty Bowl (in a Wild Area)
- {0xCD9719B2E64F2AA4, 148}, // around the Giant's Mirror (in a Wild Area)
- {0xCD48625EDC10CBFB, 150}, // on the Hammerlocke Hills (in a Wild Area)
- {0x712F3056573E23FA, 152}, // near the Giant's Cap (in a Wild Area)
- {0x593196758BA16B61, 154}, // at the Lake of Outrage (in a Wild Area)
- {0xF79DE930E6F50533, 106}, // on Route 10
- {0xA26A4595F72EDAEA, 018}, // on Route 2 // high level
- {0x56580C94EDFCE664, 028}, // on Route 3 // just rolycoly and trubbish, probably trash
- {0xCB38FEA3F71C3958, 122}, // in the Rolling Fields (in a Wild Area) // Flying Spawns butterfree/pidove
- {0x1F174D36062B8C38, 122}, // in the Rolling Fields (in a Wild Area) // Underground Spawns digglet/roggenrola
- {0x23017513039A78E7, 122}, // in the Rolling Fields (in a Wild Area) // ? Second full table, has pancham instead of bunnelby
- {0xF1BA4AAD9AAB2C1A, 126}, // at Watchtower Ruins (in a Wild Area) // Flying Spawns woobat/noibat
- {0x3D2E746F9D3F5CB5, 128}, // at East Lake Axewell (in a Wild Area) // Flying Spawns bufferfree/pidove
- {0x6E121A9CE4F58F1E, 128}, // at East Lake Axewell (in a Wild Area) // More Flying Spawns bufferfree/pidove, different rates than above
- {0x3171A0C61793816E, 134}, // at South Lake Miloch (in a Wild Area) // Flying Spawns wingull/drifloon
- {0x198E4023A1B2DDEF, 134}, // at South Lake Miloch (in a Wild Area) // ? Second table, has mostly machop/stunky/tyrogue
- {0xFAB1C08E70C0F1CA, 140}, // at the Motostoke Riverbank (in a Wild Area) // Surfing
- {0xB9F76CEE459CEC07, 142}, // in Bridge Field (in a Wild Area) // Surfing
- {0x5F4E0AB29FD3F13A, 142}, // in Bridge Field (in a Wild Area) // Flying Spawns noibat/woobat/tranquill
- {0xF603DEA4177200EA, 144}, // in the Stony Wilderness (in a Wild Area) // ? Second full table
- {0x76EE4E28DD28374E, 144}, // in the Stony Wilderness (in a Wild Area) // Flying Spawns tranquill/sigilyph
- {0x3F264B6FCB5647B4, 148}, // around the Giant's Mirror (in a Wild Area) // Flying Spawns tranquill/corvisquire
- {0x2D887A1CA9B1B99A, 146}, // in Dusty Bowl (in a Wild Area) // Flying Spawns braviary
- {0x2BE7E6A8901ECC20, 148}, // around the Giant's Mirror (in a Wild Area) // Underground Spawns dugtrio/excadrill/boldore
- {0x39F0170769BF4524, 146}, // in Dusty Bowl (in a Wild Area) // Surfing. Also used for 148,around the Giant's Mirror (in a Wild Area) surfing.
- {0xB2067FBCF8D5C7BA, 152}, // near the Giant's Cap (in a Wild Area) // Underground Spawns rolycoly/rhyhorn/boldore
- {0x48B9525945EE48B5, 144}, // in the Stony Wilderness (in a Wild Area) // ? third full table
- {0xB5756B87989661E1, 152}, // near the Giant's Cap (in a Wild Area) // ? second full table
- {0x7AB83D18C831DDEB, 152}, // near the Giant's Cap (in a Wild Area) // ? third full table
- {0xDBEF8A8593377AAA, 152}, // near the Giant's Cap (in a Wild Area) // Underground Spawns Solrock
- {0x066F97F8765BC22D, 150}, // on the Hammerlocke Hills (in a Wild Area) // Flying Spawns Unfezant/Corvisquire
- {0x87A97AFF94BC6CF2, 154}, // at the Lake of Outrage (in a Wild Area) // Surfing
- {0x94289204B628522C, 008}, // in the Slumbering Weald // early
- {0x5D02F15C043B872E, 008}, // in the Slumbering Weald // late
- {0xA4945486A2B97DFF, 018}, // on Route 2 // Surfing
- {0xAC1187E9EC166853, 092}, // on Route 9 (in Circhester Bay) // Surfing
+ {0xCD6E4FBCE1466F32, 012}, // on Route 1
+ {0xDF686EC613544BD1, 018}, // on Route 2
+ {0xD602B2A66C268F7C, 122}, // in the Rolling Fields (in a Wild Area)
+ {0x458C9CA2C0087385, 124}, // in the Dappled Grove (in a Wild Area)
+ {0xE20E6AE30AAA57D2, 126}, // at Watchtower Ruins (in a Wild Area)
+ {0xEEEEAC06BAC8D0B3, 128}, // at East Lake Axewell (in a Wild Area)
+ {0xF8D1E527F7B21FA0, 130}, // at West Lake Axewell (in a Wild Area)
+ {0xB6CFE90E0378FD79, 132}, // on Axew's Eye (in a Wild Area)
+ {0x520D8DD522E9A4C6, 134}, // at South Lake Miloch (in a Wild Area)
+ {0xBC7237A0392D8837, 136}, // near the Giant's Seat (in a Wild Area)
+ {0xB67C706F5BAE9E35, 138}, // at North Lake Miloch (in a Wild Area)
+ {0xDA910F69A1B92FED, 130}, // at West Lake Axewell (in a Wild Area) // Surfing
+ {0x7C17DB1B430F9543, 134}, // at South Lake Miloch (in a Wild Area) // Surfing
+ {0xCC0F8A437312B8AC, 128}, // at East Lake Axewell (in a Wild Area) // Surfing
+ {0x8BE2F6160986FB8E, 138}, // at North Lake Miloch (in a Wild Area) // Surfing
+ {0x0E8392C0A57D5830, 028}, // on Route 3
+ {0x82A7A328A26B9057, 030}, // in Galar Mine
+ {0x5B2BC38E044EC2B7, 032}, // on Route 4
+ {0x8D68276C03A332BE, 040}, // on Route 5
+ {0x16D2FC4840A658A5, 054}, // in Galar Mine No. 2
+ {0x3D6D58A96894575E, 052}, // in the Motostoke Outskirts
+ {0x6AA652641154B119, 140}, // at the Motostoke Riverbank (in a Wild Area)
+ {0x36A5DC94335E1E72, 142}, // in Bridge Field (in a Wild Area)
+ {0xE503416A1C05765D, 068}, // on Route 6
+ {0x201EF8E9D2A32D71, 076}, // in Glimwood Tangle
+ {0x42312695C904658C, 084}, // on Route 7
+ {0x1B95A78295F6F213, 086}, // on Route 8
+ {0xAADAC3CB6A1DFE8A, 088}, // on Route 8 (on Steamdrift Way)
+ {0x9116B224702CDCF1, 090}, // on Route 9
+ {0xCDD3B5660D2E5E67, 092}, // on Route 9 (in Circhester Bay)
+ {0x5A3B8F8147272058, 094}, // on Route 9 (in Outer Spikemuth)
+ {0xA93101EA38598995, 090}, // on Route 9 // Surfing
+ {0x0181225223DE5420, 106}, // on Route 10 // Near Station
+ {0x1F0F1AE1818C4326, 144}, // in the Stony Wilderness (in a Wild Area)
+ {0xAD11B3F3B2AC662D, 146}, // in Dusty Bowl (in a Wild Area)
+ {0xCD9719B2E64F2AA4, 148}, // around the Giant's Mirror (in a Wild Area)
+ {0xCD48625EDC10CBFB, 150}, // on the Hammerlocke Hills (in a Wild Area)
+ {0x712F3056573E23FA, 152}, // near the Giant's Cap (in a Wild Area)
+ {0x593196758BA16B61, 154}, // at the Lake of Outrage (in a Wild Area)
+ {0xF79DE930E6F50533, 106}, // on Route 10
+ {0xA26A4595F72EDAEA, 018}, // on Route 2 // high level
+ {0x56580C94EDFCE664, 028}, // on Route 3 // just rolycoly and trubbish, probably trash
+ {0xCB38FEA3F71C3958, 122}, // in the Rolling Fields (in a Wild Area) // Flying Spawns butterfree/pidove
+ {0x1F174D36062B8C38, 122}, // in the Rolling Fields (in a Wild Area) // Underground Spawns digglet/roggenrola
+ {0x23017513039A78E7, 122}, // in the Rolling Fields (in a Wild Area) // ? Second full table, has pancham instead of bunnelby
+ {0xF1BA4AAD9AAB2C1A, 126}, // at Watchtower Ruins (in a Wild Area) // Flying Spawns woobat/noibat
+ {0x3D2E746F9D3F5CB5, 128}, // at East Lake Axewell (in a Wild Area) // Flying Spawns bufferfree/pidove
+ {0x6E121A9CE4F58F1E, 128}, // at East Lake Axewell (in a Wild Area) // More Flying Spawns bufferfree/pidove, different rates than above
+ {0x3171A0C61793816E, 134}, // at South Lake Miloch (in a Wild Area) // Flying Spawns wingull/drifloon
+ {0x198E4023A1B2DDEF, 134}, // at South Lake Miloch (in a Wild Area) // ? Second table, has mostly machop/stunky/tyrogue
+ {0xFAB1C08E70C0F1CA, 140}, // at the Motostoke Riverbank (in a Wild Area) // Surfing
+ {0xB9F76CEE459CEC07, 142}, // in Bridge Field (in a Wild Area) // Surfing
+ {0x5F4E0AB29FD3F13A, 142}, // in Bridge Field (in a Wild Area) // Flying Spawns noibat/woobat/tranquill
+ {0xF603DEA4177200EA, 144}, // in the Stony Wilderness (in a Wild Area) // ? Second full table
+ {0x76EE4E28DD28374E, 144}, // in the Stony Wilderness (in a Wild Area) // Flying Spawns tranquill/sigilyph
+ {0x3F264B6FCB5647B4, 148}, // around the Giant's Mirror (in a Wild Area) // Flying Spawns tranquill/corvisquire
+ {0x2D887A1CA9B1B99A, 146}, // in Dusty Bowl (in a Wild Area) // Flying Spawns braviary
+ {0x2BE7E6A8901ECC20, 148}, // around the Giant's Mirror (in a Wild Area) // Underground Spawns dugtrio/excadrill/boldore
+ {0x39F0170769BF4524, 146}, // in Dusty Bowl (in a Wild Area) // Surfing. Also used for 148,around the Giant's Mirror (in a Wild Area) surfing.
+ {0xB2067FBCF8D5C7BA, 152}, // near the Giant's Cap (in a Wild Area) // Underground Spawns rolycoly/rhyhorn/boldore
+ {0x48B9525945EE48B5, 144}, // in the Stony Wilderness (in a Wild Area) // ? third full table
+ {0xB5756B87989661E1, 152}, // near the Giant's Cap (in a Wild Area) // ? second full table
+ {0x7AB83D18C831DDEB, 152}, // near the Giant's Cap (in a Wild Area) // ? third full table
+ {0xDBEF8A8593377AAA, 152}, // near the Giant's Cap (in a Wild Area) // Underground Spawns Solrock
+ {0x066F97F8765BC22D, 150}, // on the Hammerlocke Hills (in a Wild Area) // Flying Spawns Unfezant/Corvisquire
+ {0x87A97AFF94BC6CF2, 154}, // at the Lake of Outrage (in a Wild Area) // Surfing
+ {0x94289204B628522C, 008}, // in the Slumbering Weald // early
+ {0x5D02F15C043B872E, 008}, // in the Slumbering Weald // late
+ {0xA4945486A2B97DFF, 018}, // on Route 2 // Surfing
+ {0xAC1187E9EC166853, 092}, // on Route 9 (in Circhester Bay) // Surfing
- // DLC 1 - Isle of Armor
- {0x908A64718CA374E6, 164}, // in the Fields of Honor
- {0x908A63718CA37333, 166}, // in the Soothing Wetlands
- {0x908A62718CA37180, 168}, // in the Forest of Focus
- {0x908A69718CA37D65, 170}, // on Challenge Beach
- {0x908A68718CA37BB2, 172}, // in Brawlers' Cave
- {0x908A67718CA379FF, 174}, // on Challenge Road
- {0x908A66718CA3784C, 176}, // in Courageous Cavern
- {0x908A6D718CA38431, 178}, // in Loop Lagoon
- {0x908A6C718CA3827E, 180}, // in the Training Lowlands
- {0x90875F718CA13690, 182}, // in Warm-Up Tunnel
- {0x908760718CA13843, 184}, // in the Potbottom Desert
- {0x909170718CA9A7F8, 186}, // in the Workout Sea
- {0x909173718CA9AD11, 188}, // in the Stepping-Stone Sea
- {0x909172718CA9AB5E, 190}, // in the Insular Sea
- {0x909175718CA9B077, 192}, // in the Honeycalm Sea
- {0x908DEC718CA691D5, 194}, // on Honeycalm Island
+ // DLC 1 - Isle of Armor
+ {0x908A64718CA374E6, 164}, // in the Fields of Honor
+ {0x908A63718CA37333, 166}, // in the Soothing Wetlands
+ {0x908A62718CA37180, 168}, // in the Forest of Focus
+ {0x908A69718CA37D65, 170}, // on Challenge Beach
+ {0x908A68718CA37BB2, 172}, // in Brawlers' Cave
+ {0x908A67718CA379FF, 174}, // on Challenge Road
+ {0x908A66718CA3784C, 176}, // in Courageous Cavern
+ {0x908A6D718CA38431, 178}, // in Loop Lagoon
+ {0x908A6C718CA3827E, 180}, // in the Training Lowlands
+ {0x90875F718CA13690, 182}, // in Warm-Up Tunnel
+ {0x908760718CA13843, 184}, // in the Potbottom Desert
+ {0x909170718CA9A7F8, 186}, // in the Workout Sea
+ {0x909173718CA9AD11, 188}, // in the Stepping-Stone Sea
+ {0x909172718CA9AB5E, 190}, // in the Insular Sea
+ {0x909175718CA9B077, 192}, // in the Honeycalm Sea
+ {0x908DEC718CA691D5, 194}, // on Honeycalm Island
- {0x525D03DF0309D804, 164}, // in the Fields of Honor // Ground Spawns
- {0xB0621052994A5089, 164}, // in the Fields of Honor // Surfing
- {0x91B1D1436BAF5871, 164}, // in the Fields of Honor // Beach
- {0xC449DFAB894F632C, 178}, // in Loop Lagoon // Beach
- {0x273693DD91D7BD10, 170}, // on Challenge Beach // Beach
- {0xD61582D408C39E60, 170}, // on Challenge Beach // Surfing (River)
- {0xBECC9623CD3E8C77, 166}, // in the Soothing Wetlands // Ground Spawns
- {0x1C051CB6F97C2068, 166}, // in the Soothing Wetlands // Puddles
- {0xBC028EF260AD9406, 168}, // in the Forest of Focus // Ground Spawns
- {0x32AB88FC9797DC83, 168}, // in the Forest of Focus // Surfing
- {0x39D078468AA0DCC1, 170}, // on Challenge Beach // Ground Spawns
- {0x3BFB22D0FB5B42D2, 170}, // on Challenge Beach // Surfing (Ocean)
- {0x2B1DF6E85F9BAE28, 172}, // in Brawlers' Cave // Ground Spawns
- {0x36FE81B956D0DCB5, 172}, // in Brawlers' Cave // Surfing
- {0xBBAA199D0705405B, 174}, // on Challenge Road // Ground Spawns
- {0xFB9A7FD6D979C6DA, 176}, // in Courageous Cavern // Ground Spawns
- {0xBC0E1701C0276FCF, 176}, // in Courageous Cavern // Surfing
- {0xAC2ED08E980FCFC5, 178}, // in Loop Lagoon // Ground Spawns
- {0x7D2E205E8E300EE1, 178}, // in Loop Lagoon // Water Spawns
- {0x67E3FF10EB64FB79, 180}, // in the Training Lowlands // Beach
- {0x85E286D82C666BBC, 180}, // in the Training Lowlands // Ground Spawns
- {0x95E125D2EE3ED656, 182}, // in Warm-up Tunnel
- {0xA7F495799F209587, 184}, // in the Potbottom Desert
- {0x30AAD92559FCE81E, 186}, // in the Workout Sea // Ground Spawns
- {0x6F748A46C8E3802C, 186}, // in the Workout Sea // Surfing
- {0x97A3E0687E3C5B01, 188}, // in the Stepping-Stone Sea // Surfing
- {0xDDDFF88957FD5B5C, 190}, // in the Insular Sea // Ground Spawns
- {0xF3036CD294CE9365, 188}, // in the Stepping-Stone Sea // Ground Spawns
- {0xFB9BB438425D58DA, 190}, // in the Insular Sea // Surfing
- {0xC16C1E2A1B5FFE87, 192}, // in the Honeycalm Sea // Surfing
- {0x081D7EF6A1C192B1, 194}, // on Honeycalm Island // Ground Spawns
- {0x86EFBF49516B5555, 194}, // on Honeycalm Island // Surfing
- {0x39AB700A9F1AB71F, 180}, // in the Training Lowlands // Surfing
- {0x96C6A2A36131F383, 188}, // in the Stepping-Stone Sea // Sharpedo
- {0xC92D06352150C78A, 190}, // in the Insular Sea // Sharpedo
- {0xED1F9772AA35C3CD, 186}, // in the Workout Sea // Sharpedo
- {0x9C0049D3E6129924, 192}, // in the Honeycalm Sea // Sharpedo
+ {0x525D03DF0309D804, 164}, // in the Fields of Honor // Ground Spawns
+ {0xB0621052994A5089, 164}, // in the Fields of Honor // Surfing
+ {0x91B1D1436BAF5871, 164}, // in the Fields of Honor // Beach
+ {0xC449DFAB894F632C, 178}, // in Loop Lagoon // Beach
+ {0x273693DD91D7BD10, 170}, // on Challenge Beach // Beach
+ {0xD61582D408C39E60, 170}, // on Challenge Beach // Surfing (River)
+ {0xBECC9623CD3E8C77, 166}, // in the Soothing Wetlands // Ground Spawns
+ {0x1C051CB6F97C2068, 166}, // in the Soothing Wetlands // Puddles
+ {0xBC028EF260AD9406, 168}, // in the Forest of Focus // Ground Spawns
+ {0x32AB88FC9797DC83, 168}, // in the Forest of Focus // Surfing
+ {0x39D078468AA0DCC1, 170}, // on Challenge Beach // Ground Spawns
+ {0x3BFB22D0FB5B42D2, 170}, // on Challenge Beach // Surfing (Ocean)
+ {0x2B1DF6E85F9BAE28, 172}, // in Brawlers' Cave // Ground Spawns
+ {0x36FE81B956D0DCB5, 172}, // in Brawlers' Cave // Surfing
+ {0xBBAA199D0705405B, 174}, // on Challenge Road // Ground Spawns
+ {0xFB9A7FD6D979C6DA, 176}, // in Courageous Cavern // Ground Spawns
+ {0xBC0E1701C0276FCF, 176}, // in Courageous Cavern // Surfing
+ {0xAC2ED08E980FCFC5, 178}, // in Loop Lagoon // Ground Spawns
+ {0x7D2E205E8E300EE1, 178}, // in Loop Lagoon // Water Spawns
+ {0x67E3FF10EB64FB79, 180}, // in the Training Lowlands // Beach
+ {0x85E286D82C666BBC, 180}, // in the Training Lowlands // Ground Spawns
+ {0x95E125D2EE3ED656, 182}, // in Warm-up Tunnel
+ {0xA7F495799F209587, 184}, // in the Potbottom Desert
+ {0x30AAD92559FCE81E, 186}, // in the Workout Sea // Ground Spawns
+ {0x6F748A46C8E3802C, 186}, // in the Workout Sea // Surfing
+ {0x97A3E0687E3C5B01, 188}, // in the Stepping-Stone Sea // Surfing
+ {0xDDDFF88957FD5B5C, 190}, // in the Insular Sea // Ground Spawns
+ {0xF3036CD294CE9365, 188}, // in the Stepping-Stone Sea // Ground Spawns
+ {0xFB9BB438425D58DA, 190}, // in the Insular Sea // Surfing
+ {0xC16C1E2A1B5FFE87, 192}, // in the Honeycalm Sea // Surfing
+ {0x081D7EF6A1C192B1, 194}, // on Honeycalm Island // Ground Spawns
+ {0x86EFBF49516B5555, 194}, // on Honeycalm Island // Surfing
+ {0x39AB700A9F1AB71F, 180}, // in the Training Lowlands // Surfing
+ {0x96C6A2A36131F383, 188}, // in the Stepping-Stone Sea // Sharpedo
+ {0xC92D06352150C78A, 190}, // in the Insular Sea // Sharpedo
+ {0xED1F9772AA35C3CD, 186}, // in the Workout Sea // Sharpedo
+ {0x9C0049D3E6129924, 192}, // in the Honeycalm Sea // Sharpedo
- // DLC 2 - Crown Tundra
- {0x87E14B7187BC1CC1, 204}, // on Slippery Slope
- {0x87E1487187BC17A8, 206}, // in Freezington
- {0x87E1497187BC195B, 208}, // in Frostpoint Field
- {0x87E14E7187BC21DA, 210}, // in the Giant's Bed
- {0x87E14F7187BC238D, 212}, // in the Old Cemetery
- {0x87E14C7187BC1E74, 214}, // on Snowslide Slope
- {0x87E14D7187BC2027, 216}, // in the Tunnel to the Top
- {0x87E1427187BC0D76, 218}, // on the Path to the Peak
- {0x87E1437187BC0F29, 220}, // at the Crown Shrine
- {0x87E4507187BE5B17, 222}, // at the Giant's Foot
- {0x87E44F7187BE5964, 224}, // in Roaring-Sea Caves
- {0x87E4527187BE5E7D, 226}, // at the Frigid Sea
- {0x87E4517187BE5CCA, 228}, // in Three-Point Pass
- {0x87DA3F7187B5E9AF, 230}, // at Ballimere Lake
- {0x87DA407187B5EB62, 232}, // in Lakeside Cave
- {0x87DA417187B5ED15, 234}, // at Dyna Tree Hill
+ // DLC 2 - Crown Tundra
+ {0x87E14B7187BC1CC1, 204}, // on Slippery Slope
+ {0x87E1487187BC17A8, 206}, // in Freezington
+ {0x87E1497187BC195B, 208}, // in Frostpoint Field
+ {0x87E14E7187BC21DA, 210}, // in the Giant's Bed
+ {0x87E14F7187BC238D, 212}, // in the Old Cemetery
+ {0x87E14C7187BC1E74, 214}, // on Snowslide Slope
+ {0x87E14D7187BC2027, 216}, // in the Tunnel to the Top
+ {0x87E1427187BC0D76, 218}, // on the Path to the Peak
+ {0x87E1437187BC0F29, 220}, // at the Crown Shrine
+ {0x87E4507187BE5B17, 222}, // at the Giant's Foot
+ {0x87E44F7187BE5964, 224}, // in Roaring-Sea Caves
+ {0x87E4527187BE5E7D, 226}, // at the Frigid Sea
+ {0x87E4517187BE5CCA, 228}, // in Three-Point Pass
+ {0x87DA3F7187B5E9AF, 230}, // at Ballimere Lake
+ {0x87DA407187B5EB62, 232}, // in Lakeside Cave
+ {0x87DA417187B5ED15, 234}, // at Dyna Tree Hill
- {0xD6EA3DE40B009E55, 204}, // on Slippery Slope
- {0xADF616908BD308DF, 208}, // in Frostpoint Field
- {0x308C5EB6A846D1F0, 210}, // in the Giant's Bed
- {0x50E781F91B97C049, 212}, // in the Old Cemetery
- {0xC303110BF1EC3322, 214}, // on Snowslide Slope
- {0xB768660B0BF4C0C3, 216}, // in the Tunnel to the Top
- {0xFCB78AFCCECAF094, 218}, // on the Path to the Peak
- {0xA345459C03EA6673, 222}, // at the Giant's Foot
- {0xE4A982819ACF7292, 224}, // in Roaring-Sea Caves
- {0x18AAF85178C7B839, 226}, // at the Frigid Sea
- {0x3EC6FCDC0C77D460, 228}, // in Three-Point Pass
- {0xE5225F9325CCA74B, 230}, // at Ballimere Lake
- {0x2F1B41507D695958, 232}, // in Lakeside Cave
+ {0xD6EA3DE40B009E55, 204}, // on Slippery Slope
+ {0xADF616908BD308DF, 208}, // in Frostpoint Field
+ {0x308C5EB6A846D1F0, 210}, // in the Giant's Bed
+ {0x50E781F91B97C049, 212}, // in the Old Cemetery
+ {0xC303110BF1EC3322, 214}, // on Snowslide Slope
+ {0xB768660B0BF4C0C3, 216}, // in the Tunnel to the Top
+ {0xFCB78AFCCECAF094, 218}, // on the Path to the Peak
+ {0xA345459C03EA6673, 222}, // at the Giant's Foot
+ {0xE4A982819ACF7292, 224}, // in Roaring-Sea Caves
+ {0x18AAF85178C7B839, 226}, // at the Frigid Sea
+ {0x3EC6FCDC0C77D460, 228}, // in Three-Point Pass
+ {0xE5225F9325CCA74B, 230}, // at Ballimere Lake
+ {0x2F1B41507D695958, 232}, // in Lakeside Cave
- {0xF8A59FCA719D1EAE, 210}, // in the Giant's Bed (Surfing), also used for 222 (in the Giant's Foot Surfing)
- {0x55D8F226A42368B7, 224}, // in Roaring-Sea Caves (Surfing)
- {0x78536116469DC44D, 226}, // at the Frigid Sea (Surfing)
- {0x9BDD6D11FFBEDA3F, 230}, // at Ballimere Lake (Surfing)
- };
+ {0xF8A59FCA719D1EAE, 210}, // in the Giant's Bed (Surfing), also used for 222 (in the Giant's Foot Surfing)
+ {0x55D8F226A42368B7, 224}, // in Roaring-Sea Caves (Surfing)
+ {0x78536116469DC44D, 226}, // at the Frigid Sea (Surfing)
+ {0x9BDD6D11FFBEDA3F, 230}, // at Ballimere Lake (Surfing)
+ };
- public static readonly IReadOnlyDictionary Zones = new Dictionary
- {
- { 0x078BC1FF1A657844, "Route 1" },
- { 0x10355EFF1F4DB0B5, "Route 2" },
- { 0x776776717EA4483E, "Rolling Fields" },
- { 0x776777717EA449F1, "Dappled Grove" },
- { 0x776778717EA44BA4, "Watchtower Ruins" },
- { 0x776779717EA44D57, "East Lake Axewell" },
- { 0x77677A717EA44F0A, "West Lake Axewell" },
- { 0x77677B717EA450BD, "Axew's Eye" },
- { 0x77676C717EA43740, "South Lake Miloch" },
- { 0x77676D717EA438F3, "Giant's Seat" },
- { 0x776AFA717EA75E61, "North Lake Miloch" },
- { 0x194B97FF2492111A, "Route 3" },
- { 0x776E81717EAA799D, "Motostoke Riverbank" },
- { 0x776E7E717EAA7484, "Bridge Field" },
- { 0xDBCF5CFF0180B073, "Route 4" },
- { 0x8F67CD45F405D66E, "Slumbering Weald (Low Level)" },
- { 0xE0D6E5E78C91F4A7, "City of Motostoke" },
- { 0xE4E595FF06C510D8, "Route 5" },
- { 0x1C7150C0594994E5, "Town of Hulbury" },
- { 0x7D3B7A45E97D4A51, "Galar Mine No. 2" },
- { 0x75D83E45E5AA7953, "Galar Mine" },
- { 0x7D3B7745E97D4538, "Motostoke Outskirts" },
- { 0xA88AC04602050B95, "Glimwood Tangle" },
- { 0xEDFC32FF0C0A1B29, "Route 6" },
- { 0xF55F6BFF0FDCE70E, "Route 7" },
- { 0x449AE0FF3D19D777, "Route 8" },
- { 0x4BFDF9FF40EC6CFC, "Route 8 (on Steamdrift Way)" },
- { 0x4BFDFCFF40EC7215, "Route 9" },
- { 0x4BFDF6FF40EC67E3, "Route 9 (in Circhester Bay)" },
- { 0x4BFDFBFF40EC7062, "Route 9 (in Outer Spikemuth)" },
- { 0xB332930807F9D48A, "Route 10 (Near Station)" },
- { 0x7771E5717EAD5960, "Stony Wilderness" },
- { 0x7771E8717EAD5E79, "Dusty Bowl" },
- { 0x7771E7717EAD5CC6, "Giant's Mirror" },
- { 0x7771EA717EAD61DF, "Hammerlocke Hills" },
- { 0x7771E9717EAD602C, "Giant's Cap" },
- { 0x7771EC717EAD6545, "Lake of Outrage" },
- { 0x10355BFF1F4DAB9C, "Route 2 (High Level)" },
- { 0xB332920807F9D2D7, "Route 10" },
- { 0x8F67CB45F405D308, "Slumbering Weald (High Level)" },
- { 0xCD6E4FBCE1466F32, "Route 1" },
- { 0xDF686EC613544BD1, "Route 2" },
- { 0xD602B2A66C268F7C, "Rolling Fields" },
- { 0x458C9CA2C0087385, "Dappled Grove" },
- { 0xE20E6AE30AAA57D2, "Watchtower Ruins" },
- { 0xEEEEAC06BAC8D0B3, "East Lake Axewell" },
- { 0xF8D1E527F7B21FA0, "West Lake Axewell" },
- { 0xB6CFE90E0378FD79, "Axew's Eye" },
- { 0x520D8DD522E9A4C6, "South Lake Miloch" },
- { 0xBC7237A0392D8837, "Giant's Seat" },
- { 0xB67C706F5BAE9E35, "North Lake Miloch" },
- { 0xDA910F69A1B92FED, "West Lake Axewell (Surfing)" },
- { 0x7C17DB1B430F9543, "South Lake Miloch (Surfing)" },
- { 0xCC0F8A437312B8AC, "East Lake Axewell (Surfing)" },
- { 0x8BE2F6160986FB8E, "North Lake Miloch (Surfing)" },
- { 0x0E8392C0A57D5830, "Route 3" },
- { 0x82A7A328A26B9057, "Galar Mine" },
- { 0x5B2BC38E044EC2B7, "Route 4" },
- { 0x8D68276C03A332BE, "Route 5" },
- { 0x16D2FC4840A658A5, "Galar Mine No. 2" },
- { 0x3D6D58A96894575E, "Motostoke Outskirts" },
- { 0x6AA652641154B119, "Motostoke Riverbank" },
- { 0x36A5DC94335E1E72, "Bridge Field" },
- { 0xE503416A1C05765D, "Route 6" },
- { 0x201EF8E9D2A32D71, "Glimwood Tangle" },
- { 0x42312695C904658C, "Route 7" },
- { 0x1B95A78295F6F213, "Route 8" },
- { 0xAADAC3CB6A1DFE8A, "Route 8 (on Steamdrift Way)" },
- { 0x9116B224702CDCF1, "Route 9" },
- { 0xCDD3B5660D2E5E67, "Route 9 (in Circhester Bay)" },
- { 0x5A3B8F8147272058, "Route 9 (in Outer Spikemuth)" },
- { 0xA93101EA38598995, "Route 9 (Surfing)" },
- { 0x0181225223DE5420, "Route 10 (Near Station)" },
- { 0x1F0F1AE1818C4326, "Stony Wilderness" },
- { 0xAD11B3F3B2AC662D, "Dusty Bowl" },
- { 0xCD9719B2E64F2AA4, "Giant's Mirror" },
- { 0xCD48625EDC10CBFB, "Hammerlocke Hills" },
- { 0x712F3056573E23FA, "Giant's Cap" },
- { 0x593196758BA16B61, "Lake of Outrage" },
- { 0xF79DE930E6F50533, "Route 10" },
- { 0xA26A4595F72EDAEA, "Route 2 (High Level)" },
- { 0x56580C94EDFCE664, "Route 3 (Garbage)" },
- { 0xCB38FEA3F71C3958, "Rolling Fields (Flying)" },
- { 0x1F174D36062B8C38, "Rolling Fields (Ground)" },
- { 0x23017513039A78E7, "Rolling Fields (2)" },
- { 0xF1BA4AAD9AAB2C1A, "Watchtower Ruins (Flying)" },
- { 0x3D2E746F9D3F5CB5, "East Lake Axewell (Flying)" },
- { 0x6E121A9CE4F58F1E, "East Lake Axewell (Flying)" },
- { 0x3171A0C61793816E, "South Lake Miloch (Flying)" },
- { 0x198E4023A1B2DDEF, "South Lake Miloch (2)" },
- { 0xFAB1C08E70C0F1CA, "Motostoke Riverbank (Surfing)" },
- { 0xB9F76CEE459CEC07, "Bridge Field (Surfing)" },
- { 0x5F4E0AB29FD3F13A, "Bridge Field (Flying)" },
- { 0xF603DEA4177200EA, "Stony Wilderness (2)" },
- { 0x76EE4E28DD28374E, "Stony Wilderness (Flying)" },
- { 0x3F264B6FCB5647B4, "Giant's Mirror (Flying)" },
- { 0x2D887A1CA9B1B99A, "Dusty Bowl (Flying)" },
- { 0x2BE7E6A8901ECC20, "Giant's Mirror (Ground)" },
- { 0x39F0170769BF4524, "Dusty Bowl and Giant's Mirror (Surfing)" },
- { 0xB2067FBCF8D5C7BA, "Giant's Cap (Ground)" },
- { 0x48B9525945EE48B5, "Stony Wilderness (3)" },
- { 0xB5756B87989661E1, "Giant's Cap (2)" },
- { 0x7AB83D18C831DDEB, "Giant's Cap (3)" },
- { 0xDBEF8A8593377AAA, "Giant's Cap (Lunatone/Solrock)" },
- { 0x066F97F8765BC22D, "Hammerlocke Hills (Flying)" },
- { 0x87A97AFF94BC6CF2, "Lake of Outrage (Surfing)" },
- { 0x94289204B628522C, "Slumbering Weald (Low Level)" },
- { 0x5D02F15C043B872E, "Slumbering Weald (High Level)" },
- { 0xA4945486A2B97DFF, "Route 2 (Surfing)" },
- { 0xAC1187E9EC166853, "Route 9 (in Circhester Bay) (Surfing)" },
+ public static readonly IReadOnlyDictionary Zones = new Dictionary
+ {
+ { 0x078BC1FF1A657844, "Route 1" },
+ { 0x10355EFF1F4DB0B5, "Route 2" },
+ { 0x776776717EA4483E, "Rolling Fields" },
+ { 0x776777717EA449F1, "Dappled Grove" },
+ { 0x776778717EA44BA4, "Watchtower Ruins" },
+ { 0x776779717EA44D57, "East Lake Axewell" },
+ { 0x77677A717EA44F0A, "West Lake Axewell" },
+ { 0x77677B717EA450BD, "Axew's Eye" },
+ { 0x77676C717EA43740, "South Lake Miloch" },
+ { 0x77676D717EA438F3, "Giant's Seat" },
+ { 0x776AFA717EA75E61, "North Lake Miloch" },
+ { 0x194B97FF2492111A, "Route 3" },
+ { 0x776E81717EAA799D, "Motostoke Riverbank" },
+ { 0x776E7E717EAA7484, "Bridge Field" },
+ { 0xDBCF5CFF0180B073, "Route 4" },
+ { 0x8F67CD45F405D66E, "Slumbering Weald (Low Level)" },
+ { 0xE0D6E5E78C91F4A7, "City of Motostoke" },
+ { 0xE4E595FF06C510D8, "Route 5" },
+ { 0x1C7150C0594994E5, "Town of Hulbury" },
+ { 0x7D3B7A45E97D4A51, "Galar Mine No. 2" },
+ { 0x75D83E45E5AA7953, "Galar Mine" },
+ { 0x7D3B7745E97D4538, "Motostoke Outskirts" },
+ { 0xA88AC04602050B95, "Glimwood Tangle" },
+ { 0xEDFC32FF0C0A1B29, "Route 6" },
+ { 0xF55F6BFF0FDCE70E, "Route 7" },
+ { 0x449AE0FF3D19D777, "Route 8" },
+ { 0x4BFDF9FF40EC6CFC, "Route 8 (on Steamdrift Way)" },
+ { 0x4BFDFCFF40EC7215, "Route 9" },
+ { 0x4BFDF6FF40EC67E3, "Route 9 (in Circhester Bay)" },
+ { 0x4BFDFBFF40EC7062, "Route 9 (in Outer Spikemuth)" },
+ { 0xB332930807F9D48A, "Route 10 (Near Station)" },
+ { 0x7771E5717EAD5960, "Stony Wilderness" },
+ { 0x7771E8717EAD5E79, "Dusty Bowl" },
+ { 0x7771E7717EAD5CC6, "Giant's Mirror" },
+ { 0x7771EA717EAD61DF, "Hammerlocke Hills" },
+ { 0x7771E9717EAD602C, "Giant's Cap" },
+ { 0x7771EC717EAD6545, "Lake of Outrage" },
+ { 0x10355BFF1F4DAB9C, "Route 2 (High Level)" },
+ { 0xB332920807F9D2D7, "Route 10" },
+ { 0x8F67CB45F405D308, "Slumbering Weald (High Level)" },
+ { 0xCD6E4FBCE1466F32, "Route 1" },
+ { 0xDF686EC613544BD1, "Route 2" },
+ { 0xD602B2A66C268F7C, "Rolling Fields" },
+ { 0x458C9CA2C0087385, "Dappled Grove" },
+ { 0xE20E6AE30AAA57D2, "Watchtower Ruins" },
+ { 0xEEEEAC06BAC8D0B3, "East Lake Axewell" },
+ { 0xF8D1E527F7B21FA0, "West Lake Axewell" },
+ { 0xB6CFE90E0378FD79, "Axew's Eye" },
+ { 0x520D8DD522E9A4C6, "South Lake Miloch" },
+ { 0xBC7237A0392D8837, "Giant's Seat" },
+ { 0xB67C706F5BAE9E35, "North Lake Miloch" },
+ { 0xDA910F69A1B92FED, "West Lake Axewell (Surfing)" },
+ { 0x7C17DB1B430F9543, "South Lake Miloch (Surfing)" },
+ { 0xCC0F8A437312B8AC, "East Lake Axewell (Surfing)" },
+ { 0x8BE2F6160986FB8E, "North Lake Miloch (Surfing)" },
+ { 0x0E8392C0A57D5830, "Route 3" },
+ { 0x82A7A328A26B9057, "Galar Mine" },
+ { 0x5B2BC38E044EC2B7, "Route 4" },
+ { 0x8D68276C03A332BE, "Route 5" },
+ { 0x16D2FC4840A658A5, "Galar Mine No. 2" },
+ { 0x3D6D58A96894575E, "Motostoke Outskirts" },
+ { 0x6AA652641154B119, "Motostoke Riverbank" },
+ { 0x36A5DC94335E1E72, "Bridge Field" },
+ { 0xE503416A1C05765D, "Route 6" },
+ { 0x201EF8E9D2A32D71, "Glimwood Tangle" },
+ { 0x42312695C904658C, "Route 7" },
+ { 0x1B95A78295F6F213, "Route 8" },
+ { 0xAADAC3CB6A1DFE8A, "Route 8 (on Steamdrift Way)" },
+ { 0x9116B224702CDCF1, "Route 9" },
+ { 0xCDD3B5660D2E5E67, "Route 9 (in Circhester Bay)" },
+ { 0x5A3B8F8147272058, "Route 9 (in Outer Spikemuth)" },
+ { 0xA93101EA38598995, "Route 9 (Surfing)" },
+ { 0x0181225223DE5420, "Route 10 (Near Station)" },
+ { 0x1F0F1AE1818C4326, "Stony Wilderness" },
+ { 0xAD11B3F3B2AC662D, "Dusty Bowl" },
+ { 0xCD9719B2E64F2AA4, "Giant's Mirror" },
+ { 0xCD48625EDC10CBFB, "Hammerlocke Hills" },
+ { 0x712F3056573E23FA, "Giant's Cap" },
+ { 0x593196758BA16B61, "Lake of Outrage" },
+ { 0xF79DE930E6F50533, "Route 10" },
+ { 0xA26A4595F72EDAEA, "Route 2 (High Level)" },
+ { 0x56580C94EDFCE664, "Route 3 (Garbage)" },
+ { 0xCB38FEA3F71C3958, "Rolling Fields (Flying)" },
+ { 0x1F174D36062B8C38, "Rolling Fields (Ground)" },
+ { 0x23017513039A78E7, "Rolling Fields (2)" },
+ { 0xF1BA4AAD9AAB2C1A, "Watchtower Ruins (Flying)" },
+ { 0x3D2E746F9D3F5CB5, "East Lake Axewell (Flying)" },
+ { 0x6E121A9CE4F58F1E, "East Lake Axewell (Flying)" },
+ { 0x3171A0C61793816E, "South Lake Miloch (Flying)" },
+ { 0x198E4023A1B2DDEF, "South Lake Miloch (2)" },
+ { 0xFAB1C08E70C0F1CA, "Motostoke Riverbank (Surfing)" },
+ { 0xB9F76CEE459CEC07, "Bridge Field (Surfing)" },
+ { 0x5F4E0AB29FD3F13A, "Bridge Field (Flying)" },
+ { 0xF603DEA4177200EA, "Stony Wilderness (2)" },
+ { 0x76EE4E28DD28374E, "Stony Wilderness (Flying)" },
+ { 0x3F264B6FCB5647B4, "Giant's Mirror (Flying)" },
+ { 0x2D887A1CA9B1B99A, "Dusty Bowl (Flying)" },
+ { 0x2BE7E6A8901ECC20, "Giant's Mirror (Ground)" },
+ { 0x39F0170769BF4524, "Dusty Bowl and Giant's Mirror (Surfing)" },
+ { 0xB2067FBCF8D5C7BA, "Giant's Cap (Ground)" },
+ { 0x48B9525945EE48B5, "Stony Wilderness (3)" },
+ { 0xB5756B87989661E1, "Giant's Cap (2)" },
+ { 0x7AB83D18C831DDEB, "Giant's Cap (3)" },
+ { 0xDBEF8A8593377AAA, "Giant's Cap (Lunatone/Solrock)" },
+ { 0x066F97F8765BC22D, "Hammerlocke Hills (Flying)" },
+ { 0x87A97AFF94BC6CF2, "Lake of Outrage (Surfing)" },
+ { 0x94289204B628522C, "Slumbering Weald (Low Level)" },
+ { 0x5D02F15C043B872E, "Slumbering Weald (High Level)" },
+ { 0xA4945486A2B97DFF, "Route 2 (Surfing)" },
+ { 0xAC1187E9EC166853, "Route 9 (in Circhester Bay) (Surfing)" },
- // DLC 1 - Isle of Armor
- { 0x908A64718CA374E6, "Fields of Honor" },
- { 0x908A63718CA37333, "Soothing Wetlands" },
- { 0x908A62718CA37180, "Forest of Focus" },
- { 0x908A69718CA37D65, "Challenge Beach" },
- { 0x908A68718CA37BB2, "Brawlers' Cave" },
- { 0x908A67718CA379FF, "Challenge Road" },
- { 0x908A66718CA3784C, "Courageous Cavern" },
- { 0x908A6D718CA38431, "Loop Lagoon" },
- { 0x908A6C718CA3827E, "Training Lowlands" },
- { 0x90875F718CA13690, "Warm-Up Tunnel" },
- { 0x908760718CA13843, "Potbottom Desert" },
- { 0x909170718CA9A7F8, "Workout Sea" },
- { 0x909173718CA9AD11, "Stepping-Stone Sea" },
- { 0x909172718CA9AB5E, "Insular Sea" },
- { 0x909175718CA9B077, "Honeycalm Sea" },
- { 0x908DEC718CA691D5, "Honeycalm Island" },
+ // DLC 1 - Isle of Armor
+ { 0x908A64718CA374E6, "Fields of Honor" },
+ { 0x908A63718CA37333, "Soothing Wetlands" },
+ { 0x908A62718CA37180, "Forest of Focus" },
+ { 0x908A69718CA37D65, "Challenge Beach" },
+ { 0x908A68718CA37BB2, "Brawlers' Cave" },
+ { 0x908A67718CA379FF, "Challenge Road" },
+ { 0x908A66718CA3784C, "Courageous Cavern" },
+ { 0x908A6D718CA38431, "Loop Lagoon" },
+ { 0x908A6C718CA3827E, "Training Lowlands" },
+ { 0x90875F718CA13690, "Warm-Up Tunnel" },
+ { 0x908760718CA13843, "Potbottom Desert" },
+ { 0x909170718CA9A7F8, "Workout Sea" },
+ { 0x909173718CA9AD11, "Stepping-Stone Sea" },
+ { 0x909172718CA9AB5E, "Insular Sea" },
+ { 0x909175718CA9B077, "Honeycalm Sea" },
+ { 0x908DEC718CA691D5, "Honeycalm Island" },
- { 0x525D03DF0309D804, "Fields of Honor" },
- { 0xB0621052994A5089, "Fields of Honor (Surfing)" },
- { 0x91B1D1436BAF5871, "Fields of Honor (Beach)" },
- { 0xC449DFAB894F632C, "Loop Lagoon (Beach)" },
- { 0x273693DD91D7BD10, "Challenge Beach (Beach)" },
- { 0xD61582D408C39E60, "Challenge Beach (Surfing - River)" },
- { 0xBECC9623CD3E8C77, "Soothing Wetlands" },
- { 0x1C051CB6F97C2068, "Soothing Wetlands (Puddles)" },
- { 0xBC028EF260AD9406, "Forest of Focus" },
- { 0x32AB88FC9797DC83, "Forest of Focus (Surfing)" },
- { 0x39D078468AA0DCC1, "Challenge Beach" },
- { 0x3BFB22D0FB5B42D2, "Challenge Beach (Surfing - Ocean)" },
- { 0x2B1DF6E85F9BAE28, "Brawlers' Cave" },
- { 0x36FE81B956D0DCB5, "Brawlers' Cave (Surfing)" },
- { 0xBBAA199D0705405B, "Challenge Road" },
- { 0xFB9A7FD6D979C6DA, "Courageous Cavern" },
- { 0xBC0E1701C0276FCF, "Courageous Cavern (Surfing)" },
- { 0xAC2ED08E980FCFC5, "Loop Lagoon" },
- { 0x7D2E205E8E300EE1, "Loop Lagoon (Surfing)" },
- { 0x67E3FF10EB64FB79, "Training Lowlands (Beach)" },
- { 0x85E286D82C666BBC, "Training Lowlands" },
- { 0x95E125D2EE3ED656, "Warm-up Tunnel" },
- { 0xA7F495799F209587, "Potbottom Desert" },
- { 0x30AAD92559FCE81E, "Workout Sea" },
- { 0x6F748A46C8E3802C, "Workout Sea (Surfing)" },
- { 0x97A3E0687E3C5B01, "Stepping-Stone Sea (Surfing)" },
- { 0xDDDFF88957FD5B5C, "Insular Sea" },
- { 0xF3036CD294CE9365, "Stepping-Stone Sea" },
- { 0xFB9BB438425D58DA, "Insular Sea (Surfing)" },
- { 0xC16C1E2A1B5FFE87, "Honeycalm Sea (Surfing)" },
- { 0x081D7EF6A1C192B1, "Honeycalm Island" },
- { 0x86EFBF49516B5555, "Honeycalm Island (Surfing)" },
- { 0x39AB700A9F1AB71F, "Training Lowlands (Surfing)" },
- { 0x96C6A2A36131F383, "Stepping-Stone Sea (Sharpedo)" },
- { 0xC92D06352150C78A, "Insular Sea (Sharpedo)" },
- { 0xED1F9772AA35C3CD, "Workout Sea (Sharpedo)" },
- { 0x9C0049D3E6129924, "Honeycalm Sea (Sharpedo)" },
+ { 0x525D03DF0309D804, "Fields of Honor" },
+ { 0xB0621052994A5089, "Fields of Honor (Surfing)" },
+ { 0x91B1D1436BAF5871, "Fields of Honor (Beach)" },
+ { 0xC449DFAB894F632C, "Loop Lagoon (Beach)" },
+ { 0x273693DD91D7BD10, "Challenge Beach (Beach)" },
+ { 0xD61582D408C39E60, "Challenge Beach (Surfing - River)" },
+ { 0xBECC9623CD3E8C77, "Soothing Wetlands" },
+ { 0x1C051CB6F97C2068, "Soothing Wetlands (Puddles)" },
+ { 0xBC028EF260AD9406, "Forest of Focus" },
+ { 0x32AB88FC9797DC83, "Forest of Focus (Surfing)" },
+ { 0x39D078468AA0DCC1, "Challenge Beach" },
+ { 0x3BFB22D0FB5B42D2, "Challenge Beach (Surfing - Ocean)" },
+ { 0x2B1DF6E85F9BAE28, "Brawlers' Cave" },
+ { 0x36FE81B956D0DCB5, "Brawlers' Cave (Surfing)" },
+ { 0xBBAA199D0705405B, "Challenge Road" },
+ { 0xFB9A7FD6D979C6DA, "Courageous Cavern" },
+ { 0xBC0E1701C0276FCF, "Courageous Cavern (Surfing)" },
+ { 0xAC2ED08E980FCFC5, "Loop Lagoon" },
+ { 0x7D2E205E8E300EE1, "Loop Lagoon (Surfing)" },
+ { 0x67E3FF10EB64FB79, "Training Lowlands (Beach)" },
+ { 0x85E286D82C666BBC, "Training Lowlands" },
+ { 0x95E125D2EE3ED656, "Warm-up Tunnel" },
+ { 0xA7F495799F209587, "Potbottom Desert" },
+ { 0x30AAD92559FCE81E, "Workout Sea" },
+ { 0x6F748A46C8E3802C, "Workout Sea (Surfing)" },
+ { 0x97A3E0687E3C5B01, "Stepping-Stone Sea (Surfing)" },
+ { 0xDDDFF88957FD5B5C, "Insular Sea" },
+ { 0xF3036CD294CE9365, "Stepping-Stone Sea" },
+ { 0xFB9BB438425D58DA, "Insular Sea (Surfing)" },
+ { 0xC16C1E2A1B5FFE87, "Honeycalm Sea (Surfing)" },
+ { 0x081D7EF6A1C192B1, "Honeycalm Island" },
+ { 0x86EFBF49516B5555, "Honeycalm Island (Surfing)" },
+ { 0x39AB700A9F1AB71F, "Training Lowlands (Surfing)" },
+ { 0x96C6A2A36131F383, "Stepping-Stone Sea (Sharpedo)" },
+ { 0xC92D06352150C78A, "Insular Sea (Sharpedo)" },
+ { 0xED1F9772AA35C3CD, "Workout Sea (Sharpedo)" },
+ { 0x9C0049D3E6129924, "Honeycalm Sea (Sharpedo)" },
- // DLC 2 - Crown Tundra
- { 0x87E14B7187BC1CC1, "Slippery Slope" },
- { 0x87E1487187BC17A8, "Freezington" },
- { 0x87E1497187BC195B, "Frostpoint Field" },
- { 0x87E14E7187BC21DA, "Giant's Bed" },
- { 0x87E14F7187BC238D, "Old Cemetery" },
- { 0x87E14C7187BC1E74, "Snowslide Slope" },
- { 0x87E14D7187BC2027, "Tunnel to the Top" },
- { 0x87E1427187BC0D76, "Path to the Peak" },
- { 0x87E1437187BC0F29, "Crown Shrine" },
- { 0x87E4507187BE5B17, "Giant's Foot" },
- { 0x87E44F7187BE5964, "Roaring-Sea Caves" },
- { 0x87E4527187BE5E7D, "Frigid Sea" },
- { 0x87E4517187BE5CCA, "Three-Point Pass" },
- { 0x87DA3F7187B5E9AF, "Ballimere Lake" },
- { 0x87DA407187B5EB62, "Lakeside Cave" },
- { 0x87DA417187B5ED15, "Dyna Tree Hill" },
+ // DLC 2 - Crown Tundra
+ { 0x87E14B7187BC1CC1, "Slippery Slope" },
+ { 0x87E1487187BC17A8, "Freezington" },
+ { 0x87E1497187BC195B, "Frostpoint Field" },
+ { 0x87E14E7187BC21DA, "Giant's Bed" },
+ { 0x87E14F7187BC238D, "Old Cemetery" },
+ { 0x87E14C7187BC1E74, "Snowslide Slope" },
+ { 0x87E14D7187BC2027, "Tunnel to the Top" },
+ { 0x87E1427187BC0D76, "Path to the Peak" },
+ { 0x87E1437187BC0F29, "Crown Shrine" },
+ { 0x87E4507187BE5B17, "Giant's Foot" },
+ { 0x87E44F7187BE5964, "Roaring-Sea Caves" },
+ { 0x87E4527187BE5E7D, "Frigid Sea" },
+ { 0x87E4517187BE5CCA, "Three-Point Pass" },
+ { 0x87DA3F7187B5E9AF, "Ballimere Lake" },
+ { 0x87DA407187B5EB62, "Lakeside Cave" },
+ { 0x87DA417187B5ED15, "Dyna Tree Hill" },
- { 0xD6EA3DE40B009E55, "Slippery Slope" },
- { 0xADF616908BD308DF, "Frostpoint Field" },
- { 0x308C5EB6A846D1F0, "Giant's Bed" },
- { 0x50E781F91B97C049, "Old Cemetery" },
- { 0xC303110BF1EC3322, "Snowslide Slope" },
- { 0xB768660B0BF4C0C3, "Tunnel to the Top" },
- { 0xFCB78AFCCECAF094, "Path to the Peak" },
- { 0xA345459C03EA6673, "Giant's Foot" },
- { 0xE4A982819ACF7292, "Roaring-Sea Caves" },
- { 0x18AAF85178C7B839, "Frigid Sea" },
- { 0x3EC6FCDC0C77D460, "Three-Point Pass" },
- { 0xE5225F9325CCA74B, "Ballimere Lake" },
- { 0x2F1B41507D695958, "Lakeside Cave" },
+ { 0xD6EA3DE40B009E55, "Slippery Slope" },
+ { 0xADF616908BD308DF, "Frostpoint Field" },
+ { 0x308C5EB6A846D1F0, "Giant's Bed" },
+ { 0x50E781F91B97C049, "Old Cemetery" },
+ { 0xC303110BF1EC3322, "Snowslide Slope" },
+ { 0xB768660B0BF4C0C3, "Tunnel to the Top" },
+ { 0xFCB78AFCCECAF094, "Path to the Peak" },
+ { 0xA345459C03EA6673, "Giant's Foot" },
+ { 0xE4A982819ACF7292, "Roaring-Sea Caves" },
+ { 0x18AAF85178C7B839, "Frigid Sea" },
+ { 0x3EC6FCDC0C77D460, "Three-Point Pass" },
+ { 0xE5225F9325CCA74B, "Ballimere Lake" },
+ { 0x2F1B41507D695958, "Lakeside Cave" },
- { 0xF8A59FCA719D1EAE, "Giant's Bed / Giant's Foot (Surfing)" },
- { 0x55D8F226A42368B7, "Roaring-Sea Caves (Surfing)" },
- { 0x78536116469DC44D, "Frigid Sea (Surfing)" },
- { 0x9BDD6D11FFBEDA3F, "Ballimere Lake (Surfing)" },
- };
+ { 0xF8A59FCA719D1EAE, "Giant's Bed / Giant's Foot (Surfing)" },
+ { 0x55D8F226A42368B7, "Roaring-Sea Caves (Surfing)" },
+ { 0x78536116469DC44D, "Frigid Sea (Surfing)" },
+ { 0x9BDD6D11FFBEDA3F, "Ballimere Lake (Surfing)" },
+ };
- public static readonly IReadOnlyDictionary ZoneType = new Dictionary
- {
- {0x078BC1FF1A657844, (byte)HiddenMain}, // on Route 1
- {0x10355EFF1F4DB0B5, (byte)HiddenMain}, // on Route 2
- {0x776776717EA4483E, (byte)HiddenMain}, // in the Rolling Fields (in a Wild Area)
- {0x776777717EA449F1, (byte)HiddenMain}, // in the Dappled Grove (in a Wild Area)
- {0x776778717EA44BA4, (byte)HiddenMain}, // at Watchtower Ruins (in a Wild Area)
- {0x776779717EA44D57, (byte)HiddenMain}, // at East Lake Axewell (in a Wild Area)
- {0x77677A717EA44F0A, (byte)HiddenMain}, // at West Lake Axewell (in a Wild Area)
- {0x77677B717EA450BD, (byte)HiddenMain}, // on Axew's Eye (in a Wild Area)
- {0x77676C717EA43740, (byte)HiddenMain}, // at South Lake Miloch (in a Wild Area)
- {0x77676D717EA438F3, (byte)HiddenMain}, // near the Giant's Seat (in a Wild Area)
- {0x776AFA717EA75E61, (byte)HiddenMain}, // at North Lake Miloch (in a Wild Area)
- {0x194B97FF2492111A, (byte)HiddenMain}, // on Route 3
- {0x776E81717EAA799D, (byte)HiddenMain}, // at the Motostoke Riverbank (in a Wild Area)
- {0x776E7E717EAA7484, (byte)HiddenMain}, // in Bridge Field (in a Wild Area)
- {0xDBCF5CFF0180B073, (byte)HiddenMain}, // on Route 4
- {0x8F67CD45F405D66E, (byte)HiddenMain}, // in the Slumbering Weald
- {0xE0D6E5E78C91F4A7, (byte)OnlyFishing}, // in the city of Motostoke
- {0xE4E595FF06C510D8, (byte)HiddenMain}, // on Route 5
- {0x1C7150C0594994E5, (byte)OnlyFishing}, // in the town of Hulbury
- {0x7D3B7A45E97D4A51, (byte)HiddenMain}, // in Galar Mine No. 2
- {0x75D83E45E5AA7953, (byte)HiddenMain}, // in Galar Mine
- {0x7D3B7745E97D4538, (byte)HiddenMain}, // in the Motostoke Outskirts
- {0xA88AC04602050B95, (byte)HiddenMain}, // in Glimwood Tangle
- {0xEDFC32FF0C0A1B29, (byte)HiddenMain}, // on Route 6
- {0xF55F6BFF0FDCE70E, (byte)HiddenMain}, // on Route 7
- {0x449AE0FF3D19D777, (byte)HiddenMain}, // on Route 8
- {0x4BFDF9FF40EC6CFC, (byte)HiddenMain}, // on Route 8 (on Steamdrift Way)
- {0x4BFDFCFF40EC7215, (byte)HiddenMain}, // on Route 9
- {0x4BFDF6FF40EC67E3, (byte)HiddenMain}, // on Route 9 (in Circhester Bay)
- {0x4BFDFBFF40EC7062, (byte)HiddenMain}, // on Route 9 (in Outer Spikemuth)
- {0xB332930807F9D48A, (byte)HiddenMain}, // on Route 10 // Near Station
- {0x7771E5717EAD5960, (byte)HiddenMain}, // in the Stony Wilderness (in a Wild Area)
- {0x7771E8717EAD5E79, (byte)HiddenMain}, // in Dusty Bowl (in a Wild Area)
- {0x7771E7717EAD5CC6, (byte)HiddenMain}, // around the Giant's Mirror (in a Wild Area)
- {0x7771EA717EAD61DF, (byte)HiddenMain}, // on the Hammerlocke Hills (in a Wild Area)
- {0x7771E9717EAD602C, (byte)HiddenMain}, // near the Giant's Cap (in a Wild Area)
- {0x7771EC717EAD6545, (byte)HiddenMain}, // at the Lake of Outrage (in a Wild Area)
- {0x10355BFF1F4DAB9C, (byte)HiddenMain2}, // on Route 2
- {0xB332920807F9D2D7, (byte)HiddenMain}, // on Route 10
- {0x8F67CB45F405D308, (byte)HiddenMain2}, // in the Slumbering Weald
+ public static readonly IReadOnlyDictionary ZoneType = new Dictionary
+ {
+ {0x078BC1FF1A657844, (byte)HiddenMain}, // on Route 1
+ {0x10355EFF1F4DB0B5, (byte)HiddenMain}, // on Route 2
+ {0x776776717EA4483E, (byte)HiddenMain}, // in the Rolling Fields (in a Wild Area)
+ {0x776777717EA449F1, (byte)HiddenMain}, // in the Dappled Grove (in a Wild Area)
+ {0x776778717EA44BA4, (byte)HiddenMain}, // at Watchtower Ruins (in a Wild Area)
+ {0x776779717EA44D57, (byte)HiddenMain}, // at East Lake Axewell (in a Wild Area)
+ {0x77677A717EA44F0A, (byte)HiddenMain}, // at West Lake Axewell (in a Wild Area)
+ {0x77677B717EA450BD, (byte)HiddenMain}, // on Axew's Eye (in a Wild Area)
+ {0x77676C717EA43740, (byte)HiddenMain}, // at South Lake Miloch (in a Wild Area)
+ {0x77676D717EA438F3, (byte)HiddenMain}, // near the Giant's Seat (in a Wild Area)
+ {0x776AFA717EA75E61, (byte)HiddenMain}, // at North Lake Miloch (in a Wild Area)
+ {0x194B97FF2492111A, (byte)HiddenMain}, // on Route 3
+ {0x776E81717EAA799D, (byte)HiddenMain}, // at the Motostoke Riverbank (in a Wild Area)
+ {0x776E7E717EAA7484, (byte)HiddenMain}, // in Bridge Field (in a Wild Area)
+ {0xDBCF5CFF0180B073, (byte)HiddenMain}, // on Route 4
+ {0x8F67CD45F405D66E, (byte)HiddenMain}, // in the Slumbering Weald
+ {0xE0D6E5E78C91F4A7, (byte)OnlyFishing}, // in the city of Motostoke
+ {0xE4E595FF06C510D8, (byte)HiddenMain}, // on Route 5
+ {0x1C7150C0594994E5, (byte)OnlyFishing}, // in the town of Hulbury
+ {0x7D3B7A45E97D4A51, (byte)HiddenMain}, // in Galar Mine No. 2
+ {0x75D83E45E5AA7953, (byte)HiddenMain}, // in Galar Mine
+ {0x7D3B7745E97D4538, (byte)HiddenMain}, // in the Motostoke Outskirts
+ {0xA88AC04602050B95, (byte)HiddenMain}, // in Glimwood Tangle
+ {0xEDFC32FF0C0A1B29, (byte)HiddenMain}, // on Route 6
+ {0xF55F6BFF0FDCE70E, (byte)HiddenMain}, // on Route 7
+ {0x449AE0FF3D19D777, (byte)HiddenMain}, // on Route 8
+ {0x4BFDF9FF40EC6CFC, (byte)HiddenMain}, // on Route 8 (on Steamdrift Way)
+ {0x4BFDFCFF40EC7215, (byte)HiddenMain}, // on Route 9
+ {0x4BFDF6FF40EC67E3, (byte)HiddenMain}, // on Route 9 (in Circhester Bay)
+ {0x4BFDFBFF40EC7062, (byte)HiddenMain}, // on Route 9 (in Outer Spikemuth)
+ {0xB332930807F9D48A, (byte)HiddenMain}, // on Route 10 // Near Station
+ {0x7771E5717EAD5960, (byte)HiddenMain}, // in the Stony Wilderness (in a Wild Area)
+ {0x7771E8717EAD5E79, (byte)HiddenMain}, // in Dusty Bowl (in a Wild Area)
+ {0x7771E7717EAD5CC6, (byte)HiddenMain}, // around the Giant's Mirror (in a Wild Area)
+ {0x7771EA717EAD61DF, (byte)HiddenMain}, // on the Hammerlocke Hills (in a Wild Area)
+ {0x7771E9717EAD602C, (byte)HiddenMain}, // near the Giant's Cap (in a Wild Area)
+ {0x7771EC717EAD6545, (byte)HiddenMain}, // at the Lake of Outrage (in a Wild Area)
+ {0x10355BFF1F4DAB9C, (byte)HiddenMain2}, // on Route 2
+ {0xB332920807F9D2D7, (byte)HiddenMain}, // on Route 10
+ {0x8F67CB45F405D308, (byte)HiddenMain2}, // in the Slumbering Weald
- {0xCD6E4FBCE1466F32, (byte)SymbolMain}, // on Route 1
- {0xDF686EC613544BD1, (byte)SymbolMain}, // on Route 2
- {0xD602B2A66C268F7C, (byte)SymbolMain}, // in the Rolling Fields (in a Wild Area)
- {0x458C9CA2C0087385, (byte)SymbolMain}, // in the Dappled Grove (in a Wild Area)
- {0xE20E6AE30AAA57D2, (byte)SymbolMain}, // at Watchtower Ruins (in a Wild Area)
- {0xEEEEAC06BAC8D0B3, (byte)SymbolMain}, // at East Lake Axewell (in a Wild Area)
- {0xF8D1E527F7B21FA0, (byte)SymbolMain}, // at West Lake Axewell (in a Wild Area)
- {0xB6CFE90E0378FD79, (byte)SymbolMain}, // on Axew's Eye (in a Wild Area)
- {0x520D8DD522E9A4C6, (byte)SymbolMain}, // at South Lake Miloch (in a Wild Area)
- {0xBC7237A0392D8837, (byte)SymbolMain}, // near the Giant's Seat (in a Wild Area)
- {0xB67C706F5BAE9E35, (byte)SymbolMain}, // at North Lake Miloch (in a Wild Area)
- {0xDA910F69A1B92FED, (byte)Surfing}, // at West Lake Axewell (in a Wild Area) // Surfing
- {0x7C17DB1B430F9543, (byte)Surfing}, // at South Lake Miloch (in a Wild Area) // Surfing
- {0xCC0F8A437312B8AC, (byte)Surfing}, // at East Lake Axewell (in a Wild Area) // Surfing
- {0x8BE2F6160986FB8E, (byte)Surfing}, // at North Lake Miloch (in a Wild Area) // Surfing
- {0x0E8392C0A57D5830, (byte)SymbolMain}, // on Route 3
- {0x82A7A328A26B9057, (byte)SymbolMain}, // in Galar Mine
- {0x5B2BC38E044EC2B7, (byte)SymbolMain}, // on Route 4
- {0x8D68276C03A332BE, (byte)SymbolMain}, // on Route 5
- {0x16D2FC4840A658A5, (byte)SymbolMain}, // in Galar Mine No. 2
- {0x3D6D58A96894575E, (byte)SymbolMain}, // in the Motostoke Outskirts
- {0x6AA652641154B119, (byte)SymbolMain}, // at the Motostoke Riverbank (in a Wild Area)
- {0x36A5DC94335E1E72, (byte)SymbolMain}, // in Bridge Field (in a Wild Area)
- {0xE503416A1C05765D, (byte)SymbolMain}, // on Route 6
- {0x201EF8E9D2A32D71, (byte)Inaccessible}, // in Glimwood Tangle
- {0x42312695C904658C, (byte)SymbolMain}, // on Route 7
- {0x1B95A78295F6F213, (byte)SymbolMain}, // on Route 8
- {0xAADAC3CB6A1DFE8A, (byte)SymbolMain}, // on Route 8 (on Steamdrift Way)
- {0x9116B224702CDCF1, (byte)SymbolMain}, // on Route 9
- {0xCDD3B5660D2E5E67, (byte)SymbolMain}, // on Route 9 (in Circhester Bay)
- {0x5A3B8F8147272058, (byte)SymbolMain}, // on Route 9 (in Outer Spikemuth)
- {0xA93101EA38598995, (byte)Surfing}, // on Route 9 // Surfing
- {0x0181225223DE5420, (byte)SymbolMain}, // on Route 10 // Near Station
- {0x1F0F1AE1818C4326, (byte)SymbolMain}, // in the Stony Wilderness (in a Wild Area)
- {0xAD11B3F3B2AC662D, (byte)SymbolMain}, // in Dusty Bowl (in a Wild Area)
- {0xCD9719B2E64F2AA4, (byte)SymbolMain}, // around the Giant's Mirror (in a Wild Area)
- {0xCD48625EDC10CBFB, (byte)SymbolMain}, // on the Hammerlocke Hills (in a Wild Area)
- {0x712F3056573E23FA, (byte)SymbolMain}, // near the Giant's Cap (in a Wild Area)
- {0x593196758BA16B61, (byte)SymbolMain}, // at the Lake of Outrage (in a Wild Area)
- {0xF79DE930E6F50533, (byte)SymbolMain}, // on Route 10
- {0xA26A4595F72EDAEA, (byte)SymbolMain2}, // on Route 2 // high level
- {0x56580C94EDFCE664, (byte)Ground}, // on Route 3 // just rolycoly and trubbish, probably trash
- {0xCB38FEA3F71C3958, (byte)Sky}, // in the Rolling Fields (in a Wild Area) // Flying Spawns butterfree/pidove
- {0x1F174D36062B8C38, (byte)Ground}, // in the Rolling Fields (in a Wild Area) // Underground Spawns digglet/roggenrola
- {0x23017513039A78E7, (byte)SymbolMain2}, // in the Rolling Fields (in a Wild Area) // ? Second full table, has pancham instead of bunnelby
- {0xF1BA4AAD9AAB2C1A, (byte)Sky}, // at Watchtower Ruins (in a Wild Area) // Flying Spawns woobat/noibat
- {0x3D2E746F9D3F5CB5, (byte)Sky}, // at East Lake Axewell (in a Wild Area) // Flying Spawns bufferfree/pidove
- {0x6E121A9CE4F58F1E, (byte)Sky2}, // at East Lake Axewell (in a Wild Area) // More Flying Spawns bufferfree/pidove, different rates than above
- {0x3171A0C61793816E, (byte)Sky}, // at South Lake Miloch (in a Wild Area) // Flying Spawns wingull/drifloon
- {0x198E4023A1B2DDEF, (byte)SymbolMain2}, // at South Lake Miloch (in a Wild Area) // ? Second table, has mostly machop/stunky/tyrogue
- {0xFAB1C08E70C0F1CA, (byte)Surfing}, // at the Motostoke Riverbank (in a Wild Area) // Surfing
- {0xB9F76CEE459CEC07, (byte)Surfing}, // in Bridge Field (in a Wild Area) // Surfing
- {0x5F4E0AB29FD3F13A, (byte)Sky}, // in Bridge Field (in a Wild Area) // Flying Spawns noibat/woobat/tranquill
- {0xF603DEA4177200EA, (byte)SymbolMain2}, // in the Stony Wilderness (in a Wild Area) // ? Second full table
- {0x76EE4E28DD28374E, (byte)Sky}, // in the Stony Wilderness (in a Wild Area) // Flying Spawns tranquill/sigilyph
- {0x3F264B6FCB5647B4, (byte)Sky}, // around the Giant's Mirror (in a Wild Area) // Flying Spawns tranquill/corvisquire
- {0x2D887A1CA9B1B99A, (byte)Sky}, // in Dusty Bowl (in a Wild Area) // Flying Spawns braviary
- {0x2BE7E6A8901ECC20, (byte)Ground}, // around the Giant's Mirror (in a Wild Area) // Underground Spawns dugtrio/excadrill/boldore
- {0x39F0170769BF4524, (byte)Surfing}, // in Dusty Bowl (in a Wild Area) // Surfing. Also used for 148,around the Giant's Mirror (in a Wild Area) surfing.
- {0xB2067FBCF8D5C7BA, (byte)Ground}, // near the Giant's Cap (in a Wild Area) // Underground Spawns rolycoly/rhyhorn/boldore
- {0x48B9525945EE48B5, (byte)SymbolMain3}, // in the Stony Wilderness (in a Wild Area) // ? third full table
- {0xB5756B87989661E1, (byte)SymbolMain2}, // near the Giant's Cap (in a Wild Area) // ? second full table
- {0x7AB83D18C831DDEB, (byte)SymbolMain3}, // near the Giant's Cap (in a Wild Area) // ? third full table
- {0xDBEF8A8593377AAA, (byte)Ground2}, // near the Giant's Cap (in a Wild Area) // Underground Spawns Solrock
- {0x066F97F8765BC22D, (byte)Sky}, // on the Hammerlocke Hills (in a Wild Area) // Flying Spawns Unfezant/Corvisquire
- {0x87A97AFF94BC6CF2, (byte)Surfing}, // at the Lake of Outrage (in a Wild Area) // Surfing
- {0x94289204B628522C, (byte)SymbolMain}, // in the Slumbering Weald // early
- {0x5D02F15C043B872E, (byte)SymbolMain2}, // in the Slumbering Weald // late
- {0xA4945486A2B97DFF, (byte)Surfing}, // on Route 2 // Surfing
- {0xAC1187E9EC166853, (byte)Surfing}, // on Route 9 (in Circhester Bay) // Surfing
+ {0xCD6E4FBCE1466F32, (byte)SymbolMain}, // on Route 1
+ {0xDF686EC613544BD1, (byte)SymbolMain}, // on Route 2
+ {0xD602B2A66C268F7C, (byte)SymbolMain}, // in the Rolling Fields (in a Wild Area)
+ {0x458C9CA2C0087385, (byte)SymbolMain}, // in the Dappled Grove (in a Wild Area)
+ {0xE20E6AE30AAA57D2, (byte)SymbolMain}, // at Watchtower Ruins (in a Wild Area)
+ {0xEEEEAC06BAC8D0B3, (byte)SymbolMain}, // at East Lake Axewell (in a Wild Area)
+ {0xF8D1E527F7B21FA0, (byte)SymbolMain}, // at West Lake Axewell (in a Wild Area)
+ {0xB6CFE90E0378FD79, (byte)SymbolMain}, // on Axew's Eye (in a Wild Area)
+ {0x520D8DD522E9A4C6, (byte)SymbolMain}, // at South Lake Miloch (in a Wild Area)
+ {0xBC7237A0392D8837, (byte)SymbolMain}, // near the Giant's Seat (in a Wild Area)
+ {0xB67C706F5BAE9E35, (byte)SymbolMain}, // at North Lake Miloch (in a Wild Area)
+ {0xDA910F69A1B92FED, (byte)Surfing}, // at West Lake Axewell (in a Wild Area) // Surfing
+ {0x7C17DB1B430F9543, (byte)Surfing}, // at South Lake Miloch (in a Wild Area) // Surfing
+ {0xCC0F8A437312B8AC, (byte)Surfing}, // at East Lake Axewell (in a Wild Area) // Surfing
+ {0x8BE2F6160986FB8E, (byte)Surfing}, // at North Lake Miloch (in a Wild Area) // Surfing
+ {0x0E8392C0A57D5830, (byte)SymbolMain}, // on Route 3
+ {0x82A7A328A26B9057, (byte)SymbolMain}, // in Galar Mine
+ {0x5B2BC38E044EC2B7, (byte)SymbolMain}, // on Route 4
+ {0x8D68276C03A332BE, (byte)SymbolMain}, // on Route 5
+ {0x16D2FC4840A658A5, (byte)SymbolMain}, // in Galar Mine No. 2
+ {0x3D6D58A96894575E, (byte)SymbolMain}, // in the Motostoke Outskirts
+ {0x6AA652641154B119, (byte)SymbolMain}, // at the Motostoke Riverbank (in a Wild Area)
+ {0x36A5DC94335E1E72, (byte)SymbolMain}, // in Bridge Field (in a Wild Area)
+ {0xE503416A1C05765D, (byte)SymbolMain}, // on Route 6
+ {0x201EF8E9D2A32D71, (byte)Inaccessible}, // in Glimwood Tangle
+ {0x42312695C904658C, (byte)SymbolMain}, // on Route 7
+ {0x1B95A78295F6F213, (byte)SymbolMain}, // on Route 8
+ {0xAADAC3CB6A1DFE8A, (byte)SymbolMain}, // on Route 8 (on Steamdrift Way)
+ {0x9116B224702CDCF1, (byte)SymbolMain}, // on Route 9
+ {0xCDD3B5660D2E5E67, (byte)SymbolMain}, // on Route 9 (in Circhester Bay)
+ {0x5A3B8F8147272058, (byte)SymbolMain}, // on Route 9 (in Outer Spikemuth)
+ {0xA93101EA38598995, (byte)Surfing}, // on Route 9 // Surfing
+ {0x0181225223DE5420, (byte)SymbolMain}, // on Route 10 // Near Station
+ {0x1F0F1AE1818C4326, (byte)SymbolMain}, // in the Stony Wilderness (in a Wild Area)
+ {0xAD11B3F3B2AC662D, (byte)SymbolMain}, // in Dusty Bowl (in a Wild Area)
+ {0xCD9719B2E64F2AA4, (byte)SymbolMain}, // around the Giant's Mirror (in a Wild Area)
+ {0xCD48625EDC10CBFB, (byte)SymbolMain}, // on the Hammerlocke Hills (in a Wild Area)
+ {0x712F3056573E23FA, (byte)SymbolMain}, // near the Giant's Cap (in a Wild Area)
+ {0x593196758BA16B61, (byte)SymbolMain}, // at the Lake of Outrage (in a Wild Area)
+ {0xF79DE930E6F50533, (byte)SymbolMain}, // on Route 10
+ {0xA26A4595F72EDAEA, (byte)SymbolMain2}, // on Route 2 // high level
+ {0x56580C94EDFCE664, (byte)Ground}, // on Route 3 // just rolycoly and trubbish, probably trash
+ {0xCB38FEA3F71C3958, (byte)Sky}, // in the Rolling Fields (in a Wild Area) // Flying Spawns butterfree/pidove
+ {0x1F174D36062B8C38, (byte)Ground}, // in the Rolling Fields (in a Wild Area) // Underground Spawns digglet/roggenrola
+ {0x23017513039A78E7, (byte)SymbolMain2}, // in the Rolling Fields (in a Wild Area) // ? Second full table, has pancham instead of bunnelby
+ {0xF1BA4AAD9AAB2C1A, (byte)Sky}, // at Watchtower Ruins (in a Wild Area) // Flying Spawns woobat/noibat
+ {0x3D2E746F9D3F5CB5, (byte)Sky}, // at East Lake Axewell (in a Wild Area) // Flying Spawns bufferfree/pidove
+ {0x6E121A9CE4F58F1E, (byte)Sky2}, // at East Lake Axewell (in a Wild Area) // More Flying Spawns bufferfree/pidove, different rates than above
+ {0x3171A0C61793816E, (byte)Sky}, // at South Lake Miloch (in a Wild Area) // Flying Spawns wingull/drifloon
+ {0x198E4023A1B2DDEF, (byte)SymbolMain2}, // at South Lake Miloch (in a Wild Area) // ? Second table, has mostly machop/stunky/tyrogue
+ {0xFAB1C08E70C0F1CA, (byte)Surfing}, // at the Motostoke Riverbank (in a Wild Area) // Surfing
+ {0xB9F76CEE459CEC07, (byte)Surfing}, // in Bridge Field (in a Wild Area) // Surfing
+ {0x5F4E0AB29FD3F13A, (byte)Sky}, // in Bridge Field (in a Wild Area) // Flying Spawns noibat/woobat/tranquill
+ {0xF603DEA4177200EA, (byte)SymbolMain2}, // in the Stony Wilderness (in a Wild Area) // ? Second full table
+ {0x76EE4E28DD28374E, (byte)Sky}, // in the Stony Wilderness (in a Wild Area) // Flying Spawns tranquill/sigilyph
+ {0x3F264B6FCB5647B4, (byte)Sky}, // around the Giant's Mirror (in a Wild Area) // Flying Spawns tranquill/corvisquire
+ {0x2D887A1CA9B1B99A, (byte)Sky}, // in Dusty Bowl (in a Wild Area) // Flying Spawns braviary
+ {0x2BE7E6A8901ECC20, (byte)Ground}, // around the Giant's Mirror (in a Wild Area) // Underground Spawns dugtrio/excadrill/boldore
+ {0x39F0170769BF4524, (byte)Surfing}, // in Dusty Bowl (in a Wild Area) // Surfing. Also used for 148,around the Giant's Mirror (in a Wild Area) surfing.
+ {0xB2067FBCF8D5C7BA, (byte)Ground}, // near the Giant's Cap (in a Wild Area) // Underground Spawns rolycoly/rhyhorn/boldore
+ {0x48B9525945EE48B5, (byte)SymbolMain3}, // in the Stony Wilderness (in a Wild Area) // ? third full table
+ {0xB5756B87989661E1, (byte)SymbolMain2}, // near the Giant's Cap (in a Wild Area) // ? second full table
+ {0x7AB83D18C831DDEB, (byte)SymbolMain3}, // near the Giant's Cap (in a Wild Area) // ? third full table
+ {0xDBEF8A8593377AAA, (byte)Ground2}, // near the Giant's Cap (in a Wild Area) // Underground Spawns Solrock
+ {0x066F97F8765BC22D, (byte)Sky}, // on the Hammerlocke Hills (in a Wild Area) // Flying Spawns Unfezant/Corvisquire
+ {0x87A97AFF94BC6CF2, (byte)Surfing}, // at the Lake of Outrage (in a Wild Area) // Surfing
+ {0x94289204B628522C, (byte)SymbolMain}, // in the Slumbering Weald // early
+ {0x5D02F15C043B872E, (byte)SymbolMain2}, // in the Slumbering Weald // late
+ {0xA4945486A2B97DFF, (byte)Surfing}, // on Route 2 // Surfing
+ {0xAC1187E9EC166853, (byte)Surfing}, // on Route 9 (in Circhester Bay) // Surfing
- // DLC 1 - Isle of Armor
- {0x908A64718CA374E6, (byte)HiddenMain}, // in the Fields of Honor
- {0x908A63718CA37333, (byte)HiddenMain}, // in the Soothing Wetlands
- {0x908A62718CA37180, (byte)HiddenMain}, // in the Forest of Focus
- {0x908A69718CA37D65, (byte)HiddenMain}, // on Challenge Beach
- {0x908A68718CA37BB2, (byte)Inaccessible}, // in Brawlers' Cave
- {0x908A67718CA379FF, (byte)HiddenMain}, // on Challenge Road
- {0x908A66718CA3784C, (byte)OnlyFishing}, // in Courageous Cavern // Only fishing?
- {0x908A6D718CA38431, (byte)HiddenMain}, // in Loop Lagoon
- {0x908A6C718CA3827E, (byte)HiddenMain}, // in the Training Lowlands
- {0x90875F718CA13690, (byte)Inaccessible}, // in Warm-Up Tunnel
- {0x908760718CA13843, (byte)Inaccessible}, // in the Potbottom Desert
- {0x909170718CA9A7F8, (byte)HiddenMain}, // in the Workout Sea
- {0x909173718CA9AD11, (byte)HiddenMain}, // in the Stepping-Stone Sea
- {0x909172718CA9AB5E, (byte)HiddenMain}, // in the Insular Sea
- {0x909175718CA9B077, (byte)OnlyFishing}, // in the Honeycalm Sea // Only fishing?
- {0x908DEC718CA691D5, (byte)HiddenMain}, // on Honeycalm Island
+ // DLC 1 - Isle of Armor
+ {0x908A64718CA374E6, (byte)HiddenMain}, // in the Fields of Honor
+ {0x908A63718CA37333, (byte)HiddenMain}, // in the Soothing Wetlands
+ {0x908A62718CA37180, (byte)HiddenMain}, // in the Forest of Focus
+ {0x908A69718CA37D65, (byte)HiddenMain}, // on Challenge Beach
+ {0x908A68718CA37BB2, (byte)Inaccessible}, // in Brawlers' Cave
+ {0x908A67718CA379FF, (byte)HiddenMain}, // on Challenge Road
+ {0x908A66718CA3784C, (byte)OnlyFishing}, // in Courageous Cavern // Only fishing?
+ {0x908A6D718CA38431, (byte)HiddenMain}, // in Loop Lagoon
+ {0x908A6C718CA3827E, (byte)HiddenMain}, // in the Training Lowlands
+ {0x90875F718CA13690, (byte)Inaccessible}, // in Warm-Up Tunnel
+ {0x908760718CA13843, (byte)Inaccessible}, // in the Potbottom Desert
+ {0x909170718CA9A7F8, (byte)HiddenMain}, // in the Workout Sea
+ {0x909173718CA9AD11, (byte)HiddenMain}, // in the Stepping-Stone Sea
+ {0x909172718CA9AB5E, (byte)HiddenMain}, // in the Insular Sea
+ {0x909175718CA9B077, (byte)OnlyFishing}, // in the Honeycalm Sea // Only fishing?
+ {0x908DEC718CA691D5, (byte)HiddenMain}, // on Honeycalm Island
- {0x525D03DF0309D804, (byte)SymbolMain}, // in the Fields of Honor // Ground Spawns
- {0xB0621052994A5089, (byte)Surfing}, // in the Fields of Honor // Surfing
- {0x91B1D1436BAF5871, (byte)Ground}, // in the Fields of Honor // Beach Slowpoke
- {0xC449DFAB894F632C, (byte)Ground}, // in Loop Lagoon // Beach
- {0x273693DD91D7BD10, (byte)Ground}, // on Challenge Beach // Beach
- {0xD61582D408C39E60, (byte)Surfing}, // on Challenge Beach // Surfing (River)
- {0xBECC9623CD3E8C77, (byte)SymbolMain}, // in the Soothing Wetlands // Ground Spawns
- {0x1C051CB6F97C2068, (byte)Ground}, // in the Soothing Wetlands // Puddles
- {0xBC028EF260AD9406, (byte)SymbolMain}, // in the Forest of Focus // Ground Spawns
- {0x32AB88FC9797DC83, (byte)Surfing}, // in the Forest of Focus // Surfing
- {0x39D078468AA0DCC1, (byte)SymbolMain}, // on Challenge Beach // Ground Spawns
- {0x3BFB22D0FB5B42D2, (byte)Surfing2}, // on Challenge Beach // Surfing (Ocean)
- {0x2B1DF6E85F9BAE28, (byte)SymbolMain}, // in Brawlers' Cave // Ground Spawns
- {0x36FE81B956D0DCB5, (byte)Surfing}, // in Brawlers' Cave // Surfing
- {0xBBAA199D0705405B, (byte)SymbolMain}, // on Challenge Road // Ground Spawns
- {0xFB9A7FD6D979C6DA, (byte)SymbolMain}, // in Courageous Cavern // Ground Spawns
- {0xBC0E1701C0276FCF, (byte)Surfing}, // in Courageous Cavern // Surfing
- {0xAC2ED08E980FCFC5, (byte)SymbolMain}, // in Loop Lagoon // Ground Spawns
- {0x7D2E205E8E300EE1, (byte)Surfing}, // in Loop Lagoon // Water Spawns
- {0x67E3FF10EB64FB79, (byte)Ground}, // in the Training Lowlands // Beach
- {0x85E286D82C666BBC, (byte)SymbolMain}, // in the Training Lowlands // Ground Spawns
- {0x95E125D2EE3ED656, (byte)SymbolMain}, // in Warm-up Tunnel
- {0xA7F495799F209587, (byte)SymbolMain}, // in the Potbottom Desert
- {0x30AAD92559FCE81E, (byte)SymbolMain}, // in the Workout Sea // Ground Spawns
- {0x6F748A46C8E3802C, (byte)Surfing}, // in the Workout Sea // Surfing
- {0x97A3E0687E3C5B01, (byte)Surfing}, // in the Stepping-Stone Sea // Surfing
- {0xDDDFF88957FD5B5C, (byte)SymbolMain}, // in the Insular Sea // Ground Spawns
- {0xF3036CD294CE9365, (byte)SymbolMain}, // in the Stepping-Stone Sea // Ground Spawns
- {0xFB9BB438425D58DA, (byte)Surfing}, // in the Insular Sea // Surfing
- {0xC16C1E2A1B5FFE87, (byte)Surfing}, // in the Honeycalm Sea // Surfing
- {0x081D7EF6A1C192B1, (byte)SymbolMain}, // on Honeycalm Island // Ground Spawns
- {0x86EFBF49516B5555, (byte)Surfing}, // on Honeycalm Island // Surfing
- {0x39AB700A9F1AB71F, (byte)Surfing}, // in the Training Lowlands // Surfing
- {0x96C6A2A36131F383, (byte)Sharpedo}, // in the Stepping-Stone Sea // Sharpedo
- {0xC92D06352150C78A, (byte)Sharpedo}, // in the Insular Sea // Sharpedo
- {0xED1F9772AA35C3CD, (byte)Sharpedo}, // in the Workout Sea // Sharpedo
- {0x9C0049D3E6129924, (byte)Sharpedo}, // in the Honeycalm Sea // Sharpedo
+ {0x525D03DF0309D804, (byte)SymbolMain}, // in the Fields of Honor // Ground Spawns
+ {0xB0621052994A5089, (byte)Surfing}, // in the Fields of Honor // Surfing
+ {0x91B1D1436BAF5871, (byte)Ground}, // in the Fields of Honor // Beach Slowpoke
+ {0xC449DFAB894F632C, (byte)Ground}, // in Loop Lagoon // Beach
+ {0x273693DD91D7BD10, (byte)Ground}, // on Challenge Beach // Beach
+ {0xD61582D408C39E60, (byte)Surfing}, // on Challenge Beach // Surfing (River)
+ {0xBECC9623CD3E8C77, (byte)SymbolMain}, // in the Soothing Wetlands // Ground Spawns
+ {0x1C051CB6F97C2068, (byte)Ground}, // in the Soothing Wetlands // Puddles
+ {0xBC028EF260AD9406, (byte)SymbolMain}, // in the Forest of Focus // Ground Spawns
+ {0x32AB88FC9797DC83, (byte)Surfing}, // in the Forest of Focus // Surfing
+ {0x39D078468AA0DCC1, (byte)SymbolMain}, // on Challenge Beach // Ground Spawns
+ {0x3BFB22D0FB5B42D2, (byte)Surfing2}, // on Challenge Beach // Surfing (Ocean)
+ {0x2B1DF6E85F9BAE28, (byte)SymbolMain}, // in Brawlers' Cave // Ground Spawns
+ {0x36FE81B956D0DCB5, (byte)Surfing}, // in Brawlers' Cave // Surfing
+ {0xBBAA199D0705405B, (byte)SymbolMain}, // on Challenge Road // Ground Spawns
+ {0xFB9A7FD6D979C6DA, (byte)SymbolMain}, // in Courageous Cavern // Ground Spawns
+ {0xBC0E1701C0276FCF, (byte)Surfing}, // in Courageous Cavern // Surfing
+ {0xAC2ED08E980FCFC5, (byte)SymbolMain}, // in Loop Lagoon // Ground Spawns
+ {0x7D2E205E8E300EE1, (byte)Surfing}, // in Loop Lagoon // Water Spawns
+ {0x67E3FF10EB64FB79, (byte)Ground}, // in the Training Lowlands // Beach
+ {0x85E286D82C666BBC, (byte)SymbolMain}, // in the Training Lowlands // Ground Spawns
+ {0x95E125D2EE3ED656, (byte)SymbolMain}, // in Warm-up Tunnel
+ {0xA7F495799F209587, (byte)SymbolMain}, // in the Potbottom Desert
+ {0x30AAD92559FCE81E, (byte)SymbolMain}, // in the Workout Sea // Ground Spawns
+ {0x6F748A46C8E3802C, (byte)Surfing}, // in the Workout Sea // Surfing
+ {0x97A3E0687E3C5B01, (byte)Surfing}, // in the Stepping-Stone Sea // Surfing
+ {0xDDDFF88957FD5B5C, (byte)SymbolMain}, // in the Insular Sea // Ground Spawns
+ {0xF3036CD294CE9365, (byte)SymbolMain}, // in the Stepping-Stone Sea // Ground Spawns
+ {0xFB9BB438425D58DA, (byte)Surfing}, // in the Insular Sea // Surfing
+ {0xC16C1E2A1B5FFE87, (byte)Surfing}, // in the Honeycalm Sea // Surfing
+ {0x081D7EF6A1C192B1, (byte)SymbolMain}, // on Honeycalm Island // Ground Spawns
+ {0x86EFBF49516B5555, (byte)Surfing}, // on Honeycalm Island // Surfing
+ {0x39AB700A9F1AB71F, (byte)Surfing}, // in the Training Lowlands // Surfing
+ {0x96C6A2A36131F383, (byte)Sharpedo}, // in the Stepping-Stone Sea // Sharpedo
+ {0xC92D06352150C78A, (byte)Sharpedo}, // in the Insular Sea // Sharpedo
+ {0xED1F9772AA35C3CD, (byte)Sharpedo}, // in the Workout Sea // Sharpedo
+ {0x9C0049D3E6129924, (byte)Sharpedo}, // in the Honeycalm Sea // Sharpedo
- // DLC 2 - Crown Tundra
- {0x87E14B7187BC1CC1, (byte)HiddenMain}, // on Slippery Slope
- {0x87E1487187BC17A8, (byte)Inaccessible}, // in Freezington
- {0x87E1497187BC195B, (byte)HiddenMain}, // in Frostpoint Field
- {0x87E14E7187BC21DA, (byte)HiddenMain}, // in the Giant's Bed
- {0x87E14F7187BC238D, (byte)HiddenMain}, // in the Old Cemetery
- {0x87E14C7187BC1E74, (byte)HiddenMain}, // on Snowslide Slope
- {0x87E14D7187BC2027, (byte)Inaccessible}, // in the Tunnel to the Top
- {0x87E1427187BC0D76, (byte)Inaccessible}, // on the Path to the Peak
- {0x87E1437187BC0F29, (byte)Inaccessible}, // at the Crown Shrine
- {0x87E4507187BE5B17, (byte)HiddenMain}, // at the Giant's Foot
- {0x87E44F7187BE5964, (byte)Inaccessible}, // in Roaring-Sea Caves
- {0x87E4527187BE5E7D, (byte)HiddenMain}, // at the Frigid Sea
- {0x87E4517187BE5CCA, (byte)HiddenMain}, // in Three-Point Pass
- {0x87DA3F7187B5E9AF, (byte)HiddenMain}, // at Ballimere Lake
- {0x87DA407187B5EB62, (byte)Inaccessible}, // in Lakeside Cave
- {0x87DA417187B5ED15, (byte)Inaccessible}, // at Dyna Tree Hill
+ // DLC 2 - Crown Tundra
+ {0x87E14B7187BC1CC1, (byte)HiddenMain}, // on Slippery Slope
+ {0x87E1487187BC17A8, (byte)Inaccessible}, // in Freezington
+ {0x87E1497187BC195B, (byte)HiddenMain}, // in Frostpoint Field
+ {0x87E14E7187BC21DA, (byte)HiddenMain}, // in the Giant's Bed
+ {0x87E14F7187BC238D, (byte)HiddenMain}, // in the Old Cemetery
+ {0x87E14C7187BC1E74, (byte)HiddenMain}, // on Snowslide Slope
+ {0x87E14D7187BC2027, (byte)Inaccessible}, // in the Tunnel to the Top
+ {0x87E1427187BC0D76, (byte)Inaccessible}, // on the Path to the Peak
+ {0x87E1437187BC0F29, (byte)Inaccessible}, // at the Crown Shrine
+ {0x87E4507187BE5B17, (byte)HiddenMain}, // at the Giant's Foot
+ {0x87E44F7187BE5964, (byte)Inaccessible}, // in Roaring-Sea Caves
+ {0x87E4527187BE5E7D, (byte)HiddenMain}, // at the Frigid Sea
+ {0x87E4517187BE5CCA, (byte)HiddenMain}, // in Three-Point Pass
+ {0x87DA3F7187B5E9AF, (byte)HiddenMain}, // at Ballimere Lake
+ {0x87DA407187B5EB62, (byte)Inaccessible}, // in Lakeside Cave
+ {0x87DA417187B5ED15, (byte)Inaccessible}, // at Dyna Tree Hill
- {0xD6EA3DE40B009E55, (byte)SymbolMain}, // on Slippery Slope
- {0xADF616908BD308DF, (byte)SymbolMain}, // in Frostpoint Field
- {0x308C5EB6A846D1F0, (byte)SymbolMain}, // in the Giant's Bed
- {0x50E781F91B97C049, (byte)SymbolMain}, // in the Old Cemetery
- {0xC303110BF1EC3322, (byte)SymbolMain}, // on Snowslide Slope
- {0xB768660B0BF4C0C3, (byte)SymbolMain}, // in the Tunnel to the Top
- {0xFCB78AFCCECAF094, (byte)SymbolMain}, // on the Path to the Peak
- {0xA345459C03EA6673, (byte)SymbolMain}, // at the Giant's Foot
- {0xE4A982819ACF7292, (byte)SymbolMain}, // in Roaring-Sea Caves
- {0x18AAF85178C7B839, (byte)SymbolMain}, // at the Frigid Sea
- {0x3EC6FCDC0C77D460, (byte)SymbolMain}, // in Three-Point Pass
- {0xE5225F9325CCA74B, (byte)SymbolMain}, // at Ballimere Lake
- {0x2F1B41507D695958, (byte)SymbolMain}, // in Lakeside Cave
+ {0xD6EA3DE40B009E55, (byte)SymbolMain}, // on Slippery Slope
+ {0xADF616908BD308DF, (byte)SymbolMain}, // in Frostpoint Field
+ {0x308C5EB6A846D1F0, (byte)SymbolMain}, // in the Giant's Bed
+ {0x50E781F91B97C049, (byte)SymbolMain}, // in the Old Cemetery
+ {0xC303110BF1EC3322, (byte)SymbolMain}, // on Snowslide Slope
+ {0xB768660B0BF4C0C3, (byte)SymbolMain}, // in the Tunnel to the Top
+ {0xFCB78AFCCECAF094, (byte)SymbolMain}, // on the Path to the Peak
+ {0xA345459C03EA6673, (byte)SymbolMain}, // at the Giant's Foot
+ {0xE4A982819ACF7292, (byte)SymbolMain}, // in Roaring-Sea Caves
+ {0x18AAF85178C7B839, (byte)SymbolMain}, // at the Frigid Sea
+ {0x3EC6FCDC0C77D460, (byte)SymbolMain}, // in Three-Point Pass
+ {0xE5225F9325CCA74B, (byte)SymbolMain}, // at Ballimere Lake
+ {0x2F1B41507D695958, (byte)SymbolMain}, // in Lakeside Cave
- {0xF8A59FCA719D1EAE, (byte)Surfing}, // in the Giant's Bed (Surfing), also used for 222 (in the Giant's Foot Surfing)
- {0x55D8F226A42368B7, (byte)Surfing}, // in Roaring-Sea Caves (Surfing)
- {0x78536116469DC44D, (byte)Surfing}, // at the Frigid Sea (Surfing)
- {0x9BDD6D11FFBEDA3F, (byte)Surfing}, // at Ballimere Lake (Surfing)
- };
- }
-}
+ {0xF8A59FCA719D1EAE, (byte)Surfing}, // in the Giant's Bed (Surfing), also used for 222 (in the Giant's Foot Surfing)
+ {0x55D8F226A42368B7, (byte)Surfing}, // in Roaring-Sea Caves (Surfing)
+ {0x78536116469DC44D, (byte)Surfing}, // at the Frigid Sea (Surfing)
+ {0x9BDD6D11FFBEDA3F, (byte)Surfing}, // at Ballimere Lake (Surfing)
+ };
+}
\ No newline at end of file
diff --git a/pkNX.Game/Text/TextManager.cs b/pkNX.Game/Text/TextManager.cs
index 07f79d4c..c0a450e6 100644
--- a/pkNX.Game/Text/TextManager.cs
+++ b/pkNX.Game/Text/TextManager.cs
@@ -4,48 +4,47 @@
using pkNX.Containers;
using pkNX.Structures;
-namespace pkNX.Game
+namespace pkNX.Game;
+
+public class TextManager
{
- public class TextManager
+ private readonly TextConfig Config;
+ private readonly IReadOnlyCollection References;
+
+ private readonly Dictionary Cache = new();
+
+ public void ClearCache() => Cache.Clear();
+
+ public TextManager(GameVersion game, TextConfig config = null)
{
- private readonly TextConfig Config;
- private readonly IReadOnlyCollection References;
-
- private readonly Dictionary Cache = new();
-
- public void ClearCache() => Cache.Clear();
-
- public TextManager(GameVersion game, TextConfig config = null)
- {
- References = TextMapping.GetMapping(game);
- Config = config ?? new TextConfig(game);
- }
-
- internal string[] GetStrings(byte[] data, bool remap = false)
- {
- var txt = new TextFile(data, Config, remap);
- return txt.Lines;
- }
-
- internal string[] GetStrings(TextName file, IFileContainer textFile, bool remap = false)
- {
- if (Cache.TryGetValue(file, out var container))
- return container;
-
- var info = References.FirstOrDefault(f => f.Name == file);
- if (info == null)
- throw new ArgumentException($"Unknown {nameof(TextName)} provided.", file.ToString());
-
- byte[] data;
- string path = info.FileName;
- if (!string.IsNullOrWhiteSpace(path) && textFile is FolderContainer c)
- data = c.GetFileData(info.FileName);
- else
- data = textFile[info.Index];
-
- var lines = GetStrings(data, remap);
- Cache.Add(file, lines);
- return lines;
- }
+ References = TextMapping.GetMapping(game);
+ Config = config ?? new TextConfig(game);
}
-}
+
+ internal string[] GetStrings(byte[] data, bool remap = false)
+ {
+ var txt = new TextFile(data, Config, remap);
+ return txt.Lines;
+ }
+
+ internal string[] GetStrings(TextName file, IFileContainer textFile, bool remap = false)
+ {
+ if (Cache.TryGetValue(file, out var container))
+ return container;
+
+ var info = References.FirstOrDefault(f => f.Name == file);
+ if (info == null)
+ throw new ArgumentException($"Unknown {nameof(TextName)} provided.", file.ToString());
+
+ byte[] data;
+ string path = info.FileName;
+ if (!string.IsNullOrWhiteSpace(path) && textFile is FolderContainer c)
+ data = c.GetFileData(info.FileName);
+ else
+ data = textFile[info.Index];
+
+ var lines = GetStrings(data, remap);
+ Cache.Add(file, lines);
+ return lines;
+ }
+}
\ No newline at end of file
diff --git a/pkNX.Game/Text/TextMapping.cs b/pkNX.Game/Text/TextMapping.cs
index 8bf2ecc8..3056d9aa 100644
--- a/pkNX.Game/Text/TextMapping.cs
+++ b/pkNX.Game/Text/TextMapping.cs
@@ -1,205 +1,204 @@
using System.Collections.Generic;
using pkNX.Structures;
-namespace pkNX.Game
+namespace pkNX.Game;
+
+public static class TextMapping
{
- public static class TextMapping
+ public static IReadOnlyCollection GetMapping(GameVersion game)
{
- public static IReadOnlyCollection GetMapping(GameVersion game)
+ return game switch
{
- return game switch
- {
- GameVersion.XY => XY,
- GameVersion.ORASDEMO => AO,
- GameVersion.ORAS => AO,
- GameVersion.SMDEMO => SMDEMO,
- GameVersion.SN => SM,
- GameVersion.MN => SM,
- GameVersion.US => USUM,
- GameVersion.UM => USUM,
- GameVersion.GP => GG,
- GameVersion.GE => GG,
- GameVersion.GG => GG,
- GameVersion.SW => SWSH,
- GameVersion.SH => SWSH,
- GameVersion.SWSH => SWSH,
- GameVersion.PLA => PLA,
- _ => null
- };
- }
-
- private static readonly TextReference[] XY =
- {
- new(005, TextName.Forms),
- new(013, TextName.MoveNames),
- new(015, TextName.MoveFlavor),
- new(017, TextName.Types),
- new(020, TextName.TrainerClasses),
- new(021, TextName.TrainerNames),
- new(022, TextName.TrainerText),
- new(034, TextName.AbilityNames),
- new(047, TextName.Natures),
- new(072, TextName.metlist_00000),
- new(080, TextName.SpeciesNames),
- new(096, TextName.ItemNames),
- new(099, TextName.ItemFlavor),
- new(130, TextName.MaisonTrainerNames),
- new(131, TextName.SuperTrainerNames),
- new(141, TextName.OPowerFlavor),
- };
-
- private static readonly TextReference[] AO =
- {
- new(005, TextName.Forms),
- new(014, TextName.MoveNames),
- new(016, TextName.MoveFlavor),
- new(018, TextName.Types),
- new(021, TextName.TrainerClasses),
- new(022, TextName.TrainerNames),
- new(023, TextName.TrainerText),
- new(037, TextName.AbilityNames),
- new(051, TextName.Natures),
- new(090, TextName.metlist_00000),
- new(098, TextName.SpeciesNames),
- new(114, TextName.ItemNames),
- new(117, TextName.ItemFlavor),
- new(153, TextName.MaisonTrainerNames),
- new(154, TextName.SuperTrainerNames),
- new(165, TextName.OPowerFlavor),
- };
-
- private static readonly TextReference[] SMDEMO =
- {
- new(020, TextName.ItemFlavor),
- new(021, TextName.ItemNames),
- new(026, TextName.SpeciesNames),
- new(030, TextName.metlist_00000),
- new(044, TextName.Forms),
- new(044, TextName.Natures),
- new(046, TextName.AbilityNames),
- new(049, TextName.TrainerText),
- new(050, TextName.TrainerNames),
- new(051, TextName.TrainerClasses),
- new(052, TextName.Types),
- new(054, TextName.MoveFlavor),
- new(055, TextName.MoveNames),
- };
-
- private static readonly TextReference[] SM =
- {
- new(035, TextName.ItemFlavor),
- new(036, TextName.ItemNames),
- new(055, TextName.SpeciesNames),
- new(067, TextName.metlist_00000),
- new(086, TextName.BattleRoyalNames),
- new(087, TextName.Natures),
- new(096, TextName.AbilityNames),
- new(099, TextName.BattleTreeNames),
- new(104, TextName.TrainerText),
- new(105, TextName.TrainerNames),
- new(106, TextName.TrainerClasses),
- new(107, TextName.Types),
- new(112, TextName.MoveFlavor),
- new(113, TextName.MoveNames),
- new(114, TextName.Forms),
- new(116, TextName.SpeciesClassifications),
- new(119, TextName.PokedexEntry1),
- new(120, TextName.PokedexEntry2)
- };
-
- private static readonly TextReference[] USUM =
- {
- new(039, TextName.ItemFlavor),
- new(040, TextName.ItemNames),
- new(060, TextName.SpeciesNames),
- new(072, TextName.metlist_00000),
- new(091, TextName.BattleRoyalNames),
- new(092, TextName.Natures),
- new(101, TextName.AbilityNames),
- new(104, TextName.BattleTreeNames),
- new(109, TextName.TrainerText),
- new(110, TextName.TrainerNames),
- new(111, TextName.TrainerClasses),
- new(112, TextName.Types),
- new(117, TextName.MoveFlavor),
- new(118, TextName.MoveNames),
- new(119, TextName.Forms),
- new(121, TextName.SpeciesClassifications),
- new(124, TextName.PokedexEntry1),
- new(125, TextName.PokedexEntry2)
- };
-
- private static readonly TextReference[] GG =
- {
- new("iteminfo.dat", TextName.ItemFlavor),
- new("itemname.dat", TextName.ItemNames),
- new("monsname.dat", TextName.SpeciesNames),
- new("place_name.dat", TextName.metlist_00000),
- new("seikaku.dat", TextName.Natures),
- new("tokusei.dat", TextName.AbilityNames),
- new("tokuseiinfo.dat", TextName.AbilityFlavor),
- new("trname.dat", TextName.TrainerNames),
- new("trtype.dat", TextName.TrainerClasses),
- new("trmsg.dat", TextName.TrainerText),
- new("typename.dat", TextName.Types),
- new("wazainfo.dat", TextName.MoveFlavor),
- new("wazaname.dat", TextName.MoveNames),
- new("zkn_form.dat", TextName.Forms),
- new("zkn_type.dat", TextName.SpeciesClassifications),
- new("zukan_comment_A.dat", TextName.PokedexEntry1),
- };
-
- private static readonly TextReference[] SWSH =
- {
- new("iteminfo.dat", TextName.ItemFlavor),
- new("itemname.dat", TextName.ItemNames),
- new("monsname.dat", TextName.SpeciesNames),
- new("place_name_indirect.dat", TextName.metlist_00000),
- new("place_name_spe.dat", TextName.metlist_30000),
- new("place_name_out.dat", TextName.metlist_40000),
- new("place_name_per.dat", TextName.metlist_60000),
- new("seikaku.dat", TextName.Natures),
- new("tokusei.dat", TextName.AbilityNames),
- new("tokuseiinfo.dat", TextName.AbilityFlavor),
- new("trname.dat", TextName.TrainerNames),
- new("trtype.dat", TextName.TrainerClasses),
- new("trmsg.dat", TextName.TrainerText),
- new("typename.dat", TextName.Types),
- new("wazainfo.dat", TextName.MoveFlavor),
- new("wazaname.dat", TextName.MoveNames),
- new("zkn_form.dat", TextName.Forms),
- new("zkn_type.dat", TextName.SpeciesClassifications),
- new("zukan_comment_A.dat", TextName.PokedexEntry1),
- new("zukan_comment_B.dat", TextName.PokedexEntry2),
- new("ribbon.dat", TextName.RibbonMark),
- new("poke_memory_feeling.dat", TextName.MemoryFeelings),
- };
-
-
- private static readonly TextReference[] PLA =
- {
- new("iteminfo.dat", TextName.ItemFlavor),
- new("itemname.dat", TextName.ItemNames),
- new("monsname.dat", TextName.SpeciesNames),
- new("place_name_indirect.dat", TextName.metlist_00000),
- new("place_name_spe.dat", TextName.metlist_30000),
- new("place_name_out.dat", TextName.metlist_40000),
- new("place_name_per.dat", TextName.metlist_60000),
- new("seikaku.dat", TextName.Natures),
- new("tokusei.dat", TextName.AbilityNames),
- new("tokuseiinfo.dat", TextName.AbilityFlavor),
- new("trname.dat", TextName.TrainerNames),
- new("trtype.dat", TextName.TrainerClasses),
- new("trmsg.dat", TextName.TrainerText),
- new("typename.dat", TextName.Types),
- new("wazainfo.dat", TextName.MoveFlavor),
- new("wazaname.dat", TextName.MoveNames),
- new("zkn_form.dat", TextName.Forms),
- new("zkn_type.dat", TextName.SpeciesClassifications),
- new("zukan_comment_A.dat", TextName.PokedexEntry1),
- new("zukan_comment_B.dat", TextName.PokedexEntry2),
- new("ribbon.dat", TextName.RibbonMark),
- new("poke_memory_feeling.dat", TextName.MemoryFeelings),
+ GameVersion.XY => XY,
+ GameVersion.ORASDEMO => AO,
+ GameVersion.ORAS => AO,
+ GameVersion.SMDEMO => SMDEMO,
+ GameVersion.SN => SM,
+ GameVersion.MN => SM,
+ GameVersion.US => USUM,
+ GameVersion.UM => USUM,
+ GameVersion.GP => GG,
+ GameVersion.GE => GG,
+ GameVersion.GG => GG,
+ GameVersion.SW => SWSH,
+ GameVersion.SH => SWSH,
+ GameVersion.SWSH => SWSH,
+ GameVersion.PLA => PLA,
+ _ => null
};
}
+
+ private static readonly TextReference[] XY =
+ {
+ new(005, TextName.Forms),
+ new(013, TextName.MoveNames),
+ new(015, TextName.MoveFlavor),
+ new(017, TextName.Types),
+ new(020, TextName.TrainerClasses),
+ new(021, TextName.TrainerNames),
+ new(022, TextName.TrainerText),
+ new(034, TextName.AbilityNames),
+ new(047, TextName.Natures),
+ new(072, TextName.metlist_00000),
+ new(080, TextName.SpeciesNames),
+ new(096, TextName.ItemNames),
+ new(099, TextName.ItemFlavor),
+ new(130, TextName.MaisonTrainerNames),
+ new(131, TextName.SuperTrainerNames),
+ new(141, TextName.OPowerFlavor),
+ };
+
+ private static readonly TextReference[] AO =
+ {
+ new(005, TextName.Forms),
+ new(014, TextName.MoveNames),
+ new(016, TextName.MoveFlavor),
+ new(018, TextName.Types),
+ new(021, TextName.TrainerClasses),
+ new(022, TextName.TrainerNames),
+ new(023, TextName.TrainerText),
+ new(037, TextName.AbilityNames),
+ new(051, TextName.Natures),
+ new(090, TextName.metlist_00000),
+ new(098, TextName.SpeciesNames),
+ new(114, TextName.ItemNames),
+ new(117, TextName.ItemFlavor),
+ new(153, TextName.MaisonTrainerNames),
+ new(154, TextName.SuperTrainerNames),
+ new(165, TextName.OPowerFlavor),
+ };
+
+ private static readonly TextReference[] SMDEMO =
+ {
+ new(020, TextName.ItemFlavor),
+ new(021, TextName.ItemNames),
+ new(026, TextName.SpeciesNames),
+ new(030, TextName.metlist_00000),
+ new(044, TextName.Forms),
+ new(044, TextName.Natures),
+ new(046, TextName.AbilityNames),
+ new(049, TextName.TrainerText),
+ new(050, TextName.TrainerNames),
+ new(051, TextName.TrainerClasses),
+ new(052, TextName.Types),
+ new(054, TextName.MoveFlavor),
+ new(055, TextName.MoveNames),
+ };
+
+ private static readonly TextReference[] SM =
+ {
+ new(035, TextName.ItemFlavor),
+ new(036, TextName.ItemNames),
+ new(055, TextName.SpeciesNames),
+ new(067, TextName.metlist_00000),
+ new(086, TextName.BattleRoyalNames),
+ new(087, TextName.Natures),
+ new(096, TextName.AbilityNames),
+ new(099, TextName.BattleTreeNames),
+ new(104, TextName.TrainerText),
+ new(105, TextName.TrainerNames),
+ new(106, TextName.TrainerClasses),
+ new(107, TextName.Types),
+ new(112, TextName.MoveFlavor),
+ new(113, TextName.MoveNames),
+ new(114, TextName.Forms),
+ new(116, TextName.SpeciesClassifications),
+ new(119, TextName.PokedexEntry1),
+ new(120, TextName.PokedexEntry2)
+ };
+
+ private static readonly TextReference[] USUM =
+ {
+ new(039, TextName.ItemFlavor),
+ new(040, TextName.ItemNames),
+ new(060, TextName.SpeciesNames),
+ new(072, TextName.metlist_00000),
+ new(091, TextName.BattleRoyalNames),
+ new(092, TextName.Natures),
+ new(101, TextName.AbilityNames),
+ new(104, TextName.BattleTreeNames),
+ new(109, TextName.TrainerText),
+ new(110, TextName.TrainerNames),
+ new(111, TextName.TrainerClasses),
+ new(112, TextName.Types),
+ new(117, TextName.MoveFlavor),
+ new(118, TextName.MoveNames),
+ new(119, TextName.Forms),
+ new(121, TextName.SpeciesClassifications),
+ new(124, TextName.PokedexEntry1),
+ new(125, TextName.PokedexEntry2)
+ };
+
+ private static readonly TextReference[] GG =
+ {
+ new("iteminfo.dat", TextName.ItemFlavor),
+ new("itemname.dat", TextName.ItemNames),
+ new("monsname.dat", TextName.SpeciesNames),
+ new("place_name.dat", TextName.metlist_00000),
+ new("seikaku.dat", TextName.Natures),
+ new("tokusei.dat", TextName.AbilityNames),
+ new("tokuseiinfo.dat", TextName.AbilityFlavor),
+ new("trname.dat", TextName.TrainerNames),
+ new("trtype.dat", TextName.TrainerClasses),
+ new("trmsg.dat", TextName.TrainerText),
+ new("typename.dat", TextName.Types),
+ new("wazainfo.dat", TextName.MoveFlavor),
+ new("wazaname.dat", TextName.MoveNames),
+ new("zkn_form.dat", TextName.Forms),
+ new("zkn_type.dat", TextName.SpeciesClassifications),
+ new("zukan_comment_A.dat", TextName.PokedexEntry1),
+ };
+
+ private static readonly TextReference[] SWSH =
+ {
+ new("iteminfo.dat", TextName.ItemFlavor),
+ new("itemname.dat", TextName.ItemNames),
+ new("monsname.dat", TextName.SpeciesNames),
+ new("place_name_indirect.dat", TextName.metlist_00000),
+ new("place_name_spe.dat", TextName.metlist_30000),
+ new("place_name_out.dat", TextName.metlist_40000),
+ new("place_name_per.dat", TextName.metlist_60000),
+ new("seikaku.dat", TextName.Natures),
+ new("tokusei.dat", TextName.AbilityNames),
+ new("tokuseiinfo.dat", TextName.AbilityFlavor),
+ new("trname.dat", TextName.TrainerNames),
+ new("trtype.dat", TextName.TrainerClasses),
+ new("trmsg.dat", TextName.TrainerText),
+ new("typename.dat", TextName.Types),
+ new("wazainfo.dat", TextName.MoveFlavor),
+ new("wazaname.dat", TextName.MoveNames),
+ new("zkn_form.dat", TextName.Forms),
+ new("zkn_type.dat", TextName.SpeciesClassifications),
+ new("zukan_comment_A.dat", TextName.PokedexEntry1),
+ new("zukan_comment_B.dat", TextName.PokedexEntry2),
+ new("ribbon.dat", TextName.RibbonMark),
+ new("poke_memory_feeling.dat", TextName.MemoryFeelings),
+ };
+
+
+ private static readonly TextReference[] PLA =
+ {
+ new("iteminfo.dat", TextName.ItemFlavor),
+ new("itemname.dat", TextName.ItemNames),
+ new("monsname.dat", TextName.SpeciesNames),
+ new("place_name_indirect.dat", TextName.metlist_00000),
+ new("place_name_spe.dat", TextName.metlist_30000),
+ new("place_name_out.dat", TextName.metlist_40000),
+ new("place_name_per.dat", TextName.metlist_60000),
+ new("seikaku.dat", TextName.Natures),
+ new("tokusei.dat", TextName.AbilityNames),
+ new("tokuseiinfo.dat", TextName.AbilityFlavor),
+ new("trname.dat", TextName.TrainerNames),
+ new("trtype.dat", TextName.TrainerClasses),
+ new("trmsg.dat", TextName.TrainerText),
+ new("typename.dat", TextName.Types),
+ new("wazainfo.dat", TextName.MoveFlavor),
+ new("wazaname.dat", TextName.MoveNames),
+ new("zkn_form.dat", TextName.Forms),
+ new("zkn_type.dat", TextName.SpeciesClassifications),
+ new("zukan_comment_A.dat", TextName.PokedexEntry1),
+ new("zukan_comment_B.dat", TextName.PokedexEntry2),
+ new("ribbon.dat", TextName.RibbonMark),
+ new("poke_memory_feeling.dat", TextName.MemoryFeelings),
+ };
}
\ No newline at end of file
diff --git a/pkNX.Game/Text/TextName.cs b/pkNX.Game/Text/TextName.cs
index ba2d17a8..7b6c786d 100644
--- a/pkNX.Game/Text/TextName.cs
+++ b/pkNX.Game/Text/TextName.cs
@@ -1,39 +1,38 @@
-namespace pkNX.Game
+namespace pkNX.Game;
+
+public enum TextName
{
- public enum TextName
- {
- AbilityNames,
- AbilityFlavor,
+ AbilityNames,
+ AbilityFlavor,
- MoveNames,
- MoveFlavor,
+ MoveNames,
+ MoveFlavor,
- ItemNames,
- ItemFlavor,
+ ItemNames,
+ ItemFlavor,
- SpeciesNames,
- Types,
- Natures,
- Forms,
+ SpeciesNames,
+ Types,
+ Natures,
+ Forms,
- TrainerNames,
- TrainerClasses,
- TrainerText,
- metlist_00000,
- metlist_30000,
- metlist_40000,
- metlist_60000,
- OPowerFlavor,
- MaisonTrainerNames,
- SuperTrainerNames,
- BattleRoyalNames,
- BattleTreeNames,
+ TrainerNames,
+ TrainerClasses,
+ TrainerText,
+ metlist_00000,
+ metlist_30000,
+ metlist_40000,
+ metlist_60000,
+ OPowerFlavor,
+ MaisonTrainerNames,
+ SuperTrainerNames,
+ BattleRoyalNames,
+ BattleTreeNames,
- SpeciesClassifications,
- PokedexEntry1,
- PokedexEntry2,
+ SpeciesClassifications,
+ PokedexEntry1,
+ PokedexEntry2,
- RibbonMark,
- MemoryFeelings
- }
+ RibbonMark,
+ MemoryFeelings
}
\ No newline at end of file
diff --git a/pkNX.Game/Text/TextReference.cs b/pkNX.Game/Text/TextReference.cs
index ee89309c..e9ba14c0 100644
--- a/pkNX.Game/Text/TextReference.cs
+++ b/pkNX.Game/Text/TextReference.cs
@@ -1,22 +1,21 @@
-namespace pkNX.Game
+namespace pkNX.Game;
+
+public class TextReference
{
- public class TextReference
+ public readonly int Index;
+ public readonly TextName Name;
+ public readonly string FileName;
+
+ internal TextReference(int index, TextName name)
{
- public readonly int Index;
- public readonly TextName Name;
- public readonly string FileName;
-
- internal TextReference(int index, TextName name)
- {
- Index = index;
- Name = name;
- }
-
- internal TextReference(string fileName, TextName name)
- {
- FileName = fileName;
- Name = name;
- }
+ Index = index;
+ Name = name;
}
-}
+
+ internal TextReference(string fileName, TextName name)
+ {
+ FileName = fileName;
+ Name = name;
+ }
+}
\ No newline at end of file
diff --git a/pkNX.Game/pkNX.Game.csproj b/pkNX.Game/pkNX.Game.csproj
index cfd0c190..b27952ee 100644
--- a/pkNX.Game/pkNX.Game.csproj
+++ b/pkNX.Game/pkNX.Game.csproj
@@ -1,16 +1,11 @@
- netstandard2.0;net461
+ net6.0
Game Data Manager
10
-
-
-
-
-
diff --git a/pkNX.Randomization/Randomizers/EvolutionRandomizer.cs b/pkNX.Randomization/Randomizers/EvolutionRandomizer.cs
index 9bdeb1b9..650e1b33 100644
--- a/pkNX.Randomization/Randomizers/EvolutionRandomizer.cs
+++ b/pkNX.Randomization/Randomizers/EvolutionRandomizer.cs
@@ -1,135 +1,134 @@
-using pkNX.Structures;
+using pkNX.Structures;
-namespace pkNX.Randomization
+namespace pkNX.Randomization;
+
+///
+/// randomizer.
+///
+public class EvolutionRandomizer : Randomizer
{
- ///
- /// randomizer.
- ///
- public class EvolutionRandomizer : Randomizer
+ private readonly EvolutionSet[] Evolutions;
+ private readonly GameInfo Game;
+ private readonly IPersonalTable Personal;
+ public readonly SpeciesRandomizer RandSpec;
+ public readonly FormRandomizer RandForm;
+
+ public EvolutionRandomizer(GameInfo game, EvolutionSet[] evolutions, IPersonalTable t)
{
- private readonly EvolutionSet[] Evolutions;
- private readonly GameInfo Game;
- private readonly IPersonalTable Personal;
- public readonly SpeciesRandomizer RandSpec;
- public readonly FormRandomizer RandForm;
+ Game = game;
+ Personal = t;
+ Evolutions = evolutions;
+ RandSpec = new SpeciesRandomizer(Game, t);
+ RandForm = new FormRandomizer(t);
+ }
- public EvolutionRandomizer(GameInfo game, EvolutionSet[] evolutions, IPersonalTable t)
+ public override void Execute()
+ {
+ for (var i = 0; i < Evolutions.Length; i++)
{
- Game = game;
- Personal = t;
- Evolutions = evolutions;
- RandSpec = new SpeciesRandomizer(Game, t);
- RandForm = new FormRandomizer(t);
+ var evo = Evolutions[i];
+ if (Personal[i].HP == 0)
+ continue;
+ Randomize(evo, i);
}
+ }
- public override void Execute()
+ public void ExecuteTrade()
+ {
+ for (var i = 0; i < Evolutions.Length; i++)
{
- for (var i = 0; i < Evolutions.Length; i++)
- {
- var evo = Evolutions[i];
- if (Personal[i].HP == 0)
- continue;
- Randomize(evo, i);
- }
+ var evo = Evolutions[i];
+ ReplaceTradeMethods(evo, i);
}
+ }
- public void ExecuteTrade()
+ public void ExecuteEvolveEveryLevel()
+ {
+ for (var i = 0; i < Evolutions.Length; i++)
{
- for (var i = 0; i < Evolutions.Length; i++)
- {
- var evo = Evolutions[i];
- ReplaceTradeMethods(evo, i);
- }
+ var evo = Evolutions[i];
+ if (Personal[i].HP == 0)
+ continue;
+ Personal[i].EXPGrowth = (int)EXPGroup.Slow; // keep everything the same to preserve levels after evolving
+ MakeEvolveEveryLevel(evo, i);
}
+ }
- public void ExecuteEvolveEveryLevel()
+ private void Randomize(EvolutionSet evos, int species)
+ {
+ foreach (var evo in evos.PossibleEvolutions)
{
- for (var i = 0; i < Evolutions.Length; i++)
+ if (evo.Method != 0)
{
- var evo = Evolutions[i];
- if (Personal[i].HP == 0)
- continue;
- Personal[i].EXPGrowth = (int)EXPGroup.Slow; // keep everything the same to preserve levels after evolving
- MakeEvolveEveryLevel(evo, i);
- }
- }
-
- private void Randomize(EvolutionSet evos, int species)
- {
- foreach (var evo in evos.PossibleEvolutions)
- {
- if (evo.Method != 0)
- {
- evo.Species = (ushort)RandSpec.GetRandomSpecies(evo.Species, species);
- evo.Form = (byte)RandForm.GetRandomForme(evo.Species, false, false, true, Game.SWSH, Personal.Table);
- }
- }
- }
-
- private void ReplaceTradeMethods(EvolutionSet evos, int species)
- {
- for (var i = 0; i < evos.PossibleEvolutions.Length; i++)
- {
- var evo = evos.PossibleEvolutions[i];
- ReplaceTradeMethod(evo, species, i);
- }
- }
-
- private void ReplaceTradeMethod(EvolutionMethod evo, int species, int evoIndex)
- {
- switch (evo.Method)
- {
- case EvolutionType.Trade when Game.Generation == 6:
- evo.Method = EvolutionType.LevelUp; // trade -> level up
- evo.Argument = 30;
- return;
- case EvolutionType.Trade when Game.Generation >= 7:
- evo.Method = EvolutionType.LevelUp; // trade -> level up
- evo.Level = 30;
- return;
- case EvolutionType.TradeHeldItem:
- evo.Method = EvolutionType.LevelUpHeldItemDay;
- return;
- case EvolutionType.TradeShelmetKarrablast:
- evo.Method = EvolutionType.LevelUpWithTeammate;
- if (species == (int)Species.Karrablast)
- evo.Argument = (int)Species.Shelmet; // Karrablast with Shelmet
- if (species == (int)Species.Shelmet)
- evo.Argument = (int)Species.Karrablast; // Shelmet with Karrablast
- return;
-
- case EvolutionType.LevelUpVersion:
- evo.Method = evoIndex == 0 ? EvolutionType.LevelUpECl5 : EvolutionType.LevelUpECgeq5;
- evo.Argument = 0; // clear ver
- return;
- case EvolutionType.LevelUpVersionDay:
- evo.Method = EvolutionType.LevelUpFriendshipMorning;
- evo.Argument = 0; // clear ver
- return;
- case EvolutionType.LevelUpVersionNight:
- evo.Method = EvolutionType.LevelUpFriendshipNight;
- evo.Argument = 0; // clear ver
- return;
- }
- }
-
- private static void MakeEvolveEveryLevel(EvolutionSet evos, int species)
- {
- var evoSet = evos.PossibleEvolutions;
- evoSet[0] = new EvolutionMethod
- {
- Argument = 0, // clear
- Form = 0, // randomized later
- Level = 1,
- Method = EvolutionType.LevelUp,
- Species = (ushort)species, // randomized later
- };
-
- if (evoSet[1].HasData) // has other branched evolutions; remove them
- {
- for (int i = 1; i < evoSet.Length; i++)
- evoSet[i] = new EvolutionMethod();
+ evo.Species = (ushort)RandSpec.GetRandomSpecies(evo.Species, species);
+ evo.Form = (byte)RandForm.GetRandomForme(evo.Species, false, false, true, Game.SWSH, Personal.Table);
}
}
}
-}
\ No newline at end of file
+
+ private void ReplaceTradeMethods(EvolutionSet evos, int species)
+ {
+ for (var i = 0; i < evos.PossibleEvolutions.Length; i++)
+ {
+ var evo = evos.PossibleEvolutions[i];
+ ReplaceTradeMethod(evo, species, i);
+ }
+ }
+
+ private void ReplaceTradeMethod(EvolutionMethod evo, int species, int evoIndex)
+ {
+ switch (evo.Method)
+ {
+ case EvolutionType.Trade when Game.Generation == 6:
+ evo.Method = EvolutionType.LevelUp; // trade -> level up
+ evo.Argument = 30;
+ return;
+ case EvolutionType.Trade when Game.Generation >= 7:
+ evo.Method = EvolutionType.LevelUp; // trade -> level up
+ evo.Level = 30;
+ return;
+ case EvolutionType.TradeHeldItem:
+ evo.Method = EvolutionType.LevelUpHeldItemDay;
+ return;
+ case EvolutionType.TradeShelmetKarrablast:
+ evo.Method = EvolutionType.LevelUpWithTeammate;
+ if (species == (int)Species.Karrablast)
+ evo.Argument = (int)Species.Shelmet; // Karrablast with Shelmet
+ if (species == (int)Species.Shelmet)
+ evo.Argument = (int)Species.Karrablast; // Shelmet with Karrablast
+ return;
+
+ case EvolutionType.LevelUpVersion:
+ evo.Method = evoIndex == 0 ? EvolutionType.LevelUpECl5 : EvolutionType.LevelUpECgeq5;
+ evo.Argument = 0; // clear ver
+ return;
+ case EvolutionType.LevelUpVersionDay:
+ evo.Method = EvolutionType.LevelUpFriendshipMorning;
+ evo.Argument = 0; // clear ver
+ return;
+ case EvolutionType.LevelUpVersionNight:
+ evo.Method = EvolutionType.LevelUpFriendshipNight;
+ evo.Argument = 0; // clear ver
+ return;
+ }
+ }
+
+ private static void MakeEvolveEveryLevel(EvolutionSet evos, int species)
+ {
+ var evoSet = evos.PossibleEvolutions;
+ evoSet[0] = new EvolutionMethod
+ {
+ Argument = 0, // clear
+ Form = 0, // randomized later
+ Level = 1,
+ Method = EvolutionType.LevelUp,
+ Species = (ushort)species, // randomized later
+ };
+
+ if (evoSet[1].HasData) // has other branched evolutions; remove them
+ {
+ for (int i = 1; i < evoSet.Length; i++)
+ evoSet[i] = new EvolutionMethod();
+ }
+ }
+}
diff --git a/pkNX.Randomization/Randomizers/FormRandomizer.cs b/pkNX.Randomization/Randomizers/FormRandomizer.cs
index dd5127df..482f12f4 100644
--- a/pkNX.Randomization/Randomizers/FormRandomizer.cs
+++ b/pkNX.Randomization/Randomizers/FormRandomizer.cs
@@ -1,84 +1,82 @@
-using pkNX.Structures;
+using pkNX.Structures;
using System;
-using System.Linq;
using static pkNX.Structures.Species;
-namespace pkNX.Randomization
+namespace pkNX.Randomization;
+
+public class FormRandomizer
{
- public class FormRandomizer
+ private readonly IPersonalTable Personal;
+
+ public FormRandomizer(IPersonalTable t)
{
- private readonly IPersonalTable Personal;
+ Personal = t;
+ }
- public FormRandomizer(IPersonalTable t)
- {
- Personal = t;
- }
+ public int GetRandomForme(int species, bool mega, bool fused, bool alola, bool galar, IPersonalInfo[]? stats = null)
+ {
+ stats ??= Personal.Table;
+ if (stats[species].FormCount <= 1)
+ return 0;
+ bool IsGen6 = Personal.MaxSpeciesID == 721;
- public int GetRandomForme(int species, bool mega, bool fused, bool alola, bool galar, IPersonalInfo[]? stats = null)
+ switch ((Species)species)
{
- stats ??= Personal.Table;
- if (stats[species].FormCount <= 1)
+ // Rayquaza's Forme Count was unchanged in SWSH despite Megas being removed, so just in case the user allows random Megas, disallow this invalid Forme.
+ case Rayquaza when Personal.Table.Length == 1192:
return 0;
- bool IsGen6 = Personal.MaxSpeciesID == 721;
+ case Unown:
+ case Deerling:
+ case Sawsbuck:
+ return 31; // Random
+ case Greninja when !mega:
+ return 0;
+ case Scatterbug:
+ case Spewpa:
+ case Vivillon:
+ return 30; // save file specific
+ case Zygarde when !IsGen6:
+ return Util.Random.Next(4); // Complete Forme is battle only
+ case Minior:
+ return Util.Random.Next(7); // keep the core color a surprise
- switch ((Species)species)
+ case Meowth when galar:
+ return Util.Random.Next(3); // Kanto, Alola, Galar
+
+ // only allow Standard, not Zen Mode
+ case Darmanitan when galar:
{
- // Rayquaza's Forme Count was unchanged in SWSH despite Megas being removed, so just in case the user allows random Megas, disallow this invalid Forme.
- case Rayquaza when Personal.Table.Length == 1192:
- return 0;
- case Unown:
- case Deerling:
- case Sawsbuck:
- return 31; // Random
- case Greninja when !mega:
- return 0;
- case Scatterbug:
- case Spewpa:
- case Vivillon:
- return 30; // save file specific
- case Zygarde when !IsGen6:
- return Util.Random.Next(4); // Complete Forme is battle only
- case Minior:
- return Util.Random.Next(7); // keep the core color a surprise
-
- case Meowth when galar:
- return Util.Random.Next(3); // Kanto, Alola, Galar
-
- // only allow Standard, not Zen Mode
- case Darmanitan when galar:
- {
- int form = Util.Random.Next(stats[species].FormCount);
- return form & 2;
- }
-
- // some species have 1 invalid form among several other valid forms, handle them here
- case Pikachu when Personal.Table.Length == 1192:
- case Slowbro when galar:
- {
- int form = Util.Random.Next(stats[species].FormCount - 1);
- int banned = GetInvalidForm(species, galar, Personal);
- if (form == banned)
- form++;
- return form;
- }
+ int form = Util.Random.Next(stats[species].FormCount);
+ return form & 2;
}
- if (Personal.Table.Length == 980 && species is (int)Pikachu or (int)Eevee) // gg tableB -- no starters, they crash trainer battles.
- return 0;
- if (alola && Legal.EvolveToAlolanForms.Contains((ushort)species))
- return Util.Random.Next(2);
- if (galar && Legal.EvolveToGalarForms.Contains((ushort)species))
- return Util.Random.Next(2);
- if (!Legal.BattleExclusiveForms.Contains(species) || mega || (fused && Legal.BattleFusions.Contains(species)))
- return Util.Random.Next(stats[species].FormCount); // Slot-Random
- return 0;
+ // some species have 1 invalid form among several other valid forms, handle them here
+ case Pikachu when Personal.Table.Length == 1192:
+ case Slowbro when galar:
+ {
+ int form = Util.Random.Next(stats[species].FormCount - 1);
+ int banned = GetInvalidForm(species, galar, Personal);
+ if (form == banned)
+ form++;
+ return form;
+ }
}
- public static int GetInvalidForm(int species, bool galar, IPersonalTable stats) => species switch
- {
- (int)Pikachu when stats.Table.Length == 1192 => 8, // LGPE Partner Pikachu
- (int)Slowbro when galar => 1, // Mega Slowbro
- _ => throw new ArgumentOutOfRangeException(nameof(species))
- };
+ if (Personal.Table.Length == 980 && species is (int)Pikachu or (int)Eevee) // gg tableB -- no starters, they crash trainer battles.
+ return 0;
+ if (alola && Legal.EvolveToAlolanForms.Contains((ushort)species))
+ return Util.Random.Next(2);
+ if (galar && Legal.EvolveToGalarForms.Contains((ushort)species))
+ return Util.Random.Next(2);
+ if (!Legal.BattleExclusiveForms.Contains(species) || mega || (fused && Legal.BattleFusions.Contains(species)))
+ return Util.Random.Next(stats[species].FormCount); // Slot-Random
+ return 0;
}
+
+ public static int GetInvalidForm(int species, bool galar, IPersonalTable stats) => species switch
+ {
+ (int)Pikachu when stats.Table.Length == 1192 => 8, // LGPE Partner Pikachu
+ (int)Slowbro when galar => 1, // Mega Slowbro
+ _ => throw new ArgumentOutOfRangeException(nameof(species))
+ };
}
diff --git a/pkNX.Randomization/Randomizers/GenericRandomizer.cs b/pkNX.Randomization/Randomizers/GenericRandomizer.cs
index 90ee4573..493d85b6 100644
--- a/pkNX.Randomization/Randomizers/GenericRandomizer.cs
+++ b/pkNX.Randomization/Randomizers/GenericRandomizer.cs
@@ -1,43 +1,42 @@
-namespace pkNX.Randomization
+namespace pkNX.Randomization;
+
+/// Cyclical Shuffled Randomizer
+///
+/// The shuffled list is iterated over, and reshuffled when exhausted.
+/// The list does not repeat values until the list is exhausted.
+///
+public class GenericRandomizer
{
- /// Cyclical Shuffled Randomizer
- ///
- /// The shuffled list is iterated over, and reshuffled when exhausted.
- /// The list does not repeat values until the list is exhausted.
- ///
- public class GenericRandomizer
+ public GenericRandomizer(T[] randomValues)
{
- public GenericRandomizer(T[] randomValues)
- {
- RandomValues = randomValues;
- }
+ RandomValues = randomValues;
+ }
- private readonly T[] RandomValues;
- private int ctr;
- public int Count => RandomValues.Length;
+ private readonly T[] RandomValues;
+ private int ctr;
+ public int Count => RandomValues.Length;
- public void Reset()
- {
- ctr = 0;
+ public void Reset()
+ {
+ ctr = 0;
+ Util.Shuffle(RandomValues);
+ }
+
+ public T Next()
+ {
+ if (ctr == 0)
Util.Shuffle(RandomValues);
- }
- public T Next()
- {
- if (ctr == 0)
- Util.Shuffle(RandomValues);
+ T value = RandomValues[ctr++];
+ ctr %= RandomValues.Length;
+ return value;
+ }
- T value = RandomValues[ctr++];
- ctr %= RandomValues.Length;
- return value;
- }
-
- public T[] GetMany(int count)
- {
- var arr = new T[count];
- for (int i = 0; i < arr.Length; i++)
- arr[i] = Next();
- return arr;
- }
+ public T[] GetMany(int count)
+ {
+ var arr = new T[count];
+ for (int i = 0; i < arr.Length; i++)
+ arr[i] = Next();
+ return arr;
}
}
diff --git a/pkNX.Randomization/Randomizers/LearnsetRandomizer.cs b/pkNX.Randomization/Randomizers/LearnsetRandomizer.cs
index d86aceeb..dc123f79 100644
--- a/pkNX.Randomization/Randomizers/LearnsetRandomizer.cs
+++ b/pkNX.Randomization/Randomizers/LearnsetRandomizer.cs
@@ -1,158 +1,157 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using pkNX.Structures;
-namespace pkNX.Randomization
+namespace pkNX.Randomization;
+
+///