Reduce alloc for ot/nick by raw trash reads

Legality check now catches buffer overflow mons.
Now that I have each type exposing a trash length & charcount, should be easy to have some reusable trash byte measuring methods (see the old branch)
This commit is contained in:
Kurt 2024-05-12 14:46:58 -05:00
parent 08ed482555
commit 7b6abc0520
70 changed files with 286 additions and 138 deletions

View file

@ -18,7 +18,7 @@ public sealed class FakeSaveFile : SaveFile
public override int MaxEV => 0;
public override ReadOnlySpan<ushort> HeldItems => Legal.HeldItems_RS;
public override int GetBoxOffset(int box) => -1;
public override int MaxStringLengthOT => 5;
public override int MaxStringLengthTrainer => 5;
public override int MaxStringLengthNickname => 5;
public override ushort MaxMoveID => 5;
public override ushort MaxSpeciesID => 1;

View file

@ -202,25 +202,39 @@ public sealed record EncounterGift1 : IEncounterable, IEncounterMatch, IEncounte
return true;
}
private bool IsTrainerNameValid(PKM pk) => Trainer switch
private bool IsTrainerNameValid(PKM pk)
{
Recipient => true,
VirtualConsoleMew => pk.OriginalTrainerName == (pk.Language == 1 ? VirtualConsoleMewJPN : VirtualConsoleMewINT),
Stadium => pk.Language switch
if (Trainer == Recipient)
return true;
Span<char> trainer = stackalloc char[pk.TrashCharCountTrainer];
int len = pk.LoadString(pk.OriginalTrainerTrash, trainer);
trainer = trainer[..len];
if (Trainer == EuropeTour)
return IsTourOT(trainer);
var language = pk.Language;
if (Trainer == VirtualConsoleMew)
return trainer.SequenceEqual(language == 1 ? VirtualConsoleMewJPN : VirtualConsoleMewINT);
if (Trainer == Stadium)
{
(int)Japanese => pk.OriginalTrainerName == StadiumJPN,
_ => pk.OriginalTrainerName switch
return language switch
{
StadiumENG => true,
StadiumFRE => true,
StadiumITA => true,
StadiumSPA => true,
_ => false,
},
},
EuropeTour => IsTourOT(pk.OriginalTrainerName),
_ => true,
};
(int)Japanese => trainer.SequenceEqual(StadiumJPN),
_ => trainer switch
{
StadiumENG => true,
StadiumFRE => true,
StadiumITA => true,
StadiumSPA => true,
_ => false,
},
};
}
return true;
}
private bool IsTrainerIDValid(PKM pk) => Trainer switch
{

View file

@ -75,7 +75,12 @@ public sealed record EncounterTrade1 : IEncounterable, IEncounterMatch, IFixedTr
return pk.OriginalTrainerTrash is [StringConverter1.TradeOTCode, StringConverter1.TerminatorCode, ..];
var lang = pk.Language;
var expect = StringConverter12Transporter.GetTradeNameGen1(lang);
return pk.OriginalTrainerName == expect;
Span<char> trainer = stackalloc char[pk.TrashCharCountTrainer];
int len = pk.LoadString(pk.OriginalTrainerTrash, trainer);
trainer = trainer[..len];
return trainer.SequenceEqual(expect);
}
private int GetNicknameIndex(ReadOnlySpan<char> nickname) => GetIndex(nickname, Nicknames);

View file

@ -131,10 +131,19 @@ public sealed record EncounterTrade2 : IEncounterable, IEncounterMatch, IFixedTr
}
return true;
}
private bool IsTrainerNicknameCorrect(PKM pk)
{
// Italian and English share the same OT name for Spearow, but different nicknames. Others are like this, so we need to check both.
var lang = DetectLanguage(pk, pk.OriginalTrainerName, pk.Nickname);
Span<char> trainer = stackalloc char[pk.TrashCharCountTrainer];
int len = pk.LoadString(pk.OriginalTrainerTrash, trainer);
trainer = trainer[..len];
Span<char> nickname = stackalloc char[pk.TrashCharCountNickname];
len = pk.LoadString(pk.NicknameTrash, nickname);
nickname = nickname[..len];
var lang = DetectLanguage(pk, trainer, nickname);
return lang != -1;
}
@ -244,7 +253,9 @@ public sealed record EncounterTrade2 : IEncounterable, IEncounterMatch, IFixedTr
public bool IsNicknameMatch(PKM pk, ReadOnlySpan<char> nickname, int _)
{
// Match both.
ReadOnlySpan<char> trainer = pk.OriginalTrainerName;
Span<char> trainer = stackalloc char[pk.TrashCharCountTrainer];
int len = pk.LoadString(pk.OriginalTrainerTrash, trainer);
trainer = trainer[..len];
var lang = DetectLanguage(pk, trainer, nickname);
if (lang != -1)
return true;

View file

@ -243,15 +243,18 @@ public sealed record EncounterTrade4PID
return lang;
// Since two locales (JPN/ENG) can have the same LanguageID, check which we should be validating with.
ReadOnlySpan<char> ot = pk.OriginalTrainerName;
Span<char> trainer = stackalloc char[pk.TrashCharCountTrainer];
var len = pk.LoadString(pk.OriginalTrainerTrash, trainer);
trainer = trainer[..len];
var expect = TrainerNames[1];
var match = ot.SequenceEqual(expect);
var match = trainer.SequenceEqual(expect);
if (!match)
return 2; // verify strings with English locale instead.
return lang;
}
private int DetectTradeLanguageG4MeisterMagikarp(PKM pk,int currentLanguageID)
private int DetectTradeLanguageG4MeisterMagikarp(PKM pk, int currentLanguageID)
{
if (currentLanguageID == (int)LanguageID.English)
return (int)LanguageID.German;
@ -259,7 +262,13 @@ public sealed record EncounterTrade4PID
// All have German, regardless of origin version.
var lang = DetectTradeLanguage(pk.OriginalTrainerName, currentLanguageID);
if (lang == (int)LanguageID.English) // possible collision with FR/ES/DE. Check nickname
return pk.Nickname == Nicknames[(int)LanguageID.French] ? (int)LanguageID.French : (int)LanguageID.Spanish; // Spanish is same as English
{
Span<char> nickname = stackalloc char[pk.TrashCharCountNickname];
var len = pk.LoadString(pk.NicknameTrash, nickname);
nickname = nickname[..len];
return nickname.SequenceEqual(Nicknames[(int)LanguageID.French]) ? (int)LanguageID.French : (int)LanguageID.Spanish; // Spanish is same as English
}
return lang;
}
@ -271,8 +280,14 @@ public sealed record EncounterTrade4PID
// All have English, regardless of origin version.
var lang = DetectTradeLanguage(pk.OriginalTrainerName, currentLanguageID);
if (lang == 2) // possible collision with ES/IT. Check nickname
return pk.Nickname == Nicknames[(int)LanguageID.Italian] ? (int)LanguageID.Italian : (int)LanguageID.Spanish;
if (lang == (int)LanguageID.English) // possible collision with ES/IT. Check nickname
{
Span<char> nickname = stackalloc char[pk.TrashCharCountNickname];
var len = pk.LoadString(pk.NicknameTrash, nickname);
nickname = nickname[..len];
return nickname.SequenceEqual(Nicknames[(int)LanguageID.Italian]) ? (int)LanguageID.Italian : (int)LanguageID.Spanish;
}
return lang;
}

View file

@ -74,7 +74,12 @@ public static class MysteryGiftVerifier
return false; // no data
if (!val.HasFlag(MysteryGiftRestriction.OTReplacedOnTrade))
return false;
return CurrentOTMatchesReplaced(g.Generation, pk.OriginalTrainerName);
Span<char> trainer = stackalloc char[pk.TrashCharCountTrainer];
int len = pk.LoadString(pk.OriginalTrainerTrash, trainer);
trainer = trainer[..len];
return CurrentOTMatchesReplaced(g.Generation, trainer);
}
private static bool CanVersionReceiveGift(byte generation, int version4bit, GameVersion version)

View file

@ -1,4 +1,4 @@
#define SUPPRESS
//#define SUPPRESS
using System;
using System.Collections.Generic;

View file

@ -16,12 +16,15 @@ public sealed class NicknameVerifier : Verifier
var pk = data.Entity;
// If the Pokémon is not nicknamed, it should match one of the language strings.
var nickname = pk.Nickname;
if (nickname.Length == 0)
Span<char> nickname = stackalloc char[pk.TrashCharCountNickname];
int len = pk.LoadString(pk.NicknameTrash, nickname);
if (len == 0)
{
data.AddLine(GetInvalid(LNickLengthShort));
return;
}
nickname = nickname[..len];
if (pk.Species > SpeciesName.MaxSpeciesID)
{
data.AddLine(Get(LNickLengthShort, Severity.Invalid));
@ -65,7 +68,7 @@ public sealed class NicknameVerifier : Verifier
// Non-nicknamed strings have already been checked.
if (ParseSettings.Settings.WordFilter.IsEnabled(pk.Format) && pk.IsNicknamed)
{
if (WordFilter.IsFiltered(nickname, out var badPattern))
if (WordFilter.IsFiltered(nickname.ToString(), out var badPattern))
data.AddLine(GetInvalid($"Word Filter: {badPattern}"));
if (TrainerNameVerifier.ContainsTooManyNumbers(nickname, data.Info.Generation))
data.AddLine(GetInvalid("Word Filter: Too many numbers."));
@ -261,16 +264,40 @@ public sealed class NicknameVerifier : Verifier
}
if (format == 5 && enc.Generation != 5) // transfer
{
if (canHaveAnyLanguage)
return !SpeciesName.IsNicknamedAnyLanguage(species, nickname, 4);
expect = SpeciesName.GetSpeciesNameGeneration(species, language, 4);
return nickname.SequenceEqual(expect);
}
return IsMatch45(nickname, species, expect, language, canHaveAnyLanguage);
return false;
}
private static bool IsMatch45(ReadOnlySpan<char> nickname, ushort species, string expect, int language, bool canHaveAnyLanguage)
{
if (species is (int)Species.Farfetchd)
{
if (nickname.Length == expect.Length)
{
// Compare as upper -- different apostrophe than Gen4 encoding.
if (IsMatchUpper45(nickname, expect))
return true;
}
if (SpeciesName.IsApostropheFarfetchdLanguage(language))
return false; // must have matched above
}
if (canHaveAnyLanguage)
return !SpeciesName.IsNicknamedAnyLanguage(species, nickname, 4);
expect = SpeciesName.GetSpeciesNameGeneration(species, language, 4);
return nickname.SequenceEqual(expect);
}
private static bool IsMatchUpper45(ReadOnlySpan<char> nickname, string expect)
{
for (int i = 0; i < expect.Length; i++)
{
if (nickname[i] != char.ToUpperInvariant(expect[i]))
return false;
}
return true;
}
private static void VerifyNicknameEgg(LegalityAnalysis data)
{
var Info = data.Info;
@ -280,7 +307,7 @@ public sealed class NicknameVerifier : Verifier
if (pk.IsNicknamed != flagState)
data.AddLine(GetInvalid(flagState ? LNickFlagEggYes : LNickFlagEggNo, CheckIdentifier.Egg));
Span<char> nickname = stackalloc char[pk.MaxStringLengthNickname];
Span<char> nickname = stackalloc char[pk.TrashCharCountNickname];
int len = pk.LoadString(pk.NicknameTrash, nickname);
nickname = nickname[..len];
@ -351,7 +378,15 @@ public sealed class NicknameVerifier : Verifier
return;
}
lang = t.DetectMeisterMagikarpLanguage(pk.Nickname, pk.OriginalTrainerName, lang);
Span<char> trainer = stackalloc char[pk.TrashCharCountTrainer];
int len = pk.LoadString(pk.OriginalTrainerTrash, trainer);
trainer = trainer[..len];
Span<char> nickname = stackalloc char[pk.TrashCharCountNickname];
len = pk.LoadString(pk.NicknameTrash, nickname);
nickname = nickname[..len];
lang = t.DetectMeisterMagikarpLanguage(nickname, trainer, lang);
if (lang == -1) // err
data.AddLine(GetInvalid(string.Format(LOTLanguage, $"{Japanese}/{German}", $"{(LanguageID)pk.Language}"), CheckIdentifier.Language));
}
@ -388,7 +423,12 @@ public sealed class NicknameVerifier : Verifier
if (pk.IsNicknamed && (pk.Format < 8 || pk.FatefulEncounter))
return GetInvalid(LEncTradeChangedNickname, CheckIdentifier.Nickname);
int lang = pk.Language;
if (!t.IsTrainerMatch(pk, pk.OriginalTrainerName, lang))
Span<char> trainer = stackalloc char[pk.TrashCharCountTrainer];
int len = pk.LoadString(pk.OriginalTrainerTrash, trainer);
trainer = trainer[..len];
if (!t.IsTrainerMatch(pk, trainer, lang))
return GetInvalid(LEncTradeIndexBad, CheckIdentifier.Trainer);
return GetValid(LEncTradeUnchanged, CheckIdentifier.Nickname);
}
@ -413,7 +453,11 @@ public sealed class NicknameVerifier : Verifier
private static void VerifyTrainerName(LegalityAnalysis data, IFixedTrainer ft, int language)
{
var pk = data.Entity;
if (!ft.IsTrainerMatch(pk, pk.OriginalTrainerName, language))
Span<char> trainer = stackalloc char[pk.TrashCharCountTrainer];
int len = pk.LoadString(pk.OriginalTrainerTrash, trainer);
trainer = trainer[..len];
if (!ft.IsTrainerMatch(pk, trainer, language))
data.AddLine(GetInvalid(LEncTradeChangedOT, CheckIdentifier.Trainer));
}
}

View file

@ -3,7 +3,7 @@ using static PKHeX.Core.LegalityCheckStrings;
namespace PKHeX.Core;
/// <summary>
/// Verifies the <see cref="PKM.OriginalTrainerName"/>.
/// Verifies the <see cref="PKM.ID32"/>.
/// </summary>
public sealed class TrainerIDVerifier : Verifier
{

View file

@ -23,11 +23,16 @@ public sealed class TrainerNameVerifier : Verifier
if (!IsPlayerOriginalTrainer(enc))
return; // already verified
var ot = pk.OriginalTrainerName;
if (ot.Length == 0)
Span<char> trainer = stackalloc char[pk.TrashCharCountTrainer];
int len = pk.LoadString(pk.OriginalTrainerTrash, trainer);
if (len == 0)
{
data.AddLine(GetInvalid(LOTShort));
return;
}
trainer = trainer[..len];
if (IsOTNameSuspicious(ot))
if (IsOTNameSuspicious(trainer))
{
data.AddLine(Get(LOTSuspicious, Severity.Fishy));
}
@ -36,17 +41,17 @@ public sealed class TrainerNameVerifier : Verifier
{
VerifyOTGB(data);
}
else if (ot.Length > Legal.GetMaxLengthOT(data.Info.Generation, (LanguageID)pk.Language))
else if (trainer.Length > Legal.GetMaxLengthOT(data.Info.Generation, (LanguageID)pk.Language))
{
if (!IsEdgeCaseLength(pk, data.EncounterOriginal, ot))
if (!IsEdgeCaseLength(pk, data.EncounterOriginal, trainer))
data.AddLine(Get(LOTLong, Severity.Invalid));
}
if (ParseSettings.Settings.WordFilter.IsEnabled(pk.Format))
{
if (WordFilter.IsFiltered(ot, out var badPattern))
if (WordFilter.IsFiltered(trainer.ToString(), out var badPattern))
data.AddLine(GetInvalid($"Word Filter: {badPattern}"));
if (ContainsTooManyNumbers(ot, data.Info.Generation))
if (ContainsTooManyNumbers(trainer, data.Info.Generation))
data.AddLine(GetInvalid("Word Filter: Too many numbers."));
if (WordFilter.IsFiltered(pk.HandlingTrainerName, out badPattern))
@ -101,8 +106,11 @@ public sealed class TrainerNameVerifier : Verifier
if (enc is IFixedTrainer { IsFixedTrainer: true })
return; // already verified
string tr = pk.OriginalTrainerName;
if (tr.Length == 0)
Span<char> trainer = stackalloc char[pk.TrashCharCountTrainer];
int len = pk.LoadString(pk.OriginalTrainerTrash, trainer);
trainer = trainer[..len];
if (trainer.Length == 0)
{
if (pk is SK2 {TID16: 0, IsRental: true})
{
@ -114,7 +122,7 @@ public sealed class TrainerNameVerifier : Verifier
return;
}
}
VerifyGBOTWithinBounds(data, tr);
VerifyGBOTWithinBounds(data, trainer);
}
private void VerifyGBOTWithinBounds(LegalityAnalysis data, ReadOnlySpan<char> str)

View file

@ -28,6 +28,8 @@ public sealed class CK3(byte[] Data) : G3PKM(Data), IShadowCapture
public override Span<byte> OriginalTrainerTrash => Data.AsSpan(0x18, 22);
public override Span<byte> NicknameTrash => Data.AsSpan(0x2E, 22);
public Span<byte> NicknameCopy_Trash => Data.AsSpan(0x44, 22);
public override int TrashCharCountTrainer => 11;
public override int TrashCharCountNickname => 11;
// Future Attributes
public override ushort SpeciesInternal { get => ReadUInt16BigEndian(Data.AsSpan(0x00)); set => WriteUInt16BigEndian(Data.AsSpan(0x00), value); } // raw access

View file

@ -81,6 +81,8 @@ public sealed class PKH : PKM, IHandlerLanguage, IFormArgument, IHomeTrack, IBat
public override Span<byte> NicknameTrash => Core.NicknameTrash;
public override Span<byte> OriginalTrainerTrash => Core.OriginalTrainerTrash;
public override Span<byte> HandlingTrainerTrash => Core.HandlingTrainerTrash;
public override int TrashCharCountTrainer => 13;
public override int TrashCharCountNickname => 13;
public override bool IsUntraded => ReadUInt16LittleEndian(HandlingTrainerTrash) == 0; // immediately terminated HandlingTrainerName data (\0)
#region Core
@ -233,7 +235,7 @@ public sealed class PKH : PKM, IHandlerLanguage, IFormArgument, IHomeTrack, IBat
public override int MaxIV => 31;
public override int MaxEV => EffortValues.Max252;
public override int MaxStringLengthOT => 12;
public override int MaxStringLengthTrainer => 12;
public override int MaxStringLengthNickname => 12;
public override ushort MaxMoveID => Legal.MaxMoveID_8a;
public override ushort MaxSpeciesID => Legal.MaxSpeciesID_8a;

View file

@ -53,7 +53,7 @@ public interface IGameValueLimit
/// <summary>
/// Maximum length of a string field for a Trainer Name.
/// </summary>
int MaxStringLengthOT { get; }
int MaxStringLengthTrainer { get; }
/// <summary>
/// Maximum length of a string field for a Pokémon Nickname.

View file

@ -65,11 +65,13 @@ public sealed class PA8 : PKM, ISanityChecksum,
public override Span<byte> NicknameTrash => Data.AsSpan(0x60, 26);
public override Span<byte> HandlingTrainerTrash => Data.AsSpan(0xB8, 26);
public override Span<byte> OriginalTrainerTrash => Data.AsSpan(0x110, 26);
public override int TrashCharCountTrainer => 13;
public override int TrashCharCountNickname => 13;
// Maximums
public override int MaxIV => 31;
public override int MaxEV => EffortValues.Max252;
public override int MaxStringLengthOT => 12;
public override int MaxStringLengthTrainer => 12;
public override int MaxStringLengthNickname => 12;
public override uint PSV => ((PID >> 16) ^ (PID & 0xFFFF)) >> 4;

View file

@ -252,7 +252,7 @@ public sealed class PK1 : GBPKML, IPersonalType
=> StringConverter1.GetString(data, Japanese);
public override int LoadString(ReadOnlySpan<byte> data, Span<char> destBuffer)
=> StringConverter1.LoadString(data, destBuffer, Japanese);
public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option = StringConverterOption.None)
public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option)
=> StringConverter1.SetString(destBuffer, value, maxLength, Japanese, option);
/// <summary>

View file

@ -38,6 +38,8 @@ public sealed class PK3 : G3PKM, ISanityChecksum
// Trash Bytes
public override Span<byte> NicknameTrash => Data.AsSpan(0x08, 10); // no inaccessible terminator
public override Span<byte> OriginalTrainerTrash => Data.AsSpan(0x14, 7); // no inaccessible terminator
public override int TrashCharCountTrainer => 7;
public override int TrashCharCountNickname => 10;
// At top for System.Reflection execution order hack

View file

@ -43,6 +43,8 @@ public sealed class PK5 : PKM, ISanityChecksum,
// Trash Bytes
public override Span<byte> NicknameTrash => Data.AsSpan(0x48, 22);
public override Span<byte> OriginalTrainerTrash => Data.AsSpan(0x68, 16);
public override int TrashCharCountNickname => 11;
public override int TrashCharCountTrainer => 8;
// Future Attributes
public override uint EncryptionConstant { get => PID; set { } }
@ -297,7 +299,7 @@ public sealed class PK5 : PKM, ISanityChecksum,
public override GameVersion MaxGameID => Legal.MaxGameID_5; // B2
public override int MaxIV => 31;
public override int MaxEV => EffortValues.Max255;
public override int MaxStringLengthOT => 7;
public override int MaxStringLengthTrainer => 7;
public override int MaxStringLengthNickname => 10;
// Methods

View file

@ -65,11 +65,13 @@ public sealed class PK9 : PKM, ISanityChecksum, ITeraType, ITechRecord, IObedien
public override Span<byte> NicknameTrash => Data.AsSpan(0x58, 26);
public override Span<byte> HandlingTrainerTrash => Data.AsSpan(0xA8, 26);
public override Span<byte> OriginalTrainerTrash => Data.AsSpan(0xF8, 26);
public override int TrashCharCountTrainer => 13;
public override int TrashCharCountNickname => 13;
// Maximums
public override int MaxIV => 31;
public override int MaxEV => EffortValues.Max252;
public override int MaxStringLengthOT => 12;
public override int MaxStringLengthTrainer => 12;
public override int MaxStringLengthNickname => 12;
public override uint PSV => ((PID >> 16) ^ (PID & 0xFFFF)) >> 4;

View file

@ -252,8 +252,15 @@ public abstract class PKM : ISpeciesForm, ITrainerID32, IGeneration, IShiny, ILa
public virtual GameVersion MinGameID => 0;
public abstract int MaxIV { get; }
public abstract int MaxEV { get; }
public abstract int MaxStringLengthOT { get; }
/// <summary> Maximum length a Trainer Name can be represented as. </summary>
public abstract int MaxStringLengthTrainer { get; }
/// <summary> Maximum length a Nickname can be represented as. </summary>
public abstract int MaxStringLengthNickname { get; }
/// <summary> Total characters allocated for holding a Trainer Name. </summary>
public abstract int TrashCharCountTrainer { get; }
/// <summary> Total characters allocated for holding a Nickname. </summary>
public abstract int TrashCharCountNickname { get; }
// Derived
public virtual int SpriteItem => HeldItem;

View file

@ -18,7 +18,7 @@ public sealed class SK2 : GBPKM, ICaughtData2
private const int StringLength = 12;
public override EntityContext Context => EntityContext.Gen2;
public override int MaxStringLengthOT => StringLength;
public override int MaxStringLengthTrainer => StringLength;
public override int MaxStringLengthNickname => StringLength;
public SK2(bool jp = false) : base(PokeCrypto.SIZE_2STADIUM) => IsEncodingJapanese = jp;
@ -114,6 +114,8 @@ public sealed class SK2 : GBPKM, ICaughtData2
public override Span<byte> NicknameTrash => Data.AsSpan(0x24, StringLength);
public override Span<byte> OriginalTrainerTrash => Data.AsSpan(0x30, StringLength);
public override int TrashCharCountTrainer => StringLength;
public override int TrashCharCountNickname => StringLength;
#endregion

View file

@ -22,7 +22,7 @@ public abstract class G3PKM : PKM, IRibbonSetEvent3, IRibbonSetCommon3, IRibbonS
public sealed override GameVersion MaxGameID => Legal.MaxGameID_3;
public sealed override int MaxIV => 31;
public sealed override int MaxEV => EffortValues.Max255;
public sealed override int MaxStringLengthOT => 7;
public sealed override int MaxStringLengthTrainer => 7;
public sealed override int MaxStringLengthNickname => 10;
// Generated Attributes

View file

@ -21,7 +21,7 @@ public abstract class G4PKM : PKM, IHandlerUpdate,
public sealed override GameVersion MaxGameID => Legal.MaxGameID_4;
public sealed override int MaxIV => 31;
public sealed override int MaxEV => EffortValues.Max255;
public sealed override int MaxStringLengthOT => 7;
public sealed override int MaxStringLengthTrainer => 7;
public sealed override int MaxStringLengthNickname => 10;
public sealed override uint PSV => ((PID >> 16) ^ (PID & 0xFFFF)) >> 3;
@ -41,6 +41,8 @@ public abstract class G4PKM : PKM, IHandlerUpdate,
// Trash Bytes
public sealed override Span<byte> NicknameTrash => Data.AsSpan(0x48, 22);
public sealed override Span<byte> OriginalTrainerTrash => Data.AsSpan(0x68, 16);
public override int TrashCharCountNickname => 11;
public override int TrashCharCountTrainer => 8;
// Future Attributes
public sealed override uint EncryptionConstant { get => PID; set { } }

View file

@ -15,6 +15,8 @@ public abstract class G6PKM : PKM, ISanityChecksum, IHandlerUpdate
public sealed override Span<byte> NicknameTrash => Data.AsSpan(0x40, 26);
public sealed override Span<byte> HandlingTrainerTrash => Data.AsSpan(0x78, 26);
public sealed override Span<byte> OriginalTrainerTrash => Data.AsSpan(0xB0, 26);
public override int TrashCharCountTrainer => 13;
public override int TrashCharCountNickname => 13;
public abstract ushort Sanity { get; set; }
public abstract ushort Checksum { get; set; }
@ -122,7 +124,7 @@ public abstract class G6PKM : PKM, ISanityChecksum, IHandlerUpdate
// Maximums
public sealed override int MaxIV => 31;
public sealed override int MaxEV => EffortValues.Max252;
public sealed override int MaxStringLengthOT => 12;
public sealed override int MaxStringLengthTrainer => 12;
public sealed override int MaxStringLengthNickname => 12;
}

View file

@ -41,11 +41,13 @@ public abstract class G8PKM : PKM, ISanityChecksum,
public override Span<byte> NicknameTrash => Data.AsSpan(0x58, 26);
public override Span<byte> HandlingTrainerTrash => Data.AsSpan(0xA8, 26);
public override Span<byte> OriginalTrainerTrash => Data.AsSpan(0xF8, 26);
public override int TrashCharCountTrainer => 13;
public override int TrashCharCountNickname => 13;
// Maximums
public override int MaxIV => 31;
public override int MaxEV => EffortValues.Max252;
public override int MaxStringLengthOT => 12;
public override int MaxStringLengthTrainer => 12;
public override int MaxStringLengthNickname => 12;
public override uint PSV => ((PID >> 16) ^ (PID & 0xFFFF)) >> 4;

View file

@ -77,9 +77,9 @@ public abstract class GBPKM : PKM
if (StringConverter1.IsG12German(OriginalTrainerTrash))
return (int)LanguageID.German; // german
Span<char> nick = stackalloc char[11];
int len = StringConverter1.LoadString(NicknameTrash, nick, false);
int lang = SpeciesName.GetSpeciesNameLanguage(Species, nick[..len], Format);
Span<char> nickname = stackalloc char[TrashCharCountNickname];
int len = StringConverter1.LoadString(NicknameTrash, nickname, false);
int lang = SpeciesName.GetSpeciesNameLanguage(Species, nickname[..len], Format);
if (lang > 0)
return lang;
return 0;
@ -255,8 +255,14 @@ public abstract class GBPKM : PKM
internal void ImportFromFuture(PKM pk)
{
Nickname = pk.Nickname;
OriginalTrainerName = pk.OriginalTrainerName;
Span<char> nickname = stackalloc char[pk.TrashCharCountNickname];
pk.LoadString(pk.NicknameTrash, nickname);
SetString(NicknameTrash, nickname, MaxStringLengthNickname, StringConverterOption.Clear50);
Span<char> trainer = stackalloc char[pk.TrashCharCountTrainer];
pk.LoadString(pk.OriginalTrainerTrash, trainer);
SetString(OriginalTrainerTrash, nickname, MaxStringLengthTrainer, StringConverterOption.Clear50);
IV_ATK = pk.IV_ATK / 2;
IV_DEF = pk.IV_DEF / 2;
IV_SPC = pk.IV_SPA / 2;

View file

@ -11,7 +11,7 @@ public abstract class GBPKML : GBPKM
{
internal const int StringLengthJapanese = 6;
internal const int StringLengthNotJapan = 11;
public sealed override int MaxStringLengthOT => Japanese ? 5 : 7;
public sealed override int MaxStringLengthTrainer => Japanese ? 5 : 7;
public sealed override int MaxStringLengthNickname => Japanese ? 5 : 10;
public sealed override bool Japanese => RawOT.Length == StringLengthJapanese;
@ -21,6 +21,8 @@ public abstract class GBPKML : GBPKM
// Trash Bytes
public sealed override Span<byte> NicknameTrash => RawNickname;
public sealed override Span<byte> OriginalTrainerTrash => RawOT;
public override int TrashCharCountTrainer => RawOT.Length;
public override int TrashCharCountNickname => RawNickname.Length;
protected GBPKML([ConstantExpected] int size, bool jp = false) : base(size)
{

View file

@ -98,9 +98,10 @@ public static class StringConverter345
{
for (int i = 0; i < input.Length; i++)
{
if (IsInvalid45(input[i]))
var c = input[i];
if (IsInvalid45(c))
input[i] = '?';
if (input[i] == '') // Farfetchd and CHDING nicknames
else if (c == '') // Farfetchd and CHDING nicknames
input[i] = '\''; // Wrong apostrophe, nice. Only is corrected when converted to Gen6.
}
}

View file

@ -255,8 +255,11 @@ public static class EntityConverter
if (pk.Nickname.Length > limit.MaxStringLengthNickname)
pk.Nickname = pk.Nickname[..pk.MaxStringLengthNickname];
if (pk.OriginalTrainerName.Length > limit.MaxStringLengthOT)
pk.OriginalTrainerName = pk.OriginalTrainerName[..pk.MaxStringLengthOT];
Span<char> trainer = stackalloc char[pk.TrashCharCountTrainer];
int len = pk.LoadString(pk.OriginalTrainerTrash, trainer);
var max = limit.MaxStringLengthTrainer;
if (len > max)
pk.SetString(pk.OriginalTrainerTrash, trainer[..max], max, StringConverterOption.None);
if (pk.Move1 > limit.MaxMoveID || pk.Move2 > limit.MaxMoveID || pk.Move3 > limit.MaxMoveID || pk.Move4 > limit.MaxMoveID)
pk.ClearInvalidMoves();

View file

@ -224,21 +224,26 @@ public static class EntitySorting
/// <summary>
/// Gets if the current handler is the original trainer.
/// </summary>
/// <param name="trainer">The <see cref="ITrainerInfo"/> requesting the check.</param>
/// <param name="tr">The <see cref="ITrainerInfo"/> requesting the check.</param>
/// <param name="pk">Pokémon data</param>
/// <param name="checkGame">Toggle to check the game's version or not</param>
/// <returns>True if OT, false if not OT.</returns>
public static bool IsOriginalHandler(this ITrainerInfo trainer, PKM pk, bool checkGame)
public static bool IsOriginalHandler(this ITrainerInfo tr, PKM pk, bool checkGame)
{
if (pk.Format >= 6)
return pk.CurrentHandler != 1;
if (checkGame && trainer.Version != pk.Version)
if (checkGame && tr.Version != pk.Version)
return false;
if (trainer.TID16 != pk.TID16 || trainer.SID16 != pk.SID16)
if (tr.TID16 != pk.TID16 || tr.SID16 != pk.SID16)
return false;
if (trainer.Gender != pk.OriginalTrainerGender)
if (tr.Gender != pk.OriginalTrainerGender)
return false;
return trainer.OT == pk.OriginalTrainerName;
Span<char> trainer = stackalloc char[pk.TrashCharCountTrainer];
int len = pk.LoadString(pk.OriginalTrainerTrash, trainer);
trainer = trainer[..len];
return trainer.SequenceEqual(tr.OT);
}
/// <summary>

View file

@ -97,7 +97,7 @@ public static class SpeciesName
return arr[species];
}
private static bool IsApostropheFarfetchdLanguage(int language) => language is 2 or 4 or 7;
public static bool IsApostropheFarfetchdLanguage(int language) => language is 2 or 4 or 7;
/// <summary>
/// Gets a Pokémon's default name for the desired language ID and generation.

View file

@ -28,6 +28,8 @@ public sealed class XK3 : G3PKM, IShadowCapture
public override Span<byte> OriginalTrainerTrash => Data.AsSpan(0x38, 22);
public override Span<byte> NicknameTrash => Data.AsSpan(0x4E, 22);
public Span<byte> NicknameCopy_Trash => Data.AsSpan(0x64, 22);
public override int TrashCharCountTrainer => 11;
public override int TrashCharCountNickname => 11;
public override ushort SpeciesInternal { get => ReadUInt16BigEndian(Data.AsSpan(0x00)); set => WriteUInt16BigEndian(Data.AsSpan(0x00), value); } // raw access
public override ushort Species { get => SpeciesConverter.GetNational3(SpeciesInternal); set => SpeciesInternal = SpeciesConverter.GetInternal3(value); }

View file

@ -205,7 +205,7 @@ public sealed class SAV1 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo
public override int MaxIV => 15;
public override byte Generation => 1;
public override EntityContext Context => EntityContext.Gen1;
public override int MaxStringLengthOT => Japanese ? 5 : 7;
public override int MaxStringLengthTrainer => Japanese ? 5 : 7;
public override int MaxStringLengthNickname => Japanese ? 5 : 10;
public override int BoxSlotCount => Japanese ? 30 : 20;
@ -233,8 +233,8 @@ public sealed class SAV1 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo
public override string OT
{
get => GetString(Data.AsSpan(Offsets.OT, MaxStringLengthOT));
set => SetString(Data.AsSpan(Offsets.OT, MaxStringLengthOT + 1), value, MaxStringLengthOT, StringConverterOption.ClearZero);
get => GetString(Data.AsSpan(Offsets.OT, MaxStringLengthTrainer));
set => SetString(Data.AsSpan(Offsets.OT, MaxStringLengthTrainer + 1), value, MaxStringLengthTrainer, StringConverterOption.ClearZero);
}
public Span<byte> OriginalTrainerTrash { get => Data.AsSpan(Offsets.OT, StringLength); set { if (value.Length == StringLength) value.CopyTo(Data.AsSpan(Offsets.OT)); } }
@ -261,8 +261,8 @@ public sealed class SAV1 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo
public string Rival
{
get => GetString(Data.AsSpan(Offsets.Rival, MaxStringLengthOT));
set => SetString(Data.AsSpan(Offsets.Rival, MaxStringLengthOT), value, MaxStringLengthOT, StringConverterOption.Clear50);
get => GetString(Data.AsSpan(Offsets.Rival, MaxStringLengthTrainer));
set => SetString(Data.AsSpan(Offsets.Rival, MaxStringLengthTrainer), value, MaxStringLengthTrainer, StringConverterOption.Clear50);
}
public Span<byte> Rival_Trash { get => Data.AsSpan(Offsets.Rival, StringLength); set { if (value.Length == StringLength) value.CopyTo(Data.AsSpan(Offsets.Rival)); } }

View file

@ -24,7 +24,7 @@ public sealed class SAV1Stadium : SAV_STADIUM
private int StringLength => Japanese ? StringLengthJ : StringLengthU;
private const int StringLengthJ = 6;
private const int StringLengthU = 11;
public override int MaxStringLengthOT => StringLength;
public override int MaxStringLengthTrainer => StringLength;
public override int MaxStringLengthNickname => StringLength;
public override int BoxCount => Japanese ? 8 : 12;
public override int BoxSlotCount => Japanese ? 30 : 20;

View file

@ -22,7 +22,7 @@ public sealed class SAV1StadiumJ : SAV_STADIUM
public override byte Generation => 1;
public override EntityContext Context => EntityContext.Gen1;
private const int StringLength = 6; // Japanese Only
public override int MaxStringLengthOT => StringLength;
public override int MaxStringLengthTrainer => StringLength;
public override int MaxStringLengthNickname => StringLength;
public override int BoxCount => 4; // 8 boxes stored sequentially; latter 4 are backups
public override int BoxSlotCount => 30;

View file

@ -258,7 +258,7 @@ public sealed class SAV2 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo
public override int MaxIV => 15;
public override byte Generation => 2;
public override EntityContext Context => EntityContext.Gen2;
public override int MaxStringLengthOT => Japanese || Korean ? 5 : 7;
public override int MaxStringLengthTrainer => Japanese || Korean ? 5 : 7;
public override int MaxStringLengthNickname => Japanese || Korean ? 5 : 10;
public override int BoxSlotCount => Japanese ? 30 : 20;
@ -303,8 +303,8 @@ public sealed class SAV2 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo
public override string OT
{
get => GetString(Data.AsSpan(Offsets.Trainer1 + 2, (Korean ? 2 : 1) * MaxStringLengthOT));
set => SetString(Data.AsSpan(Offsets.Trainer1 + 2, (Korean ? 2 : 1) * MaxStringLengthOT), value, 8, StringConverterOption.Clear50);
get => GetString(Data.AsSpan(Offsets.Trainer1 + 2, (Korean ? 2 : 1) * MaxStringLengthTrainer));
set => SetString(Data.AsSpan(Offsets.Trainer1 + 2, (Korean ? 2 : 1) * MaxStringLengthTrainer), value, 8, StringConverterOption.Clear50);
}
public Span<byte> OriginalTrainerTrash
@ -315,8 +315,8 @@ public sealed class SAV2 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo
public string Rival
{
get => GetString(Data.AsSpan(Offsets.Rival, (Korean ? 2 : 1) * MaxStringLengthOT));
set => SetString(Data.AsSpan(Offsets.Rival, (Korean ? 2 : 1) * MaxStringLengthOT), value, 8, StringConverterOption.Clear50);
get => GetString(Data.AsSpan(Offsets.Rival, (Korean ? 2 : 1) * MaxStringLengthTrainer));
set => SetString(Data.AsSpan(Offsets.Rival, (Korean ? 2 : 1) * MaxStringLengthTrainer), value, 8, StringConverterOption.Clear50);
}
public Span<byte> Rival_Trash

View file

@ -21,7 +21,7 @@ public sealed class SAV2Stadium : SAV_STADIUM, IBoxDetailName
public override byte Generation => 2;
public override EntityContext Context => EntityContext.Gen2;
private const int StringLength = 12;
public override int MaxStringLengthOT => StringLength;
public override int MaxStringLengthTrainer => StringLength;
public override int MaxStringLengthNickname => StringLength;
public override int BoxCount => Japanese ? 9 : 14;
public override int BoxSlotCount => Japanese ? 30 : 20;

View file

@ -191,7 +191,7 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37, IBoxDetai
public sealed override int MaxEV => EffortValues.Max255;
public sealed override byte Generation => 3;
public sealed override EntityContext Context => EntityContext.Gen3;
public sealed override int MaxStringLengthOT => 7;
public sealed override int MaxStringLengthTrainer => 7;
public sealed override int MaxStringLengthNickname => 10;
public sealed override int MaxMoney => 999999;
@ -324,7 +324,7 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37, IBoxDetai
get => GetString(OriginalTrainerTrash);
set
{
int len = Japanese ? 5 : MaxStringLengthOT;
int len = Japanese ? 5 : MaxStringLengthTrainer;
SetString(OriginalTrainerTrash[..len], value, len, StringConverterOption.ClearFF); // match the game-init FF terminating pattern
}
}

View file

@ -121,7 +121,7 @@ public sealed class SAV3Colosseum : SaveFile, IGCSaveFile, IBoxDetailName, IDayc
public override int MaxEV => EffortValues.Max255;
public override byte Generation => 3;
public override EntityContext Context => EntityContext.Gen3;
public override int MaxStringLengthOT => 10; // as evident by Mattle Ho-Oh
public override int MaxStringLengthTrainer => 10; // as evident by Mattle Ho-Oh
public override int MaxStringLengthNickname => 10;
public override int MaxMoney => 9999999;

View file

@ -115,7 +115,7 @@ public sealed class SAV3RSBox : SaveFile, IGCSaveFile, IBoxDetailName, IBoxDetai
public override int MaxEV => EffortValues.Max255;
public override byte Generation => 3;
public override EntityContext Context => EntityContext.Gen3;
public override int MaxStringLengthOT => 7;
public override int MaxStringLengthTrainer => 7;
public override int MaxStringLengthNickname => 10;
public override int MaxMoney => 999999;

View file

@ -192,7 +192,7 @@ public sealed class SAV3XD : SaveFile, IGCSaveFile, IBoxDetailName, IDaycareStor
public override int MaxEV => EffortValues.Max255;
public override byte Generation => 3;
public override EntityContext Context => EntityContext.Gen3;
public override int MaxStringLengthOT => 7;
public override int MaxStringLengthTrainer => 7;
public override int MaxStringLengthNickname => 10;
public override int MaxMoney => 9999999;

View file

@ -96,7 +96,7 @@ public abstract class SAV4 : SaveFile, IEventFlag37, IDaycareStorage, IDaycareRa
public sealed override EntityContext Context => EntityContext.Gen4;
public int EventFlagCount => 0xB60; // 2912
public int EventWorkCount => (EventFlag - EventWork) >> 1;
public sealed override int MaxStringLengthOT => 7;
public sealed override int MaxStringLengthTrainer => 7;
public sealed override int MaxStringLengthNickname => 10;
public sealed override int MaxMoney => 999999;
public sealed override int MaxCoins => 50_000;
@ -243,7 +243,7 @@ public abstract class SAV4 : SaveFile, IEventFlag37, IDaycareStorage, IDaycareRa
public override string OT
{
get => GetString(General.Slice(Trainer1, 16));
set => SetString(General.Slice(Trainer1, 16), value, MaxStringLengthOT, StringConverterOption.ClearZero);
set => SetString(General.Slice(Trainer1, 16), value, MaxStringLengthTrainer, StringConverterOption.ClearZero);
}
public override uint ID32
@ -325,7 +325,7 @@ public abstract class SAV4 : SaveFile, IEventFlag37, IDaycareStorage, IDaycareRa
public string Rival
{
get => GetString(Rival_Trash);
set => SetString(Rival_Trash, value, MaxStringLengthOT, StringConverterOption.ClearZero);
set => SetString(Rival_Trash, value, MaxStringLengthTrainer, StringConverterOption.ClearZero);
}
public abstract Span<byte> Rival_Trash { get; set; }

View file

@ -107,7 +107,7 @@ public sealed class SAV4BR : SaveFile, IBoxDetailName
public override int MaxEV => EffortValues.Max255;
public override byte Generation => 4;
public override EntityContext Context => EntityContext.Gen4;
public override int MaxStringLengthOT => 7;
public override int MaxStringLengthTrainer => 7;
public override int MaxStringLengthNickname => 10;
public override int MaxMoney => 999999;
public override int Language => (int)LanguageID.English; // prevent KOR from inhabiting

View file

@ -116,8 +116,8 @@ public sealed class SAV4DP : SAV4Sinnoh
public override Span<byte> Rival_Trash
{
get => General.Slice(0x25A8, MaxStringLengthOT * 2);
set { if (value.Length == MaxStringLengthOT * 2) value.CopyTo(General[0x25A8..]); }
get => General.Slice(0x25A8, MaxStringLengthTrainer * 2);
set { if (value.Length == MaxStringLengthTrainer * 2) value.CopyTo(General[0x25A8..]); }
}
public override int X2 { get => ReadUInt16LittleEndian(General[0x25FA..]); set => WriteUInt16LittleEndian(General[0x25FA..], (ushort)value); }

View file

@ -190,10 +190,10 @@ public sealed class SAV4HGSS : SAV4, IBoxDetailName, IBoxDetailWallpaper
public override Span<byte> Rival_Trash
{
get => RivalSpan;
set { if (value.Length == MaxStringLengthOT * 2) value.CopyTo(RivalSpan); }
set { if (value.Length == MaxStringLengthTrainer * 2) value.CopyTo(RivalSpan); }
}
private Span<byte> RivalSpan => General.Slice(0x22D4, MaxStringLengthOT * 2);
private Span<byte> RivalSpan => General.Slice(0x22D4, MaxStringLengthTrainer * 2);
public override int X2 { get => ReadUInt16LittleEndian(General[0x236E..]); set => WriteUInt16LittleEndian(General[0x236E..], (ushort)value); }
public override int Y2 { get => ReadUInt16LittleEndian(General[0x2372..]); set => WriteUInt16LittleEndian(General[0x2372..], (ushort)value); }

View file

@ -161,10 +161,10 @@ public sealed class SAV4Pt : SAV4Sinnoh
public override Span<byte> Rival_Trash
{
get => RivalSpan;
set { if (value.Length == MaxStringLengthOT * 2) value.CopyTo(RivalSpan); }
set { if (value.Length == MaxStringLengthTrainer * 2) value.CopyTo(RivalSpan); }
}
private Span<byte> RivalSpan => General.Slice(0x27E8, MaxStringLengthOT * 2);
private Span<byte> RivalSpan => General.Slice(0x27E8, MaxStringLengthTrainer * 2);
public override int X2 { get => ReadUInt16LittleEndian(General[0x287E..]); set => WriteUInt16LittleEndian(General[0x287E..], (ushort)value); }
public override int Y2 { get => ReadUInt16LittleEndian(General[0x2882..]); set => WriteUInt16LittleEndian(General[0x2882..], (ushort)value); }

View file

@ -26,7 +26,7 @@ public abstract class SAV5 : SaveFile, ISaveBlock5BW, IEventFlagProvider37, IBox
public override int MaxEV => EffortValues.Max255;
public override byte Generation => 5;
public override EntityContext Context => EntityContext.Gen5;
public override int MaxStringLengthOT => 7;
public override int MaxStringLengthTrainer => 7;
public override int MaxStringLengthNickname => 10;
public override ushort MaxMoveID => Legal.MaxMoveID_5;

View file

@ -65,13 +65,13 @@ public sealed class SAV5B2W2 : SAV5, ISaveBlock5B2W2
public string Rival
{
get => GetString(Rival_Trash);
set => SetString(Rival_Trash, value, MaxStringLengthOT, StringConverterOption.ClearZero);
set => SetString(Rival_Trash, value, MaxStringLengthTrainer, StringConverterOption.ClearZero);
}
public Span<byte> Rival_Trash
{
get => Data.AsSpan(0x23BA4, MaxStringLengthOT * 2);
set { if (value.Length == MaxStringLengthOT * 2) value.CopyTo(Data.AsSpan(0x23BA4)); }
get => Data.AsSpan(0x23BA4, MaxStringLengthTrainer * 2);
set { if (value.Length == MaxStringLengthTrainer * 2) value.CopyTo(Data.AsSpan(0x23BA4)); }
}
public override Memory<byte> BattleVideoNative => Data.AsMemory(0x4C000, BattleVideo5.SIZE_USED);

View file

@ -26,7 +26,7 @@ public abstract class SAV6 : SAV_BEEF, ITrainerStatRecord, ISaveBlock6Core, IReg
public sealed override int MaxEV => EffortValues.Max252;
public sealed override byte Generation => 6;
public sealed override EntityContext Context => EntityContext.Gen6;
public override int MaxStringLengthOT => 12;
public override int MaxStringLengthTrainer => 12;
public override int MaxStringLengthNickname => 12;
public override ushort MaxSpeciesID => Legal.MaxSpeciesID_6;

View file

@ -75,7 +75,7 @@ public abstract class SAV7 : SAV_BEEF, ITrainerStatRecord, ISaveBlock7Main, IReg
public override int MaxEV => EffortValues.Max252;
public override byte Generation => 7;
public override EntityContext Context => EntityContext.Gen7;
public override int MaxStringLengthOT => 12;
public override int MaxStringLengthTrainer => 12;
public override int MaxStringLengthNickname => 12;
public override int MaxBallID => Legal.MaxBallID_7; // 26

View file

@ -80,7 +80,7 @@ public sealed class SAV7b : SAV_BEEF, ISaveBlock7b, IGameSync, IMysteryGiftStora
public override int MaxIV => 31;
public override int MaxEV => EffortValues.Max252;
public override int MaxStringLengthOT => 12;
public override int MaxStringLengthTrainer => 12;
public override int MaxStringLengthNickname => 12;
public override bool HasParty => false; // handled via team slots

View file

@ -119,7 +119,7 @@ public sealed class SAV8BS : SaveFile, ISaveFileRevision, ITrainerStatRecord, IE
public override byte Generation => 8;
public override EntityContext Context => EntityContext.Gen8b;
public override PersonalTable8BDSP Personal => PersonalTable.BDSP;
public override int MaxStringLengthOT => 12;
public override int MaxStringLengthTrainer => 12;
public override int MaxStringLengthNickname => 12;
public override ushort MaxMoveID => Legal.MaxMoveID_8b;
public override ushort MaxSpeciesID => Legal.MaxSpeciesID_8b;
@ -283,7 +283,7 @@ public sealed class SAV8BS : SaveFile, ISaveFileRevision, ITrainerStatRecord, IE
public string Rival
{
get => GetString(Data.AsSpan(0x55F4, 0x1A));
set => SetString(Data.AsSpan(0x55F4, 0x1A), value, MaxStringLengthOT, StringConverterOption.ClearZero);
set => SetString(Data.AsSpan(0x55F4, 0x1A), value, MaxStringLengthTrainer, StringConverterOption.ClearZero);
}
public short ZoneID // map

View file

@ -67,7 +67,7 @@ public sealed class SAV8LA : SaveFile, ISaveBlock8LA, ISCBlockArray, ISaveFileRe
public override int MaxEV => EffortValues.Max252;
public override byte Generation => 8;
public override EntityContext Context => EntityContext.Gen8a;
public override int MaxStringLengthOT => 12;
public override int MaxStringLengthTrainer => 12;
public override int MaxStringLengthNickname => 12;
public override bool ChecksumsValid => true;

View file

@ -151,7 +151,7 @@ public sealed class SAV8SWSH : SaveFile, ISaveBlock8SWSH, ITrainerStatRecord, IS
public override int MaxEV => EffortValues.Max252;
public override byte Generation => 8;
public override EntityContext Context => EntityContext.Gen8;
public override int MaxStringLengthOT => 12;
public override int MaxStringLengthTrainer => 12;
public override int MaxStringLengthNickname => 12;
protected override PK8 GetPKM(byte[] data) => new(data);
protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray8(data);

View file

@ -157,7 +157,7 @@ public sealed class SAV9SV : SaveFile, ISaveBlock9Main, ISCBlockArray, ISaveFile
public override int MaxEV => EffortValues.Max252;
public override byte Generation => 9;
public override EntityContext Context => EntityContext.Gen9;
public override int MaxStringLengthOT => 12;
public override int MaxStringLengthTrainer => 12;
public override int MaxStringLengthNickname => 12;
protected override PK9 GetPKM(byte[] data) => new(data);
protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray9(data);

View file

@ -88,7 +88,7 @@ public abstract class SaveFile : ITrainerInfo, IGameValueLimit, IGeneration, IVe
#region Stored PKM Limits
public abstract IPersonalTable Personal { get; }
public abstract int MaxStringLengthOT { get; }
public abstract int MaxStringLengthTrainer { get; }
public abstract int MaxStringLengthNickname { get; }
public abstract ushort MaxMoveID { get; }
public abstract ushort MaxSpeciesID { get; }

View file

@ -42,7 +42,7 @@ public abstract class BulkStorage : SaveFile
public sealed override int MaxItemID => blank.MaxItemID;
public sealed override int MaxBallID => blank.MaxBallID;
public sealed override GameVersion MaxGameID => blank.MaxGameID;
public sealed override int MaxStringLengthOT => blank.MaxStringLengthOT;
public sealed override int MaxStringLengthTrainer => blank.MaxStringLengthTrainer;
public sealed override int MaxStringLengthNickname => blank.MaxStringLengthNickname;
public bool IsBigEndian => blank is BK4 or XK3 or CK3;

View file

@ -13,7 +13,7 @@ public sealed class PlayerData5(SAV5 sav, Memory<byte> raw) : SaveBlock<SAV5>(sa
public string OT
{
get => SAV.GetString(OriginalTrainerTrash);
set => SAV.SetString(OriginalTrainerTrash, value, SAV.MaxStringLengthOT, StringConverterOption.ClearZero);
set => SAV.SetString(OriginalTrainerTrash, value, SAV.MaxStringLengthTrainer, StringConverterOption.ClearZero);
}
public uint ID32

View file

@ -106,7 +106,7 @@ public class MyStatus6(SAV6 sav, Memory<byte> raw) : SaveBlock<SAV6>(sav, raw),
public string OT
{
get => SAV.GetString(OriginalTrainerTrash);
set => SAV.SetString(OriginalTrainerTrash, value, SAV.MaxStringLengthOT, StringConverterOption.ClearZero);
set => SAV.SetString(OriginalTrainerTrash, value, SAV.MaxStringLengthTrainer, StringConverterOption.ClearZero);
}
private Span<byte> GetSayingSpan(int say) => Data.Slice(GetSayingOffset(say), SAV6.LongStringLength);

View file

@ -18,7 +18,7 @@ public sealed class FieldMenu7(SAV7 sav, Memory<byte> raw) : SaveBlock<SAV7>(sav
public string RotomOT
{
get => SAV.GetString(RotomNameSpan);
set => SAV.SetString(RotomNameSpan, value, SAV.MaxStringLengthOT, StringConverterOption.ClearZero);
set => SAV.SetString(RotomNameSpan, value, SAV.MaxStringLengthTrainer, StringConverterOption.ClearZero);
}
private Span<byte> RotomNameSpan => Data.Slice(0x30, 0x1A);

View file

@ -16,6 +16,6 @@ public sealed class Misc7b(SAV7b sav, Memory<byte> raw) : SaveBlock<SAV7b>(sav,
public string Rival
{
get => SAV.GetString(Rival_Trash);
set => SAV.SetString(Rival_Trash, value, SAV.MaxStringLengthOT, StringConverterOption.ClearZero);
set => SAV.SetString(Rival_Trash, value, SAV.MaxStringLengthTrainer, StringConverterOption.ClearZero);
}
}

View file

@ -65,7 +65,7 @@ public sealed class MyStatus7b(SAV7b sav, Memory<byte> raw) : SaveBlock<SAV7b>(s
public string OT
{
get => SAV.GetString(OriginalTrainerTrash);
set => SAV.SetString(OriginalTrainerTrash, value, SAV.MaxStringLengthOT, StringConverterOption.ClearZero);
set => SAV.SetString(OriginalTrainerTrash, value, SAV.MaxStringLengthTrainer, StringConverterOption.ClearZero);
}
// The value here corresponds to a Trainer Class string (ranging from 000 to 383, use pkNX to get a full list).

View file

@ -98,7 +98,7 @@ public sealed class MyStatus7(SAV7 sav, Memory<byte> raw) : SaveBlock<SAV7>(sav,
public string OT
{
get => SAV.GetString(OriginalTrainerTrash);
set => SAV.SetString(OriginalTrainerTrash, value, SAV.MaxStringLengthOT, StringConverterOption.ClearZero);
set => SAV.SetString(OriginalTrainerTrash, value, SAV.MaxStringLengthTrainer, StringConverterOption.ClearZero);
}
public int DressUpSkinColor

View file

@ -19,7 +19,7 @@ public sealed class MyStatus8b(SAV8BS sav, Memory<byte> raw) : SaveBlock<SAV8BS>
public string OT
{
get => SAV.GetString(OriginalTrainerTrash);
set => SAV.SetString(OriginalTrainerTrash, value, SAV.MaxStringLengthOT, StringConverterOption.ClearZero);
set => SAV.SetString(OriginalTrainerTrash, value, SAV.MaxStringLengthTrainer, StringConverterOption.ClearZero);
}
public uint ID32

View file

@ -62,7 +62,7 @@ public sealed class MyStatus8a(SAV8LA sav, SCBlock block) : SaveBlock<SAV8LA>(sa
public string OT
{
get => SAV.GetString(OriginalTrainerTrash);
set => SAV.SetString(OriginalTrainerTrash, value, SAV.MaxStringLengthOT, StringConverterOption.ClearZero);
set => SAV.SetString(OriginalTrainerTrash, value, SAV.MaxStringLengthTrainer, StringConverterOption.ClearZero);
}
public byte Unk_0x50

View file

@ -170,7 +170,7 @@ public sealed class MyStatus8(SAV8SWSH sav, SCBlock block) : SaveBlock<SAV8SWSH>
public string OT
{
get => SAV.GetString(OriginalTrainerTrash);
set => SAV.SetString(OriginalTrainerTrash, value, SAV.MaxStringLengthOT, StringConverterOption.ClearZero);
set => SAV.SetString(OriginalTrainerTrash, value, SAV.MaxStringLengthTrainer, StringConverterOption.ClearZero);
}
// D0

View file

@ -12,7 +12,7 @@ public sealed class TrainerCard8(SAV8SWSH sav, SCBlock block) : SaveBlock<SAV8SW
public string OT
{
get => SAV.GetString(OriginalTrainerTrash);
set => SAV.SetString(OriginalTrainerTrash, value, SAV.MaxStringLengthOT, StringConverterOption.ClearZero);
set => SAV.SetString(OriginalTrainerTrash, value, SAV.MaxStringLengthTrainer, StringConverterOption.ClearZero);
}
public byte Language

View file

@ -83,7 +83,7 @@ public sealed class MyStatus9(SAV9SV sav, SCBlock block) : SaveBlock<SAV9SV>(sav
public string OT
{
get => SAV.GetString(OriginalTrainerTrash);
set => SAV.SetString(OriginalTrainerTrash, value, SAV.MaxStringLengthOT, StringConverterOption.ClearZero);
set => SAV.SetString(OriginalTrainerTrash, value, SAV.MaxStringLengthTrainer, StringConverterOption.ClearZero);
}
public byte BirthMonth { get => Data[0x5A]; set => Data[0x5A] = value; }

View file

@ -1997,8 +1997,8 @@ public sealed partial class PKMEditor : UserControl, IMainEditor
PopulateFields(Entity);
// Save File Specific Limits
TB_OT.MaxLength = Entity.MaxStringLengthOT;
TB_HT.MaxLength = Entity.MaxStringLengthOT;
TB_OT.MaxLength = Entity.MaxStringLengthTrainer;
TB_HT.MaxLength = Entity.MaxStringLengthTrainer;
TB_Nickname.MaxLength = Entity.MaxStringLengthNickname;
// Hide Unused Tabs

View file

@ -131,7 +131,7 @@ public partial class PokePreview : Form
display.Visible = true;
}
public static Size MeasureSize(string text, Font font)
public static Size MeasureSize(ReadOnlySpan<char> text, Font font)
{
const TextFormatFlags flags = TextFormatFlags.LeftAndRightPadding | TextFormatFlags.VerticalCenter;
return TextRenderer.MeasureText(text, font, new Size(), flags);

View file

@ -18,7 +18,7 @@ public partial class SAV_SimpleTrainer : Form
Loading = true;
cba = [CHK_1, CHK_2, CHK_3, CHK_4, CHK_5, CHK_6, CHK_7, CHK_8];
TB_OTName.MaxLength = SAV.MaxStringLengthOT;
TB_OTName.MaxLength = SAV.MaxStringLengthTrainer;
B_MaxCash.Click += (sender, e) => MT_Money.Text = SAV.MaxMoney.ToString();
B_MaxCoins.Click += (sender, e) => MT_Coins.Text = SAV.MaxCoins.ToString();
MT_Money.Mask = "".PadRight((int)Math.Floor(Math.Log10(SAV.MaxMoney) + 1), '0');