diff --git a/PKHeX.Core/Legality/Bulk/DuplicateGiftChecker.cs b/PKHeX.Core/Legality/Bulk/DuplicateGiftChecker.cs index 07fb8505d..799bbdcbe 100644 --- a/PKHeX.Core/Legality/Bulk/DuplicateGiftChecker.cs +++ b/PKHeX.Core/Legality/Bulk/DuplicateGiftChecker.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using System.Linq; using static PKHeX.Core.CheckIdentifier; @@ -15,14 +17,21 @@ public sealed class DuplicateGiftChecker : IBulkAnalyzer private static void CheckDuplicateOwnedGifts(BulkAnalysis input) { var all = input.AllData; - var combined = new CombinedReference[all.Count]; - for (int i = 0; i < combined.Length; i++) - combined[i] = new CombinedReference(all[i], input.AllAnalysis[i]); + var initialCapacity = all.Count / 20; // less likely to have gift eggs + var combined = new List(initialCapacity); + for (int i = 0; i < all.Count; i++) + { + var c = all[i]; + var la = input.AllAnalysis[i]; + if (!IsEventEgg(c, la)) + continue; + combined.Add(new(c, la)); + } - var dupes = combined.Where(z => - z.Analysis.Info.Generation >= 3 - && z.Analysis.EncounterMatch is MysteryGift { IsEgg: true } && !z.Slot.Entity.WasTradedEgg) - .GroupBy(z => ((MysteryGift)z.Analysis.EncounterMatch).CardTitle); + if (combined.Count < 2) + return; // not enough to compare + + var dupes = combined.GroupBy(EventEggGroupKey); foreach (var dupe in dupes) { @@ -38,4 +47,21 @@ public sealed class DuplicateGiftChecker : IBulkAnalyzer input.AddLine(first, second, $"Receipt of the same egg mystery gifts detected: {dupe.Key}", Encounter); } } + + private static string EventEggGroupKey(CombinedReference z) + { + var enc = z.Analysis.EncounterMatch; + if (enc is not MysteryGift mg) + throw new Exception("Expected a mystery gift."); + return mg.CardTitle + $"{mg.Species}"; // differentiator for duplicate named event eggs -- species should be enough? + } + + private static bool IsEventEgg(SlotCache c, LegalityAnalysis la) + { + if (la.Info.Generation < 3) + return false; // don't care + if (la.EncounterMatch is not MysteryGift { IsEgg: true }) + return false; // only interested in ^ + return !c.Entity.WasTradedEgg; + } } diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/Lump/EncounterGenerator8X.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/Lump/EncounterGenerator8X.cs index 45f489ebd..1066c9557 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/Lump/EncounterGenerator8X.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/Lump/EncounterGenerator8X.cs @@ -24,13 +24,14 @@ public sealed class EncounterGenerator8X : IEncounterGenerator public IEnumerable GetEncounters(PKM pk, EvoCriteria[] chain, LegalInfo info) => pk.Version switch { GO => EncounterGeneratorGO.Instance.GetEncounters(pk, chain, info), - PLA => EncounterGenerator8a.Instance.GetEncounters(pk, chain, info), - BD or SP => EncounterGenerator8b.Instance.GetEncounters(pk, chain, info), + PLA when pk is not PK8 => EncounterGenerator8a.Instance.GetEncounters(pk, chain, info), + BD or SP when pk is not PK8 => EncounterGenerator8b.Instance.GetEncounters(pk, chain, info), SW when pk.MetLocation == LocationsHOME.SWLA => EncounterGenerator8a.Instance.GetEncounters(pk, chain, info), SW when pk.MetLocation == LocationsHOME.SWBD => EncounterGenerator8b.Instance.GetEncountersSWSH(pk, chain, BD), SH when pk.MetLocation == LocationsHOME.SHSP => EncounterGenerator8b.Instance.GetEncountersSWSH(pk, chain, SP), SW when pk.MetLocation == LocationsHOME.SWSL => EncounterGenerator9.Instance.GetEncountersSWSH(pk, chain, SL), SH when pk.MetLocation == LocationsHOME.SHVL => EncounterGenerator9.Instance.GetEncountersSWSH(pk, chain, VL), - _ => EncounterGenerator8.Instance.GetEncounters(pk, chain, info), + SW or SH => EncounterGenerator8.Instance.GetEncounters(pk, chain, info), + _ => [], }; } diff --git a/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8a.cs b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8a.cs index e10546e17..bfc2e1a55 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8a.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8a.cs @@ -42,8 +42,6 @@ public record struct EncounterEnumerator8a(PKM Entity, EvoCriteria[] Chain) : IE case YieldState.Start: if (Chain.Length == 0) break; - if (Entity is PK8 { SWSH: false }) - break; if (Entity.IsEgg) break; diff --git a/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8b.cs b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8b.cs index 68f085509..6f3b4cf95 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8b.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/Search/EncounterEnumerator8b.cs @@ -1,7 +1,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Diagnostics; namespace PKHeX.Core; @@ -57,7 +56,6 @@ public record struct EncounterEnumerator8b(PKM Entity, EvoCriteria[] Chain, Game switch (State) { case YieldState.Start: - Debug.Assert(Entity is not PK8); if (Chain.Length == 0) break; diff --git a/PKHeX.Core/Legality/Encounters/Verifiers/EncounterVerifier.cs b/PKHeX.Core/Legality/Encounters/Verifiers/EncounterVerifier.cs index d8672b860..42ea498fe 100644 --- a/PKHeX.Core/Legality/Encounters/Verifiers/EncounterVerifier.cs +++ b/PKHeX.Core/Legality/Encounters/Verifiers/EncounterVerifier.cs @@ -24,7 +24,7 @@ public static class EncounterVerifier EncounterEgg e => VerifyEncounterEgg(pk, e.Generation), EncounterShadow3Colo { IsEReader: true } when pk.Language != (int)LanguageID.Japanese => GetInvalid(LG3EReader), EncounterStatic3 { Species: (int)Species.Mew } when pk.Language != (int)LanguageID.Japanese => GetInvalid(LEncUnreleasedEMewJP), - EncounterStatic3 { Species: (int)Species.Deoxys } when pk.Language == (int)LanguageID.Japanese => GetInvalid(LEncUnreleased), + EncounterStatic3 { Species: (int)Species.Deoxys, Location: 200 } when pk.Language == (int)LanguageID.Japanese => GetInvalid(LEncUnreleased), EncounterStatic4 { Roaming: true } when pk is G4PKM { MetLocation: 193, GroundTile: GroundTileType.Water } => GetInvalid(LG4InvalidTileR45Surf), MysteryGift g => VerifyEncounterEvent(pk, g), { IsEgg: true } when !pk.IsEgg => VerifyEncounterEgg(pk, enc.Generation), diff --git a/PKHeX.Core/PKM/CK3.cs b/PKHeX.Core/PKM/CK3.cs index 4afb150b2..51c99ac39 100644 --- a/PKHeX.Core/PKM/CK3.cs +++ b/PKHeX.Core/PKM/CK3.cs @@ -26,7 +26,7 @@ public sealed class CK3(byte[] Data) : G3PKM(Data), IShadowCapture // Trash Bytes public override Span OriginalTrainerTrash => Data.AsSpan(0x18, 22); - public Span NicknameDisplay_Trash => Data.AsSpan(0x2E, 22); + public Span NicknameDisplayTrash => Data.AsSpan(0x2E, 22); public override Span NicknameTrash => Data.AsSpan(0x44, 22); public override int TrashCharCountTrainer => 11; public override int TrashCharCountNickname => 11; @@ -48,12 +48,12 @@ public sealed class CK3(byte[] Data) : G3PKM(Data), IShadowCapture public override ushort SID16 { get => ReadUInt16BigEndian(Data.AsSpan(0x14)); set => WriteUInt16BigEndian(Data.AsSpan(0x14), value); } public override ushort TID16 { get => ReadUInt16BigEndian(Data.AsSpan(0x16)); set => WriteUInt16BigEndian(Data.AsSpan(0x16), value); } public override string OriginalTrainerName { get => GetString(OriginalTrainerTrash); set => SetString(OriginalTrainerTrash, value, 10, StringConverterOption.None); } - public string NicknameDisplay { get => GetString(NicknameDisplay_Trash); set => SetString(NicknameDisplay_Trash, value, 10, StringConverterOption.None); } + public string NicknameDisplay { get => GetString(NicknameDisplayTrash); set => SetString(NicknameDisplayTrash, value, 10, StringConverterOption.None); } public override string Nickname { get => GetString(NicknameTrash); set { SetString(NicknameTrash, value, 10, StringConverterOption.None); ResetNicknameDisplay(); } } public void ResetNicknameDisplay() { - var current = NicknameDisplay_Trash; + var current = NicknameDisplayTrash; NicknameTrash.CopyTo(current); if (CurrentRegion == GCRegion.NTSC_J) current[10..].Clear(); // clamp to 5 chars at most diff --git a/PKHeX.Core/PKM/XK3.cs b/PKHeX.Core/PKM/XK3.cs index 84e5893ce..55617e0aa 100644 --- a/PKHeX.Core/PKM/XK3.cs +++ b/PKHeX.Core/PKM/XK3.cs @@ -26,7 +26,7 @@ public sealed class XK3 : G3PKM, IShadowCapture // Trash Bytes public override Span OriginalTrainerTrash => Data.AsSpan(0x38, 22); - public Span NicknameDisplay_Trash => Data.AsSpan(0x4E, 22); + public Span NicknameDisplayTrash => Data.AsSpan(0x4E, 22); public override Span NicknameTrash => Data.AsSpan(0x64, 22); public override int TrashCharCountTrainer => 11; public override int TrashCharCountNickname => 11; @@ -90,12 +90,12 @@ public sealed class XK3 : G3PKM, IShadowCapture public GCRegion OriginalRegion { get => (GCRegion)Data[0x36]; set => Data[0x36] = (byte)value; } public override int Language { get => Core.Language.GetMainLangIDfromGC(Data[0x37]); set => Data[0x37] = Core.Language.GetGCLangIDfromMain((byte)value); } public override string OriginalTrainerName { get => GetString(OriginalTrainerTrash); set => SetString(OriginalTrainerTrash, value, 10, StringConverterOption.None); } - public string NicknameDisplay { get => GetString(NicknameDisplay_Trash); set => SetString(NicknameDisplay_Trash, value, 10, StringConverterOption.None); } + public string NicknameDisplay { get => GetString(NicknameDisplayTrash); set => SetString(NicknameDisplayTrash, value, 10, StringConverterOption.None); } public override string Nickname { get => GetString(NicknameTrash); set { SetString(NicknameTrash, value, 10, StringConverterOption.None); ResetNicknameDisplay(); } } public void ResetNicknameDisplay() { - var current = NicknameDisplay_Trash; + var current = NicknameDisplayTrash; NicknameTrash.CopyTo(current); if (CurrentRegion == GCRegion.NTSC_J) current[10..].Clear(); // clamp to 5 chars at most diff --git a/PKHeX.Core/Saves/SAV1.cs b/PKHeX.Core/Saves/SAV1.cs index 66f673dd5..13a36c45a 100644 --- a/PKHeX.Core/Saves/SAV1.cs +++ b/PKHeX.Core/Saves/SAV1.cs @@ -295,7 +295,7 @@ public sealed class SAV1 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo set => SetString(Data.AsSpan(Offsets.Rival, MaxStringLengthTrainer), value, MaxStringLengthTrainer, StringConverterOption.Clear50); } - public Span Rival_Trash { get => Data.AsSpan(Offsets.Rival, StringLength); set { if (value.Length == StringLength) value.CopyTo(Data.AsSpan(Offsets.Rival)); } } + public Span RivalTrash { get => Data.AsSpan(Offsets.Rival, StringLength); set { if (value.Length == StringLength) value.CopyTo(Data.AsSpan(Offsets.Rival)); } } public byte RivalStarter { get => Data[Offsets.Starter - 2]; set => Data[Offsets.Starter - 2] = value; } public bool Yellow => Starter == 0x54; // Pikachu @@ -312,10 +312,10 @@ public sealed class SAV1 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo set => Data[Offsets.PikaFriendship] = value; } - public int PikaBeachScore + public uint PikaBeachScore { - get => BinaryCodedDecimal.ToInt32LE(Data.AsSpan(Offsets.PikaBeachScore, 2)); - set => BinaryCodedDecimal.WriteBytesLE(Data.AsSpan(Offsets.PikaBeachScore, 2), Math.Min(9999, value)); + get => BinaryCodedDecimal.ReadUInt32LittleEndian(Data.AsSpan(Offsets.PikaBeachScore, 2)); + set => BinaryCodedDecimal.WriteUInt32LittleEndian(Data.AsSpan(Offsets.PikaBeachScore, 2), Math.Min(9999, value)); } public override string PlayTimeString => !PlayedMaximum ? base.PlayTimeString : $"{base.PlayTimeString} {Checksums.CRC16_CCITT(Data):X4}"; @@ -400,21 +400,21 @@ public sealed class SAV1 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo public override uint Money { - get => (uint)BinaryCodedDecimal.ToInt32BE(Data.AsSpan(Offsets.Money, 3)); + get => BinaryCodedDecimal.ReadUInt32BigEndian(Data.AsSpan(Offsets.Money, 3)); set { value = (uint)Math.Min(value, MaxMoney); - BinaryCodedDecimal.WriteBytesBE(Data.AsSpan(Offsets.Money, 3), (int)value); + BinaryCodedDecimal.WriteUInt32BigEndian(Data.AsSpan(Offsets.Money, 3), value); } } public uint Coin { - get => (uint)BinaryCodedDecimal.ToInt32BE(Data.AsSpan(Offsets.Coin, 2)); + get => BinaryCodedDecimal.ReadUInt32BigEndian(Data.AsSpan(Offsets.Coin, 2)); set { value = (ushort)Math.Min(value, MaxCoins); - BinaryCodedDecimal.WriteBytesBE(Data.AsSpan(Offsets.Coin, 2), (int)value); + BinaryCodedDecimal.WriteUInt32BigEndian(Data.AsSpan(Offsets.Coin, 2), value); } } diff --git a/PKHeX.Core/Saves/SAV2.cs b/PKHeX.Core/Saves/SAV2.cs index ce21bce8e..9f0c5b450 100644 --- a/PKHeX.Core/Saves/SAV2.cs +++ b/PKHeX.Core/Saves/SAV2.cs @@ -328,7 +328,7 @@ public sealed class SAV2 : SaveFile, ILangDeviantSave, IEventFlagArray, IEventWo set => SetString(Data.AsSpan(Offsets.Rival, (Korean ? 2 : 1) * MaxStringLengthTrainer), value, 8, StringConverterOption.Clear50); } - public Span Rival_Trash + public Span RivalTrash { get => Data.AsSpan(Offsets.Rival, StringLength); set { if (value.Length == StringLength) value.CopyTo(Data.AsSpan(Offsets.Rival)); } diff --git a/PKHeX.Core/Saves/SAV4.cs b/PKHeX.Core/Saves/SAV4.cs index 9b1ca1be3..ff6b7d51b 100644 --- a/PKHeX.Core/Saves/SAV4.cs +++ b/PKHeX.Core/Saves/SAV4.cs @@ -334,11 +334,11 @@ public abstract class SAV4 : SaveFile, IEventFlag37, IDaycareStorage, IDaycareRa public string Rival { - get => GetString(Rival_Trash); - set => SetString(Rival_Trash, value, MaxStringLengthTrainer, StringConverterOption.ClearZero); + get => GetString(RivalTrash); + set => SetString(RivalTrash, value, MaxStringLengthTrainer, StringConverterOption.ClearZero); } - public abstract Span Rival_Trash { get; set; } + public abstract Span RivalTrash { get; set; } public abstract int X2 { get; set; } public abstract int Y2 { get; set; } diff --git a/PKHeX.Core/Saves/SAV4DP.cs b/PKHeX.Core/Saves/SAV4DP.cs index e03d3a692..12e63b6e1 100644 --- a/PKHeX.Core/Saves/SAV4DP.cs +++ b/PKHeX.Core/Saves/SAV4DP.cs @@ -115,7 +115,7 @@ public sealed class SAV4DP : SAV4Sinnoh public override int X { get => ReadUInt16LittleEndian(General[0x1240..]); set => WriteUInt16LittleEndian(General[0x1240..], (ushort)(X2 = value)); } public override int Y { get => ReadUInt16LittleEndian(General[0x1244..]); set => WriteUInt16LittleEndian(General[0x1244..], (ushort)(Y2 = value)); } - public override Span Rival_Trash + public override Span RivalTrash { get => General.Slice(0x25A8, MaxStringLengthTrainer * 2); set { if (value.Length == MaxStringLengthTrainer * 2) value.CopyTo(General[0x25A8..]); } diff --git a/PKHeX.Core/Saves/SAV4HGSS.cs b/PKHeX.Core/Saves/SAV4HGSS.cs index 71b3b2612..d31bd2dca 100644 --- a/PKHeX.Core/Saves/SAV4HGSS.cs +++ b/PKHeX.Core/Saves/SAV4HGSS.cs @@ -188,7 +188,7 @@ public sealed class SAV4HGSS : SAV4, IBoxDetailName, IBoxDetailWallpaper public override int X { get => ReadUInt16LittleEndian(General[0x123C..]); set => WriteUInt16LittleEndian(General[0x123C..], (ushort)(X2 = value)); } public override int Y { get => ReadUInt16LittleEndian(General[0x1240..]); set => WriteUInt16LittleEndian(General[0x1240..], (ushort)(Y2 = value)); } - public override Span Rival_Trash + public override Span RivalTrash { get => RivalSpan; set { if (value.Length == MaxStringLengthTrainer * 2) value.CopyTo(RivalSpan); } diff --git a/PKHeX.Core/Saves/SAV4Pt.cs b/PKHeX.Core/Saves/SAV4Pt.cs index 6d515a0d8..e67bd01ae 100644 --- a/PKHeX.Core/Saves/SAV4Pt.cs +++ b/PKHeX.Core/Saves/SAV4Pt.cs @@ -154,7 +154,7 @@ public sealed class SAV4Pt : SAV4Sinnoh public override int X { get => ReadUInt16LittleEndian(General[0x1288..]); set => WriteUInt16LittleEndian(General[0x1288..], (ushort)(X2 = value)); } public override int Y { get => ReadUInt16LittleEndian(General[0x128C..]); set => WriteUInt16LittleEndian(General[0x128C..], (ushort)(Y2 = value)); } - public override Span Rival_Trash + public override Span RivalTrash { get => RivalSpan; set { if (value.Length == MaxStringLengthTrainer * 2) value.CopyTo(RivalSpan); } diff --git a/PKHeX.Core/Saves/SAV5.cs b/PKHeX.Core/Saves/SAV5.cs index e877f426c..051636d2a 100644 --- a/PKHeX.Core/Saves/SAV5.cs +++ b/PKHeX.Core/Saves/SAV5.cs @@ -57,8 +57,9 @@ public abstract class SAV5 : SaveFile, ISaveBlock5BW, IEventFlagProvider37, IBox public override bool ChecksumsValid => AllBlocks.GetChecksumsValid(Data); public override string ChecksumInfo => AllBlocks.GetChecksumInfo(Data); - protected int CGearInfoOffset; - protected int CGearDataOffset; + private int CGearInfoOffset => AllBlocks[32].Offset; // 0x1C000 - Options / Skin Info + protected abstract int CGearDataOffset { get; } // extdata + public sealed override bool HasPokeDex => true; // Daycare public int DaycareSlotCount => 2; diff --git a/PKHeX.Core/Saves/SAV5B2W2.cs b/PKHeX.Core/Saves/SAV5B2W2.cs index e78f598bd..e4a2585d4 100644 --- a/PKHeX.Core/Saves/SAV5B2W2.cs +++ b/PKHeX.Core/Saves/SAV5B2W2.cs @@ -9,31 +9,14 @@ namespace PKHeX.Core; /// public sealed class SAV5B2W2 : SAV5, ISaveBlock5B2W2 { - public SAV5B2W2() : base(SaveUtil.SIZE_G5RAW) - { - Blocks = new SaveBlockAccessor5B2W2(this); - Initialize(); - } - - public SAV5B2W2(byte[] data) : base(data) - { - Blocks = new SaveBlockAccessor5B2W2(this); - Initialize(); - } + public SAV5B2W2() : base(SaveUtil.SIZE_G5RAW) => Blocks = new SaveBlockAccessor5B2W2(this); + public SAV5B2W2(byte[] data) : base(data) => Blocks = new SaveBlockAccessor5B2W2(this); public override PersonalTable5B2W2 Personal => PersonalTable.B2W2; public SaveBlockAccessor5B2W2 Blocks { get; } protected override SAV5B2W2 CloneInternal() => new((byte[]) Data.Clone()); public override int MaxItemID => Legal.MaxItemID_5_B2W2; - - private void Initialize() - { - CGearInfoOffset = 0x1C000; - CGearDataOffset = 0x52800; - } - - public override bool HasPokeDex => true; - + public override IReadOnlyList AllBlocks => Blocks.BlockInfo; public override MyItem Items => Blocks.Items; public override Zukan5 Zukan => Blocks.Zukan; @@ -65,11 +48,11 @@ public sealed class SAV5B2W2 : SAV5, ISaveBlock5B2W2 public string Rival { - get => GetString(Rival_Trash); - set => SetString(Rival_Trash, value, MaxStringLengthTrainer, StringConverterOption.ClearZero); + get => GetString(RivalTrash); + set => SetString(RivalTrash, value, MaxStringLengthTrainer, StringConverterOption.ClearZero); } - public Span Rival_Trash + public Span RivalTrash { get => Data.AsSpan(0x23BA4, MaxStringLengthTrainer * 2); set { if (value.Length == MaxStringLengthTrainer * 2) value.CopyTo(Data.AsSpan(0x23BA4)); } @@ -79,4 +62,5 @@ public sealed class SAV5B2W2 : SAV5, ISaveBlock5B2W2 public override Memory BattleVideoDownload1 => Data.AsMemory(0x4DA00, BattleVideo5.SIZE_USED); public override Memory BattleVideoDownload2 => Data.AsMemory(0x4F400, BattleVideo5.SIZE_USED); public override Memory BattleVideoDownload3 => Data.AsMemory(0x50E00, BattleVideo5.SIZE_USED); + protected override int CGearDataOffset => 0x52800; // ^ + 0x1A00 spacing } diff --git a/PKHeX.Core/Saves/SAV5BW.cs b/PKHeX.Core/Saves/SAV5BW.cs index 372cc1bc7..008eeed59 100644 --- a/PKHeX.Core/Saves/SAV5BW.cs +++ b/PKHeX.Core/Saves/SAV5BW.cs @@ -9,31 +9,14 @@ namespace PKHeX.Core; /// public sealed class SAV5BW : SAV5 { - public SAV5BW() : base(SaveUtil.SIZE_G5RAW) - { - Blocks = new SaveBlockAccessor5BW(this); - Initialize(); - } - - public SAV5BW(byte[] data) : base(data) - { - Blocks = new SaveBlockAccessor5BW(this); - Initialize(); - } + public SAV5BW() : base(SaveUtil.SIZE_G5RAW) => Blocks = new SaveBlockAccessor5BW(this); + public SAV5BW(byte[] data) : base(data) => Blocks = new SaveBlockAccessor5BW(this); public override PersonalTable5BW Personal => PersonalTable.BW; public SaveBlockAccessor5BW Blocks { get; } protected override SAV5BW CloneInternal() => new((byte[])Data.Clone()); public override int MaxItemID => Legal.MaxItemID_5_BW; - public override bool HasPokeDex => true; - - private void Initialize() - { - CGearInfoOffset = 0x1C000; - CGearDataOffset = 0x52000; - } - public override IReadOnlyList AllBlocks => Blocks.BlockInfo; public override MyItem5BW Items => Blocks.Items; public override Zukan5 Zukan => Blocks.Zukan; @@ -62,4 +45,5 @@ public sealed class SAV5BW : SAV5 public override Memory BattleVideoDownload1 => Data.AsMemory(0x4C000, BattleVideo5.SIZE_USED); public override Memory BattleVideoDownload2 => Data.AsMemory(0x4E000, BattleVideo5.SIZE_USED); public override Memory BattleVideoDownload3 => Data.AsMemory(0x50000, BattleVideo5.SIZE_USED); + protected override int CGearDataOffset => 0x52000; // ^ + 0x2000 spacing } diff --git a/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo5.cs b/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo5.cs index c99d05db9..578f2fd4a 100644 --- a/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo5.cs +++ b/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo5.cs @@ -15,6 +15,7 @@ public sealed class BattleVideo5(Memory Raw) : IBattleVideo private const int SIZE_FOOTER = 0x14; public const int SIZE_USED = SIZE + SIZE_FOOTER; //public const int SIZE_BLOCK = 0x2000; + //bw is 0x2000 between each video, b2w2 is 0x1A00 private Span Data => Raw.Span[..SIZE_USED]; diff --git a/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchMii.cs b/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchMii.cs index baa84c2eb..1eed95430 100644 --- a/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchMii.cs +++ b/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchMii.cs @@ -10,11 +10,11 @@ public sealed class RanchMii(byte[] Data) public uint MiiId { get => ReadUInt32BigEndian(Data); set => WriteUInt32BigEndian(Data, value); } public uint SystemId { get => ReadUInt32BigEndian(Data.AsSpan(0x04)); set => WriteUInt32BigEndian(Data.AsSpan(0x04), value); } - public Span Name_Trash => Data.AsSpan(0x10, 0x18); + public Span MiiNameTrash => Data.AsSpan(0x10, 0x18); public string MiiName { - get => StringConverter4GC.GetStringUnicode(Name_Trash); - set => StringConverter4GC.SetStringUnicode(value, Name_Trash, value.Length, StringConverterOption.None); + get => StringConverter4GC.GetStringUnicode(MiiNameTrash); + set => StringConverter4GC.SetStringUnicode(value, MiiNameTrash, value.Length, StringConverterOption.None); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchTrainerMii.cs b/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchTrainerMii.cs index 17727b1af..0c26bac8a 100644 --- a/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchTrainerMii.cs +++ b/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchTrainerMii.cs @@ -14,7 +14,7 @@ public sealed class RanchTrainerMii(byte[] Data) public ushort TrainerId { get => ReadUInt16LittleEndian(Data.AsSpan(0x0C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0C), value); } public ushort SecretId { get => ReadUInt16LittleEndian(Data.AsSpan(0x0E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0E), value); } - private Span Trainer_Trash => Data.AsSpan(0x10, 0x10); + private Span OriginalTrainerTrash => Data.AsSpan(0x10, 0x10); // 0x20-23: ?? // 0x24: ?? @@ -27,7 +27,7 @@ public sealed class RanchTrainerMii(byte[] Data) public string TrainerName { - get => StringConverter4.GetString(Trainer_Trash); - set => StringConverter4.SetString(Trainer_Trash, value, 7, Language); + get => StringConverter4.GetString(OriginalTrainerTrash); + set => StringConverter4.SetString(OriginalTrainerTrash, value, 7, Language); } } diff --git a/PKHeX.Core/Saves/Substructures/Gen6/HallFame6Entity.cs b/PKHeX.Core/Saves/Substructures/Gen6/HallFame6Entity.cs index 5a649bf4b..70706acb7 100644 --- a/PKHeX.Core/Saves/Substructures/Gen6/HallFame6Entity.cs +++ b/PKHeX.Core/Saves/Substructures/Gen6/HallFame6Entity.cs @@ -39,7 +39,7 @@ public readonly ref struct HallFame6Entity public bool IsNicknamed { get => Nick == 1; set => Nick = value ? 1u : 0u; } public bool IsShiny { get => Shiny == 1; set => Shiny = value ? 1u : 0u; } - private Span Nick_Trash => Data.Slice(0x18, 24); + private Span NicknameTrash => Data.Slice(0x18, 24); private Span OriginalTrainerTrash => Data.Slice(0x30, 24); // Don't mimic in-game behavior of not clearing strings. First entry should always have clean trash. @@ -47,14 +47,14 @@ public readonly ref struct HallFame6Entity public void ClearTrash() { - Nick_Trash.Clear(); + NicknameTrash.Clear(); OriginalTrainerTrash.Clear(); } public string Nickname { - get => StringConverter6.GetString(Nick_Trash); - set => StringConverter6.SetString(Nick_Trash, value, 12, Language, Option); + get => StringConverter6.GetString(NicknameTrash); + set => StringConverter6.SetString(NicknameTrash, value, 12, Language, Option); } public string OriginalTrainerName diff --git a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/Misc7b.cs b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/Misc7b.cs index 08cbb7c7d..e1620a5f5 100644 --- a/PKHeX.Core/Saves/Substructures/Gen7/LGPE/Misc7b.cs +++ b/PKHeX.Core/Saves/Substructures/Gen7/LGPE/Misc7b.cs @@ -11,11 +11,11 @@ public sealed class Misc7b(SAV7b sav, Memory raw) : SaveBlock(sav, set => WriteUInt32LittleEndian(Data[4..], value); } - private Span Rival_Trash => Data.Slice(0x200, 0x1A); + private Span RivalTrash => Data.Slice(0x200, 0x1A); public string Rival { - get => SAV.GetString(Rival_Trash); - set => SAV.SetString(Rival_Trash, value, SAV.MaxStringLengthTrainer, StringConverterOption.ClearZero); + get => SAV.GetString(RivalTrash); + set => SAV.SetString(RivalTrash, value, SAV.MaxStringLengthTrainer, StringConverterOption.ClearZero); } } diff --git a/PKHeX.Core/Saves/Substructures/Rentals/RentalTeam9.cs b/PKHeX.Core/Saves/Substructures/Rentals/RentalTeam9.cs index eabb588c1..728144bfe 100644 --- a/PKHeX.Core/Saves/Substructures/Rentals/RentalTeam9.cs +++ b/PKHeX.Core/Saves/Substructures/Rentals/RentalTeam9.cs @@ -37,9 +37,9 @@ public sealed class RentalTeam9(byte[] Data) : IRentalTeam, IPokeGroup set => WriteUInt16LittleEndian(Data.AsSpan(OFS_META + 0x00), value); } - private Span Player_Trash => Data.AsSpan(OFS_META + 0x02, LEN_OT * sizeof(char)); + private Span OriginalTrainerTrash => Data.AsSpan(OFS_META + 0x02, LEN_OT * sizeof(char)); - private Span TeamName_Trash => Data.AsSpan(OFS_META + 0x18, LEN_TEAMNAME * sizeof(char)); + private Span TeamNameTrash => Data.AsSpan(OFS_META + 0x18, LEN_TEAMNAME * sizeof(char)); public uint Language { @@ -49,14 +49,14 @@ public sealed class RentalTeam9(byte[] Data) : IRentalTeam, IPokeGroup public string PlayerName { - get => StringConverter8.GetString(Player_Trash); - set => StringConverter8.SetString(Player_Trash, value, 10); + get => StringConverter8.GetString(OriginalTrainerTrash); + set => StringConverter8.SetString(OriginalTrainerTrash, value, 10); } public string TeamName { - get => StringConverter8.GetString(TeamName_Trash); - set => StringConverter8.SetString(TeamName_Trash, value, LEN_TEAMNAME); + get => StringConverter8.GetString(TeamNameTrash); + set => StringConverter8.SetString(TeamNameTrash, value, LEN_TEAMNAME); } public uint EntityCount diff --git a/PKHeX.Core/Saves/Util/SaveLanguage.cs b/PKHeX.Core/Saves/Util/SaveLanguage.cs index 69b139293..c3f105ea6 100644 --- a/PKHeX.Core/Saves/Util/SaveLanguage.cs +++ b/PKHeX.Core/Saves/Util/SaveLanguage.cs @@ -145,8 +145,7 @@ public static class SaveLanguage // Check for underscores too; replace the input w/ spaces to underscore Span tmp = stackalloc char[value.Length]; - value.CopyTo(tmp); - tmp.Replace(' ', '_'); + value.Replace(tmp, ' ', '_'); return Contains(span, tmp); } diff --git a/PKHeX.Core/Util/BinaryCodedDecimal.cs b/PKHeX.Core/Util/BinaryCodedDecimal.cs index 20e6b6374..08e679965 100644 --- a/PKHeX.Core/Util/BinaryCodedDecimal.cs +++ b/PKHeX.Core/Util/BinaryCodedDecimal.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Runtime.CompilerServices; namespace PKHeX.Core; @@ -8,34 +8,39 @@ namespace PKHeX.Core; /// public static class BinaryCodedDecimal { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void PushDigits(ref uint result, uint b) + => result = checked((result * 100) + (10 * (b >> 4)) + (b & 0xf)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte GetLowestTuple(uint value) + => (byte)((((value / 10) % 10) << 4) | (value % 10)); + /// /// Returns a 32-bit signed integer converted from bytes in a Binary Coded Decimal format byte array. /// /// Input byte array to read from. - public static int ToInt32BE(ReadOnlySpan input) + public static uint ReadUInt32BigEndian(ReadOnlySpan input) { - int result = 0; + uint result = 0; foreach (var b in input) PushDigits(ref result, b); return result; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void PushDigits(ref int result, byte b) => result = (result * 100) + (10 * (b >> 4)) + (b & 0xf); - /// /// Writes the to the buffer. /// - public static void WriteBytesBE(Span data, int value) + public static void WriteUInt32BigEndian(Span data, uint value) { for (int i = data.Length - 1; i >= 0; i--, value /= 100) - data[i] = (byte)((((value / 10) % 10) << 4) | (value % 10)); + data[i] = GetLowestTuple(value); } - /// - public static int ToInt32LE(ReadOnlySpan input) + /// + public static uint ReadUInt32LittleEndian(ReadOnlySpan input) { - int result = 0; + uint result = 0; for (int i = input.Length - 1; i >= 0; i--) PushDigits(ref result, input[i]); return result; @@ -44,9 +49,9 @@ public static class BinaryCodedDecimal /// /// Writes the to the buffer. /// - public static void WriteBytesLE(Span data, int value) + public static void WriteUInt32LittleEndian(Span data, uint value) { for (int i = 0; i < data.Length; i++, value /= 100) - data[i] = (byte)((((value / 10) % 10) << 4) | (value % 10)); + data[i] = GetLowestTuple(value); } } diff --git a/Tests/PKHeX.Core.Tests/Util/ConvertUtilTests.cs b/Tests/PKHeX.Core.Tests/Util/ConvertUtilTests.cs index 223d6e7a8..771f0647f 100644 --- a/Tests/PKHeX.Core.Tests/Util/ConvertUtilTests.cs +++ b/Tests/PKHeX.Core.Tests/Util/ConvertUtilTests.cs @@ -50,32 +50,32 @@ public class ConvertUtilTests } [Theory] - [InlineData(0x12345678, 12345678)] - public void CheckConvertBCD_Little(uint raw, int expect) + [InlineData(0x12345678, 12345678u)] + public void CheckConvertBCD_Little(uint raw, uint expect) { Span data = stackalloc byte[4]; WriteUInt32LittleEndian(data, raw); - var result = BinaryCodedDecimal.ToInt32LE(data); + var result = BinaryCodedDecimal.ReadUInt32LittleEndian(data); result.Should().Be(expect); Span newData = stackalloc byte[4]; - BinaryCodedDecimal.WriteBytesLE(newData, result); + BinaryCodedDecimal.WriteUInt32LittleEndian(newData, result); data.SequenceEqual(newData).Should().BeTrue(); } [Theory] - [InlineData(0x78563412, 12345678)] - public void CheckConvertBCD_Big(uint raw, int expect) + [InlineData(0x12345678, 12345678u)] + public void CheckConvertBCD_Big(uint raw, uint expect) { Span data = stackalloc byte[4]; - WriteUInt32LittleEndian(data, raw); + WriteUInt32BigEndian(data, raw); - var result = BinaryCodedDecimal.ToInt32BE(data); + var result = BinaryCodedDecimal.ReadUInt32BigEndian(data); result.Should().Be(expect); Span newData = stackalloc byte[4]; - BinaryCodedDecimal.WriteBytesBE(newData, result); + BinaryCodedDecimal.WriteUInt32BigEndian(newData, result); data.SequenceEqual(newData).Should().BeTrue(); } }