From c10c2a0dc25e3f3cc3e15df3077205bc6c9b0ebe Mon Sep 17 00:00:00 2001 From: Kurt Date: Mon, 27 May 2024 18:21:11 -0500 Subject: [PATCH] Add Trash Byte verification for Switch formats (#4283) * Extract logic, finish impl for switch era * Extract trash checks to static class * Reduce some allocations in OT name compares --- .../Templates/Gen2/EncounterGift2.cs | 21 ++- .../Templates/Gen8/EncounterStatic8Nest.cs | 5 +- .../Templates/Gen8/EncounterStatic8U.cs | 35 ++-- .../Interfaces/ITrashUnderlaySpecies.cs | 6 + .../Formatting/LegalityCheckStrings.cs | 9 + PKHeX.Core/Legality/LegalityAnalysis.cs | 1 + PKHeX.Core/Legality/LegalityAnalyzers.cs | 1 + .../Legality/Verifiers/TrainerNameVerifier.cs | 2 +- .../Legality/Verifiers/TrashByteVerifier.cs | 165 ++++++++++++++++++ PKHeX.Core/MysteryGifts/WC6.cs | 2 +- PKHeX.Core/MysteryGifts/WC7.cs | 2 +- PKHeX.Core/MysteryGifts/WC8.cs | 12 +- PKHeX.Core/PKM/BK4.cs | 4 +- PKHeX.Core/PKM/CK3.cs | 4 +- PKHeX.Core/PKM/HOME/PKH.cs | 4 +- PKHeX.Core/PKM/PA8.cs | 9 +- PKHeX.Core/PKM/PB7.cs | 4 +- PKHeX.Core/PKM/PB8.cs | 9 +- PKHeX.Core/PKM/PK1.cs | 4 +- PKHeX.Core/PKM/PK4.cs | 4 +- PKHeX.Core/PKM/PK5.cs | 9 +- PKHeX.Core/PKM/PK6.cs | 4 +- PKHeX.Core/PKM/PK7.cs | 4 +- PKHeX.Core/PKM/PK8.cs | 9 +- PKHeX.Core/PKM/PK9.cs | 9 +- PKHeX.Core/PKM/RK4.cs | 4 +- PKHeX.Core/PKM/Shared/G4PKM.cs | 5 +- PKHeX.Core/PKM/Shared/G6PKM.cs | 5 +- PKHeX.Core/PKM/Shared/GBPKM.cs | 5 +- PKHeX.Core/PKM/Strings/StringConverter8.cs | 10 +- PKHeX.Core/PKM/Strings/Trash/TrashBytes.cs | 35 ---- PKHeX.Core/PKM/Strings/Trash/TrashBytes8.cs | 2 +- PKHeX.Core/PKM/Strings/Trash/TrashBytesGB.cs | 2 +- .../PKM/Strings/Trash/TrashBytesUTF16.cs | 114 ++++++++++++ PKHeX.Core/PKM/Strings/Trash/TrashMatch.cs | 23 ++- PKHeX.Core/PKM/XK3.cs | 4 +- PKHeX.Core/Saves/Abstractions/ITrainerInfo.cs | 13 +- .../Subforms/Save Editors/SAV_BoxList.cs | 2 +- ...- Test - 6DDD2B435BBC alolan evolution.pk8 | Bin 0 -> 344 bytes ...- Test - 6E112B435BBC alolan evolution.pk8 | Bin 344 -> 0 bytes 40 files changed, 435 insertions(+), 127 deletions(-) create mode 100644 PKHeX.Core/Legality/Encounters/Templates/Interfaces/ITrashUnderlaySpecies.cs create mode 100644 PKHeX.Core/Legality/Verifiers/TrashByteVerifier.cs delete mode 100644 PKHeX.Core/PKM/Strings/Trash/TrashBytes.cs create mode 100644 PKHeX.Core/PKM/Strings/Trash/TrashBytesUTF16.cs create mode 100644 Tests/PKHeX.Core.Tests/Legality/Legal/Generation 8/0105-01 - Test - 6DDD2B435BBC alolan evolution.pk8 delete mode 100644 Tests/PKHeX.Core.Tests/Legality/Legal/Generation 8/105-01 - Test - 6E112B435BBC alolan evolution.pk8 diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterGift2.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterGift2.cs index a909a02a2..97e063971 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterGift2.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen2/EncounterGift2.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using static PKHeX.Core.LanguageID; using static PKHeX.Core.EncounterGift2.TrainerType; @@ -227,8 +228,6 @@ private bool IsLanguageValid(int pkLanguage) private bool IsTrainerNameValid(PKM pk) => Trainer switch { Recipient => true, - GiftStadiumJPN => pk.OriginalTrainerName == StadiumJPN, - GiftStadiumENG => pk.OriginalTrainerName == StadiumENG, GiftStadiumINT => pk.OriginalTrainerName switch { StadiumGER => true, @@ -237,10 +236,26 @@ private bool IsLanguageValid(int pkLanguage) StadiumSPA => true, _ => false, }, - PokemonCenterNewYork => IsTrainerPCNY(pk.OriginalTrainerName), + GiftStadiumJPN => IsTrainerName(pk, StadiumJPN), + GiftStadiumENG => IsTrainerName(pk, StadiumENG), + PokemonCenterNewYork => IsTrainerPCNY(pk), _ => true, }; + private static bool IsTrainerPCNY(PKM pk) + { + Span ot = stackalloc char[pk.MaxStringLengthTrainer]; + int len = pk.LoadString(pk.OriginalTrainerTrash, ot); + return IsTrainerPCNY(ot[..len]); + } + + private static bool IsTrainerName(PKM pk, [ConstantExpected] string name) + { + Span ot = stackalloc char[pk.MaxStringLengthTrainer]; + int len = pk.LoadString(pk.OriginalTrainerTrash, ot); + return ot[..len].SequenceEqual(name); + } + private bool IsTrainerIDValid(ITrainerID16 pk) => Trainer switch { Recipient => true, diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8Nest.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8Nest.cs index 9c4073cf6..9fe4a4a3b 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8Nest.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8Nest.cs @@ -48,6 +48,9 @@ public abstract record EncounterStatic8Nest(GameVersion Version) public PK8 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted); + protected virtual void SetTrainerName(ReadOnlySpan name, PK8 pk) => + pk.SetString(pk.OriginalTrainerTrash, name, name.Length, StringConverterOption.None); + public PK8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) { var version = this.GetCompatibleVersion(tr.Version); @@ -67,7 +70,6 @@ public PK8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) Version = version, Language = lang, OriginalTrainerGender = tr.Gender, - OriginalTrainerName = tr.OT, OriginalTrainerFriendship = pi.BaseFriendship, Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation), @@ -75,6 +77,7 @@ public PK8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) DynamaxLevel = DynamaxLevel, CanGigantamax = CanGigantamax, }; + SetTrainerName(tr.OT, pk); SetPINGA(pk, criteria, pi); diff --git a/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8U.cs b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8U.cs index c3dc1fff4..a1495ebe0 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8U.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/Gen8/EncounterStatic8U.cs @@ -41,6 +41,16 @@ public static EncounterStatic8U Read(ReadOnlySpan data) } protected override ushort GetLocation() => Location; + protected override void SetTrainerName(ReadOnlySpan name, PK8 pk) + { + if (ShouldHaveScientistTrash) + { + var scientist = GetScientistName(pk.Language); + pk.SetString(pk.OriginalTrainerTrash, scientist, scientist.Length, StringConverterOption.None); + } + base.SetTrainerName(name, pk); + } + // no downleveling, unlike all other raids protected override bool IsMatchLevel(PKM pk) => pk.MetLevel == Level; protected override bool IsMatchLocation(PKM pk) => Location == pk.MetLocation; @@ -50,31 +60,6 @@ public static EncounterStatic8U Read(ReadOnlySpan data) public bool ShouldHaveScientistTrash => !SpeciesCategory.IsLegendary(Species) && !SpeciesCategory.IsSubLegendary(Species); - protected override void FinishCorrelation(PK8 pk, ulong seed) - { - if (!ShouldHaveScientistTrash) - return; - - ApplyTrashBytes(pk); - } - - public void ApplyTrashBytes(PKM pk) - { - // Normally we would apply the trash before applying the OT, but we already did. - // Just add in the expected trash after the OT. - var ot = pk.OriginalTrainerTrash; - var language = pk.Language; - var scientist = GetScientistName(language); - StringConverter8.ApplyTrashBytes(ot, scientist); - } - - public static TrashMatch HasScientistTrash(PKM pk) - { - var language = pk.Language; - var name = GetScientistName(language); - return StringConverter8.GetTrashState(pk.OriginalTrainerTrash, name); - } - public static ReadOnlySpan GetScientistName(int language) => language switch { (int)LanguageID.Japanese => "けんきゅういん", diff --git a/PKHeX.Core/Legality/Encounters/Templates/Interfaces/ITrashUnderlaySpecies.cs b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/ITrashUnderlaySpecies.cs new file mode 100644 index 000000000..c50e0f321 --- /dev/null +++ b/PKHeX.Core/Legality/Encounters/Templates/Interfaces/ITrashUnderlaySpecies.cs @@ -0,0 +1,6 @@ +namespace PKHeX.Core; + +public interface ITrashUnderlaySpecies +{ + bool IsTrashUnderlaySpecies(PKM pk); +} diff --git a/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs b/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs index dcd96dedc..a184599bd 100644 --- a/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs +++ b/PKHeX.Core/Legality/Formatting/LegalityCheckStrings.cs @@ -57,6 +57,7 @@ public static class LegalityCheckStrings public static string L_XOT { get; set; } = "OT"; public static string L_XHT { get; set; } = "HT"; + public static string L_XNickname { get; set; } = "Nickname"; public static string L_XKorean { get; set; } = "Korean"; public static string L_XKoreanNon { get; set; } = "Non-Korean"; public static string L_XEnigmaBerry_0 { get; set; } = "{0} Berry"; @@ -499,6 +500,14 @@ public static class LegalityCheckStrings public static string LTransferPIDECXor { get; set; } = "Encryption Constant matches shinyxored PID."; public static string LTransferTrackerMissing { get; set; } = "Pokémon HOME Transfer Tracker is missing."; public static string LTransferTrackerShouldBeZero { get; set; } = "Pokémon HOME Transfer Tracker should be 0."; + + public static string LTrashBytesExpected_0 { get; set; } = "Expected Trash Bytes: {0}"; + public static string LTrashBytesExpected { get; set; } = "Expected Trash Bytes."; + public static string LTrashBytesMismatchInitial { get; set; } = "Expected initial trash bytes to match the encounter."; + public static string LTrashBytesMissingTerminator { get; set; } = "Final terminator missing."; + public static string LTrashBytesShouldBeEmpty { get; set; } = "Trash Bytes should be cleared."; + public static string LTrashBytesUnexpected { get; set; } = "Unexpected Trash Bytes."; + #endregion } diff --git a/PKHeX.Core/Legality/LegalityAnalysis.cs b/PKHeX.Core/Legality/LegalityAnalysis.cs index 19c898bdc..48a79e3a3 100644 --- a/PKHeX.Core/Legality/LegalityAnalysis.cs +++ b/PKHeX.Core/Legality/LegalityAnalysis.cs @@ -327,6 +327,7 @@ private void UpdateChecks() HyperTraining.Verify(this); MiscValues.VerifyVersionEvolution(this); + Trash.Verify(this); if (format < 8) return; diff --git a/PKHeX.Core/Legality/LegalityAnalyzers.cs b/PKHeX.Core/Legality/LegalityAnalyzers.cs index 95a0759f3..9e3069d6a 100644 --- a/PKHeX.Core/Legality/LegalityAnalyzers.cs +++ b/PKHeX.Core/Legality/LegalityAnalyzers.cs @@ -35,4 +35,5 @@ internal static class LegalityAnalyzers public static readonly MarkVerifier Mark = new(); public static readonly LegendsArceusVerifier Arceus = new(); public static readonly AwakenedValueVerifier Awakening = new(); + public static readonly TrashByteVerifier Trash = new(); } diff --git a/PKHeX.Core/Legality/Verifiers/TrainerNameVerifier.cs b/PKHeX.Core/Legality/Verifiers/TrainerNameVerifier.cs index c741c72e1..b737f0057 100644 --- a/PKHeX.Core/Legality/Verifiers/TrainerNameVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/TrainerNameVerifier.cs @@ -86,7 +86,7 @@ public static bool IsEdgeCaseLength(PKM pk, IEncounterTemplate e, ReadOnlySpan +/// Verifies the trash bytes of various strings. +/// +public sealed class TrashByteVerifier : Verifier +{ + protected override CheckIdentifier Identifier => CheckIdentifier.TrashBytes; + + private static string Format(StringSource s) => s switch + { + Nickname => L_XNickname, + OriginalTrainer => L_XOT, + HandlingTrainer => L_XHT, + _ => throw new ArgumentOutOfRangeException(nameof(s)), + }; + + private static string Format(StringSource s, string msg) => string.Format(L_F0_1, Format(s), msg); + + public override void Verify(LegalityAnalysis data) + { + var pk = data.Entity; + if (pk.Format >= 8 || pk.Context == EntityContext.Gen7b) + { + VerifyTrashBytesHOME(data, pk); + } + else if (pk.Format == 4) + { + var enc = data.EncounterMatch; + if (enc is PCD pcd) + VerifyTrashBytesPCD(data, pk, pcd); + else if (enc.Generation == 3) + VerifyTrashBytesPalPark(data, pk); + } + } + + private void VerifyTrashBytesPalPark(LegalityAnalysis data, PKM pk) + { + if (pk.Japanese) + { + // Trash bytes should be zero. + if (!TrashBytesUTF16.IsTrashEmpty(pk.OriginalTrainerTrash)) + data.AddLine(GetInvalid(Format(Nickname, LTrashBytesShouldBeEmpty))); + } + else + { + // Should have trash bytes from the transfer process. + if (TrashBytesUTF16.IsTrashEmpty(pk.OriginalTrainerTrash)) + data.AddLine(GetInvalid(Format(Nickname, LTrashBytesExpected))); + } + } + + private void VerifyTrashBytesPCD(LegalityAnalysis data, PKM pk, PCD pcd) + { + var enc = pcd.Gift.PK; + var ot = enc.OriginalTrainerTrash; + if (!ot.SequenceEqual(pk.OriginalTrainerTrash)) + data.AddLine(GetInvalid(Format(OriginalTrainer, LTrashBytesMismatchInitial))); + + if (pcd.Species != pk.Species) + return; // Evolved, trash bytes are rewritten. + + var nick = enc.NicknameTrash; + if (!nick.SequenceEqual(pk.NicknameTrash)) + data.AddLine(GetInvalid(Format(Nickname, LTrashBytesMismatchInitial))); + } + + private void VerifyTrashBytesHOME(LegalityAnalysis data, PKM pk) + { + if (!TrashBytesUTF16.IsFinalTerminatorPresent(pk.NicknameTrash)) + data.AddLine(GetInvalid(Format(Nickname, LTrashBytesMissingTerminator))); + if (!TrashBytesUTF16.IsFinalTerminatorPresent(pk.OriginalTrainerTrash)) + data.AddLine(GetInvalid(Format(OriginalTrainer, LTrashBytesMissingTerminator))); + if (!TrashBytesUTF16.IsFinalTerminatorPresent(pk.HandlingTrainerTrash)) + data.AddLine(GetInvalid(Format(HandlingTrainer, LTrashBytesMissingTerminator))); + + if (pk.IsEgg) + { + if (!pk.IsTradedEgg || pk.SWSH) + VerifyTrashEmpty(data, pk.HandlingTrainerTrash, HandlingTrainer); + else + VerifyTrashNotEmpty(data, pk.HandlingTrainerTrash, HandlingTrainer); + VerifyTrashNone(data, pk.OriginalTrainerTrash, OriginalTrainer); + + // Species name is overwritten by "Egg" + var origName = SpeciesName.GetSpeciesName(pk.Species, pk.Language); + VerifyTrashSpecific(data, pk.NicknameTrash, origName, Nickname); + return; + } + + VerifyTrashNickname(data, pk.NicknameTrash); + var enc = data.Info.EncounterMatch; + if (enc is EncounterEgg && pk.WasTradedEgg) + { + // Allow Traded eggs to have a single layer of OT trash bytes. + VerifyTrashSingle(data, pk.OriginalTrainerTrash, OriginalTrainer); + if (!pk.SWSH) // SW/SH does not update the HT data. + VerifyTrashNotEmpty(data, pk.HandlingTrainerTrash, HandlingTrainer); + } + else if (enc is EncounterStatic8U { ShouldHaveScientistTrash: true }) + { + var under = EncounterStatic8U.GetScientistName(pk.Language); + VerifyTrashSpecific(data, pk.OriginalTrainerTrash, under, OriginalTrainer); + } + else + { + VerifyTrashNone(data, pk.OriginalTrainerTrash, OriginalTrainer); + } + } + + private void VerifyTrashNickname(LegalityAnalysis data, ReadOnlySpan span) + { + var pk = data.Entity; + if (pk.IsNicknamed) + { + var origName = SpeciesName.GetSpeciesName(pk.Species, pk.Language); + VerifyTrashSpecific(data, span, origName, Nickname, Severity.Fishy); + } + else + { + VerifyTrashNone(data, span, Nickname, Severity.Fishy); + } + } + + private void VerifyTrashSingle(LegalityAnalysis data, ReadOnlySpan span, StringSource s) + { + var result = TrashBytesUTF16.IsTrashSingleOrNone(span); + if (result.IsInvalid()) + data.AddLine(GetInvalid(Format(s, LTrashBytesShouldBeEmpty))); + } + + private void VerifyTrashSpecific(LegalityAnalysis data, ReadOnlySpan span, ReadOnlySpan under, StringSource s, + Severity severity = Severity.Invalid) + { + var result = TrashBytesUTF16.IsTrashSpecific(span, under); + if (result.IsInvalid()) + data.AddLine(Get(Format(s, string.Format(LTrashBytesExpected_0, under.ToString())), severity)); + } + + private void VerifyTrashNone(LegalityAnalysis data, ReadOnlySpan span, StringSource s, + Severity severity = Severity.Invalid) + { + var result = TrashBytesUTF16.IsTrashNone(span); + if (result.IsInvalid()) + data.AddLine(Get(Format(s, LTrashBytesShouldBeEmpty), severity)); + } + + private void VerifyTrashNotEmpty(LegalityAnalysis data, ReadOnlySpan span, StringSource s) + { + if (!TrashBytesUTF16.IsTrashNotEmpty(span)) + data.AddLine(GetInvalid(Format(s, LTrashBytesExpected))); + } + + private void VerifyTrashEmpty(LegalityAnalysis data, ReadOnlySpan span, StringSource s) + { + if (!TrashBytesUTF16.IsTrashEmpty(span)) + data.AddLine(GetInvalid(Format(s, LTrashBytesShouldBeEmpty))); + } +} + +public enum StringSource : byte { Nickname, OriginalTrainer, HandlingTrainer } diff --git a/PKHeX.Core/MysteryGifts/WC6.cs b/PKHeX.Core/MysteryGifts/WC6.cs index 27f66de78..91e8665b8 100644 --- a/PKHeX.Core/MysteryGifts/WC6.cs +++ b/PKHeX.Core/MysteryGifts/WC6.cs @@ -538,7 +538,7 @@ public override bool IsMatchExact(PKM pk, EvoCriteria evo) } if (OTGender != pk.OriginalTrainerGender) return false; } - if (!string.IsNullOrEmpty(OriginalTrainerName) && OriginalTrainerName != pk.OriginalTrainerName) return false; + if (IsOriginalTrainerNameSet && OriginalTrainerName != pk.OriginalTrainerName) return false; if (PIDType == ShinyType6.FixedValue && pk.PID != PID) return false; if (!Shiny.IsValid(pk)) return false; if (OriginGame != 0 && (GameVersion)OriginGame != pk.Version) return false; diff --git a/PKHeX.Core/MysteryGifts/WC7.cs b/PKHeX.Core/MysteryGifts/WC7.cs index eb8a0566f..5967cfc10 100644 --- a/PKHeX.Core/MysteryGifts/WC7.cs +++ b/PKHeX.Core/MysteryGifts/WC7.cs @@ -577,7 +577,7 @@ public override bool IsMatchExact(PKM pk, EvoCriteria evo) if (TID16 != pk.TID16) return false; if (OTGender != pk.OriginalTrainerGender) return false; } - if (!string.IsNullOrEmpty(OriginalTrainerName) && OriginalTrainerName != pk.OriginalTrainerName) return false; + if (IsOriginalTrainerNameSet && OriginalTrainerName != pk.OriginalTrainerName) return false; if (OriginGame != 0 && (GameVersion)OriginGame != pk.Version) return false; if (EncryptionConstant != 0 && EncryptionConstant != pk.EncryptionConstant) return false; if (Language != 0 && Language != pk.Language) return false; diff --git a/PKHeX.Core/MysteryGifts/WC8.cs b/PKHeX.Core/MysteryGifts/WC8.cs index d0e359492..e75c0031f 100644 --- a/PKHeX.Core/MysteryGifts/WC8.cs +++ b/PKHeX.Core/MysteryGifts/WC8.cs @@ -8,7 +8,7 @@ namespace PKHeX.Core; /// Generation 8 Mystery Gift Template File /// public sealed class WC8(byte[] Data) : DataMysteryGift(Data), ILangNick, INature, IGigantamax, IDynamaxLevel, IRibbonIndex, IMemoryOT, ILangNicknamedTemplate, IRelearn, IEncounterServerDate, IRestrictVersion, - IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetCommon3, IRibbonSetCommon4, IRibbonSetCommon6, IRibbonSetCommon7, IRibbonSetCommon8, IRibbonSetMark8 + IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetCommon3, IRibbonSetCommon4, IRibbonSetCommon6, IRibbonSetCommon7, IRibbonSetCommon8, IRibbonSetMark8, ITrashUnderlaySpecies { public WC8() : this(new byte[Size]) { } @@ -391,6 +391,7 @@ private static int GetOTOffset(int language) public bool IsHOMEGift => CardID >= 9000; public bool CanHandleOT(int language) => !GetHasOT(language); + public bool IsTrashUnderlaySpecies(PKM pk) => GetIsNicknamed(pk.Language); public override GameVersion Version => OriginGame != 0 ? (GameVersion)OriginGame : GameVersion.SWSH; @@ -403,9 +404,9 @@ public override PK8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) byte currentLevel = Level > 0 ? Level : (byte)(1 + rnd.Next(100)); var metLevel = MetLevel > 0 ? MetLevel : currentLevel; var pi = PersonalTable.SWSH.GetFormEntry(Species, Form); - var language = tr.Language; - bool hasOT = GetHasOT(language); var version = OriginGame != 0 ? (GameVersion)OriginGame : this.GetCompatibleVersion(tr.Version); + var language = (int)Core.Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version); + bool hasOT = GetHasOT(language); var pk = new PK8 { @@ -494,8 +495,9 @@ public override PK8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) var nickname_language = GetLanguage(language); pk.Language = nickname_language != 0 ? nickname_language : tr.Language; - pk.IsNicknamed = GetIsNicknamed(language); - pk.Nickname = pk.IsNicknamed ? GetNickname(language) : SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, Generation); + pk.Nickname = SpeciesName.GetSpeciesNameGeneration(Species, pk.Language, Generation); + if (GetIsNicknamed(language)) + pk.Nickname = GetNickname(language); for (var i = 0; i < RibbonBytesCount; i++) { diff --git a/PKHeX.Core/PKM/BK4.cs b/PKHeX.Core/PKM/BK4.cs index b15e73fe4..d80310d56 100644 --- a/PKHeX.Core/PKM/BK4.cs +++ b/PKHeX.Core/PKM/BK4.cs @@ -320,8 +320,8 @@ public override int LoadString(ReadOnlySpan data, Span destBuffer) public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) => StringConverter4.SetString(destBuffer, value, maxLength, Language, option); public override int GetStringTerminatorIndex(ReadOnlySpan data) - => TrashBytes.GetTerminatorIndex(data, StringConverter4.Terminator); + => TrashBytesUTF16.GetTerminatorIndex(data, StringConverter4.Terminator); public override int GetStringLength(ReadOnlySpan data) - => TrashBytes.GetStringLength(data, StringConverter4.Terminator); + => TrashBytesUTF16.GetStringLength(data, StringConverter4.Terminator); public override int GetBytesPerChar() => 2; } diff --git a/PKHeX.Core/PKM/CK3.cs b/PKHeX.Core/PKM/CK3.cs index c490e7b63..4afb150b2 100644 --- a/PKHeX.Core/PKM/CK3.cs +++ b/PKHeX.Core/PKM/CK3.cs @@ -249,8 +249,8 @@ public override int LoadString(ReadOnlySpan data, Span destBuffer) public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) => StringConverter3GC.SetString(destBuffer, value, maxLength, option); public override int GetStringTerminatorIndex(ReadOnlySpan data) - => TrashBytes.GetTerminatorIndex(data, StringConverter3GC.TerminatorBigEndian); + => TrashBytesUTF16.GetTerminatorIndex(data, StringConverter3GC.TerminatorBigEndian); public override int GetStringLength(ReadOnlySpan data) - => TrashBytes.GetStringLength(data, StringConverter3GC.TerminatorBigEndian); + => TrashBytesUTF16.GetStringLength(data, StringConverter3GC.TerminatorBigEndian); public override int GetBytesPerChar() => 2; } diff --git a/PKHeX.Core/PKM/HOME/PKH.cs b/PKHeX.Core/PKM/HOME/PKH.cs index 7b06b9454..56a1e0765 100644 --- a/PKHeX.Core/PKM/HOME/PKH.cs +++ b/PKHeX.Core/PKM/HOME/PKH.cs @@ -433,9 +433,9 @@ public override int LoadString(ReadOnlySpan data, Span destBuffer) public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) => StringConverter8.SetString(destBuffer, value, maxLength, option); public override int GetStringTerminatorIndex(ReadOnlySpan data) - => TrashBytes.GetTerminatorIndex(data); + => TrashBytesUTF16.GetTerminatorIndex(data); public override int GetStringLength(ReadOnlySpan data) - => TrashBytes.GetStringLength(data); + => TrashBytesUTF16.GetStringLength(data); public override int GetBytesPerChar() => 2; /// diff --git a/PKHeX.Core/PKM/PA8.cs b/PKHeX.Core/PKM/PA8.cs index b33be327c..1bfd3d11b 100644 --- a/PKHeX.Core/PKM/PA8.cs +++ b/PKHeX.Core/PKM/PA8.cs @@ -587,7 +587,10 @@ public bool BelongsTo(ITrainerInfo tr) return false; if (tr.Gender != OriginalTrainerGender) return false; - return tr.OT == OriginalTrainerName; + + Span ot = stackalloc char[MaxStringLengthTrainer]; + int len = LoadString(OriginalTrainerTrash, ot); + return ot[..len].SequenceEqual(tr.OT); } public void UpdateHandler(ITrainerInfo tr) @@ -739,8 +742,8 @@ public override int LoadString(ReadOnlySpan data, Span destBuffer) public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) => StringConverter8.SetString(destBuffer, value, maxLength, option); public override int GetStringTerminatorIndex(ReadOnlySpan data) - => TrashBytes.GetTerminatorIndex(data); + => TrashBytesUTF16.GetTerminatorIndex(data); public override int GetStringLength(ReadOnlySpan data) - => TrashBytes.GetStringLength(data); + => TrashBytesUTF16.GetStringLength(data); public override int GetBytesPerChar() => 2; } diff --git a/PKHeX.Core/PKM/PB7.cs b/PKHeX.Core/PKM/PB7.cs index 2a5030c38..996186247 100644 --- a/PKHeX.Core/PKM/PB7.cs +++ b/PKHeX.Core/PKM/PB7.cs @@ -594,8 +594,8 @@ public override int LoadString(ReadOnlySpan data, Span destBuffer) public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) => StringConverter8.SetString(destBuffer, value, maxLength, option); public override int GetStringTerminatorIndex(ReadOnlySpan data) - => TrashBytes.GetTerminatorIndex(data); + => TrashBytesUTF16.GetTerminatorIndex(data); public override int GetStringLength(ReadOnlySpan data) - => TrashBytes.GetStringLength(data); + => TrashBytesUTF16.GetStringLength(data); public override int GetBytesPerChar() => 2; } diff --git a/PKHeX.Core/PKM/PB8.cs b/PKHeX.Core/PKM/PB8.cs index 8759ce40f..64653694b 100644 --- a/PKHeX.Core/PKM/PB8.cs +++ b/PKHeX.Core/PKM/PB8.cs @@ -52,7 +52,10 @@ public bool BelongsTo(ITrainerInfo tr) return false; if (tr.Gender != OriginalTrainerGender) return false; - return tr.OT == OriginalTrainerName; + + Span ot = stackalloc char[MaxStringLengthTrainer]; + int len = LoadString(OriginalTrainerTrash, ot); + return ot[..len].SequenceEqual(tr.OT); } public void UpdateHandler(ITrainerInfo tr) @@ -142,8 +145,8 @@ public override int LoadString(ReadOnlySpan data, Span destBuffer) public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) => StringConverter8.SetString(destBuffer, value, maxLength, option); public override int GetStringTerminatorIndex(ReadOnlySpan data) - => TrashBytes.GetTerminatorIndex(data); + => TrashBytesUTF16.GetTerminatorIndex(data); public override int GetStringLength(ReadOnlySpan data) - => TrashBytes.GetStringLength(data); + => TrashBytesUTF16.GetStringLength(data); public override int GetBytesPerChar() => 2; } diff --git a/PKHeX.Core/PKM/PK1.cs b/PKHeX.Core/PKM/PK1.cs index 1466c4ef2..6a8e444f2 100644 --- a/PKHeX.Core/PKM/PK1.cs +++ b/PKHeX.Core/PKM/PK1.cs @@ -255,9 +255,9 @@ public override int LoadString(ReadOnlySpan data, Span destBuffer) public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) => StringConverter1.SetString(destBuffer, value, maxLength, Japanese, option); public override int GetStringTerminatorIndex(ReadOnlySpan data) - => TrashBytes.GetTerminatorIndex(data, StringConverter4.Terminator); + => TrashBytesUTF16.GetTerminatorIndex(data, StringConverter4.Terminator); public override int GetStringLength(ReadOnlySpan data) - => TrashBytes.GetStringLength(data, StringConverter4.Terminator); + => TrashBytesUTF16.GetStringLength(data, StringConverter4.Terminator); public override int GetBytesPerChar() => 2; /// diff --git a/PKHeX.Core/PKM/PK4.cs b/PKHeX.Core/PKM/PK4.cs index a8d5ad1d5..b24fef37b 100644 --- a/PKHeX.Core/PKM/PK4.cs +++ b/PKHeX.Core/PKM/PK4.cs @@ -396,8 +396,8 @@ public override int LoadString(ReadOnlySpan data, Span destBuffer) public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) => StringConverter4.SetString(destBuffer, value, maxLength, Language, option); public override int GetStringTerminatorIndex(ReadOnlySpan data) - => TrashBytes.GetTerminatorIndex(data, StringConverter4.Terminator); + => TrashBytesUTF16.GetTerminatorIndex(data, StringConverter4.Terminator); public override int GetStringLength(ReadOnlySpan data) - => TrashBytes.GetStringLength(data, StringConverter4.Terminator); + => TrashBytesUTF16.GetStringLength(data, StringConverter4.Terminator); public override int GetBytesPerChar() => 2; } diff --git a/PKHeX.Core/PKM/PK5.cs b/PKHeX.Core/PKM/PK5.cs index 061140254..6ed7d4e2b 100644 --- a/PKHeX.Core/PKM/PK5.cs +++ b/PKHeX.Core/PKM/PK5.cs @@ -318,7 +318,10 @@ public bool BelongsTo(ITrainerInfo tr) return false; if (tr.Gender != OriginalTrainerGender) return false; - return tr.OT == OriginalTrainerName; + + Span ot = stackalloc char[MaxStringLengthTrainer]; + int len = LoadString(OriginalTrainerTrash, ot); + return ot[..len].SequenceEqual(tr.OT); } public void UpdateHandler(ITrainerInfo tr) @@ -573,9 +576,9 @@ public override int LoadString(ReadOnlySpan data, Span destBuffer) public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) => StringConverter5.SetString(destBuffer, value, maxLength, Language, option); public override int GetStringTerminatorIndex(ReadOnlySpan data) - => TrashBytes.GetTerminatorIndex(data, StringConverter5.Terminator); + => TrashBytesUTF16.GetTerminatorIndex(data, StringConverter5.Terminator); public override int GetStringLength(ReadOnlySpan data) - => TrashBytes.GetStringLength(data, StringConverter5.Terminator); + => TrashBytesUTF16.GetStringLength(data, StringConverter5.Terminator); public override int GetBytesPerChar() => 2; /// diff --git a/PKHeX.Core/PKM/PK6.cs b/PKHeX.Core/PKM/PK6.cs index 9da5badaf..97554002f 100644 --- a/PKHeX.Core/PKM/PK6.cs +++ b/PKHeX.Core/PKM/PK6.cs @@ -532,8 +532,8 @@ public override int LoadString(ReadOnlySpan data, Span destBuffer) public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) => StringConverter6.SetString(destBuffer, value, maxLength, Language, option); public override int GetStringTerminatorIndex(ReadOnlySpan data) - => TrashBytes.GetTerminatorIndex(data); + => TrashBytesUTF16.GetTerminatorIndex(data); public override int GetStringLength(ReadOnlySpan data) - => TrashBytes.GetStringLength(data); + => TrashBytesUTF16.GetStringLength(data); public override int GetBytesPerChar() => 2; } diff --git a/PKHeX.Core/PKM/PK7.cs b/PKHeX.Core/PKM/PK7.cs index b2210e571..65d80a4f6 100644 --- a/PKHeX.Core/PKM/PK7.cs +++ b/PKHeX.Core/PKM/PK7.cs @@ -557,9 +557,9 @@ public override int LoadString(ReadOnlySpan data, Span destBuffer) public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) => StringConverter7.SetString(destBuffer, value, maxLength, Language); public override int GetStringTerminatorIndex(ReadOnlySpan data) - => TrashBytes.GetTerminatorIndex(data); + => TrashBytesUTF16.GetTerminatorIndex(data); public override int GetStringLength(ReadOnlySpan data) - => TrashBytes.GetStringLength(data); + => TrashBytesUTF16.GetStringLength(data); public override int GetBytesPerChar() => 2; } diff --git a/PKHeX.Core/PKM/PK8.cs b/PKHeX.Core/PKM/PK8.cs index 66c247627..0a64a7d42 100644 --- a/PKHeX.Core/PKM/PK8.cs +++ b/PKHeX.Core/PKM/PK8.cs @@ -43,7 +43,10 @@ public bool BelongsTo(ITrainerInfo tr) return false; if (tr.Gender != OriginalTrainerGender) return false; - return tr.OT == OriginalTrainerName; + + Span ot = stackalloc char[MaxStringLengthTrainer]; + int len = LoadString(OriginalTrainerTrash, ot); + return ot[..len].SequenceEqual(tr.OT); } public void UpdateHandler(ITrainerInfo tr) @@ -125,8 +128,8 @@ public override int LoadString(ReadOnlySpan data, Span destBuffer) public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) => StringConverter8.SetString(destBuffer, value, maxLength, option); public override int GetStringTerminatorIndex(ReadOnlySpan data) - => TrashBytes.GetTerminatorIndex(data); + => TrashBytesUTF16.GetTerminatorIndex(data); public override int GetStringLength(ReadOnlySpan data) - => TrashBytes.GetStringLength(data); + => TrashBytesUTF16.GetStringLength(data); public override int GetBytesPerChar() => 2; } diff --git a/PKHeX.Core/PKM/PK9.cs b/PKHeX.Core/PKM/PK9.cs index accb2fb02..f32a3a781 100644 --- a/PKHeX.Core/PKM/PK9.cs +++ b/PKHeX.Core/PKM/PK9.cs @@ -566,7 +566,10 @@ public bool BelongsToSkipVersion(ITrainerInfo tr) return false; if (tr.Language != Language) return false; - return tr.OT == OriginalTrainerName; + + Span ot = stackalloc char[MaxStringLengthTrainer]; + int len = LoadString(OriginalTrainerTrash, ot); + return ot[..len].SequenceEqual(tr.OT); } public void UpdateHandler(ITrainerInfo tr) @@ -678,8 +681,8 @@ public override int LoadString(ReadOnlySpan data, Span destBuffer) public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) => StringConverter8.SetString(destBuffer, value, maxLength, option); public override int GetStringTerminatorIndex(ReadOnlySpan data) - => TrashBytes.GetTerminatorIndex(data); + => TrashBytesUTF16.GetTerminatorIndex(data); public override int GetStringLength(ReadOnlySpan data) - => TrashBytes.GetStringLength(data); + => TrashBytesUTF16.GetStringLength(data); public override int GetBytesPerChar() => 2; } diff --git a/PKHeX.Core/PKM/RK4.cs b/PKHeX.Core/PKM/RK4.cs index 5dfe1ec5a..e260d4518 100644 --- a/PKHeX.Core/PKM/RK4.cs +++ b/PKHeX.Core/PKM/RK4.cs @@ -358,8 +358,8 @@ public override int LoadString(ReadOnlySpan data, Span destBuffer) public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) => StringConverter4.SetString(destBuffer, value, maxLength, Language, option); public override int GetStringTerminatorIndex(ReadOnlySpan data) - => TrashBytes.GetTerminatorIndex(data, StringConverter4.Terminator); + => TrashBytesUTF16.GetTerminatorIndex(data, StringConverter4.Terminator); public override int GetStringLength(ReadOnlySpan data) - => TrashBytes.GetStringLength(data, StringConverter4.Terminator); + => TrashBytesUTF16.GetStringLength(data, StringConverter4.Terminator); public override int GetBytesPerChar() => 2; } diff --git a/PKHeX.Core/PKM/Shared/G4PKM.cs b/PKHeX.Core/PKM/Shared/G4PKM.cs index e28959dbe..cc1197ca2 100644 --- a/PKHeX.Core/PKM/Shared/G4PKM.cs +++ b/PKHeX.Core/PKM/Shared/G4PKM.cs @@ -290,7 +290,10 @@ public bool BelongsTo(ITrainerInfo tr) return false; if (tr.Gender != OriginalTrainerGender) return false; - return tr.OT == OriginalTrainerName; + + Span ot = stackalloc char[MaxStringLengthTrainer]; + int len = LoadString(OriginalTrainerTrash, ot); + return ot[..len].SequenceEqual(tr.OT); } public void UpdateHandler(ITrainerInfo tr) diff --git a/PKHeX.Core/PKM/Shared/G6PKM.cs b/PKHeX.Core/PKM/Shared/G6PKM.cs index ffb2f09fc..f5dbafac1 100644 --- a/PKHeX.Core/PKM/Shared/G6PKM.cs +++ b/PKHeX.Core/PKM/Shared/G6PKM.cs @@ -89,7 +89,10 @@ public bool BelongsTo(ITrainerInfo tr) return false; if (tr.Gender != OriginalTrainerGender) return false; - return tr.OT == OriginalTrainerName; + + Span ot = stackalloc char[MaxStringLengthTrainer]; + int len = LoadString(OriginalTrainerTrash, ot); + return ot[..len].SequenceEqual(tr.OT); } public void UpdateHandler(ITrainerInfo tr) diff --git a/PKHeX.Core/PKM/Shared/GBPKM.cs b/PKHeX.Core/PKM/Shared/GBPKM.cs index 5e6b6188b..59adf765f 100644 --- a/PKHeX.Core/PKM/Shared/GBPKM.cs +++ b/PKHeX.Core/PKM/Shared/GBPKM.cs @@ -62,7 +62,10 @@ protected bool IsNicknamedBank get { var spName = SpeciesName.GetSpeciesNameGeneration(Species, GuessedLanguage(), Format); - return Nickname != spName; + + Span nickname = stackalloc char[TrashCharCountNickname]; + int len = LoadString(NicknameTrash, nickname); + return !nickname[..len].SequenceEqual(spName); } } diff --git a/PKHeX.Core/PKM/Strings/StringConverter8.cs b/PKHeX.Core/PKM/Strings/StringConverter8.cs index 57ddb7997..dec1c3d86 100644 --- a/PKHeX.Core/PKM/Strings/StringConverter8.cs +++ b/PKHeX.Core/PKM/Strings/StringConverter8.cs @@ -85,9 +85,9 @@ private static void WriteCharacters(Span destBuffer, ReadOnlySpan va /// Indication of the under string's presence. public static TrashMatch ApplyTrashBytes(Span top, ReadOnlySpan under) { - var index = TrashBytes.GetStringLength(top); + var index = TrashBytesUTF16.GetStringLength(top); if (index == -1) - return TrashMatch.Skipped; + return TrashMatch.TooLongToTell; index++; // hop over the terminator if (index >= under.Length) // Overlapping return TrashMatch.TooLongToTell; @@ -107,9 +107,9 @@ public static TrashMatch ApplyTrashBytes(Span top, ReadOnlySpan unde public static TrashMatch GetTrashState(ReadOnlySpan top, ReadOnlySpan under) { if (under.Length == 0) - return TrashMatch.Skipped; + return TrashMatch.TooLongToTell; - var index = TrashBytes.GetStringLength(top); + var index = TrashBytesUTF16.GetStringLength(top); if ((uint)index >= under.Length) return TrashMatch.TooLongToTell; index++; // hop over the terminator @@ -177,7 +177,7 @@ private static void TrimHalfSpaces(Span u16) var region = u16[..length]; char seek = ' '; - if (BitConverter.IsLittleEndian) + if (!BitConverter.IsLittleEndian) seek = (char)ReverseEndianness(' '); var trim = region.Trim(seek); diff --git a/PKHeX.Core/PKM/Strings/Trash/TrashBytes.cs b/PKHeX.Core/PKM/Strings/Trash/TrashBytes.cs deleted file mode 100644 index aa10137bf..000000000 --- a/PKHeX.Core/PKM/Strings/Trash/TrashBytes.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace PKHeX.Core; - -/// -/// 16-bit encoded string utility -/// -public static class TrashBytes -{ - /// - /// Gets the length of the string based on the terminator. - /// - /// Buffer to check the length of. - /// String terminator to search for. - /// Decoded index (char) of the terminator, or max length if not found. - public static int GetStringLength(ReadOnlySpan buffer, ushort terminator = 0) - { - int index = GetTerminatorIndex(buffer, terminator); - return index == -1 ? buffer.Length / 2 : index; - } - - /// - /// Returns a 16-bit aligned index of the terminator. - /// - /// Backing buffer of the string. - /// Terminator character to search for. - /// Decoded index (char) of the terminator, or -1 if not found. - /// When used on a raw string, returns the computed length of the string, assuming a terminator is present. - public static int GetTerminatorIndex(ReadOnlySpan buffer, ushort terminator = 0) - { - var u16 = MemoryMarshal.Cast(buffer); - return u16.IndexOf(terminator); - } -} diff --git a/PKHeX.Core/PKM/Strings/Trash/TrashBytes8.cs b/PKHeX.Core/PKM/Strings/Trash/TrashBytes8.cs index 23ba53d23..4fcf981bc 100644 --- a/PKHeX.Core/PKM/Strings/Trash/TrashBytes8.cs +++ b/PKHeX.Core/PKM/Strings/Trash/TrashBytes8.cs @@ -7,7 +7,7 @@ namespace PKHeX.Core; /// public static class TrashBytes8 { - /// + /// public static int GetStringLength(ReadOnlySpan buffer) { int index = GetTerminatorIndex(buffer); diff --git a/PKHeX.Core/PKM/Strings/Trash/TrashBytesGB.cs b/PKHeX.Core/PKM/Strings/Trash/TrashBytesGB.cs index fdff997d5..9964c6332 100644 --- a/PKHeX.Core/PKM/Strings/Trash/TrashBytesGB.cs +++ b/PKHeX.Core/PKM/Strings/Trash/TrashBytesGB.cs @@ -8,7 +8,7 @@ namespace PKHeX.Core; /// public static class TrashBytesGB { - /// + /// public static int GetStringLength(ReadOnlySpan buffer) { int index = GetTerminatorIndex(buffer); diff --git a/PKHeX.Core/PKM/Strings/Trash/TrashBytesUTF16.cs b/PKHeX.Core/PKM/Strings/Trash/TrashBytesUTF16.cs new file mode 100644 index 000000000..7e911d776 --- /dev/null +++ b/PKHeX.Core/PKM/Strings/Trash/TrashBytesUTF16.cs @@ -0,0 +1,114 @@ +using System; +using System.Runtime.InteropServices; +using static System.Buffers.Binary.BinaryPrimitives; + +namespace PKHeX.Core; + +/// +/// 16-bit encoded string utility +/// +public static class TrashBytesUTF16 +{ + /// + /// Gets the length of the string based on the terminator. + /// + /// Buffer to check the length of. + /// String terminator to search for. + /// Decoded index (char) of the terminator, or max length if not found. + public static int GetStringLength(ReadOnlySpan buffer, ushort terminator = 0) + { + int index = GetTerminatorIndex(buffer, terminator); + return index == -1 ? buffer.Length / 2 : index; + } + + /// + /// Returns a 16-bit aligned index of the terminator. + /// + /// Backing buffer of the string. + /// Terminator character to search for. + /// Decoded index (char) of the terminator, or -1 if not found. + /// When used on a raw string, returns the computed length of the string, assuming a terminator is present. + public static int GetTerminatorIndex(ReadOnlySpan buffer, ushort terminator = 0) + { + var u16 = MemoryMarshal.Cast(buffer); + return u16.IndexOf(terminator); + } + + public static TrashMatch IsUnderlayerPresent(ReadOnlySpan under, ReadOnlySpan data, int charsUsed) + { + var input = MemoryMarshal.Cast(data); + return IsUnderlayerPresent(under, input, charsUsed); + } + + public static TrashMatch IsUnderlayerPresent(ReadOnlySpan under, ReadOnlySpan input, int charsUsed) + { + if (charsUsed >= under.Length) + return TrashMatch.TooLongToTell; + + for (int i = charsUsed; i < under.Length; i++) + { + var c = input[i]; + if (!BitConverter.IsLittleEndian) + c = (char)ReverseEndianness(c); + if (c == under[i]) + continue; + return TrashMatch.NotPresent; + } + return TrashMatch.Present; + } + + public static bool IsTrashNotEmpty(ReadOnlySpan span) => span.ContainsAnyExcept(0) || span.Length == 0; + public static bool IsTrashEmpty(ReadOnlySpan span) => !span.ContainsAnyExcept(0) || span.Length == 0; + + public static bool IsFinalTerminatorPresent(ReadOnlySpan buffer, byte terminator = 0) + => buffer[^1] == terminator && buffer[^2] == terminator; + + private const int BytesPerChar = 2; + + public static TrashMatch IsTrashNone(ReadOnlySpan span) + { + var charsUsed = GetTerminatorIndex(span) + 1; + var start = charsUsed * BytesPerChar; + if ((uint)start >= span.Length) + return TrashMatch.TooLongToTell; + + var remain = span[start..]; + if (!IsTrashEmpty(remain)) + return TrashMatch.NotEmpty; + return TrashMatch.PresentNone; + } + + public static TrashMatch IsTrashSingleOrNone(ReadOnlySpan span) + { + var charsUsed = GetTerminatorIndex(span) + 1; + var start = charsUsed * BytesPerChar; + if ((uint)start >= span.Length) + return TrashMatch.TooLongToTell; + + var remain = span[start..]; + var end = GetTerminatorIndex(span) + 1; + start = end * BytesPerChar; + if ((uint)start < remain.Length && !IsTrashEmpty(remain[start..])) + return TrashMatch.NotEmpty; + + return end == 1 ? TrashMatch.PresentNone : TrashMatch.PresentSingle; + } + + public static TrashMatch IsTrashSpecific(ReadOnlySpan span, ReadOnlySpan under) + { + var charsUsed = GetTerminatorIndex(span) + 1; + var start = charsUsed * BytesPerChar; + if (start >= span.Length) + return TrashMatch.TooLongToTell; + + var check = IsUnderlayerPresent(under, span, charsUsed); + if (check.IsInvalid()) + return TrashMatch.NotPresent; + + start = Math.Max(start, under.Length * BytesPerChar); + if ((uint)start < span.Length && !IsTrashEmpty(span[start..])) + return TrashMatch.NotEmpty; + + return TrashMatch.Present; + } +} diff --git a/PKHeX.Core/PKM/Strings/Trash/TrashMatch.cs b/PKHeX.Core/PKM/Strings/Trash/TrashMatch.cs index e3f420c9a..1556670a8 100644 --- a/PKHeX.Core/PKM/Strings/Trash/TrashMatch.cs +++ b/PKHeX.Core/PKM/Strings/Trash/TrashMatch.cs @@ -1,4 +1,4 @@ -namespace PKHeX.Core; +namespace PKHeX.Core; public enum TrashMatch { @@ -7,10 +7,7 @@ public enum TrashMatch /// NotPresent, - /// - /// Expected under-layer of trash was found. - /// - Present, + NotEmpty, /// /// Displayed string is too long, with all bytes covering the initial trash. @@ -18,7 +15,19 @@ public enum TrashMatch TooLongToTell, /// - /// Ignored due to other issues that would be flagged by other checks. + /// Expected under-layer of trash was found. /// - Skipped, + Present, + + PresentNone, + + PresentSingle, + + PresentMulti, +} + +public static class TrashMatchExtensions +{ + public static bool IsPresent(this TrashMatch match) => match >= TrashMatch.Present; + public static bool IsInvalid(this TrashMatch match) => match < TrashMatch.TooLongToTell; } diff --git a/PKHeX.Core/PKM/XK3.cs b/PKHeX.Core/PKM/XK3.cs index 8acc52d76..84e5893ce 100644 --- a/PKHeX.Core/PKM/XK3.cs +++ b/PKHeX.Core/PKM/XK3.cs @@ -253,8 +253,8 @@ public override int LoadString(ReadOnlySpan data, Span destBuffer) public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) => StringConverter3GC.SetString(destBuffer, value, maxLength, option); public override int GetStringTerminatorIndex(ReadOnlySpan data) - => TrashBytes.GetTerminatorIndex(data, StringConverter3GC.TerminatorBigEndian); + => TrashBytesUTF16.GetTerminatorIndex(data, StringConverter3GC.TerminatorBigEndian); public override int GetStringLength(ReadOnlySpan data) - => TrashBytes.GetStringLength(data, StringConverter3GC.TerminatorBigEndian); + => TrashBytesUTF16.GetStringLength(data, StringConverter3GC.TerminatorBigEndian); public override int GetBytesPerChar() => 2; } diff --git a/PKHeX.Core/Saves/Abstractions/ITrainerInfo.cs b/PKHeX.Core/Saves/Abstractions/ITrainerInfo.cs index b957606f0..566d84100 100644 --- a/PKHeX.Core/Saves/Abstractions/ITrainerInfo.cs +++ b/PKHeX.Core/Saves/Abstractions/ITrainerInfo.cs @@ -1,3 +1,5 @@ +using System; + namespace PKHeX.Core; /// @@ -68,7 +70,11 @@ public static bool IsFromTrainerNoVersion(ITrainerInfo tr, PKM pk) { if (tr.ID32 != pk.ID32) return false; - if (tr.OT != pk.OriginalTrainerName) + + Span ot = stackalloc char[pk.MaxStringLengthTrainer]; + int len = pk.LoadString(pk.OriginalTrainerTrash, ot); + ot = ot[..len]; + if (!ot.SequenceEqual(tr.OT)) return false; if (pk.Format == 3) @@ -104,7 +110,10 @@ public static bool IsFromTrainerEgg(this ITrainerInfo tr, PKM pk) else { return false; } } - if (tr.OT != pk.OriginalTrainerName) + Span ot = stackalloc char[pk.MaxStringLengthTrainer]; + int len = pk.LoadString(pk.OriginalTrainerTrash, ot); + ot = ot[..len]; + if (!ot.SequenceEqual(tr.OT)) return false; return true; diff --git a/PKHeX.WinForms/Subforms/Save Editors/SAV_BoxList.cs b/PKHeX.WinForms/Subforms/Save Editors/SAV_BoxList.cs index 84a680a29..c580f0167 100644 --- a/PKHeX.WinForms/Subforms/Save Editors/SAV_BoxList.cs +++ b/PKHeX.WinForms/Subforms/Save Editors/SAV_BoxList.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Windows.Forms; diff --git a/Tests/PKHeX.Core.Tests/Legality/Legal/Generation 8/0105-01 - Test - 6DDD2B435BBC alolan evolution.pk8 b/Tests/PKHeX.Core.Tests/Legality/Legal/Generation 8/0105-01 - Test - 6DDD2B435BBC alolan evolution.pk8 new file mode 100644 index 0000000000000000000000000000000000000000..8cf2ebb8b75f385cd32b0280b1ec1527e9332fce GIT binary patch literal 344 zcmdlJ?X1nfa5p!Tfr;^OIZwTV6F-A10}BHP*l3s9GBL3*Ff#o4!v+>1o(N$`WhiDS z0cvpkkE$yY%H={8W^iHrU(c`)T@hYE(%kGFt3OqlOYi%45S&E7=fe_k_dvSpu@nx1m>bS zf&pQk2|_uTbqB&1;gSF%hNM@14B|iwARz)(Y9fTDhk+pl=zbYSM#fN}>y7|r0iD4a AzW@LL