diff --git a/PKHeX.Core/Editing/Showdown/ShowdownSet.cs b/PKHeX.Core/Editing/Showdown/ShowdownSet.cs index e94ab5a4a..01c163204 100644 --- a/PKHeX.Core/Editing/Showdown/ShowdownSet.cs +++ b/PKHeX.Core/Editing/Showdown/ShowdownSet.cs @@ -424,7 +424,7 @@ public sealed class ShowdownSet : IBattleTemplate private string GetSpeciesNickname(string specForm) { - if (Nickname.Length == 0) + if (Nickname.Length == 0 || Nickname == specForm) return specForm; bool isNicknamed = SpeciesName.IsNicknamedAnyLanguage(Species, Nickname, Context.Generation()); if (!isNicknamed) diff --git a/PKHeX.Core/Legality/Verifiers/NicknameVerifier.cs b/PKHeX.Core/Legality/Verifiers/NicknameVerifier.cs index e91e826f1..34d812c60 100644 --- a/PKHeX.Core/Legality/Verifiers/NicknameVerifier.cs +++ b/PKHeX.Core/Legality/Verifiers/NicknameVerifier.cs @@ -280,7 +280,10 @@ public sealed class NicknameVerifier : Verifier if (pk.IsNicknamed != flagState) data.AddLine(GetInvalid(flagState ? LNickFlagEggYes : LNickFlagEggNo, CheckIdentifier.Egg)); - ReadOnlySpan nickname = pk.Nickname; + Span nickname = stackalloc char[pk.MaxStringLengthNickname]; + int len = pk.LoadString(pk.NicknameTrash, nickname); + nickname = nickname[..len]; + if (pk.Format == 2 && !SpeciesName.IsNicknamedAnyLanguage(0, nickname, 2)) data.AddLine(GetValid(LNickMatchLanguageEgg, CheckIdentifier.Egg)); else if (!nickname.SequenceEqual(SpeciesName.GetEggName(pk.Language, Info.Generation))) diff --git a/PKHeX.Core/MysteryGifts/PCD.cs b/PKHeX.Core/MysteryGifts/PCD.cs index 150b9489a..14df6efeb 100644 --- a/PKHeX.Core/MysteryGifts/PCD.cs +++ b/PKHeX.Core/MysteryGifts/PCD.cs @@ -76,7 +76,7 @@ public sealed class PCD(byte[] Data) public override string CardTitle { get => StringConverter4.GetString(CardTitleSpan); - set => StringConverter4.SetString(CardTitleSpan, value, TitleLength / 2, StringConverterOption.ClearFF); + set => StringConverter4.SetString(CardTitleSpan, value, TitleLength / 2, 0, StringConverterOption.ClearFF); } public ushort CardCompatibility => ReadUInt16LittleEndian(Data.AsSpan(0x14C)); // rest of bytes we don't really care about diff --git a/PKHeX.Core/MysteryGifts/PGF.cs b/PKHeX.Core/MysteryGifts/PGF.cs index 372f38eea..691e41a75 100644 --- a/PKHeX.Core/MysteryGifts/PGF.cs +++ b/PKHeX.Core/MysteryGifts/PGF.cs @@ -57,7 +57,7 @@ public sealed class PGF(byte[] Data) : DataMysteryGift(Data), IRibbonSetEvent3, public string Nickname { get => StringConverter5.GetString(Data.AsSpan(0x1E, 11 * 2)); - set => StringConverter5.SetString(Data.AsSpan(0x1E, 11 * 2), value, 11, StringConverterOption.ClearFF); + set => StringConverter5.SetString(Data.AsSpan(0x1E, 11 * 2), value, 11, Language, StringConverterOption.ClearFF); } public Nature Nature { get => (Nature)Data[0x34]; set => Data[0x34] = (byte)value; } @@ -83,7 +83,7 @@ public sealed class PGF(byte[] Data) : DataMysteryGift(Data), IRibbonSetEvent3, public override string OriginalTrainerName { get => StringConverter5.GetString(Data.AsSpan(0x4A, 8 * 2)); - set => StringConverter5.SetString(Data.AsSpan(0x4A, 8 * 2), value, 8, StringConverterOption.ClearFF); + set => StringConverter5.SetString(Data.AsSpan(0x4A, 8 * 2), value, 8, Language, StringConverterOption.ClearFF); } public byte OTGender { get => Data[0x5A]; set => Data[0x5A] = value; } @@ -93,7 +93,7 @@ public sealed class PGF(byte[] Data) : DataMysteryGift(Data), IRibbonSetEvent3, public override string CardTitle { get => StringConverter5.GetString(Data.AsSpan(0x60, 37 * 2)); - set => StringConverter5.SetString(Data.AsSpan(0x60, 37 * 2), value, 36, StringConverterOption.ClearZero); + set => StringConverter5.SetString(Data.AsSpan(0x60, 37 * 2), value, 36, Language, StringConverterOption.ClearZero); } // Card Attributes diff --git a/PKHeX.Core/MysteryGifts/PL6.cs b/PKHeX.Core/MysteryGifts/PL6.cs index dfa14a01e..6cf116951 100644 --- a/PKHeX.Core/MysteryGifts/PL6.cs +++ b/PKHeX.Core/MysteryGifts/PL6.cs @@ -35,7 +35,7 @@ public sealed class PL6(Memory Raw) /// /// Name of data source /// - public string Origin { get => StringConverter6.GetString(Source); set => StringConverter6.SetString(Source, value, 54, StringConverterOption.ClearZero); } + public string Origin { get => StringConverter6.GetString(Source); set => StringConverter6.SetString(Source, value, 54, 0, StringConverterOption.ClearZero); } // Pokemon transfer flags? public uint Flags1 { get => ReadUInt32LittleEndian(Data[0x099..]); set => WriteUInt32LittleEndian(Data[0x099..], value); } @@ -109,7 +109,7 @@ public sealed class LinkEntity6(Memory Raw) : IRibbonSetEvent3, IRibbonSet public string Nickname { get => StringConverter6.GetString(NicknameTrash); - set => StringConverter6.SetString(NicknameTrash, value, 12, StringConverterOption.ClearZero); + set => StringConverter6.SetString(NicknameTrash, value, 12, Language, StringConverterOption.ClearZero); } public Nature Nature { get => (Nature)Data[0x38]; set => Data[0x38] = (byte)value; } @@ -141,7 +141,7 @@ public sealed class LinkEntity6(Memory Raw) : IRibbonSetEvent3, IRibbonSet public string OT { get => StringConverter6.GetString(OriginalTrainerTrash); - set => StringConverter6.SetString(OriginalTrainerTrash, value, 12, StringConverterOption.ClearZero); + set => StringConverter6.SetString(OriginalTrainerTrash, value, 12, Language, StringConverterOption.ClearZero); } public int Level { get => Data[0x68]; set => Data[0x68] = (byte)value; } diff --git a/PKHeX.Core/MysteryGifts/WC6.cs b/PKHeX.Core/MysteryGifts/WC6.cs index b73343221..bb0daa2c7 100644 --- a/PKHeX.Core/MysteryGifts/WC6.cs +++ b/PKHeX.Core/MysteryGifts/WC6.cs @@ -43,7 +43,7 @@ public sealed class WC6(byte[] Data) : DataMysteryGift(Data), IRibbonSetEvent3, { // Max len 36 char, followed by null terminator get => StringConverter6.GetString(Data.AsSpan(2, 0x4A)); - set => StringConverter6.SetString(Data.AsSpan(2, 0x4A), value, 36, StringConverterOption.ClearZero); + set => StringConverter6.SetString(Data.AsSpan(2, 0x4A), value, 36, Language, StringConverterOption.ClearZero); } internal uint RawDate @@ -170,7 +170,7 @@ public sealed class WC6(byte[] Data) : DataMysteryGift(Data), IRibbonSetEvent3, public string Nickname { get => StringConverter6.GetString(Data.AsSpan(0x86, 0x1A)); - set => StringConverter6.SetString(Data.AsSpan(0x86, 0x1A), value, 12, StringConverterOption.ClearZero); + set => StringConverter6.SetString(Data.AsSpan(0x86, 0x1A), value, 12, Language, StringConverterOption.ClearZero); } public Nature Nature { get => (Nature)Data[0xA0]; set => Data[0xA0] = (byte)value; } @@ -199,7 +199,7 @@ public sealed class WC6(byte[] Data) : DataMysteryGift(Data), IRibbonSetEvent3, public override string OriginalTrainerName { get => StringConverter6.GetString(Data.AsSpan(0xB6, 0x1A)); - set => StringConverter6.SetString(Data.AsSpan(0xB6, 0x1A), value, 12, StringConverterOption.ClearZero); + set => StringConverter6.SetString(Data.AsSpan(0xB6, 0x1A), value, 12, Language, StringConverterOption.ClearZero); } public override byte Level { get => Data[0xD0]; set => Data[0xD0] = value; } diff --git a/PKHeX.Core/PKM/BK4.cs b/PKHeX.Core/PKM/BK4.cs index 0f6977e2e..bd7e2a147 100644 --- a/PKHeX.Core/PKM/BK4.cs +++ b/PKHeX.Core/PKM/BK4.cs @@ -200,7 +200,18 @@ public sealed class BK4 : G4PKM #endregion #region Block C - public override string Nickname { get => StringConverter4GC.GetString(NicknameTrash); set => StringConverter4GC.SetString(NicknameTrash, value, 10, StringConverterOption.None); } + + public override string Nickname + { + get => StringConverter4GC.GetString(NicknameTrash); + set + { + var language = Language; + CheckKoreanNidoranDPPt(value, ref language); + StringConverter4GC.SetString(NicknameTrash, value, 10, language, StringConverterOption.None); + } + } + // 0x5E unused public override GameVersion Version { get => (GameVersion)Data[0x5F]; set => Data[0x5F] = (byte)value; } private byte RIB8 { get => Data[0x60]; set => Data[0x60] = value; } // Sinnoh 3 @@ -243,7 +254,7 @@ public sealed class BK4 : G4PKM #endregion #region Block D - public override string OriginalTrainerName { get => StringConverter4GC.GetString(OriginalTrainerTrash); set => StringConverter4GC.SetString(OriginalTrainerTrash, value, 7, StringConverterOption.None); } + public override string OriginalTrainerName { get => StringConverter4GC.GetString(OriginalTrainerTrash); set => StringConverter4GC.SetString(OriginalTrainerTrash, value, 7, Language, StringConverterOption.None); } public override byte EggYear { get => Data[0x78]; set => Data[0x78] = value; } public override byte EggMonth { get => Data[0x79]; set => Data[0x79] = value; } public override byte EggDay { get => Data[0x7A]; set => Data[0x7A] = value; } @@ -307,5 +318,5 @@ public sealed class BK4 : G4PKM public override int LoadString(ReadOnlySpan data, Span destBuffer) => StringConverter4.LoadString(data, destBuffer); public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) - => StringConverter4.SetString(destBuffer, value, maxLength, option); + => StringConverter4.SetString(destBuffer, value, maxLength, Language, option); } diff --git a/PKHeX.Core/PKM/HOME/GameDataPK8.cs b/PKHeX.Core/PKM/HOME/GameDataPK8.cs index c7b7b0b85..757b962e6 100644 --- a/PKHeX.Core/PKM/HOME/GameDataPK8.cs +++ b/PKHeX.Core/PKM/HOME/GameDataPK8.cs @@ -113,10 +113,17 @@ public sealed class GameDataPK8 : HomeOptional1, IGameDataSide, IGigantamax Ability = (ushort)pk.Ability; pkh.MarkingValue &= 0b1111_1111_1111; - if (!pk.IsNicknamed) - pkh.Nickname = SpeciesName.GetSpeciesNameImportHOME(pk.Species, pk.Language, 8); - if (FormInfo.IsTotemForm(pk.Species, pk.Form)) - pkh.Form = FormInfo.GetTotemBaseForm(pk.Species, pk.Form); + StringConverter8.NormalizeHalfWidth(pkh.OriginalTrainerTrash); + if (pk.IsNicknamed) + { + StringConverter8.NormalizeHalfWidth(pkh.NicknameTrash); + } + else + { + pkh.NicknameTrash.Clear(); + var reset = SpeciesName.GetSpeciesNameImportHOME(pk.Species, pk.Language, 8); + pkh.SetString(pkh.NicknameTrash, reset, reset.Length, StringConverterOption.None); + } } public PK8 ConvertToPKM(PKH pkh) diff --git a/PKHeX.Core/PKM/PK4.cs b/PKHeX.Core/PKM/PK4.cs index 4600d918f..02ba3d808 100644 --- a/PKHeX.Core/PKM/PK4.cs +++ b/PKHeX.Core/PKM/PK4.cs @@ -183,7 +183,12 @@ public sealed class PK4 : G4PKM public override string Nickname { get => StringConverter4.GetString(NicknameTrash); - set => StringConverter4.SetString(NicknameTrash, value, 10, StringConverterOption.None); + set + { + var language = Language; + CheckKoreanNidoranDPPt(value, ref language); + StringConverter4.SetString(NicknameTrash, value, 10, language, StringConverterOption.None); + } } // 0x5E unused @@ -231,7 +236,7 @@ public sealed class PK4 : G4PKM public override string OriginalTrainerName { get => StringConverter4.GetString(OriginalTrainerTrash); - set => StringConverter4.SetString(OriginalTrainerTrash, value, 7, StringConverterOption.None); + set => StringConverter4.SetString(OriginalTrainerTrash, value, 7, Language, StringConverterOption.None); } public override byte EggYear { get => Data[0x78]; set => Data[0x78] = value; } @@ -352,8 +357,8 @@ public sealed class PK4 : G4PKM pk5.Ball = Ball; // Transfer Nickname and OT Name, update encoding - TransferTrash(NicknameTrash, pk5.NicknameTrash); - TransferTrash(OriginalTrainerTrash, pk5.OriginalTrainerTrash); + TransferTrash(NicknameTrash, pk5.NicknameTrash, Language); + TransferTrash(OriginalTrainerTrash, pk5.OriginalTrainerTrash, Language); // Fix Level pk5.MetLevel = pk5.CurrentLevel; @@ -376,12 +381,12 @@ public sealed class PK4 : G4PKM return pk5; } - public static void TransferTrash(ReadOnlySpan src, Span dest) + public static void TransferTrash(ReadOnlySpan src, Span dest, int language) { Span temp = stackalloc char[13]; var len = StringConverter4.LoadString(src, temp); StringConverter345.TransferGlyphs45(temp[..len]); - StringConverter5.SetString(dest, temp[..len], len); + StringConverter5.SetString(dest, temp[..len], len, language); } public override string GetString(ReadOnlySpan data) @@ -389,5 +394,5 @@ public sealed class PK4 : G4PKM public override int LoadString(ReadOnlySpan data, Span destBuffer) => StringConverter4.LoadString(data, destBuffer); public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) - => StringConverter4.SetString(destBuffer, value, maxLength, option); + => StringConverter4.SetString(destBuffer, value, maxLength, Language, option); } diff --git a/PKHeX.Core/PKM/PK5.cs b/PKHeX.Core/PKM/PK5.cs index c957c924c..9e0defd54 100644 --- a/PKHeX.Core/PKM/PK5.cs +++ b/PKHeX.Core/PKM/PK5.cs @@ -191,7 +191,18 @@ public sealed class PK5 : PKM, ISanityChecksum, #endregion #region Block C - public override string Nickname { get => StringConverter5.GetString(NicknameTrash); set => StringConverter5.SetString(NicknameTrash, value, 10, StringConverterOption.None); } + + public override string Nickname + { + get => StringConverter5.GetString(NicknameTrash); + set + { + var language = Language; + CheckKoreanNidoranDPPt(value, ref language); + StringConverter5.SetString(NicknameTrash, value, 10, language, StringConverterOption.None); + } + } + // 0x5E unused public override GameVersion Version { get => (GameVersion)Data[0x5F]; set => Data[0x5F] = (byte)value; } private byte RIB8 { get => Data[0x60]; set => Data[0x60] = value; } // Sinnoh 3 @@ -234,7 +245,7 @@ public sealed class PK5 : PKM, ISanityChecksum, #endregion #region Block D - public override string OriginalTrainerName { get => StringConverter5.GetString(OriginalTrainerTrash); set => StringConverter5.SetString(OriginalTrainerTrash, value, 7, StringConverterOption.None); } + public override string OriginalTrainerName { get => StringConverter5.GetString(OriginalTrainerTrash); set => StringConverter5.SetString(OriginalTrainerTrash, value, 7, Language, StringConverterOption.None); } public override byte EggYear { get => Data[0x78]; set => Data[0x78] = value; } public override byte EggMonth { get => Data[0x79]; set => Data[0x79] = value; } public override byte EggDay { get => Data[0x7A]; set => Data[0x7A] = value; } @@ -558,5 +569,24 @@ public sealed class PK5 : PKM, ISanityChecksum, public override int LoadString(ReadOnlySpan data, Span destBuffer) => StringConverter5.LoadString(data, destBuffer); public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) - => StringConverter5.SetString(destBuffer, value, maxLength, option); + => StringConverter5.SetString(destBuffer, value, maxLength, Language, option); + + /// + /// Gen4->Gen5 chars transfer without resetting the name. Still relevant even as PK5. + private void CheckKoreanNidoranDPPt(ReadOnlySpan value, ref int language) + { + if (language != (int)LanguageID.Korean) + return; + if (IsNicknamed) + return; + if (Version is not (GameVersion.D or GameVersion.P or GameVersion.Pt)) + return; + // Full-width gender symbols for not-nicknamed Nidoran in D/P/Pt + // Full/Half is technically legal either way as trainers can reset the nickname in HG/SS, or vice versa for origins. + // Still try to set whichever it originated with. Default would be half, but if it's a Nidoran species name, set full-width. + if (Species == (int)Core.Species.NidoranM && value is "니드런♂") + language = 1; // Use Japanese to force full-width encoding of the gender symbol. + else if (Species == (int)Core.Species.NidoranF && value is "니드런♀") + language = 1; + } } diff --git a/PKHeX.Core/PKM/PK6.cs b/PKHeX.Core/PKM/PK6.cs index fee9bb976..ace2a4f5f 100644 --- a/PKHeX.Core/PKM/PK6.cs +++ b/PKHeX.Core/PKM/PK6.cs @@ -227,7 +227,7 @@ public sealed class PK6 : G6PKM, IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetC public override string Nickname { get => StringConverter6.GetString(NicknameTrash); - set => StringConverter6.SetString(NicknameTrash, value, 12, StringConverterOption.None); + set => StringConverter6.SetString(NicknameTrash, value, 12, Language, StringConverterOption.None); } public override ushort Move1 @@ -304,7 +304,7 @@ public sealed class PK6 : G6PKM, IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetC public override string HandlingTrainerName { get => StringConverter6.GetString(HandlingTrainerTrash); - set => StringConverter6.SetString(HandlingTrainerTrash, value, 12, StringConverterOption.None); + set => StringConverter6.SetString(HandlingTrainerTrash, value, 12, Language, StringConverterOption.None); } public override byte HandlingTrainerGender { get => Data[0x92]; set => Data[0x92] = value; } public override byte CurrentHandler { get => Data[0x93]; set => Data[0x93] = value; } @@ -340,7 +340,7 @@ public sealed class PK6 : G6PKM, IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetC public override string OriginalTrainerName { get => StringConverter6.GetString(OriginalTrainerTrash); - set => StringConverter6.SetString(OriginalTrainerTrash, value, 12, StringConverterOption.None); + set => StringConverter6.SetString(OriginalTrainerTrash, value, 12, Language, StringConverterOption.None); } public override byte OriginalTrainerFriendship { get => Data[0xCA]; set => Data[0xCA] = value; } public byte OriginalTrainerAffection { get => Data[0xCB]; set => Data[0xCB] = value; } @@ -529,5 +529,5 @@ public sealed class PK6 : G6PKM, IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetC public override int LoadString(ReadOnlySpan data, Span destBuffer) => StringConverter6.LoadString(data, destBuffer); public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) - => StringConverter6.SetString(destBuffer, value, maxLength, option); + => StringConverter6.SetString(destBuffer, value, maxLength, Language, option); } diff --git a/PKHeX.Core/PKM/RK4.cs b/PKHeX.Core/PKM/RK4.cs index 24052bac0..e0788831f 100644 --- a/PKHeX.Core/PKM/RK4.cs +++ b/PKHeX.Core/PKM/RK4.cs @@ -186,7 +186,12 @@ public sealed class RK4 : G4PKM public override string Nickname { get => StringConverter4.GetString(NicknameTrash); - set => StringConverter4.SetString(NicknameTrash, value, 10, StringConverterOption.None); + set + { + var language = Language; + CheckKoreanNidoranDPPt(value, ref language); + StringConverter4.SetString(NicknameTrash, value, 10, language, StringConverterOption.None); + } } // 0x5E unused @@ -234,7 +239,7 @@ public sealed class RK4 : G4PKM public override string OriginalTrainerName { get => StringConverter4.GetString(OriginalTrainerTrash); - set => StringConverter4.SetString(OriginalTrainerTrash, value, 7, StringConverterOption.None); + set => StringConverter4.SetString(OriginalTrainerTrash, value, 7, Language, StringConverterOption.None); } public override byte EggYear { get => Data[0x78]; set => Data[0x78] = value; } @@ -314,7 +319,7 @@ public sealed class RK4 : G4PKM public override string HandlingTrainerName { get => StringConverter4.GetString(HandlingTrainerTrash); - set => StringConverter4.SetString(HandlingTrainerTrash, value, 7, StringConverterOption.None); + set => StringConverter4.SetString(HandlingTrainerTrash, value, 7, Language, StringConverterOption.None); } #endregion @@ -351,5 +356,5 @@ public sealed class RK4 : G4PKM public override int LoadString(ReadOnlySpan data, Span destBuffer) => StringConverter4.LoadString(data, destBuffer); public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) - => StringConverter4.SetString(destBuffer, value, maxLength, option); + => StringConverter4.SetString(destBuffer, value, maxLength, Language, option); } diff --git a/PKHeX.Core/PKM/Shared/G4PKM.cs b/PKHeX.Core/PKM/Shared/G4PKM.cs index 114add24b..941901367 100644 --- a/PKHeX.Core/PKM/Shared/G4PKM.cs +++ b/PKHeX.Core/PKM/Shared/G4PKM.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; +using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; @@ -497,6 +498,27 @@ public abstract class G4PKM : PKM, IHandlerUpdate, // Strings need to change between Little Endian and Big Endian var s = MemoryMarshal.Cast(src); var d = MemoryMarshal.Cast(dest); - System.Buffers.Binary.BinaryPrimitives.ReverseEndianness(s, d); + ReverseEndianness(s, d); + } + + /// + /// Nidoran originating from Korean D/P/Pt games use the Full-width gender symbols instead of the other Gen4-Gen7 games which use Half-width. + /// + /// + protected void CheckKoreanNidoranDPPt(ReadOnlySpan value, ref int language) + { + if (language != (int)LanguageID.Korean) + return; + if (IsNicknamed) + return; + if (Version is not (GameVersion.D or GameVersion.P or GameVersion.Pt)) + return; + // Full-width gender symbols for not-nicknamed Nidoran in D/P/Pt + // Full/Half is technically legal either way as trainers can reset the nickname in HG/SS, or vice versa for origins. + // Still try to set whichever it originated with. Default would be half, but if it's a Nidoran species name, set full-width. + if (Species == (int)Core.Species.NidoranM && value is "니드런♂") + language = 1; // Use Japanese to force full-width encoding of the gender symbol. + else if (Species == (int)Core.Species.NidoranF && value is "니드런♀") + language = 1; } } diff --git a/PKHeX.Core/PKM/Strings/StringConverter.cs b/PKHeX.Core/PKM/Strings/StringConverter.cs index 797f8ca92..95e5b3ab2 100644 --- a/PKHeX.Core/PKM/Strings/StringConverter.cs +++ b/PKHeX.Core/PKM/Strings/StringConverter.cs @@ -76,30 +76,46 @@ public static class StringConverter byte generation, bool jp, bool isBigEndian, int language = 0) => generation switch { 3 when isBigEndian => StringConverter3GC.SetString(destBuffer, value, maxLength, option), - 4 when isBigEndian => StringConverter4GC.SetString(destBuffer, value, maxLength, option), + 4 when isBigEndian => StringConverter4GC.SetString(destBuffer, value, maxLength, language, option), 1 => StringConverter1.SetString(destBuffer, value, maxLength, jp, option), 2 => StringConverter2.SetString(destBuffer, value, maxLength, language, option), 3 => StringConverter3.SetString(destBuffer, value, maxLength, language, option), - 4 => StringConverter4.SetString(destBuffer, value, maxLength, option), - 5 => StringConverter5.SetString(destBuffer, value, maxLength, option), - 6 => StringConverter6.SetString(destBuffer, value, maxLength, option), + 4 => StringConverter4.SetString(destBuffer, value, maxLength, language, option), + 5 => StringConverter5.SetString(destBuffer, value, maxLength, language, option), + 6 => StringConverter6.SetString(destBuffer, value, maxLength, language, option), 7 => StringConverter7.SetString(destBuffer, value, maxLength, language, option), 8 => StringConverter8.SetString(destBuffer, value, maxLength, option), 9 => StringConverter8.SetString(destBuffer, value, maxLength, option), _ => throw new ArgumentOutOfRangeException(nameof(generation)), }; + /// + /// Full-width gender 16-bit char representation. + /// + public const char FGF = '\u2640'; // '♀' + /// + public const char FGM = '\u2642'; // '♂' + + /// + /// Half-width gender 16-bit char representation. + /// + /// + /// Exact value is the value when converted to Generation 6 & 7 encoding. + /// Once transferred to the Nintendo Switch era, the value is converted to full-width. + /// + public const char HGM = '\uE08E'; // '♂' + /// + public const char HGF = '\uE08F'; // '♀' + /// /// Converts full width to single width /// /// Input character to sanitize. - internal static char SanitizeChar(char chr) => chr switch + public static char NormalizeGenderSymbol(char chr) => chr switch { - '\uE08F' => '♀', - '\uE08E' => '♂', - '\u246E' => '♀', - '\u246D' => '♂', + HGM => FGM, // '♂' + HGF => FGF, // '♀' _ => chr, }; @@ -108,27 +124,10 @@ public static class StringConverter /// /// Input character to set back to data /// Checks if the overall string is full-width - internal static char UnSanitizeChar(char chr, bool fullWidth = false) + public static char UnNormalizeGenderSymbol(char chr, bool fullWidth = false) => fullWidth ? chr : chr switch { - if (fullWidth) // jp/ko/zh strings - return chr; // keep as full width - - return chr switch - { - '\u2640' => '\uE08F', - '\u2642' => '\uE08E', - _ => chr, - }; - } - - /// - /// Converts full width to half width when appropriate, for Gen5 and prior. - /// - /// Input character to set back to data - internal static char UnSanitizeChar5(char chr) => chr switch - { - '\u2640' => '\u246E', - '\u2642' => '\u246D', + FGM => HGM, // '♂' + FGF => HGF, // '♀' _ => chr, }; @@ -143,7 +142,7 @@ public static class StringConverter { if (c >> 12 is (0 or 0xE)) continue; - if (c is '\u2640' or '\u2642') // ♀♂ + if (c is FGF or FGM) // ♀♂ continue; return true; } diff --git a/PKHeX.Core/PKM/Strings/StringConverter3.cs b/PKHeX.Core/PKM/Strings/StringConverter3.cs index 6efeacf79..723ab0208 100644 --- a/PKHeX.Core/PKM/Strings/StringConverter3.cs +++ b/PKHeX.Core/PKM/Strings/StringConverter3.cs @@ -14,6 +14,11 @@ public static class StringConverter3 private const byte QuoteLeftByte = 0xB1; private const byte QuoteRightByte = 0xB2; + private const char FGM = '♂'; + private const char FGF = '♀'; + private const char HGM = StringConverter4Util.HGM; // '♂' + private const char HGF = StringConverter4Util.HGF; // '♀' + /// /// Converts a Generation 3 encoded value array to string. /// @@ -53,7 +58,7 @@ public static class StringConverter3 }; // Convert to Unicode if (c == Terminator) // Stop if Terminator/Invalid break; - c = StringConverter.SanitizeChar(c); + c = StringConverter4Util.NormalizeGenderSymbol(c); result[i] = c; } return i; @@ -84,12 +89,15 @@ public static class StringConverter3 else if (option is StringConverterOption.ClearZero) buffer.Clear(); - var table = (language == (int)LanguageID.Japanese) ? G3_JP : G3_EN; + bool jp = language == (int)LanguageID.Japanese; + var table = jp ? G3_JP : G3_EN; int i = 0; for (; i < value.Length; i++) { - var c = StringConverter.UnSanitizeChar5(value[i]); - if (!TryGetIndex(table, c, language, out var b)) + var chr = value[i]; + if (!jp) + chr = StringConverter4Util.UnNormalizeGenderSymbol(chr); + if (!TryGetIndex(table, chr, language, out var b)) break; buffer[i] = b; } @@ -193,7 +201,7 @@ public static class StringConverter3 'ィ', 'ゥ', 'ェ', 'ォ', 'ャ', 'ュ', 'ョ', 'ガ', 'ギ', 'グ', 'ゲ', 'ゴ', 'ザ', 'ジ', 'ズ', 'ゼ', // 8 'ゾ', 'ダ', 'ヂ', 'ヅ', 'デ', 'ド', 'バ', 'ビ', 'ブ', 'ベ', 'ボ', 'パ', 'ピ', 'プ', 'ペ', 'ポ', // 9 'ッ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '!', '?', '.', '-', '・',// A - '⑬', '“', '”', '‘', '’', '⑭', '⑮', '$', ',', '⑧', '/', 'A', 'B', 'C', 'D', 'E', // B + '⑬', '“', '”', '‘', '’', HGM, HGF, '$', ',', '⑧', '/', 'A', 'B', 'C', 'D', 'E', // B 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', // C 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', // D 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '►', // E @@ -216,7 +224,7 @@ public static class StringConverter3 'ィ', 'ゥ', 'ェ', 'ォ', 'ャ', 'ュ', 'ョ', 'ガ', 'ギ', 'グ', 'ゲ', 'ゴ', 'ザ', 'ジ', 'ズ', 'ゼ', // 8 'ゾ', 'ダ', 'ヂ', 'ヅ', 'デ', 'ド', 'バ', 'ビ', 'ブ', 'ベ', 'ボ', 'パ', 'ピ', 'プ', 'ペ', 'ポ', // 9 'ッ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '!', '?', '。', 'ー', '・', // A - '…', '『', '』', '「', '」', '♂', '♀', '円', '.', '×', '/', 'A', 'B', 'C', 'D', 'E', // B + '…', '『', '』', '「', '」', FGM, FGF, '円', '.', '×', '/', 'A', 'B', 'C', 'D', 'E', // B 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', // C 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', // D 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '►', // E diff --git a/PKHeX.Core/PKM/Strings/StringConverter345.cs b/PKHeX.Core/PKM/Strings/StringConverter345.cs index 2ae05381e..554700b93 100644 --- a/PKHeX.Core/PKM/Strings/StringConverter345.cs +++ b/PKHeX.Core/PKM/Strings/StringConverter345.cs @@ -31,7 +31,7 @@ public static class StringConverter345 { Span result = stackalloc char[data.Length]; int count = TransferGlyphs34(data, result, language, maxLength); - StringConverter4.SetString(dest, result[..count], maxLength, StringConverterOption.None); + StringConverter4.SetString(dest, result[..count], maxLength, language, StringConverterOption.None); } private static int TransferGlyphs34(ReadOnlySpan data, Span result, int language, int maxLength) diff --git a/PKHeX.Core/PKM/Strings/StringConverter4.cs b/PKHeX.Core/PKM/Strings/StringConverter4.cs index 9c2825bfc..a2b58564b 100644 --- a/PKHeX.Core/PKM/Strings/StringConverter4.cs +++ b/PKHeX.Core/PKM/Strings/StringConverter4.cs @@ -28,25 +28,27 @@ public static class StringConverter4 public static int LoadString(ReadOnlySpan data, Span result) { int i = 0; + int ctr = 0; for (; i < data.Length; i += 2) { var value = ReadUInt16LittleEndian(data[i..]); if (value == Terminator) break; char chr = (char)ConvertValue2CharG4(value); - chr = StringConverter.SanitizeChar(chr); - result[i/2] = chr; + chr = NormalizeGenderSymbol(chr); + result[ctr++] = chr; } - return i/2; + return ctr; } /// Gets the bytes for a 4th Generation String /// Span of bytes to write encoded string data /// Decoded string. /// Maximum length of the input + /// Language specific conversion /// Buffer pre-formatting option /// Encoded data. - public static int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, + public static int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, int language, StringConverterOption option = StringConverterOption.ClearZero) { if (value.Length > maxLength) @@ -55,10 +57,12 @@ public static class StringConverter4 if (option is StringConverterOption.ClearZero) destBuffer.Clear(); + bool isHalfWidth = language == (int)LanguageID.Korean || !StringConverter.GetIsFullWidthString(value); for (int i = 0; i < value.Length; i++) { var chr = value[i]; - chr = StringConverter.UnSanitizeChar5(chr); + if (isHalfWidth) + chr = UnNormalizeGenderSymbol(chr); ushort val = ConvertChar2ValueG4(chr); WriteUInt16LittleEndian(destBuffer[(i * 2)..], val); } diff --git a/PKHeX.Core/PKM/Strings/StringConverter4GC.cs b/PKHeX.Core/PKM/Strings/StringConverter4GC.cs index b65b17db9..124f23e6d 100644 --- a/PKHeX.Core/PKM/Strings/StringConverter4GC.cs +++ b/PKHeX.Core/PKM/Strings/StringConverter4GC.cs @@ -31,16 +31,17 @@ public static class StringConverter4GC public static int LoadString(ReadOnlySpan data, Span result) { int i = 0; + int ctr = 0; for (; i < data.Length; i += 2) { var value = ReadUInt16BigEndian(data[i..]); if (value == Terminator) break; char chr = (char)ConvertValue2CharG4(value); - chr = StringConverter.SanitizeChar(chr); - result[i/2] = chr; + chr = NormalizeGenderSymbol(chr); + result[ctr++] = chr; } - return i/2; + return ctr; } /// @@ -49,9 +50,10 @@ public static class StringConverter4GC /// Span of bytes to write encoded string data /// String to be converted. /// Maximum length of string + /// Language specific conversion /// Buffer pre-formatting option /// Byte array containing encoded character data - public static int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, + public static int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, int language, StringConverterOption option = StringConverterOption.ClearZero) { if (value.Length > maxLength) @@ -60,10 +62,12 @@ public static class StringConverter4GC if (option is StringConverterOption.ClearZero) destBuffer.Clear(); + bool isHalfWidth = language == (int)LanguageID.Korean || !StringConverter.GetIsFullWidthString(value); for (int i = 0; i < value.Length; i++) { var chr = value[i]; - chr = StringConverter.UnSanitizeChar5(chr); + if (isHalfWidth) + chr = UnNormalizeGenderSymbol(chr); ushort val = ConvertChar2ValueG4(chr); WriteUInt16BigEndian(destBuffer[(i * 2)..], val); } @@ -95,15 +99,15 @@ public static class StringConverter4GC public static int LoadStringUnicode(ReadOnlySpan data, Span result) { int i = 0; + int ctr = 0; for (; i < data.Length; i += 2) { char chr = (char)ReadUInt16BigEndian(data[i..]); if (chr == TerminatorChar) break; - chr = StringConverter.SanitizeChar(chr); - result[i/2] = chr; + result[ctr++] = chr; } - return i/2; + return ctr; } /// @@ -126,7 +130,6 @@ public static class StringConverter4GC for (int i = 0; i < value.Length; i++) { var c = value[i]; - c = StringConverter.UnSanitizeChar5(c); WriteUInt16BigEndian(destBuffer[(i * 2)..], c); } diff --git a/PKHeX.Core/PKM/Strings/StringConverter4Util.cs b/PKHeX.Core/PKM/Strings/StringConverter4Util.cs index f273b239f..e209ec812 100644 --- a/PKHeX.Core/PKM/Strings/StringConverter4Util.cs +++ b/PKHeX.Core/PKM/Strings/StringConverter4Util.cs @@ -72,6 +72,37 @@ public static class StringConverter4Util private const char NUL = (char)StringConverter4.Terminator; private const char EMP = NUL; // Empty, not available on keyboard. + /// + /// Half-width gender 16-bit char representation. + /// + /// Exact value is the value when converted to Generation 5's encoding. + public const char HGM = '\u246D'; // '♂' + /// + public const char HGF = '\u246E'; // '♀' + + /// + /// Converts full width to single width from the 0x246D/0x246E characters. + /// + /// Input character to sanitize. + public static char NormalizeGenderSymbol(char chr) => chr switch + { + HGM => '♂', + HGF => '♀', + _ => chr, + }; + + /// + /// Converts full width to half width when appropriate + /// + /// Input character to set back to data + /// Checks if the overall string is full-width + public static char UnNormalizeGenderSymbol(char chr, bool fullWidth = false) => fullWidth ? chr : chr switch + { + '♂' => HGM, + '♀' => HGF, + _ => chr, + }; + public static ReadOnlySpan TableINT => [ NUL, ' ', 'ぁ', 'あ', 'ぃ', 'い', 'ぅ', 'う', 'ぇ', 'え', 'ぉ', 'お', 'か', 'が', 'き', 'ぎ', // 000-00F @@ -101,7 +132,7 @@ public static class StringConverter4Util 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', 'ð', // 180-18F 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', '⑨', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ', 'ÿ', 'Œ', // 190-19F 'œ', 'Ş', 'ş', 'ª', 'º', '⑩', '⑪', '⑫', '$', '¡', '¿', '!', '?', ',', '.', '⑬', // 1A0-1AF - '・', '/', '‘', '’', '“', '”', '„', '«', '»', '(', ')', '⑭', '⑮', '+', '-', '*', // 1B0-1BF + '・', '/', '‘', '’', '“', '”', '„', '«', '»', '(', ')', HGM, HGF, '+', '-', '*', // 1B0-1BF '#', '=', '&', '~', ':', ';', '⑯', '⑰', '⑱', '⑲', '⑳', '⑴', '⑵', '⑶', '⑷', '⑸', // 1C0-1CF '@', '⑹', '%', '⑺', '⑻', '⑼', '⑽', '⑾', '⑿', '⒀', '⒁', '⒂', '⒃', '⒄', ' ', '⒅', // 1D0-1DF '⒆', '⒇', '⒈', '⒉', '⒊', '⒋', '⒌', '⒍', '°', '_', '_', '⒎', '⒏', // 1E0-1EC* diff --git a/PKHeX.Core/PKM/Strings/StringConverter5.cs b/PKHeX.Core/PKM/Strings/StringConverter5.cs index 420462b6f..4d27701f8 100644 --- a/PKHeX.Core/PKM/Strings/StringConverter5.cs +++ b/PKHeX.Core/PKM/Strings/StringConverter5.cs @@ -27,23 +27,25 @@ public static class StringConverter5 public static int LoadString(ReadOnlySpan data, Span result) { int i = 0; + int ctr = 0; for (; i < data.Length; i += 2) { var value = ReadUInt16LittleEndian(data[i..]); if (value == TerminatorFFFF) break; - result[i/2] = StringConverter.SanitizeChar((char)value); + result[ctr++] = (char)value; } - return i/2; + return ctr; } /// Gets the bytes for a Generation 5 string. /// Span of bytes to write encoded string data /// Decoded string. /// Maximum length of the input + /// Language specific conversion /// Buffer pre-formatting option /// Encoded data. - public static int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, + public static int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, int language, StringConverterOption option = StringConverterOption.ClearZero) { if (value.Length > maxLength) @@ -54,11 +56,13 @@ public static class StringConverter5 else if (option is StringConverterOption.ClearFF) destBuffer.Fill(0xFF); + bool isHalfWidth = language == (int)LanguageID.Korean || !StringConverter.GetIsFullWidthString(value); for (int i = 0; i < value.Length; i++) { - char c = value[i]; - ushort val = StringConverter.UnSanitizeChar5(c); - WriteUInt16LittleEndian(destBuffer[(i * 2)..], val); + var chr = value[i]; + if (isHalfWidth) + chr = StringConverter4Util.UnNormalizeGenderSymbol(chr); + WriteUInt16LittleEndian(destBuffer[(i * 2)..], chr); } int count = value.Length * 2; diff --git a/PKHeX.Core/PKM/Strings/StringConverter6.cs b/PKHeX.Core/PKM/Strings/StringConverter6.cs index 6c55d7e7e..0ba419478 100644 --- a/PKHeX.Core/PKM/Strings/StringConverter6.cs +++ b/PKHeX.Core/PKM/Strings/StringConverter6.cs @@ -28,23 +28,26 @@ public static class StringConverter6 public static int LoadString(ReadOnlySpan data, Span result) { int i = 0; + int ctr = 0; for (; i < data.Length; i += 2) { var value = ReadUInt16LittleEndian(data[i..]); if (value == TerminatorNull) break; - result[i/2] = StringConverter.SanitizeChar((char)value); + result[ctr++] = StringConverter.NormalizeGenderSymbol((char)value); } - return i/2; + return ctr; } /// Gets the bytes for a Generation 6 string. /// Span of bytes to write encoded string data /// Decoded string. /// Maximum length of the input + /// Language specific conversion /// Buffer pre-formatting option /// Encoded data. public static int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, + int language, StringConverterOption option = StringConverterOption.ClearZero) { if (value.Length > maxLength) @@ -53,13 +56,13 @@ public static class StringConverter6 if (option is StringConverterOption.ClearZero) destBuffer.Clear(); - bool isFullWidth = StringConverter.GetIsFullWidthString(value); + bool isHalfWidth = language == (int)LanguageID.Korean || !StringConverter.GetIsFullWidthString(value); for (int i = 0; i < value.Length; i++) { - char c = value[i]; - if (!isFullWidth) - c = StringConverter.UnSanitizeChar(c); - WriteUInt16LittleEndian(destBuffer[(i * 2)..], c); + var chr = value[i]; + if (isHalfWidth) + chr = StringConverter.UnNormalizeGenderSymbol(chr); + WriteUInt16LittleEndian(destBuffer[(i * 2)..], chr); } int count = value.Length * 2; diff --git a/PKHeX.Core/PKM/Strings/StringConverter7.cs b/PKHeX.Core/PKM/Strings/StringConverter7.cs index c8a8d0418..2f9000a1d 100644 --- a/PKHeX.Core/PKM/Strings/StringConverter7.cs +++ b/PKHeX.Core/PKM/Strings/StringConverter7.cs @@ -28,24 +28,25 @@ public static class StringConverter7 public static int LoadString(ReadOnlySpan data, Span result) { int i = 0; + int ctr = 0; for (; i < data.Length; i += 2) { - var value = ReadUInt16LittleEndian(data[i..]); + var value = (char)ReadUInt16LittleEndian(data[i..]); if (value == TerminatorNull) break; - result[i/2] = StringConverter.SanitizeChar((char)value); - } - var span = result[..(i/2)]; - StringConverter7ZH.RemapChineseGlyphsBin2String(span); - return i/2; + result[ctr++] = StringConverter7ZH.IsPrivateChar(value) + ? StringConverter7ZH.GetUnicodeChar(value) + : StringConverter.NormalizeGenderSymbol(value); + } + return ctr; } /// Gets the bytes for a Generation 7 string. /// Span of bytes to write encoded string data /// Decoded string. /// Maximum length of the input - /// Language specific conversion (Chinese) + /// Language specific conversion /// Buffer pre-formatting option /// Chinese string remapping should be attempted (only Pokémon names, without Nickname flag set) /// Encoded data. @@ -58,15 +59,15 @@ public static class StringConverter7 if (option is StringConverterOption.ClearZero) destBuffer.Clear(); - bool isFullWidth = StringConverter.GetIsFullWidthString(value); + bool isHalfWidth = language == (int)LanguageID.Korean || !StringConverter.GetIsFullWidthString(value); for (int i = 0; i < value.Length; i++) { - char c = value[i]; - if (!isFullWidth) - c = StringConverter.UnSanitizeChar(c); + var chr = value[i]; + if (isHalfWidth) + chr = StringConverter.UnNormalizeGenderSymbol(chr); if (chinese) - c = StringConverter7ZH.GetPrivateChar(c, language == (int)LanguageID.ChineseT); - WriteUInt16LittleEndian(destBuffer[(i * 2)..], c); + chr = StringConverter7ZH.GetPrivateChar(chr, language == (int)LanguageID.ChineseT); + WriteUInt16LittleEndian(destBuffer[(i * 2)..], chr); } int count = value.Length * 2; diff --git a/PKHeX.Core/PKM/Strings/StringConverter7ZH.cs b/PKHeX.Core/PKM/Strings/StringConverter7ZH.cs index cee2b73c8..13c51afdc 100644 --- a/PKHeX.Core/PKM/Strings/StringConverter7ZH.cs +++ b/PKHeX.Core/PKM/Strings/StringConverter7ZH.cs @@ -12,8 +12,8 @@ namespace PKHeX.Core; /// public static class StringConverter7ZH { - private static bool IsPrivateChar(ushort glyph) => glyph is >= Start and <= End; - private static char GetUnicodeChar(char glyph) => Table[glyph - Start]; + internal static bool IsPrivateChar(ushort glyph) => glyph is >= Start and <= End; + internal static char GetUnicodeChar(char glyph) => Table[glyph - Start]; public static bool IsTraditional(ushort glyph) => glyph is (>= StartTraditional and <= EndTraditional) or (>= StartTraditionalUSUM and <= EndTraditionalUSUM); public static bool IsSimplified(ushort glyph) => glyph is (>= StartSimplified and <= EndSimplified) or (>= StartSimplifiedUSUM and <= EndSimplifiedUSUM); @@ -50,21 +50,6 @@ public static class StringConverter7ZH return chr; } - /// - /// Converts a Generation 7 in-game Chinese string to Unicode string. - /// - /// In-game Chinese string. - /// Unicode string. - internal static void RemapChineseGlyphsBin2String(Span input) - { - for (int i = 0; i < input.Length; i++) - { - var val = input[i]; - if (IsPrivateChar(val)) - input[i] = GetUnicodeChar(val); - } - } - #region Gen 7 Chinese Character Table private const ushort Start = 0xE800; diff --git a/PKHeX.Core/PKM/Strings/StringConverter8.cs b/PKHeX.Core/PKM/Strings/StringConverter8.cs index beb5ee410..56e15d06b 100644 --- a/PKHeX.Core/PKM/Strings/StringConverter8.cs +++ b/PKHeX.Core/PKM/Strings/StringConverter8.cs @@ -137,4 +137,33 @@ public static class StringConverter8 WriteCharacters(expect, under); return relevantSection.SequenceEqual(expect); } + + /// + /// Used when importing a 3DS string into HOME. + /// + public static void NormalizeHalfWidth(Span str) + { + if (BitConverter.IsLittleEndian) + { + var u16 = MemoryMarshal.Cast(str); + foreach (ref var c in u16) + { + if (c == TerminatorNull) + return; + c = NormalizeHalfWidth(c); + } + } + + // Slower path for Big-Endian runtimes. + for (int i = 0; i < str.Length; i += 2) + { + var data = str[i..]; + var c = ReadUInt16LittleEndian(data); + if (c == TerminatorNull) + return; + WriteUInt16LittleEndian(data, NormalizeHalfWidth((char)c)); + } + } + + private static char NormalizeHalfWidth(char str) => StringConverter.NormalizeGenderSymbol(str); } diff --git a/PKHeX.Core/Saves/SAV4.cs b/PKHeX.Core/Saves/SAV4.cs index c15356832..0685b6096 100644 --- a/PKHeX.Core/Saves/SAV4.cs +++ b/PKHeX.Core/Saves/SAV4.cs @@ -483,7 +483,7 @@ public abstract class SAV4 : SaveFile, IEventFlag37, IDaycareStorage, IDaycareRa public sealed override int LoadString(ReadOnlySpan data, Span destBuffer) => StringConverter4.LoadString(data, destBuffer); public sealed override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) - => StringConverter4.SetString(destBuffer, value, maxLength, option); + => StringConverter4.SetString(destBuffer, value, maxLength, Language, option); #region Event Flag/Event Work public bool GetEventFlag(int flagNumber) diff --git a/PKHeX.Core/Saves/SAV5.cs b/PKHeX.Core/Saves/SAV5.cs index 45e20a323..7b2582555 100644 --- a/PKHeX.Core/Saves/SAV5.cs +++ b/PKHeX.Core/Saves/SAV5.cs @@ -122,7 +122,7 @@ public abstract class SAV5 : SaveFile, ISaveBlock5BW, IEventFlagProvider37, IBox public sealed override int LoadString(ReadOnlySpan data, Span result) => StringConverter5.LoadString(data, result); public sealed override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) - => StringConverter5.SetString(destBuffer, value, maxLength, option); + => StringConverter5.SetString(destBuffer, value, maxLength, Language, option); // DLC private int CGearSkinInfoOffset => CGearInfoOffset + (this is SAV5B2W2 ? 0x10 : 0) + 0x24; diff --git a/PKHeX.Core/Saves/SAV6.cs b/PKHeX.Core/Saves/SAV6.cs index ac75f7eef..663e8b537 100644 --- a/PKHeX.Core/Saves/SAV6.cs +++ b/PKHeX.Core/Saves/SAV6.cs @@ -143,7 +143,7 @@ public abstract class SAV6 : SAV_BEEF, ITrainerStatRecord, ISaveBlock6Core, IReg public sealed override int LoadString(ReadOnlySpan data, Span destBuffer) => StringConverter6.LoadString(data, destBuffer); public sealed override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) - => StringConverter6.SetString(destBuffer, value, maxLength, option); + => StringConverter6.SetString(destBuffer, value, maxLength, Language, option); public int GetRecord(int recordID) => Records.GetRecord(recordID); public int GetRecordOffset(int recordID) => Records.GetRecordOffset(recordID); diff --git a/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo6.cs b/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo6.cs index f12f3f46e..c2e946691 100644 --- a/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo6.cs +++ b/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo6.cs @@ -30,13 +30,13 @@ public sealed class BattleVideo6(byte[] Data) : IBattleVideo public string Debug1 { get => StringConverter6.GetString(Data.AsSpan(0x6, 0x1A)); - set => StringConverter6.SetString(Data.AsSpan(0x6, 0x1A), value, 12, StringConverterOption.ClearZero); + set => StringConverter6.SetString(Data.AsSpan(0x6, 0x1A), value, 12, 0, StringConverterOption.ClearZero); } public string Debug2 { get => StringConverter6.GetString(Data.AsSpan(0x50, 0x1A)); - set => StringConverter6.SetString(Data.AsSpan(0x50, 0x1A), value, 12, StringConverterOption.ClearZero); + set => StringConverter6.SetString(Data.AsSpan(0x50, 0x1A), value, 12, 0, StringConverterOption.ClearZero); } public ulong RNGConst1 { get => ReadUInt64LittleEndian(Data.AsSpan(0x1A0)); set => WriteUInt64LittleEndian(Data.AsSpan(0x1A0), value); } @@ -70,7 +70,7 @@ public sealed class BattleVideo6(byte[] Data) : IBattleVideo { var span = Data.AsSpan(0xEC + (0x1A * i), 0x1A); string tr = value[i] == NPC ? string.Empty : value[i]; - StringConverter6.SetString(span, tr, 12, StringConverterOption.ClearZero); + StringConverter6.SetString(span, tr, 12, 0, StringConverterOption.ClearZero); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen4/Dendou4.cs b/PKHeX.Core/Saves/Substructures/Gen4/Dendou4.cs index 00458a37f..8d469a55c 100644 --- a/PKHeX.Core/Saves/Substructures/Gen4/Dendou4.cs +++ b/PKHeX.Core/Saves/Substructures/Gen4/Dendou4.cs @@ -3,7 +3,7 @@ using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; -public sealed class Dendou4(Memory raw) +public sealed class Dendou4(Memory raw, int language) { private const int SIZE = 0x2AB0; private const int SIZE_FOOTER = 0x10; @@ -25,7 +25,7 @@ public sealed class Dendou4(Memory raw) { ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, MaxRecords); var slice = Data.Slice(index * Dendou4Record.SIZE, Dendou4Record.SIZE); - return new Dendou4Record(slice); + return new Dendou4Record(slice, language); } private const int EndDataOffset = MaxRecords * Dendou4Record.SIZE; // 0x2AA8 @@ -63,9 +63,14 @@ public readonly ref struct Dendou4Record // u8 Day private readonly Span Data; + private readonly int Language; // ReSharper disable once ConvertToPrimaryConstructor - public Dendou4Record(Span data) => Data = data; + public Dendou4Record(Span data, int language) + { + Data = data; + Language = language; + } public Dendou4Entity this[int index] => GetEntity(index); @@ -78,7 +83,7 @@ public readonly ref struct Dendou4Record { ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, Count); var slice = Data.Slice(index * Dendou4Entity.SIZE, Dendou4Entity.SIZE); - return new Dendou4Entity(slice); + return new Dendou4Entity(slice, Language); } } @@ -86,9 +91,15 @@ public readonly ref struct Dendou4Entity { public const int SIZE = 0x3C; private readonly Span Data; + private readonly int Language; // ReSharper disable once ConvertToPrimaryConstructor - public Dendou4Entity(Span data) => Data = data; + public Dendou4Entity(Span data, int language) + { + Data = data; + Language = language; + } + public ushort Species { get => ReadUInt16LittleEndian(Data); set => WriteUInt16LittleEndian(Data, value); } public byte Level { get => Data[2]; set => Data[2] = value; } public byte Form { get => Data[3]; set => Data[3] = value; } @@ -100,12 +111,12 @@ public readonly ref struct Dendou4Entity public string Nickname { get => StringConverter4.GetString(NicknameTrash); - set => StringConverter4.SetString(NicknameTrash, value, 10, StringConverterOption.None); + set => StringConverter4.SetString(NicknameTrash, value, 10, Language, StringConverterOption.None); } public string OriginalTrainerName { get => StringConverter4.GetString(OriginalTrainerTrash); - set => StringConverter4.SetString(OriginalTrainerTrash, value, 7, StringConverterOption.None); + set => StringConverter4.SetString(OriginalTrainerTrash, value, 7, 0, StringConverterOption.None); } public ushort Move1 { get => ReadUInt16LittleEndian(Data[0x32..]); set => WriteUInt16LittleEndian(Data[0x32..], value); } diff --git a/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchTrainerMii.cs b/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchTrainerMii.cs index 30888a355..17727b1af 100644 --- a/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchTrainerMii.cs +++ b/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchTrainerMii.cs @@ -23,9 +23,11 @@ public sealed class RanchTrainerMii(byte[] Data) // 0x28-29: ?? // 0x2A-2B: ?? + private static byte Language => 0; + public string TrainerName { get => StringConverter4.GetString(Trainer_Trash); - set => StringConverter4.SetString(Trainer_Trash, value, 7); + set => StringConverter4.SetString(Trainer_Trash, value, 7, Language); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen6/HallFame6Entity.cs b/PKHeX.Core/Saves/Substructures/Gen6/HallFame6Entity.cs index dbeb5f091..5a649bf4b 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/HallFame6Entity.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/HallFame6Entity.cs @@ -7,8 +7,14 @@ public readonly ref struct HallFame6Entity { public const int SIZE = 0x48; private readonly Span Data; + private readonly int Language; + // ReSharper disable once ConvertToPrimaryConstructor - public HallFame6Entity(Span data) => Data = data; + public HallFame6Entity(Span data, int language) + { + Data = data; + Language = language; + } public ushort Species { get => ReadUInt16LittleEndian(Data); set => WriteUInt16LittleEndian(Data, value); } public ushort HeldItem { get => ReadUInt16LittleEndian(Data[0x02..]); set => WriteUInt16LittleEndian(Data[0x02..], value); } @@ -48,13 +54,13 @@ public readonly ref struct HallFame6Entity public string Nickname { get => StringConverter6.GetString(Nick_Trash); - set => StringConverter6.SetString(Nick_Trash, value, 12, Option); + set => StringConverter6.SetString(Nick_Trash, value, 12, Language, Option); } public string OriginalTrainerName { get => StringConverter6.GetString(OriginalTrainerTrash); - set => StringConverter6.SetString(OriginalTrainerTrash, value, 12, Option); + set => StringConverter6.SetString(OriginalTrainerTrash, value, 12, Language, Option); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6.cs b/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6.cs index 65c51a127..95604e4da 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/SecretBase/SecretBase6.cs @@ -80,15 +80,17 @@ public class SecretBase6(Memory raw) private const int NameLength = (0x1A / 2) - 1; // + terminator private const int MessageLength = (0x22 / 2) - 1; // + terminator + private static int Language => 0; + public string TrainerName { get => StringConverter6.GetString(Data.Slice(0x21A, NameLengthBytes)); - set => StringConverter6.SetString(Data.Slice(0x21A, NameLengthBytes), value, NameLength, StringConverterOption.ClearZero); + set => StringConverter6.SetString(Data.Slice(0x21A, NameLengthBytes), value, NameLength, Language, StringConverterOption.ClearZero); } private Span GetMessageSpan(int index) => Data.Slice(0x234 + (MessageLengthBytes * index), MessageLengthBytes); private string GetMessage(int index) => StringConverter6.GetString(GetMessageSpan(index)); - private void SetMessage(int index, ReadOnlySpan value) => StringConverter6.SetString(GetMessageSpan(index), value, MessageLength, StringConverterOption.ClearZero); + private void SetMessage(int index, ReadOnlySpan value) => StringConverter6.SetString(GetMessageSpan(index), value, MessageLength, Language, StringConverterOption.ClearZero); public string TeamName { get => GetMessage(0); set => SetMessage(0, value); } public string TeamSlogan { get => GetMessage(1); set => SetMessage(1, value); } diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/UgItemUtil.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/UgItemUtil.cs index db055fbcc..139f761d5 100644 --- a/PKHeX.Core/Saves/Substructures/Gen8/BS/UgItemUtil.cs +++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/UgItemUtil.cs @@ -35,8 +35,8 @@ public static class UgItemUtil } #region Table - private static readonly IReadOnlyList Items = new UgItemDef[] - { + private static readonly IReadOnlyList Items = + [ new(000,000,000,000,000), // None new(001, -1,001, -1, -1), // Red Sphere S new(002, -1,002, -1, -1), // Blue Sphere S @@ -994,6 +994,6 @@ public static class UgItemUtil new(954, -1, -1, -1, -1), // new(955, -1, -1, -1, -1), // new(956, -1, -1, -1, -1), // - }; + ]; #endregion } diff --git a/PKHeX.Core/Saves/Substructures/Mail/Mail4.cs b/PKHeX.Core/Saves/Substructures/Mail/Mail4.cs index 3a6fa28de..2bbd1ee22 100644 --- a/PKHeX.Core/Saves/Substructures/Mail/Mail4.cs +++ b/PKHeX.Core/Saves/Substructures/Mail/Mail4.cs @@ -44,7 +44,7 @@ public sealed class Mail4 : MailDetail public override byte AuthorLanguage { get => Data[5]; set => Data[5] = value; } public override byte AuthorVersion { get => Data[6]; set => Data[6] = value; } public override int MailType { get => Data[7]; set => Data[7] = (byte)value; } - public override string AuthorName { get => StringConverter4.GetString(Data.AsSpan(8, 0x10)); set => StringConverter4.SetString(Data.AsSpan(8, 0x10), value, 7, StringConverterOption.ClearFF); } + public override string AuthorName { get => StringConverter4.GetString(Data.AsSpan(8, 0x10)); set => StringConverter4.SetString(Data.AsSpan(8, 0x10), value, 7, AuthorLanguage, StringConverterOption.ClearFF); } public ushort GetAppearSpecies(int index) => ReadUInt16LittleEndian(Data.AsSpan(0x1C - (index * 2))); public void SetAppearSpecies(int index, ushort value) => WriteUInt16LittleEndian(Data.AsSpan(0x1C - (index * 2)), (ushort)(value == 0 ? 0xFFFF : value)); public override ushort GetMessage(int index1, int index2) => ReadUInt16LittleEndian(Data.AsSpan(0x20 + (((index1 * 4) + index2) * 2))); diff --git a/PKHeX.Core/Saves/Substructures/Mail/Mail5.cs b/PKHeX.Core/Saves/Substructures/Mail/Mail5.cs index 7cabda297..60c88733e 100644 --- a/PKHeX.Core/Saves/Substructures/Mail/Mail5.cs +++ b/PKHeX.Core/Saves/Substructures/Mail/Mail5.cs @@ -40,7 +40,7 @@ public sealed class Mail5 : MailDetail public override byte AuthorLanguage { get => Data[5]; set => Data[5] = value; } public override byte AuthorVersion { get => Data[6]; set => Data[6] = value; } public override int MailType { get => Data[7]; set => Data[7] = (byte)value; } - public override string AuthorName { get => StringConverter5.GetString(Data.AsSpan(8, 0x10)); set => StringConverter5.SetString(Data.AsSpan(8, 0x10), value, 7, StringConverterOption.ClearZero); } + public override string AuthorName { get => StringConverter5.GetString(Data.AsSpan(8, 0x10)); set => StringConverter5.SetString(Data.AsSpan(8, 0x10), value, 7, AuthorLanguage, StringConverterOption.ClearZero); } public int GetMisc(int index) => ReadUInt16LittleEndian(Data.AsSpan(0x1C - (index * 2))); public void SetMisc(int index, int value) => WriteUInt16LittleEndian(Data.AsSpan(0x1C - (index * 2)), (ushort)value); public ushort MessageEnding { get => ReadUInt16LittleEndian(Data.AsSpan(0x1E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x1E), value); } diff --git a/PKHeX.WinForms/Subforms/Save Editors/Gen6/SAV_HallOfFame.cs b/PKHeX.WinForms/Subforms/Save Editors/Gen6/SAV_HallOfFame.cs index 39fec19c2..b4fdb4831 100644 --- a/PKHeX.WinForms/Subforms/Save Editors/Gen6/SAV_HallOfFame.cs +++ b/PKHeX.WinForms/Subforms/Save Editors/Gen6/SAV_HallOfFame.cs @@ -120,7 +120,7 @@ public partial class SAV_HallOfFame : Form for (int i = 0; i < 6; i++) { var slice = data[(i * HallFame6Entity.SIZE)..]; - var entry = new HallFame6Entity(slice); + var entry = new HallFame6Entity(slice, SAV.Language); if (entry.Species == 0) continue; moncount++; @@ -156,7 +156,7 @@ public partial class SAV_HallOfFame : Form int index = LB_DataEntry.SelectedIndex; var member = (Convert.ToInt32(NUP_PartyIndex.Value) - 1); var slice = Fame.GetEntity(index, member); - var entry = new HallFame6Entity(slice); + var entry = new HallFame6Entity(slice, SAV.Language); CB_Species.SelectedValue = (int)entry.Species; CB_HeldItem.SelectedValue = (int)entry.HeldItem; CB_Move1.SelectedValue = (int)entry.Move1; @@ -196,7 +196,7 @@ public partial class SAV_HallOfFame : Form int member = Convert.ToInt32(NUP_PartyIndex.Value) - 1; var slice = Fame.GetEntity(index, member); - var entry = new HallFame6Entity(slice) + var entry = new HallFame6Entity(slice, SAV.Language) { Species = Convert.ToUInt16(CB_Species.SelectedValue), HeldItem = Convert.ToUInt16(CB_HeldItem.SelectedValue), diff --git a/Tests/PKHeX.Core.Tests/PKM/StringTests.cs b/Tests/PKHeX.Core.Tests/PKM/StringTests.cs index 93608e0f9..7b6b850be 100644 --- a/Tests/PKHeX.Core.Tests/PKM/StringTests.cs +++ b/Tests/PKHeX.Core.Tests/PKM/StringTests.cs @@ -11,7 +11,7 @@ public class StringTests { const string name_fabian = "Fabian♂"; var pk = new PK7 { OriginalTrainerName = name_fabian }; - Span byte_fabian = + ReadOnlySpan byte_fabian = [ 0x46, 0x00, // F 0x61, 0x00, // a @@ -30,7 +30,7 @@ public class StringTests { const string name_nidoran = "ニドラン♀"; var pk = new PK7 { Nickname = name_nidoran }; - Span byte_nidoran = + ReadOnlySpan byte_nidoran = [ 0xCB, 0x30, // ニ 0xC9, 0x30, // ド diff --git a/Tests/PKHeX.Core.Tests/Simulator/ShowdownSetTests.cs b/Tests/PKHeX.Core.Tests/Simulator/ShowdownSetTests.cs index 8c0a3a3e1..8f56ec6ce 100644 --- a/Tests/PKHeX.Core.Tests/Simulator/ShowdownSetTests.cs +++ b/Tests/PKHeX.Core.Tests/Simulator/ShowdownSetTests.cs @@ -7,6 +7,11 @@ namespace PKHeX.Core.Tests.Simulator; public class ShowdownSetTests { + static ShowdownSetTests() + { + ParseSettings.Settings.Handler.CheckActiveHandler = false; + } + [Fact] public void SimulatorGetParse() {