diff --git a/PKHeX.Core/Saves/SAV4.cs b/PKHeX.Core/Saves/SAV4.cs
index 032b44308..08f1834bf 100644
--- a/PKHeX.Core/Saves/SAV4.cs
+++ b/PKHeX.Core/Saves/SAV4.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Linq;
namespace PKHeX.Core
{
@@ -29,6 +28,7 @@ public abstract class SAV4 : SaveFile
protected abstract int StorageSize { get; }
protected abstract int GeneralSize { get; }
protected abstract int StorageStart { get; }
+ public abstract Zukan4 Dex { get; }
///
public override bool GetFlag(int offset, int bitIndex) => FlagUtil.GetFlag(General, offset, bitIndex);
@@ -573,286 +573,9 @@ protected override DataMysteryGift[] MysteryGiftCards
}
}
- protected override void SetDex(PKM pkm)
- {
- if (pkm.Species == 0)
- return;
- if (pkm.Species > MaxSpeciesID)
- return;
-
- const int brSize = 0x40;
- int bit = pkm.Species - 1;
- byte mask = (byte)(1 << (bit & 7));
- int ofs = PokeDex + (bit >> 3) + 0x4;
-
- /* 4 BitRegions with 0x40*8 bits
- * Region 0: Caught (Captured/Owned) flags
- * Region 1: Seen flags
- * Region 2/3: Toggle for gender display
- * 4 possible states: 00, 01, 10, 11
- * 00 - 1Seen: Male Only
- * 01 - 2Seen: Male First, Female Second
- * 10 - 2Seen: Female First, Male Second
- * 11 - 1Seen: Female Only
- * (bit1 ^ bit2) + 1 = forms in dex
- * bit2 = male/female shown first toggle
- */
-
- // Set the Species Owned Flag
- General[ofs + (brSize * 0)] |= mask;
-
- // Check if already Seen
- if ((General[ofs + (brSize * 1)] & mask) == 0) // Not seen
- {
- General[ofs + (brSize * 1)] |= mask; // Set seen
- int gr = pkm.PersonalInfo.Gender;
- switch (gr)
- {
- case 255: // Genderless
- case 0: // Male Only
- General[ofs + (brSize * 2)] &= (byte)~mask; // unset
- General[ofs + (brSize * 3)] &= (byte)~mask; // unset
- break;
- case 254: // Female Only
- General[ofs + (brSize * 2)] |= mask;
- General[ofs + (brSize * 3)] |= mask;
- break;
- default: // Male or Female
- bool m = (General[ofs + (brSize * 2)] & mask) != 0;
- bool f = (General[ofs + (brSize * 3)] & mask) != 0;
- if (m || f) // bit already set?
- break;
- int gender = pkm.Gender & 1;
- General[ofs + (brSize * 2)] &= (byte)~mask; // unset
- General[ofs + (brSize * 3)] &= (byte)~mask; // unset
- gender ^= 1; // Set OTHER gender seen bit so it appears second
- General[ofs + (brSize * (2 + gender))] |= mask;
- break;
- }
- }
-
- int FormOffset1 = PokeDex + 4 + (brSize * 4) + 4;
- var forms = GetForms(pkm.Species);
- if (forms.Length > 0)
- {
- if (pkm.Species == (int)Species.Unown) // Unown
- {
- for (int i = 0; i < 0x1C; i++)
- {
- byte val = General[FormOffset1 + 4 + i];
- if (val == pkm.Form)
- break; // already set
- if (val != 0xFF)
- continue; // keep searching
-
- General[FormOffset1 + 4 + i] = (byte)pkm.Form;
- break; // form now set
- }
- }
- else if (pkm.Species == (int)Species.Pichu && HGSS) // Pichu (HGSS Only)
- {
- int form = pkm.Form == 1 ? 2 : pkm.Gender;
- if (TryInsertForm(forms, form))
- SetForms(pkm.Species, forms);
- }
- else
- {
- if (TryInsertForm(forms, pkm.Form))
- SetForms(pkm.Species, forms);
- }
- }
-
- int dpl = 1 + Array.IndexOf(DPLangSpecies, pkm.Species);
- if (DP && dpl <= 0)
- return;
-
- // Set the Language
- int PokeDexLanguageFlags = FormOffset1 + (HGSS ? 0x3C : 0x20);
- int lang = GetGen4LanguageBitIndex(pkm.Language);
- General[PokeDexLanguageFlags + (DP ? dpl : pkm.Species)] |= (byte)(1 << lang);
- }
-
- private static readonly int[] DPLangSpecies = { 23, 25, 54, 77, 120, 129, 202, 214, 215, 216, 228, 278, 287, 315 };
-
- private static int GetGen4LanguageBitIndex(int lang) => --lang switch
- {
- 3 => 4, // invert ITA/GER
- 4 => 3, // invert ITA/GER
- > 5 => 0, // Japanese
- < 0 => 1, // English
- _ => lang,
- };
-
- public override bool GetCaught(int species)
- {
- int bit = species - 1;
- int bd = bit >> 3; // div8
- int bm = bit & 7; // mod8
- int ofs = PokeDex // Raw Offset
- + 0x4; // Magic
- return (1 << bm & General[ofs + bd]) != 0;
- }
-
- public override bool GetSeen(int species)
- {
- const int brSize = 0x40;
-
- int bit = species - 1;
- int bd = bit >> 3; // div8
- int bm = bit & 7; // mod8
- int ofs = PokeDex // Raw Offset
- + 0x4; // Magic
-
- return (1 << bm & General[ofs + bd + (brSize * 1)]) != 0;
- }
-
- public int[] GetForms(int species)
- {
- const int brSize = 0x40;
- if (species == (int)Species.Deoxys)
- {
- uint val = (uint) (General[PokeDex + 0x4 + (1 * brSize) - 1] | General[PokeDex + 0x4 + (2 * brSize) - 1] << 8);
- return GetDexFormValues(val, 4, 4);
- }
-
- int FormOffset1 = PokeDex + 4 + (4 * brSize) + 4;
- switch (species)
- {
- case (int)Species.Shellos: // Shellos
- return GetDexFormValues(General[FormOffset1 + 0], 1, 2);
- case (int)Species.Gastrodon: // Gastrodon
- return GetDexFormValues(General[FormOffset1 + 1], 1, 2);
- case (int)Species.Burmy: // Burmy
- return GetDexFormValues(General[FormOffset1 + 2], 2, 3);
- case (int)Species.Wormadam: // Wormadam
- return GetDexFormValues(General[FormOffset1 + 3], 2, 3);
- case (int)Species.Unown: // Unown
- return General.Slice(FormOffset1 + 4, 0x1C).Select(i => (int)i).ToArray();
- }
- if (DP)
- return Array.Empty();
-
- int PokeDexLanguageFlags = FormOffset1 + (HGSS ? 0x3C : 0x20);
- int FormOffset2 = PokeDexLanguageFlags + 0x1F4;
- return species switch
- {
- (int)Species.Rotom => GetDexFormValues(BitConverter.ToUInt32(General, FormOffset2), 3, 6),
- (int)Species.Shaymin => GetDexFormValues(General[FormOffset2 + 4], 1, 2),
- (int)Species.Giratina => GetDexFormValues(General[FormOffset2 + 5], 1, 2),
- (int)Species.Pichu when HGSS => GetDexFormValues(General[FormOffset2 + 6], 2, 3),
- _ => Array.Empty()
- };
- }
-
- public void SetForms(int species, int[] forms)
- {
- const int brSize = 0x40;
- switch (species)
- {
- case (int)Species.Deoxys: // Deoxys
- uint newval = SetDexFormValues(forms, 4, 4);
- General[PokeDex + 0x4 + (1 * brSize) - 1] = (byte) (newval & 0xFF);
- General[PokeDex + 0x4 + (2 * brSize) - 1] = (byte) ((newval >> 8) & 0xFF);
- break;
- }
-
- int FormOffset1 = PokeDex + 4 + (4 * brSize) + 4;
- switch (species)
- {
- case (int)Species.Shellos: // Shellos
- General[FormOffset1 + 0] = (byte)SetDexFormValues(forms, 1, 2);
- return;
- case (int)Species.Gastrodon: // Gastrodon
- General[FormOffset1 + 1] = (byte)SetDexFormValues(forms, 1, 2);
- return;
- case (int)Species.Burmy: // Burmy
- General[FormOffset1 + 2] = (byte)SetDexFormValues(forms, 2, 3);
- return;
- case (int)Species.Wormadam: // Wormadam
- General[FormOffset1 + 3] = (byte)SetDexFormValues(forms, 2, 3);
- return;
- case (int)Species.Unown: // Unown
- int ofs = FormOffset1 + 4;
- int len = forms.Length;
- Array.Resize(ref forms, 0x1C);
- for (int i = len; i < forms.Length; i++)
- forms[i] = 0xFF;
- Array.Copy(forms.Select(b => (byte)b).ToArray(), 0, General, ofs, forms.Length);
- return;
- }
-
- if (DP)
- return;
-
- int PokeDexLanguageFlags = FormOffset1 + (HGSS ? 0x3C : 0x20);
- int FormOffset2 = PokeDexLanguageFlags + 0x1F4;
- switch (species)
- {
- case (int)Species.Rotom: // Rotom
- BitConverter.GetBytes(SetDexFormValues(forms, 3, 6)).CopyTo(General, FormOffset2);
- return;
- case (int)Species.Shaymin: // Shaymin
- General[FormOffset2 + 4] = (byte)SetDexFormValues(forms, 1, 2);
- return;
- case (int)Species.Giratina: // Giratina
- General[FormOffset2 + 5] = (byte)SetDexFormValues(forms, 1, 2);
- return;
- case (int)Species.Pichu when HGSS: // Pichu
- General[FormOffset2 + 6] = (byte)SetDexFormValues(forms, 2, 3);
- return;
- }
- }
-
- private static int[] GetDexFormValues(uint Value, int BitsPerForm, int readCt)
- {
- int[] Forms = new int[readCt];
- int n1 = 0xFF >> (8 - BitsPerForm);
- for (int i = 0; i < Forms.Length; i++)
- {
- int val = (int)(Value >> (i * BitsPerForm)) & n1;
- if (n1 == val && BitsPerForm > 1)
- Forms[i] = -1;
- else
- Forms[i] = val;
- }
-
- // (BitsPerForm > 1) was already handled, handle (BitsPerForm == 1)
- if (BitsPerForm == 1 && Forms[0] == Forms[1] && Forms[0] == 1)
- Forms[0] = Forms[1] = -1;
-
- return Forms;
- }
-
- private static uint SetDexFormValues(int[] Forms, int BitsPerForm, int readCt)
- {
- int n1 = 0xFF >> (8 - BitsPerForm);
- uint Value = 0xFFFFFFFF << (readCt * BitsPerForm);
- for (int i = 0; i < Forms.Length; i++)
- {
- int val = Forms[i];
- if (val == -1)
- val = n1;
-
- Value |= (uint)(val << (BitsPerForm*i));
- if (i >= readCt)
- throw new ArgumentException("Array count should be less than bitfield count", nameof(Forms));
- }
- return Value;
- }
-
- private static bool TryInsertForm(int[] forms, int form)
- {
- if (Array.IndexOf(forms, form) >= 0)
- return false; // already in list
-
- // insert at first empty
- var index = Array.IndexOf(forms, -1);
- if (index < 0)
- return false; // no free slots?
-
- forms[index] = form;
- return true;
- }
+ protected override void SetDex(PKM pkm) => Dex.SetDex(pkm);
+ public override bool GetCaught(int species) => Dex.GetCaught(species);
+ public override bool GetSeen(int species) => Dex.GetSeen(species);
public int DexUpgraded
{
diff --git a/PKHeX.Core/Saves/SAV4DP.cs b/PKHeX.Core/Saves/SAV4DP.cs
index e45c439b0..201375468 100644
--- a/PKHeX.Core/Saves/SAV4DP.cs
+++ b/PKHeX.Core/Saves/SAV4DP.cs
@@ -8,8 +8,20 @@ namespace PKHeX.Core
///
public sealed class SAV4DP : SAV4Sinnoh
{
- public SAV4DP() => Initialize();
- public SAV4DP(byte[] data) : base(data) => Initialize();
+ public SAV4DP()
+ {
+ Initialize();
+ Dex = new Zukan4(this, PokeDex);
+ }
+
+ public SAV4DP(byte[] data) : base(data)
+ {
+ Initialize();
+ Dex = new Zukan4(this, PokeDex);
+ }
+
+ public override Zukan4 Dex { get; }
+
protected override SAV4 CloneInternal4() => State.Exportable ? new SAV4DP(Data) : new SAV4DP();
public override PersonalTable Personal => PersonalTable.DP;
public override IReadOnlyList HeldItems => Legal.HeldItems_DP;
diff --git a/PKHeX.Core/Saves/SAV4HGSS.cs b/PKHeX.Core/Saves/SAV4HGSS.cs
index 4e40782d5..2e428f237 100644
--- a/PKHeX.Core/Saves/SAV4HGSS.cs
+++ b/PKHeX.Core/Saves/SAV4HGSS.cs
@@ -8,8 +8,19 @@ namespace PKHeX.Core
///
public sealed class SAV4HGSS : SAV4
{
- public SAV4HGSS() => Initialize();
- public SAV4HGSS(byte[] data) : base(data) => Initialize();
+ public SAV4HGSS()
+ {
+ Initialize();
+ Dex = new Zukan4(this, PokeDex);
+ }
+
+ public SAV4HGSS(byte[] data) : base(data)
+ {
+ Initialize();
+ Dex = new Zukan4(this, PokeDex);
+ }
+
+ public override Zukan4 Dex { get; }
protected override SAV4 CloneInternal4() => State.Exportable ? new SAV4HGSS(Data) : new SAV4HGSS();
public override PersonalTable Personal => PersonalTable.HGSS;
diff --git a/PKHeX.Core/Saves/SAV4Pt.cs b/PKHeX.Core/Saves/SAV4Pt.cs
index fb651b30a..a8227b434 100644
--- a/PKHeX.Core/Saves/SAV4Pt.cs
+++ b/PKHeX.Core/Saves/SAV4Pt.cs
@@ -7,8 +7,19 @@ namespace PKHeX.Core
///
public sealed class SAV4Pt : SAV4Sinnoh
{
- public SAV4Pt() => Initialize();
- public SAV4Pt(byte[] data) : base(data) => Initialize();
+ public SAV4Pt()
+ {
+ Initialize();
+ Dex = new Zukan4(this, PokeDex);
+ }
+
+ public SAV4Pt(byte[] data) : base(data)
+ {
+ Initialize();
+ Dex = new Zukan4(this, PokeDex);
+ }
+
+ public override Zukan4 Dex { get; }
protected override SAV4 CloneInternal4() => State.Exportable ? new SAV4Pt(Data) : new SAV4Pt();
public override PersonalTable Personal => PersonalTable.Pt;
public override IReadOnlyList HeldItems => Legal.HeldItems_Pt;
@@ -94,4 +105,4 @@ public override IReadOnlyList Inventory
set => value.SaveAll(General);
}
}
-}
\ No newline at end of file
+}
diff --git a/PKHeX.Core/Saves/Substructures/PokeDex/Zukan4.cs b/PKHeX.Core/Saves/Substructures/PokeDex/Zukan4.cs
new file mode 100644
index 000000000..ccc981aee
--- /dev/null
+++ b/PKHeX.Core/Saves/Substructures/PokeDex/Zukan4.cs
@@ -0,0 +1,532 @@
+using System;
+using System.Linq;
+
+namespace PKHeX.Core
+{
+ ///
+ /// Pokédex structure used by games.
+ ///
+ public sealed class Zukan4 : ZukanBase
+ {
+ private readonly byte[] Data;
+ private readonly int Offset;
+
+ // General structure: u32 magic, 4*bitflags, u32 spinda, form flags, language flags, more form flags, upgrade flags
+
+ /* 4 BitRegions with 0x40*8 bits
+ * Region 0: Caught (Captured/Owned) flags
+ * Region 1: Seen flags
+ * Region 2: First Seen Gender
+ * Region 3: Second Seen Gender
+ * When setting a newly seen species (first time), we set the gender bit to both First and Second regions.
+ * When setting an already-seen species, we set the Second region bit if the now-seen gender-bit is not equal to the first-seen bit.
+ * 4 possible states: 00, 01, 10, 11
+ * 00 - 1Seen: Male Only
+ * 01 - 2Seen: Male First, Female Second
+ * 10 - 2Seen: Female First, Male Second
+ * 11 - 1Seen: Female Only
+ * assuming the species is seen, (bit1 ^ bit2) + 1 = genders in dex
+ */
+
+ public const string GENDERLESS = "Genderless";
+ public const string MALE = "Male";
+ public const string FEMALE = "Female";
+ private const int SIZE_REGION = 0x40;
+ private const int COUNT_REGION = 4;
+ private const int OFS_SPINDA = sizeof(uint) + (COUNT_REGION * SIZE_REGION);
+ private const int OFS_FORM1 = OFS_SPINDA + sizeof(uint);
+
+ private bool HGSS => SAV is SAV4HGSS;
+ private bool DP => SAV is SAV4DP;
+
+ public Zukan4(SAV4 sav, int offset) : base(sav, offset)
+ {
+ Data = sav.General;
+ Offset = offset;
+ }
+
+ public uint Magic { get => BitConverter.ToUInt32(Data, Offset); set => BitConverter.GetBytes(value).CopyTo(Data, Offset); }
+
+ public override bool GetCaught(int species) => GetRegionFlag(0, species - 1);
+ public override bool GetSeen(int species) => GetRegionFlag(1, species - 1);
+ public int GetSeenGenderFirst(int species) => GetRegionFlag(2, species - 1) ? 1 : 0;
+ public int GetSeenGenderSecond(int species) => GetRegionFlag(3, species - 1) ? 1 : 0;
+ public bool GetSeenSingleGender(int species) => GetSeenGenderFirst(species) == GetSeenGenderSecond(species);
+
+ private bool GetRegionFlag(int region, int index)
+ {
+ var ofs = Offset + 4 + (region * SIZE_REGION) + (index >> 3);
+ return FlagUtil.GetFlag(Data, ofs, index);
+ }
+
+ public void SetCaught(int species, bool value = true) => SetRegionFlag(0, species - 1, value);
+ public void SetSeen(int species, bool value = true) => SetRegionFlag(1, species - 1, value);
+ public void SetSeenGenderFirst(int species, int value = 0) => SetRegionFlag(2, species - 1, value == 1);
+ public void SetSeenGenderSecond(int species, int value = 0) => SetRegionFlag(3, species - 1, value == 1);
+
+ private void SetRegionFlag(int region, int index, bool value)
+ {
+ var ofs = Offset + 4 + (region * SIZE_REGION) + (index >> 3);
+ FlagUtil.SetFlag(Data, ofs, index, value);
+ }
+
+ public uint SpindaPID { get => BitConverter.ToUInt32(Data, Offset + OFS_SPINDA); set => BitConverter.GetBytes(value).CopyTo(Data, Offset); }
+
+ public static string[] GetFormNames4Dex(int species)
+ {
+ string[] formNames = FormConverter.GetFormList(species, GameInfo.Strings.types, GameInfo.Strings.forms, Array.Empty(), 4);
+ if (species == (int)Species.Pichu)
+ formNames = new[] { MALE, FEMALE, formNames[1] }; // Spiky
+ return formNames;
+ }
+
+ public int[] GetForms(int species)
+ {
+ const int brSize = 0x40;
+ if (species == (int)Species.Deoxys)
+ {
+ uint val = (uint)(Data[Offset + 0x4 + (1 * brSize) - 1] | Data[Offset + 0x4 + (2 * brSize) - 1] << 8);
+ return GetDexFormValues(val, 4, 4);
+ }
+
+ int FormOffset1 = Offset + 4 + (4 * brSize) + 4;
+ switch (species)
+ {
+ case (int)Species.Shellos: // Shellos
+ return GetDexFormValues(Data[FormOffset1 + 0], 1, 2);
+ case (int)Species.Gastrodon: // Gastrodon
+ return GetDexFormValues(Data[FormOffset1 + 1], 1, 2);
+ case (int)Species.Burmy: // Burmy
+ return GetDexFormValues(Data[FormOffset1 + 2], 2, 3);
+ case (int)Species.Wormadam: // Wormadam
+ return GetDexFormValues(Data[FormOffset1 + 3], 2, 3);
+ case (int)Species.Unown: // Unown
+ return Data.Slice(FormOffset1 + 4, 0x1C).Select(i => (int)i).ToArray();
+ }
+ if (DP)
+ return Array.Empty();
+
+ int PokeDexLanguageFlags = FormOffset1 + (HGSS ? 0x3C : 0x20);
+ int FormOffset2 = PokeDexLanguageFlags + 0x1F4;
+ return species switch
+ {
+ (int)Species.Rotom => GetDexFormValues(BitConverter.ToUInt32(Data, FormOffset2), 3, 6),
+ (int)Species.Shaymin => GetDexFormValues(Data[FormOffset2 + 4], 1, 2),
+ (int)Species.Giratina => GetDexFormValues(Data[FormOffset2 + 5], 1, 2),
+ (int)Species.Pichu when HGSS => GetDexFormValues(Data[FormOffset2 + 6], 2, 3),
+ _ => Array.Empty()
+ };
+ }
+
+ public void SetForms(int species, int[] forms)
+ {
+ const int brSize = 0x40;
+ switch (species)
+ {
+ case (int)Species.Deoxys: // Deoxys
+ uint newval = SetDexFormValues(forms, 4, 4);
+ Data[Offset + 0x4 + (1 * brSize) - 1] = (byte)(newval & 0xFF);
+ Data[Offset + 0x4 + (2 * brSize) - 1] = (byte)((newval >> 8) & 0xFF);
+ break;
+ }
+
+ int FormOffset1 = Offset + OFS_FORM1;
+ switch (species)
+ {
+ case (int)Species.Shellos: // Shellos
+ Data[FormOffset1 + 0] = (byte)SetDexFormValues(forms, 1, 2);
+ return;
+ case (int)Species.Gastrodon: // Gastrodon
+ Data[FormOffset1 + 1] = (byte)SetDexFormValues(forms, 1, 2);
+ return;
+ case (int)Species.Burmy: // Burmy
+ Data[FormOffset1 + 2] = (byte)SetDexFormValues(forms, 2, 3);
+ return;
+ case (int)Species.Wormadam: // Wormadam
+ Data[FormOffset1 + 3] = (byte)SetDexFormValues(forms, 2, 3);
+ return;
+ case (int)Species.Unown: // Unown
+ int ofs = FormOffset1 + 4;
+ int len = forms.Length;
+ Array.Resize(ref forms, 0x1C);
+ for (int i = len; i < forms.Length; i++)
+ forms[i] = 0xFF;
+ Array.Copy(forms.Select(b => (byte)b).ToArray(), 0, Data, ofs, forms.Length);
+ return;
+ }
+
+ if (DP)
+ return;
+
+ int PokeDexLanguageFlags = FormOffset1 + (HGSS ? 0x3C : 0x20);
+ int FormOffset2 = PokeDexLanguageFlags + 0x1F4;
+ switch (species)
+ {
+ case (int)Species.Rotom: // Rotom
+ BitConverter.GetBytes(SetDexFormValues(forms, 3, 6)).CopyTo(Data, FormOffset2);
+ return;
+ case (int)Species.Shaymin: // Shaymin
+ Data[FormOffset2 + 4] = (byte)SetDexFormValues(forms, 1, 2);
+ return;
+ case (int)Species.Giratina: // Giratina
+ Data[FormOffset2 + 5] = (byte)SetDexFormValues(forms, 1, 2);
+ return;
+ case (int)Species.Pichu when HGSS: // Pichu
+ Data[FormOffset2 + 6] = (byte)SetDexFormValues(forms, 2, 3);
+ return;
+ }
+ }
+
+ private static int[] GetDexFormValues(uint Value, int BitsPerForm, int readCt)
+ {
+ int[] Forms = new int[readCt];
+ int n1 = 0xFF >> (8 - BitsPerForm);
+ for (int i = 0; i < Forms.Length; i++)
+ {
+ int val = (int)(Value >> (i * BitsPerForm)) & n1;
+ if (n1 == val && BitsPerForm > 1)
+ Forms[i] = -1;
+ else
+ Forms[i] = val;
+ }
+
+ // (BitsPerForm > 1) was already handled, handle (BitsPerForm == 1)
+ if (BitsPerForm == 1 && Forms[0] == Forms[1] && Forms[0] == 1)
+ Forms[0] = Forms[1] = -1;
+
+ return Forms;
+ }
+
+ private static uint SetDexFormValues(int[] Forms, int BitsPerForm, int readCt)
+ {
+ int n1 = 0xFF >> (8 - BitsPerForm);
+ uint Value = 0xFFFFFFFF << (readCt * BitsPerForm);
+ for (int i = 0; i < Forms.Length; i++)
+ {
+ int val = Forms[i];
+ if (val == -1)
+ val = n1;
+
+ Value |= (uint)(val << (BitsPerForm * i));
+ if (i >= readCt)
+ throw new ArgumentException("Array count should be less than bitfield count", nameof(Forms));
+ }
+ return Value;
+ }
+
+ private static bool TryInsertForm(int[] forms, int form)
+ {
+ if (Array.IndexOf(forms, form) >= 0)
+ return false; // already in list
+
+ // insert at first empty
+ var index = Array.IndexOf(forms, -1);
+ if (index < 0)
+ return false; // no free slots?
+
+ forms[index] = form;
+ return true;
+ }
+
+ public int GetUnownFormIndex(int form)
+ {
+ var ofs = Offset + OFS_FORM1 + 4;
+ for (int i = 0; i < 0x1C; i++)
+ {
+ byte val = Data[ofs + i];
+ if (val == form)
+ return i;
+ if (val == 0xFF)
+ return -1;
+ }
+ return -1;
+ }
+
+ public int GetUnownFormIndexNext(int form)
+ {
+ var ofs = Offset + OFS_FORM1 + 4;
+ for (int i = 0; i < 0x1C; i++)
+ {
+ byte val = Data[ofs + i];
+ if (val == form)
+ return i;
+ if (val == 0xFF)
+ return i;
+ }
+
+ return -1;
+ }
+
+ public void ClearUnownForms()
+ {
+ var ofs = Offset + OFS_FORM1 + 4;
+ for (int i = 0; i < 0x1C; i++)
+ Data[ofs + i] = 0xFF;
+ }
+
+ public bool GetUnownForm(int form) => GetUnownFormIndex(form) != -1;
+
+ public void AddUnownForm(int form)
+ {
+ var index = GetUnownFormIndexNext(form);
+ if (index == -1)
+ return;
+
+ var ofs = Offset + OFS_FORM1 + 4;
+ Data[ofs + index] = (byte)form;
+ }
+
+ public override void SetDex(PKM pkm)
+ {
+ int species = pkm.Species;
+ if (species == 0)
+ return;
+ if (species > Legal.MaxSpeciesID_4)
+ return;
+
+ var gender = pkm.Gender;
+ var form = pkm.Form;
+ var language = pkm.Language;
+ SetDex(species, gender, form, language);
+ }
+
+ private void SetDex(int species, int gender, int form, int language)
+ {
+ SetCaught(species);
+ SetSeenGender(species, gender);
+ SetForms(species, form, gender);
+ SetLanguage(species, language);
+ }
+
+ public void SetSeenGender(int species, int gender)
+ {
+ if (!GetSeen(species))
+ SetSeenGenderNew(species, gender);
+ else if (GetSeenSingleGender(species))
+ SetSeenGenderSecond(species, gender);
+ }
+
+ public void SetSeenGenderNew(int species, int gender)
+ {
+ SetSeenGenderFirst(species, gender);
+ SetSeenGenderSecond(species, gender);
+ }
+
+ public void SetSeenGenderNeither(int species)
+ {
+ SetSeenGenderFirst(species, 0);
+ SetSeenGenderSecond(species, 0);
+ }
+
+ private void SetForms(int species, int form, int gender)
+ {
+ if (species == (int)Species.Unown) // Unown
+ {
+ AddUnownForm(form);
+ return;
+ }
+
+ var forms = GetForms(species);
+ if (forms.Length == 0)
+ return;
+
+ if (species == (int)Species.Pichu && HGSS) // Pichu (HGSS Only)
+ {
+ int formID = form == 1 ? 2 : gender;
+ if (TryInsertForm(forms, formID))
+ SetForms(species, forms);
+ }
+ else
+ {
+ if (TryInsertForm(forms, form))
+ SetForms(species, forms);
+ }
+ }
+
+ public void SetLanguage(int species, int language, bool value = true)
+ {
+ int lang = GetGen4LanguageBitIndex(language);
+ SetLanguageBitIndex(species, lang, value);
+ }
+
+ public bool GetLanguageBitIndex(int species, int lang)
+ {
+ int dpl = 1 + Array.IndexOf(DPLangSpecies, species);
+ if (DP && dpl < 0)
+ return false;
+ int FormOffset1 = Offset + OFS_FORM1;
+ int PokeDexLanguageFlags = FormOffset1 + (HGSS ? 0x3C : 0x20);
+
+ var ofs = PokeDexLanguageFlags + (DP ? dpl : species);
+ return FlagUtil.GetFlag(Data, ofs, lang & 7);
+ }
+
+ public void SetLanguageBitIndex(int species, int lang, bool value)
+ {
+ int dpl = 1 + Array.IndexOf(DPLangSpecies, species);
+ if (DP && dpl <= 0)
+ return;
+ int FormOffset1 = Offset + OFS_FORM1;
+ int PokeDexLanguageFlags = FormOffset1 + (HGSS ? 0x3C : 0x20);
+
+ var ofs = PokeDexLanguageFlags + (DP ? dpl : species);
+ FlagUtil.SetFlag(Data, ofs, lang & 7, value);
+ }
+
+ public bool HasLanguage(int species) => GetSpeciesLanguageByteIndex(species) >= 0;
+
+ private int GetSpeciesLanguageByteIndex(int species)
+ {
+ if (DP)
+ return Array.IndexOf(DPLangSpecies, species);
+ return species;
+ }
+
+ private static readonly int[] DPLangSpecies = { 23, 25, 54, 77, 120, 129, 202, 214, 215, 216, 228, 278, 287, 315 };
+
+ public static int GetGen4LanguageBitIndex(int lang) => --lang switch
+ {
+ 3 => 4, // invert ITA/GER
+ 4 => 3, // invert ITA/GER
+ > 5 => 0, // Japanese
+ < 0 => 1, // English
+ _ => lang,
+ };
+
+ [Flags]
+ public enum SetDexArgs
+ {
+ None,
+ SeenAll = 1 << 0,
+
+ CaughtNone = 1 << 1,
+ CaughtAll = 1 << 2,
+
+ SetNoLanguages = 1 << 3,
+ SetAllLanguages = 1 << 4,
+ SetSingleLanguage = 1 << 5,
+
+ SetAllForms = 1 << 6,
+
+ Complete = SeenAll | CaughtAll | SetAllLanguages | SetAllForms,
+ }
+
+ public void ModifyAll(int species, SetDexArgs args, int lang = 0)
+ {
+ if (args == SetDexArgs.None)
+ {
+ ClearSeen(species);
+ return;
+ }
+ if ((args & SetDexArgs.SeenAll) != 0)
+ CompleteSeen(species);
+
+ if ((args & SetDexArgs.CaughtNone) != 0)
+ {
+ SetCaught(species, false);
+ ClearLanguages(species);
+ }
+ else if ((args & SetDexArgs.CaughtAll) != 0)
+ {
+ SetCaught(species);
+ }
+
+ if ((args & SetDexArgs.SetNoLanguages) != 0)
+ {
+ ClearLanguages(species);
+ }
+ if ((args & SetDexArgs.SetAllLanguages) != 0)
+ {
+ SetLanguages(species);
+ }
+ else if ((args & SetDexArgs.SetSingleLanguage) != 0)
+ {
+ SetLanguage(species, lang);
+ }
+
+ if ((args & SetDexArgs.SetAllForms) != 0)
+ {
+ CompleteForms(species);
+ }
+ }
+
+ private void CompleteForms(int species)
+ {
+ var forms = GetFormNames4Dex(species);
+ if (forms.Length <= 1)
+ return;
+
+ var values = forms.Select((_, i) => i).ToArray();
+ SetForms(species, values);
+ }
+
+ private void CompleteSeen(int species)
+ {
+ SetSeen(species);
+ var pi = PersonalTable.HGSS[species];
+ if (pi.IsDualGender)
+ {
+ SetSeenGenderFirst(species, 0);
+ SetSeenGenderSecond(species, 1);
+ }
+ else
+ {
+ SetSeenGender(species, pi.FixedGender & 1);
+ }
+ }
+
+ public void ClearSeen(int species)
+ {
+ SetCaught(species, false);
+ SetSeen(species, false);
+ SetSeenGenderNeither(species);
+
+ SetForms(species, Array.Empty());
+ ClearLanguages(species);
+ }
+
+ private const int LangCount = 6;
+ private void ClearLanguages(int species)
+ {
+ for (int i = 0; i < 8; i++)
+ SetLanguageBitIndex(species, i, false);
+ }
+
+ private void SetLanguages(int species, bool value = true)
+ {
+ for (int i = 0; i < LangCount; i++)
+ SetLanguageBitIndex(species, i, value);
+ }
+
+ // Bulk Manipulation
+ public override void CompleteDex(bool shinyToo = false) => IterateAll(z => ModifyAll(z, SetDexArgs.Complete));
+ public override void SeenNone() => IterateAll(ClearSeen);
+ public override void CaughtNone() => IterateAll(z => SetCaught(z, false));
+ public override void SeenAll(bool shinyToo = false) => IterateAll(CompleteSeen);
+
+ public override void SetDexEntryAll(int species, bool shinyToo = false) => ModifyAll(species, SetDexArgs.Complete);
+ public override void ClearDexEntryAll(int species) => ModifyAll(species, SetDexArgs.None);
+
+ private static void IterateAll(Action a)
+ {
+ for (int i = 1; i <= Legal.MaxSpeciesID_4; i++)
+ a(i);
+ }
+
+ public override void SetAllSeen(bool value = true, bool shinyToo = false)
+ {
+ if (!value)
+ {
+ SeenNone();
+ return;
+ }
+ IterateAll(CompleteSeen);
+ }
+
+ public override void CaughtAll(bool shinyToo = false)
+ {
+ SeenAll();
+ IterateAll(z => SetCaught(z));
+ }
+ }
+}
diff --git a/PKHeX.WinForms/Subforms/Save Editors/Gen4/SAV_Pokedex4.cs b/PKHeX.WinForms/Subforms/Save Editors/Gen4/SAV_Pokedex4.cs
index 858d48e8a..09306d691 100644
--- a/PKHeX.WinForms/Subforms/Save Editors/Gen4/SAV_Pokedex4.cs
+++ b/PKHeX.WinForms/Subforms/Save Editors/Gen4/SAV_Pokedex4.cs
@@ -3,6 +3,7 @@
using System.Linq;
using System.Windows.Forms;
using PKHeX.Core;
+using static PKHeX.Core.Zukan4;
namespace PKHeX.WinForms
{
@@ -46,12 +47,12 @@ public SAV_Pokedex4(SaveFile sav)
private readonly CheckBox[] CL;
private bool editing;
private int species = -1;
- private const int brSize = 0x40;
private const int LangCount = 6; // No Korean
private void ChangeCBSpecies(object sender, EventArgs e)
{
- if (editing) return;
+ if (editing)
+ return;
SetEntry();
editing = true;
@@ -64,7 +65,8 @@ private void ChangeCBSpecies(object sender, EventArgs e)
private void ChangeLBSpecies(object sender, EventArgs e)
{
- if (editing) return;
+ if (editing)
+ return;
SetEntry();
editing = true;
@@ -74,71 +76,45 @@ private void ChangeLBSpecies(object sender, EventArgs e)
editing = false;
}
- private const string GENDERLESS = "Genderless";
- private const string MALE = "Male";
- private const string FEMALE = "Female";
- private static readonly int[] DPLangSpecies = { 23, 25, 54, 77, 120, 129, 202, 214, 215, 216, 228, 278, 287, 315 };
-
private void GetEntry()
{
- // Load Bools for the data
- int bit = species - 1;
- byte mask = (byte)(1 << (bit & 7));
- int ofs = SAV.PokeDex + (bit >> 3) + 0x4;
- int FormOffset1 = SAV.PokeDex + 4 + (brSize * 4) + 4;
- int PokeDexLanguageFlags = FormOffset1 + (SAV.HGSS ? 0x3C : 0x20);
- int l_ofs = !SAV.DP ? species : 1 + Array.IndexOf(DPLangSpecies, species);
- if (l_ofs > 0)
+ var dex = SAV.Dex;
+
+ CHK_Caught.Checked = dex.GetCaught(species);
+ CHK_Seen.Checked = dex.GetSeen(species);
+
+ // Genders
+ LoadGenders(CHK_Seen.Checked);
+
+ // Forms
+ LoadForms();
+
+ // Language
+ LoadLanguage();
+ }
+
+ private void LoadLanguage()
+ {
+ var dex = SAV.Dex;
+ if (dex.HasLanguage(species))
{
- l_ofs += PokeDexLanguageFlags;
for (int i = 0; i < LangCount; i++)
- {
- CL[i].Enabled = true;
- CL[i].Checked = SAV.GetFlag(l_ofs, i);
- }
+ CL[i].Checked = dex.GetLanguageBitIndex(species, i);
}
else
{
+ GB_Language.Enabled = false;
for (int i = 0; i < LangCount; i++)
- CL[i].Enabled = CL[i].Checked = false;
+ CL[i].Checked = false;
}
+ }
- bool bit2 = (SAV.General[ofs + (brSize * 2)] & mask) != 0;
- bool bit3 = (SAV.General[ofs + (brSize * 3)] & mask) != 0;
-
- CHK_Seen.Checked = (SAV.General[ofs + (brSize * 1)] & mask) != 0;
- CHK_Caught.Checked = (SAV.General[ofs + (brSize * 0)] & mask) != 0;
-
- // Genders
- LB_Gender.Items.Clear();
- LB_NGender.Items.Clear();
-
- var active = CHK_Seen.Checked ? LB_Gender : LB_NGender;
- var inactive = LB_NGender;
- var other = bit2 ^ bit3 ? active : inactive;
-
- switch (SAV.Personal[species].Gender)
- {
- case 255: // Genderless
- active.Items.Add(GENDERLESS);
- break;
- case 0:
- active.Items.Add(MALE);
- break;
- case 254:
- active.Items.Add(FEMALE);
- break;
- default:
- active.Items.Add(bit2 ? FEMALE : MALE);
- other.Items.Add(bit2 ? MALE : FEMALE);
- break;
- }
-
- // Forms
+ private void LoadForms()
+ {
LB_Form.Items.Clear();
LB_NForm.Items.Clear();
- var forms = SAV.GetForms(species);
+ var forms = SAV.Dex.GetForms(species);
if (forms.Length == 0)
return;
@@ -151,12 +127,33 @@ private void GetEntry()
LB_NForm.Items.AddRange(not);
}
- private static string[] GetFormNames4Dex(int species)
+ private void LoadGenders(bool seen)
{
- string[] formNames = FormConverter.GetFormList(species, GameInfo.Strings.types, GameInfo.Strings.forms, Main.GenderSymbols, 4);
- if (species == (int)Species.Pichu)
- formNames = new[] { MALE, FEMALE, formNames[1] }; // Spiky
- return formNames;
+ var dex = SAV.Dex;
+ var first = seen ? LB_Gender : LB_NGender;
+ var second = dex.GetSeenSingleGender(species) ? LB_NGender : first;
+
+ LB_Gender.Items.Clear();
+ LB_NGender.Items.Clear();
+ var pi = SAV.Personal[species];
+ var gr = pi.Gender;
+ switch (gr)
+ {
+ case 255: // Genderless
+ first.Items.Add(GENDERLESS);
+ break;
+ case 0:
+ first.Items.Add(MALE);
+ break;
+ case 254:
+ first.Items.Add(FEMALE);
+ break;
+ default:
+ var firstFem = dex.GetSeenGenderFirst(species) == 1;
+ first.Items.Add(firstFem ? FEMALE : MALE);
+ second.Items.Add(firstFem ? MALE : FEMALE);
+ break;
+ }
}
private void SetEntry()
@@ -164,73 +161,34 @@ private void SetEntry()
if (species < 0)
return;
- int bit = species - 1;
- byte mask = (byte)(1 << (bit & 7));
- int ofs = SAV.PokeDex + (bit >> 3) + 0x4;
-
- // Check if already Seen
- if (!CHK_Seen.Checked || LB_Gender.Items.Count == 0)
+ var dex = SAV.Dex;
+ dex.SetCaught(species, CHK_Caught.Checked);
+ dex.SetSeen(species, CHK_Seen.Checked);
+ dex.SetSeenGenderNeither(species);
+ if (LB_Gender.Items.Count != 0)
{
- SAV.General[ofs + (brSize * 0)] &= (byte)~mask;
- SAV.General[ofs + (brSize * 1)] &= (byte)~mask;
- SAV.General[ofs + (brSize * 2)] &= (byte)~mask;
- SAV.General[ofs + (brSize * 3)] &= (byte)~mask;
- }
- else // Is Seen
- {
- // Set the Species Owned Flag
- if (CHK_Caught.Checked)
- SAV.General[ofs + (brSize * 0)] |= mask;
- else
- SAV.General[ofs + (brSize * 0)] &= (byte)~mask;
-
- SAV.General[ofs + (brSize * 1)] |= mask;
- switch ((string)LB_Gender.Items[0])
- {
- case GENDERLESS:
- SAV.General[ofs + (brSize * 2)] &= (byte)~mask;
- SAV.General[ofs + (brSize * 3)] &= (byte)~mask;
- break;
- case FEMALE:
- SAV.General[ofs + (brSize * 2)] |= mask; // set
- if (LB_Gender.Items.Count != 1) // Male present
- SAV.General[ofs + (brSize * 3)] &= (byte)~mask; // unset
- else
- SAV.General[ofs + (brSize * 3)] |= mask; // set
- break;
- case MALE:
- SAV.General[ofs + (brSize * 2)] &= (byte)~mask; // unset
- if (LB_Gender.Items.Count != 1) // Female present
- SAV.General[ofs + (brSize * 3)] |= mask; // set
- else
- SAV.General[ofs + (brSize * 3)] &= (byte)~mask; // unset
- break;
- default:
- throw new ArgumentException("Invalid Gender???");
- }
+ var femaleFirst = LB_Gender.Items[0].ToString() == FEMALE;
+ var firstGender = femaleFirst ? 1 : 0;
+ dex.SetSeenGenderNew(species, firstGender);
+ if (LB_Gender.Items.Count != 1)
+ dex.SetSeenGenderSecond(species, firstGender ^ 1);
}
- int FormOffset1 = SAV.PokeDex + 4 + (4 * brSize) + 4;
- int PokeDexLanguageFlags = FormOffset1 + (SAV.HGSS ? 0x3C : 0x20);
- int l_ofs = !SAV.DP ? species : (1 + Array.IndexOf(DPLangSpecies, species));
- if (l_ofs > 0)
+ if (dex.HasLanguage(species))
{
- l_ofs += PokeDexLanguageFlags;
for (int i = 0; i < LangCount; i++)
- SAV.SetFlag(l_ofs, i, CL[i].Checked);
+ dex.SetLanguageBitIndex(species, i, CL[i].Checked);
}
- var forms = SAV.GetForms(species);
+ var forms = SAV.Dex.GetForms(species);
if (forms.Length > 0)
{
int[] arr = new int[LB_Form.Items.Count];
string[] formNames = GetFormNames4Dex(species);
for (int i = 0; i < LB_Form.Items.Count; i++)
arr[i] = Array.IndexOf(formNames, (string)LB_Form.Items[i]);
- SAV.SetForms(species, arr);
+ SAV.Dex.SetForms(species, arr);
}
-
- editing = false;
}
private void B_Cancel_Click(object sender, EventArgs e)
@@ -250,21 +208,10 @@ private void B_Save_Click(object sender, EventArgs e)
private void B_GiveAll_Click(object sender, EventArgs e)
{
- if (GB_Language.Enabled)
- {
- CHK_L1.Checked =
- CHK_L2.Checked =
- CHK_L3.Checked =
- CHK_L4.Checked =
- CHK_L5.Checked =
- CHK_L6.Checked = ModifierKeys != Keys.Control;
- }
- CHK_Caught.Checked = CHK_Seen.Checked = ModifierKeys != Keys.Control;
-
- if (ModifierKeys == Keys.Control)
- SeenNone();
- else
- SeenAll();
+ bool all = ModifierKeys != Keys.Control;
+ var args = all ? SetDexArgs.Complete : SetDexArgs.None;
+ SAV.Dex.ModifyAll(species, args, GetGen4LanguageBitIndex(SAV.Language));
+ GetEntry();
}
private void B_Modify_Click(object sender, EventArgs e)
@@ -273,67 +220,28 @@ private void B_Modify_Click(object sender, EventArgs e)
modifyMenu.Show(btn.PointToScreen(new Point(0, btn.Height)));
}
- private void SeenNone()
- {
- LB_NGender.Items.AddRange(LB_Gender.Items);
- LB_Gender.Items.Clear();
-
- LB_NForm.Items.AddRange(LB_Form.Items);
- LB_Form.Items.Clear();
- CHK_Seen.Checked = false;
- foreach (var c in CL)
- c.Checked = false;
- }
-
- private void SeenAll()
- {
- LB_Gender.Items.AddRange(LB_NGender.Items);
- LB_NGender.Items.Clear();
-
- LB_Form.Items.AddRange(LB_NForm.Items);
- LB_NForm.Items.Clear();
- CHK_Seen.Checked = true;
- }
-
private void ModifyAll(object sender, EventArgs e)
{
- int lang = SAV.Language - 1;
- if (lang is < 0 or > 5) // KOR or Invalid
- lang = 0;
+ SetEntry();
- bool seenA = sender == mnuSeenAll || sender == mnuCaughtAll || sender == mnuComplete;
- bool seenN = sender == mnuSeenNone;
- bool caughtA = sender == mnuCaughtAll || sender == mnuComplete;
- bool caughtN = sender == mnuCaughtNone || sender == mnuSeenNone;
+ var lang = GetGen4LanguageBitIndex(SAV.Language);
+ SetDexArgs args;
+ if (sender == mnuComplete)
+ args = SetDexArgs.Complete;
+ else if (sender == mnuCaughtAll)
+ args = SetDexArgs.CaughtAll;
+ else if (sender == mnuCaughtNone)
+ args = SetDexArgs.CaughtNone;
+ else if (sender == mnuSeenAll)
+ args = SetDexArgs.SeenAll;
+ else
+ args = SetDexArgs.None;
for (int i = 0; i < LB_Species.Items.Count; i++)
- {
- LB_Species.SelectedIndex = i;
+ SAV.Dex.ModifyAll(i + 1, args, lang);
- if (seenN) // move all to none
- SeenNone();
- else if (seenA) // move all to seen
- SeenAll();
-
- if (caughtA)
- {
- CHK_Caught.Checked = true;
- for (int j = 0; j < CL.Length; j++) // set SAV language (and others if Complete)
- CL[j].Checked = sender == mnuComplete || (mnuCaughtNone != sender && j == lang);
- }
- else if (caughtN)
- {
- CHK_Caught.Checked = false;
- }
- else if (!CHK_Seen.Checked)
- {
- foreach (CheckBox t in CL)
- t.Checked = false;
- }
- }
-
- SetEntry();
GetEntry();
+ System.Media.SystemSounds.Asterisk.Play();
}
private void CHK_Seen_CheckedChanged(object sender, EventArgs e)
@@ -342,8 +250,8 @@ private void CHK_Seen_CheckedChanged(object sender, EventArgs e)
{
if (!CHK_Seen.Checked) // move all to none
{
- CHK_Caught.Checked = false;
- SeenNone();
+ SAV.Dex.ClearSeen(species);
+ GetEntry();
}
else if (LB_NGender.Items.Count > 0)
{