From 7cfdb8a4663c2f843e994b8a9f2be6439b3b07c8 Mon Sep 17 00:00:00 2001 From: Kurt Date: Wed, 2 Mar 2022 18:05:13 -0800 Subject: [PATCH] Move hashing to SCBlockAccessor, fix #3455 Fixes #3455 by adding bool for insular sea, and adjusting overall progress values as listed. Closes #3456 (supersedes) Updates SCBlockAccessor to eliminate bounds check (integer overflow, which isn't possible with our array size), adds some overloads, and xmldoc. Co-Authored-By: Jonathan Herbert <3344332+foohyfooh@users.noreply.github.com> --- PKHeX.Core/Saves/Access/SCBlockAccessor.cs | 157 +++++++++--------- .../Saves/Encryption/SwishCrypto/FnvHash.cs | 9 +- PKHeX.Core/Saves/SAV8SWSH.cs | 24 +-- 3 files changed, 100 insertions(+), 90 deletions(-) diff --git a/PKHeX.Core/Saves/Access/SCBlockAccessor.cs b/PKHeX.Core/Saves/Access/SCBlockAccessor.cs index 8be6a270d..bf5fa8374 100644 --- a/PKHeX.Core/Saves/Access/SCBlockAccessor.cs +++ b/PKHeX.Core/Saves/Access/SCBlockAccessor.cs @@ -1,87 +1,92 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; -namespace PKHeX.Core +namespace PKHeX.Core; + +/// +/// block accessor, where blocks are ordered by ascending . +/// +public abstract class SCBlockAccessor : ISaveBlockAccessor { + public abstract IReadOnlyList BlockInfo { get; } + + /// + public object GetBlockValue(uint key) => GetBlock(key).GetValue(); + + /// + public void SetBlockValue(uint key, object value) => GetBlock(key).SetValue(value); + + /// Checks if there is any with the requested . + public bool HasBlock(uint key) => FindIndex(BlockInfo, key) != -1; + + /// Returns the reference with the corresponding . + public SCBlock GetBlock(uint key) => Find(BlockInfo, key); + + #region Ease of Use Overloads + /// + /// Block name (un-hashed) + public SCBlock GetBlock(string name) => GetBlock(Hash(name.AsSpan())); + + /// + public SCBlock GetBlock(ReadOnlySpan name) => GetBlock(Hash(name)); + private static uint Hash(ReadOnlySpan name) => (uint)FnvHash.HashFnv1a_64(name); + + /// + public SCBlock GetBlock(ReadOnlySpan name) => GetBlock(Hash(name)); + private static uint Hash(ReadOnlySpan name) => (uint)FnvHash.HashFnv1a_64(name); + #endregion + /// - /// block accessor, where blocks are ordered by ascending . + /// Tries to grab the actual block, and returns a new dummy if the block does not exist. /// - public abstract class SCBlockAccessor : ISaveBlockAccessor + /// Block Key + /// Block if exists, dummy if not. Dummy key will not match requested key. + public SCBlock GetBlockSafe(uint key) => FindOrDefault(BlockInfo, key); + + private static SCBlock Find(IReadOnlyList array, uint key) { - public abstract IReadOnlyList BlockInfo { get; } + var index = FindIndex(array, key); + if (index != -1) + return array[index]; + throw new KeyNotFoundException(nameof(key)); + } - public object GetBlockValue(uint key) => GetBlock(key).GetValue(); - public void SetBlockValue(uint key, object value) => GetBlock(key).SetValue(value); - public bool HasBlock(uint key) => FindIndex(BlockInfo, key) != -1; + private static SCBlock FindOrDefault(IReadOnlyList array, uint key) + { + var index = FindIndex(array, key); + if (index != -1) + return array[index]; + return new SCBlock(0, SCTypeCode.None); + } - // Rather than storing a dictionary of keys, we can abuse the fact that the SCBlock[] is stored in order of ascending block key. - // Binary Search doesn't require extra memory like a Dictionary would; also, we only need to find a few blocks. - public SCBlock GetBlock(uint key) => BinarySearch(BlockInfo, key); - - /// - /// Tries to grab the actual block, and returns a new dummy if the block does not exist. - /// - /// Block Key - /// Block if exists, dummy if not. Dummy key will not match requested key. - public SCBlock GetBlockSafe(uint key) => BinarySearchSafe(BlockInfo, key); - - private static SCBlock BinarySearch(IReadOnlyList arr, uint key) + /// + /// Finds a specified within the . + /// + /// + /// Rather than storing a dictionary of keys, we can abuse the fact that the is stored in order of ascending block key. + ///

+ /// Binary Search doesn't require extra memory like a Dictionary would; also, we usually only need to find a few blocks. + ///
+ /// Index-able collection + /// to find. + /// Returns -1 if no match found. + private static int FindIndex(IReadOnlyList array, uint key) + { + int min = 0; + int max = array.Count - 1; + do { - int min = 0; - int max = arr.Count - 1; - do - { - int mid = (min + max) / 2; - var entry = arr[mid]; - var ek = entry.Key; - if (key == ek) - return entry; + int mid = min + ((max - min) >> 1); + var entry = array[mid]; + var ek = entry.Key; + if (key == ek) + return mid; - if (key < ek) - max = mid - 1; - else - min = mid + 1; - } while (min <= max); - throw new KeyNotFoundException(nameof(key)); - } - - private static SCBlock BinarySearchSafe(IReadOnlyList arr, uint key) - { - int min = 0; - int max = arr.Count - 1; - do - { - int mid = (min + max) / 2; - var entry = arr[mid]; - var ek = entry.Key; - if (key == ek) - return entry; - - if (key < ek) - max = mid - 1; - else - min = mid + 1; - } while (min <= max); - return new SCBlock(0, SCTypeCode.None); - } - - private static int FindIndex(IReadOnlyList arr, uint key) - { - int min = 0; - int max = arr.Count - 1; - do - { - int mid = (min + max) / 2; - var entry = arr[mid]; - var ek = entry.Key; - if (key == ek) - return mid; - - if (key < ek) - max = mid - 1; - else - min = mid + 1; - } while (min <= max); - return -1; - } + if (key < ek) + max = mid - 1; + else + min = mid + 1; + } while (min <= max); + return -1; } } diff --git a/PKHeX.Core/Saves/Encryption/SwishCrypto/FnvHash.cs b/PKHeX.Core/Saves/Encryption/SwishCrypto/FnvHash.cs index 7215db485..40a294531 100644 --- a/PKHeX.Core/Saves/Encryption/SwishCrypto/FnvHash.cs +++ b/PKHeX.Core/Saves/Encryption/SwishCrypto/FnvHash.cs @@ -1,22 +1,23 @@ -using System.Collections.Generic; +using System; namespace PKHeX.Core; /// /// Fowler–Noll–Vo non-cryptographic hash /// +/// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function public static class FnvHash { 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. + /// 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) + public static ulong HashFnv1a_64(ReadOnlySpan input, ulong hash = kOffsetBasis_64) { foreach (var c in input) { @@ -32,7 +33,7 @@ public static ulong HashFnv1a_64(IEnumerable input, ulong hash = kOffsetBa /// Input sequence /// Initial hash value /// Computed hash code - public static ulong HashFnv1a_64(IEnumerable input, ulong hash = kOffsetBasis_64) + public static ulong HashFnv1a_64(ReadOnlySpan input, ulong hash = kOffsetBasis_64) { foreach (var c in input) { diff --git a/PKHeX.Core/Saves/SAV8SWSH.cs b/PKHeX.Core/Saves/SAV8SWSH.cs index 3cef76b23..175812a0c 100644 --- a/PKHeX.Core/Saves/SAV8SWSH.cs +++ b/PKHeX.Core/Saves/SAV8SWSH.cs @@ -209,6 +209,7 @@ public void UnlockAllDiglett() if (SaveRevision == 0) return; // no blocks + // Zone specific values (int Zone, int Max)[] zones = { (0201, 16), // Fields of Honor @@ -229,33 +230,36 @@ public void UnlockAllDiglett() (0231, 9), // Honeycalm Island }; var s = Blocks; - static uint Hash(string str) => (uint)FnvHash.HashFnv1a_64(str); foreach (var (zone, max) in zones) { var baseName = $"z_wr{zone:0000}_F_DHIGUDA"; - s.GetBlock(Hash(baseName)).ChangeBooleanType(SCTypeCode.Bool2); + s.GetBlock(baseName).ChangeBooleanType(SCTypeCode.Bool2); for (int i = 0; i <= max; i++) { var otherName = $"{baseName}_{i}"; - s.GetBlock(Hash(otherName)).ChangeBooleanType(SCTypeCode.Bool2); + s.GetBlock(otherName).ChangeBooleanType(SCTypeCode.Bool2); } var countName = $"WK_EV_R1_DHIG_WR{zone:0000}"; var value = max + 2; if (zone == 0223) // trio - value++; - s.GetBlock(Hash(countName)).SetValue((uint)value); + value += 2; + s.GetBlock(countName).SetValue((uint)value); } - const string TRIO = "z_wr0223_F_TRIO"; - s.GetBlock(Hash(TRIO)).ChangeBooleanType(SCTypeCode.Bool2); + // Atypical named values + const string TRIO1 = "z_wr0223_F_TRIO"; + const string TRIO2 = "FE_R1_DHIGUDA_TRIO"; + s.GetBlock(TRIO1).ChangeBooleanType(SCTypeCode.Bool2); + s.GetBlock(TRIO2).ChangeBooleanType(SCTypeCode.Bool2); + // Overall named values const string unreported = "WK_EV_R1_DHIGUDA_ADD"; const string totalCount = "WK_EV_R1_DHIGUDA_COUNT"; const string progressCt = "WK_EV_R1_DHIGUDA_PROGRESS"; - s.GetBlock(Hash(unreported)).SetValue((uint)0); // none unreported - s.GetBlock(Hash(totalCount)).SetValue((uint)150); // all obtained count - s.GetBlock(Hash(progressCt)).SetValue((uint)11); // all obtained progress value + s.GetBlock(unreported).SetValue((uint)150); // 150 unreported + s.GetBlock(totalCount).SetValue((uint)1); // obtained count + s.GetBlock(progressCt).SetValue((uint)1); // none obtained progress value } } }