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
This commit is contained in:
Kurt 2024-05-27 18:21:11 -05:00 committed by GitHub
parent ca2bd3baf4
commit c10c2a0dc2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 435 additions and 127 deletions

View file

@ -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 @@ public sealed record EncounterGift2
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 @@ public sealed record EncounterGift2
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<char> 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<char> 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,

View file

@ -48,6 +48,9 @@ public abstract record EncounterStatic8Nest<T>(GameVersion Version)
public PK8 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted);
protected virtual void SetTrainerName(ReadOnlySpan<char> 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 abstract record EncounterStatic8Nest<T>(GameVersion Version)
Version = version,
Language = lang,
OriginalTrainerGender = tr.Gender,
OriginalTrainerName = tr.OT,
OriginalTrainerFriendship = pi.BaseFriendship,
Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation),
@ -75,6 +77,7 @@ public abstract record EncounterStatic8Nest<T>(GameVersion Version)
DynamaxLevel = DynamaxLevel,
CanGigantamax = CanGigantamax,
};
SetTrainerName(tr.OT, pk);
SetPINGA(pk, criteria, pi);

View file

@ -41,6 +41,16 @@ public sealed record EncounterStatic8U : EncounterStatic8Nest<EncounterStatic8U>
}
protected override ushort GetLocation() => Location;
protected override void SetTrainerName(ReadOnlySpan<char> 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 sealed record EncounterStatic8U : EncounterStatic8Nest<EncounterStatic8U>
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<char> GetScientistName(int language) => language switch
{
(int)LanguageID.Japanese => "けんきゅういん",

View file

@ -0,0 +1,6 @@
namespace PKHeX.Core;
public interface ITrashUnderlaySpecies
{
bool IsTrashUnderlaySpecies(PKM pk);
}

View file

@ -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
}

View file

@ -327,6 +327,7 @@ public sealed class LegalityAnalysis
HyperTraining.Verify(this);
MiscValues.VerifyVersionEvolution(this);
Trash.Verify(this);
if (format < 8)
return;

View file

@ -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();
}

View file

@ -86,7 +86,7 @@ public sealed class TrainerNameVerifier : Verifier
if (e is IFixedTrainer { IsFixedTrainer: true })
return true; // already verified
if (e is MysteryGift mg && mg.OriginalTrainerName.Length == ot.Length)
if (e is WC3 { Species: (int)Species.HoOh } mattle && pk is CK3 && mattle.OriginalTrainerName.Length == ot.Length)
return true; // Mattle Ho-Oh
return false;
}

View file

@ -0,0 +1,165 @@
using System;
using static PKHeX.Core.LegalityCheckStrings;
using static PKHeX.Core.StringSource;
namespace PKHeX.Core;
/// <summary>
/// Verifies the trash bytes of various strings.
/// </summary>
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<byte> 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<byte> span, StringSource s)
{
var result = TrashBytesUTF16.IsTrashSingleOrNone(span);
if (result.IsInvalid())
data.AddLine(GetInvalid(Format(s, LTrashBytesShouldBeEmpty)));
}
private void VerifyTrashSpecific(LegalityAnalysis data, ReadOnlySpan<byte> span, ReadOnlySpan<char> 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<byte> 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<byte> span, StringSource s)
{
if (!TrashBytesUTF16.IsTrashNotEmpty(span))
data.AddLine(GetInvalid(Format(s, LTrashBytesExpected)));
}
private void VerifyTrashEmpty(LegalityAnalysis data, ReadOnlySpan<byte> span, StringSource s)
{
if (!TrashBytesUTF16.IsTrashEmpty(span))
data.AddLine(GetInvalid(Format(s, LTrashBytesShouldBeEmpty)));
}
}
public enum StringSource : byte { Nickname, OriginalTrainer, HandlingTrainer }

View file

@ -538,7 +538,7 @@ public sealed class WC6(byte[] Data) : DataMysteryGift(Data), IRibbonSetEvent3,
}
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;

View file

@ -577,7 +577,7 @@ public sealed class WC7(byte[] Data) : DataMysteryGift(Data), IRibbonSetEvent3,
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;

View file

@ -8,7 +8,7 @@ namespace PKHeX.Core;
/// Generation 8 Mystery Gift Template File
/// </summary>
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 @@ public sealed class WC8(byte[] Data) : DataMysteryGift(Data), ILangNick, INature
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 sealed class WC8(byte[] Data) : DataMysteryGift(Data), ILangNick, INature
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 sealed class WC8(byte[] Data) : DataMysteryGift(Data), ILangNick, INature
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++)
{

View file

@ -320,8 +320,8 @@ public sealed class BK4 : G4PKM
public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option)
=> StringConverter4.SetString(destBuffer, value, maxLength, Language, option);
public override int GetStringTerminatorIndex(ReadOnlySpan<byte> data)
=> TrashBytes.GetTerminatorIndex(data, StringConverter4.Terminator);
=> TrashBytesUTF16.GetTerminatorIndex(data, StringConverter4.Terminator);
public override int GetStringLength(ReadOnlySpan<byte> data)
=> TrashBytes.GetStringLength(data, StringConverter4.Terminator);
=> TrashBytesUTF16.GetStringLength(data, StringConverter4.Terminator);
public override int GetBytesPerChar() => 2;
}

View file

@ -249,8 +249,8 @@ public sealed class CK3(byte[] Data) : G3PKM(Data), IShadowCapture
public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option)
=> StringConverter3GC.SetString(destBuffer, value, maxLength, option);
public override int GetStringTerminatorIndex(ReadOnlySpan<byte> data)
=> TrashBytes.GetTerminatorIndex(data, StringConverter3GC.TerminatorBigEndian);
=> TrashBytesUTF16.GetTerminatorIndex(data, StringConverter3GC.TerminatorBigEndian);
public override int GetStringLength(ReadOnlySpan<byte> data)
=> TrashBytes.GetStringLength(data, StringConverter3GC.TerminatorBigEndian);
=> TrashBytesUTF16.GetStringLength(data, StringConverter3GC.TerminatorBigEndian);
public override int GetBytesPerChar() => 2;
}

View file

@ -433,9 +433,9 @@ public sealed class PKH : PKM, IHandlerLanguage, IFormArgument, IHomeTrack, IBat
public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option)
=> StringConverter8.SetString(destBuffer, value, maxLength, option);
public override int GetStringTerminatorIndex(ReadOnlySpan<byte> data)
=> TrashBytes.GetTerminatorIndex(data);
=> TrashBytesUTF16.GetTerminatorIndex(data);
public override int GetStringLength(ReadOnlySpan<byte> data)
=> TrashBytes.GetStringLength(data);
=> TrashBytesUTF16.GetStringLength(data);
public override int GetBytesPerChar() => 2;
/// <summary>

View file

@ -587,7 +587,10 @@ public sealed class PA8 : PKM, ISanityChecksum,
return false;
if (tr.Gender != OriginalTrainerGender)
return false;
return tr.OT == OriginalTrainerName;
Span<char> 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 sealed class PA8 : PKM, ISanityChecksum,
public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option)
=> StringConverter8.SetString(destBuffer, value, maxLength, option);
public override int GetStringTerminatorIndex(ReadOnlySpan<byte> data)
=> TrashBytes.GetTerminatorIndex(data);
=> TrashBytesUTF16.GetTerminatorIndex(data);
public override int GetStringLength(ReadOnlySpan<byte> data)
=> TrashBytes.GetStringLength(data);
=> TrashBytesUTF16.GetStringLength(data);
public override int GetBytesPerChar() => 2;
}

View file

@ -594,8 +594,8 @@ public sealed class PB7 : G6PKM, IHyperTrain, IAwakened, IScaledSizeValue, IComb
public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option)
=> StringConverter8.SetString(destBuffer, value, maxLength, option);
public override int GetStringTerminatorIndex(ReadOnlySpan<byte> data)
=> TrashBytes.GetTerminatorIndex(data);
=> TrashBytesUTF16.GetTerminatorIndex(data);
public override int GetStringLength(ReadOnlySpan<byte> data)
=> TrashBytes.GetStringLength(data);
=> TrashBytesUTF16.GetStringLength(data);
public override int GetBytesPerChar() => 2;
}

View file

@ -52,7 +52,10 @@ public sealed class PB8 : G8PKM
return false;
if (tr.Gender != OriginalTrainerGender)
return false;
return tr.OT == OriginalTrainerName;
Span<char> 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 sealed class PB8 : G8PKM
public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option)
=> StringConverter8.SetString(destBuffer, value, maxLength, option);
public override int GetStringTerminatorIndex(ReadOnlySpan<byte> data)
=> TrashBytes.GetTerminatorIndex(data);
=> TrashBytesUTF16.GetTerminatorIndex(data);
public override int GetStringLength(ReadOnlySpan<byte> data)
=> TrashBytes.GetStringLength(data);
=> TrashBytesUTF16.GetStringLength(data);
public override int GetBytesPerChar() => 2;
}

View file

@ -255,9 +255,9 @@ public sealed class PK1 : GBPKML, IPersonalType
public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option)
=> StringConverter1.SetString(destBuffer, value, maxLength, Japanese, option);
public override int GetStringTerminatorIndex(ReadOnlySpan<byte> data)
=> TrashBytes.GetTerminatorIndex(data, StringConverter4.Terminator);
=> TrashBytesUTF16.GetTerminatorIndex(data, StringConverter4.Terminator);
public override int GetStringLength(ReadOnlySpan<byte> data)
=> TrashBytes.GetStringLength(data, StringConverter4.Terminator);
=> TrashBytesUTF16.GetStringLength(data, StringConverter4.Terminator);
public override int GetBytesPerChar() => 2;
/// <summary>

View file

@ -396,8 +396,8 @@ public sealed class PK4 : G4PKM
public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option)
=> StringConverter4.SetString(destBuffer, value, maxLength, Language, option);
public override int GetStringTerminatorIndex(ReadOnlySpan<byte> data)
=> TrashBytes.GetTerminatorIndex(data, StringConverter4.Terminator);
=> TrashBytesUTF16.GetTerminatorIndex(data, StringConverter4.Terminator);
public override int GetStringLength(ReadOnlySpan<byte> data)
=> TrashBytes.GetStringLength(data, StringConverter4.Terminator);
=> TrashBytesUTF16.GetStringLength(data, StringConverter4.Terminator);
public override int GetBytesPerChar() => 2;
}

View file

@ -318,7 +318,10 @@ public sealed class PK5 : PKM, ISanityChecksum,
return false;
if (tr.Gender != OriginalTrainerGender)
return false;
return tr.OT == OriginalTrainerName;
Span<char> 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 sealed class PK5 : PKM, ISanityChecksum,
public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option)
=> StringConverter5.SetString(destBuffer, value, maxLength, Language, option);
public override int GetStringTerminatorIndex(ReadOnlySpan<byte> data)
=> TrashBytes.GetTerminatorIndex(data, StringConverter5.Terminator);
=> TrashBytesUTF16.GetTerminatorIndex(data, StringConverter5.Terminator);
public override int GetStringLength(ReadOnlySpan<byte> data)
=> TrashBytes.GetStringLength(data, StringConverter5.Terminator);
=> TrashBytesUTF16.GetStringLength(data, StringConverter5.Terminator);
public override int GetBytesPerChar() => 2;
/// <inheritdoc cref="G4PKM.CheckKoreanNidoranDPPt"/>

View file

@ -532,8 +532,8 @@ public sealed class PK6 : G6PKM, IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetC
public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option)
=> StringConverter6.SetString(destBuffer, value, maxLength, Language, option);
public override int GetStringTerminatorIndex(ReadOnlySpan<byte> data)
=> TrashBytes.GetTerminatorIndex(data);
=> TrashBytesUTF16.GetTerminatorIndex(data);
public override int GetStringLength(ReadOnlySpan<byte> data)
=> TrashBytes.GetStringLength(data);
=> TrashBytesUTF16.GetStringLength(data);
public override int GetBytesPerChar() => 2;
}

View file

@ -557,9 +557,9 @@ public sealed class PK7 : G6PKM, IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetC
public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option)
=> StringConverter7.SetString(destBuffer, value, maxLength, Language);
public override int GetStringTerminatorIndex(ReadOnlySpan<byte> data)
=> TrashBytes.GetTerminatorIndex(data);
=> TrashBytesUTF16.GetTerminatorIndex(data);
public override int GetStringLength(ReadOnlySpan<byte> data)
=> TrashBytes.GetStringLength(data);
=> TrashBytesUTF16.GetStringLength(data);
public override int GetBytesPerChar() => 2;
}

View file

@ -43,7 +43,10 @@ public sealed class PK8 : G8PKM, IHandlerUpdate
return false;
if (tr.Gender != OriginalTrainerGender)
return false;
return tr.OT == OriginalTrainerName;
Span<char> 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 sealed class PK8 : G8PKM, IHandlerUpdate
public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option)
=> StringConverter8.SetString(destBuffer, value, maxLength, option);
public override int GetStringTerminatorIndex(ReadOnlySpan<byte> data)
=> TrashBytes.GetTerminatorIndex(data);
=> TrashBytesUTF16.GetTerminatorIndex(data);
public override int GetStringLength(ReadOnlySpan<byte> data)
=> TrashBytes.GetStringLength(data);
=> TrashBytesUTF16.GetStringLength(data);
public override int GetBytesPerChar() => 2;
}

View file

@ -566,7 +566,10 @@ public sealed class PK9 : PKM, ISanityChecksum, ITeraType, ITechRecord, IObedien
return false;
if (tr.Language != Language)
return false;
return tr.OT == OriginalTrainerName;
Span<char> 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 sealed class PK9 : PKM, ISanityChecksum, ITeraType, ITechRecord, IObedien
public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option)
=> StringConverter8.SetString(destBuffer, value, maxLength, option);
public override int GetStringTerminatorIndex(ReadOnlySpan<byte> data)
=> TrashBytes.GetTerminatorIndex(data);
=> TrashBytesUTF16.GetTerminatorIndex(data);
public override int GetStringLength(ReadOnlySpan<byte> data)
=> TrashBytes.GetStringLength(data);
=> TrashBytesUTF16.GetStringLength(data);
public override int GetBytesPerChar() => 2;
}

View file

@ -358,8 +358,8 @@ public sealed class RK4 : G4PKM
public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option)
=> StringConverter4.SetString(destBuffer, value, maxLength, Language, option);
public override int GetStringTerminatorIndex(ReadOnlySpan<byte> data)
=> TrashBytes.GetTerminatorIndex(data, StringConverter4.Terminator);
=> TrashBytesUTF16.GetTerminatorIndex(data, StringConverter4.Terminator);
public override int GetStringLength(ReadOnlySpan<byte> data)
=> TrashBytes.GetStringLength(data, StringConverter4.Terminator);
=> TrashBytesUTF16.GetStringLength(data, StringConverter4.Terminator);
public override int GetBytesPerChar() => 2;
}

View file

@ -290,7 +290,10 @@ public abstract class G4PKM : PKM, IHandlerUpdate,
return false;
if (tr.Gender != OriginalTrainerGender)
return false;
return tr.OT == OriginalTrainerName;
Span<char> ot = stackalloc char[MaxStringLengthTrainer];
int len = LoadString(OriginalTrainerTrash, ot);
return ot[..len].SequenceEqual(tr.OT);
}
public void UpdateHandler(ITrainerInfo tr)

View file

@ -89,7 +89,10 @@ public abstract class G6PKM : PKM, ISanityChecksum, IHandlerUpdate
return false;
if (tr.Gender != OriginalTrainerGender)
return false;
return tr.OT == OriginalTrainerName;
Span<char> ot = stackalloc char[MaxStringLengthTrainer];
int len = LoadString(OriginalTrainerTrash, ot);
return ot[..len].SequenceEqual(tr.OT);
}
public void UpdateHandler(ITrainerInfo tr)

View file

@ -62,7 +62,10 @@ public abstract class GBPKM : PKM
get
{
var spName = SpeciesName.GetSpeciesNameGeneration(Species, GuessedLanguage(), Format);
return Nickname != spName;
Span<char> nickname = stackalloc char[TrashCharCountNickname];
int len = LoadString(NicknameTrash, nickname);
return !nickname[..len].SequenceEqual(spName);
}
}

View file

@ -85,9 +85,9 @@ public static class StringConverter8
/// <returns>Indication of the under string's presence.</returns>
public static TrashMatch ApplyTrashBytes(Span<byte> top, ReadOnlySpan<char> 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 class StringConverter8
public static TrashMatch GetTrashState(ReadOnlySpan<byte> top, ReadOnlySpan<char> 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 @@ public static class StringConverter8
var region = u16[..length];
char seek = ' ';
if (BitConverter.IsLittleEndian)
if (!BitConverter.IsLittleEndian)
seek = (char)ReverseEndianness(' ');
var trim = region.Trim(seek);

View file

@ -1,35 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace PKHeX.Core;
/// <summary>
/// 16-bit encoded string utility
/// </summary>
public static class TrashBytes
{
/// <summary>
/// Gets the length of the string based on the terminator.
/// </summary>
/// <param name="buffer">Buffer to check the length of.</param>
/// <param name="terminator">String terminator to search for.</param>
/// <returns>Decoded index (char) of the terminator, or max length if not found.</returns>
public static int GetStringLength(ReadOnlySpan<byte> buffer, ushort terminator = 0)
{
int index = GetTerminatorIndex(buffer, terminator);
return index == -1 ? buffer.Length / 2 : index;
}
/// <summary>
/// Returns a 16-bit aligned index of the terminator.
/// </summary>
/// <param name="buffer">Backing buffer of the string.</param>
/// <param name="terminator">Terminator character to search for.</param>
/// <returns>Decoded index (char) of the terminator, or -1 if not found.</returns>
/// <remarks>When used on a raw string, returns the computed length of the string, assuming a terminator is present.</remarks>
public static int GetTerminatorIndex(ReadOnlySpan<byte> buffer, ushort terminator = 0)
{
var u16 = MemoryMarshal.Cast<byte, ushort>(buffer);
return u16.IndexOf(terminator);
}
}

View file

@ -7,7 +7,7 @@ namespace PKHeX.Core;
/// </summary>
public static class TrashBytes8
{
/// <inheritdoc cref="TrashBytes.GetStringLength"/>
/// <inheritdoc cref="TrashBytesUTF16.GetStringLength"/>
public static int GetStringLength(ReadOnlySpan<byte> buffer)
{
int index = GetTerminatorIndex(buffer);

View file

@ -8,7 +8,7 @@ namespace PKHeX.Core;
/// </summary>
public static class TrashBytesGB
{
/// <inheritdoc cref="TrashBytes.GetStringLength"/>
/// <inheritdoc cref="TrashBytesUTF16.GetStringLength"/>
public static int GetStringLength(ReadOnlySpan<byte> buffer)
{
int index = GetTerminatorIndex(buffer);

View file

@ -0,0 +1,114 @@
using System;
using System.Runtime.InteropServices;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
/// <summary>
/// 16-bit encoded string utility
/// </summary>
public static class TrashBytesUTF16
{
/// <summary>
/// Gets the length of the string based on the terminator.
/// </summary>
/// <param name="buffer">Buffer to check the length of.</param>
/// <param name="terminator">String terminator to search for.</param>
/// <returns>Decoded index (char) of the terminator, or max length if not found.</returns>
public static int GetStringLength(ReadOnlySpan<byte> buffer, ushort terminator = 0)
{
int index = GetTerminatorIndex(buffer, terminator);
return index == -1 ? buffer.Length / 2 : index;
}
/// <summary>
/// Returns a 16-bit aligned index of the terminator.
/// </summary>
/// <param name="buffer">Backing buffer of the string.</param>
/// <param name="terminator">Terminator character to search for.</param>
/// <returns>Decoded index (char) of the terminator, or -1 if not found.</returns>
/// <remarks>When used on a raw string, returns the computed length of the string, assuming a terminator is present.</remarks>
public static int GetTerminatorIndex(ReadOnlySpan<byte> buffer, ushort terminator = 0)
{
var u16 = MemoryMarshal.Cast<byte, ushort>(buffer);
return u16.IndexOf(terminator);
}
public static TrashMatch IsUnderlayerPresent(ReadOnlySpan<char> under, ReadOnlySpan<byte> data, int charsUsed)
{
var input = MemoryMarshal.Cast<byte, char>(data);
return IsUnderlayerPresent(under, input, charsUsed);
}
public static TrashMatch IsUnderlayerPresent(ReadOnlySpan<char> under, ReadOnlySpan<char> 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<byte> span) => span.ContainsAnyExcept<byte>(0) || span.Length == 0;
public static bool IsTrashEmpty(ReadOnlySpan<byte> span) => !span.ContainsAnyExcept<byte>(0) || span.Length == 0;
public static bool IsFinalTerminatorPresent(ReadOnlySpan<byte> buffer, byte terminator = 0)
=> buffer[^1] == terminator && buffer[^2] == terminator;
private const int BytesPerChar = 2;
public static TrashMatch IsTrashNone(ReadOnlySpan<byte> 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<byte> 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<byte> span, ReadOnlySpan<char> 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;
}
}

View file

@ -1,4 +1,4 @@
namespace PKHeX.Core;
namespace PKHeX.Core;
public enum TrashMatch
{
@ -7,10 +7,7 @@ public enum TrashMatch
/// </summary>
NotPresent,
/// <summary>
/// Expected under-layer of trash was found.
/// </summary>
Present,
NotEmpty,
/// <summary>
/// Displayed string is too long, with all bytes covering the initial trash.
@ -18,7 +15,19 @@ public enum TrashMatch
TooLongToTell,
/// <summary>
/// Ignored due to other issues that would be flagged by other checks.
/// Expected under-layer of trash was found.
/// </summary>
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;
}

View file

@ -253,8 +253,8 @@ public sealed class XK3 : G3PKM, IShadowCapture
public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option)
=> StringConverter3GC.SetString(destBuffer, value, maxLength, option);
public override int GetStringTerminatorIndex(ReadOnlySpan<byte> data)
=> TrashBytes.GetTerminatorIndex(data, StringConverter3GC.TerminatorBigEndian);
=> TrashBytesUTF16.GetTerminatorIndex(data, StringConverter3GC.TerminatorBigEndian);
public override int GetStringLength(ReadOnlySpan<byte> data)
=> TrashBytes.GetStringLength(data, StringConverter3GC.TerminatorBigEndian);
=> TrashBytesUTF16.GetStringLength(data, StringConverter3GC.TerminatorBigEndian);
public override int GetBytesPerChar() => 2;
}

View file

@ -1,3 +1,5 @@
using System;
namespace PKHeX.Core;
/// <summary>
@ -68,7 +70,11 @@ public static class TrainerInfoExtensions
{
if (tr.ID32 != pk.ID32)
return false;
if (tr.OT != pk.OriginalTrainerName)
Span<char> 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 class TrainerInfoExtensions
else { return false; }
}
if (tr.OT != pk.OriginalTrainerName)
Span<char> ot = stackalloc char[pk.MaxStringLengthTrainer];
int len = pk.LoadString(pk.OriginalTrainerTrash, ot);
ot = ot[..len];
if (!ot.SequenceEqual(tr.OT))
return false;
return true;

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics;
using System.Windows.Forms;