diff --git a/PKHeX.Core/PKM/Util/PKX.cs b/PKHeX.Core/PKM/Util/PKX.cs
index bd1a3c053..e0c9e9c48 100644
--- a/PKHeX.Core/PKM/Util/PKX.cs
+++ b/PKHeX.Core/PKM/Util/PKX.cs
@@ -492,7 +492,7 @@ private static void CryptPKM45(byte[] data, uint pv, uint chk, int blockSize)
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void CryptArray(byte[] data, uint seed, int start, int end)
+ public static void CryptArray(byte[] data, uint seed, int start, int end)
{
for (int i = start; i < end; i += 2)
Crypt(data, ref seed, i);
diff --git a/PKHeX.Core/Saves/Substructures/EntreeForest/EntreeForest.cs b/PKHeX.Core/Saves/Substructures/EntreeForest/EntreeForest.cs
new file mode 100644
index 000000000..558d63ee2
--- /dev/null
+++ b/PKHeX.Core/Saves/Substructures/EntreeForest/EntreeForest.cs
@@ -0,0 +1,119 @@
+using System;
+
+namespace PKHeX.Core
+{
+ public class EntreeForest
+ {
+ ///
+ /// Areas 1 thru 8 have 20 slots.
+ ///
+ private const byte Count18 = 20;
+
+ ///
+ /// 9th Area has only 10 slots.
+ ///
+ private const byte Count9 = 10;
+
+ private const int TotalSlots = Count18 + (3 * 8 * Count18) + (3 * Count9);
+
+ ///
+ /// Areas 3 thru 8 can be unlocked (set a value 0 to 6).
+ ///
+ private const byte MaxUnlock38Areas = 6;
+
+
+ private readonly byte[] Data;
+
+ public EntreeForest(byte[] data)
+ {
+ Data = data;
+ PKX.CryptArray(data, EncryptionSeed, 0, data.Length);
+ }
+ public byte[] Write()
+ {
+ byte[] data = (byte[])Data.Clone();
+ PKX.CryptArray(data, EncryptionSeed, 0, data.Length);
+ return data;
+ }
+
+ ///
+ /// Gets all Entree Slot data.
+ ///
+ public EntreeSlot[] Slots
+ {
+ get
+ {
+ var slots = new EntreeSlot[TotalSlots];
+ for (int i = 0; i < slots.Length; i++)
+ slots[i] = new EntreeSlot(Data, i * 4) { Area = GetSlotArea(i) };
+ return slots;
+ }
+ }
+
+ ///
+ /// Determines if the 9th Area is available to enter.
+ ///
+ public bool Unlock9thArea
+ {
+ get => Data[0x848] == 1;
+ set => Data[0x848] = (byte)(value ? 1 : 0);
+ }
+
+ ///
+ /// Determines how many extra areas are available to enter. Areas 1 & 2 are already available by default.
+ ///
+ public int Unlock38Areas
+ {
+ get => Data[0x849];
+ set => Data[0x849] = (byte)Math.Max(MaxUnlock38Areas, value);
+ }
+
+ public uint EncryptionSeed
+ {
+ get => BitConverter.ToUInt32(Data, 0x84C);
+ private set => BitConverter.GetBytes(value).CopyTo(Data, 0x84C);
+ }
+ public void UnlockAllAreas()
+ {
+ Unlock38Areas = MaxUnlock38Areas;
+ Unlock9thArea = true;
+ }
+
+ public void DeleteAll()
+ {
+ foreach (var e in Slots)
+ e.Delete();
+ }
+
+ private static EntreeForestArea GetSlotArea(int index)
+ {
+ if (index < Count18)
+ return EntreeForestArea.Deepest;
+ index -= Count18;
+
+ const int slots9 = 3 * Count9;
+ if (index < slots9)
+ return EntreeForestArea.Ninth | GetSlotPosition(index / Count9);
+ index -= slots9;
+
+ const int slots18 = 3 * Count18;
+ int area = index / slots18;
+ if (area >= 8)
+ throw new ArgumentOutOfRangeException(nameof(index));
+ index %= slots18;
+
+ return (EntreeForestArea)((int)EntreeForestArea.First << area) | GetSlotPosition(index / Count18);
+ }
+
+ private static EntreeForestArea GetSlotPosition(int index)
+ {
+ switch (index)
+ {
+ case 0: return EntreeForestArea.Center;
+ case 1: return EntreeForestArea.Left;
+ case 2: return EntreeForestArea.Right;
+ default: throw new ArgumentOutOfRangeException();
+ }
+ }
+ }
+}
diff --git a/PKHeX.Core/Saves/Substructures/EntreeForest/EntreeForestArea.cs b/PKHeX.Core/Saves/Substructures/EntreeForest/EntreeForestArea.cs
new file mode 100644
index 000000000..5349ebe90
--- /dev/null
+++ b/PKHeX.Core/Saves/Substructures/EntreeForest/EntreeForestArea.cs
@@ -0,0 +1,23 @@
+using System;
+
+namespace PKHeX.Core
+{
+ [Flags]
+ public enum EntreeForestArea
+ {
+ Deepest = 1 << 0,
+ First = 1 << 1,
+ Second = 1 << 2,
+ Third = 1 << 3,
+ Fourth = 1 << 4,
+ Fifth = 1 << 5,
+ Sixth = 1 << 6,
+ Seventh = 1 << 7,
+ Eighth = 1 << 8,
+ Ninth = 1 << 9,
+
+ Left = 1 << 10,
+ Right = 1 << 11,
+ Center = 1 << 12,
+ }
+}
\ No newline at end of file
diff --git a/PKHeX.Core/Saves/Substructures/EntreeForest/EntreeSlot.cs b/PKHeX.Core/Saves/Substructures/EntreeForest/EntreeSlot.cs
new file mode 100644
index 000000000..3aa56711a
--- /dev/null
+++ b/PKHeX.Core/Saves/Substructures/EntreeForest/EntreeSlot.cs
@@ -0,0 +1,57 @@
+using System;
+
+namespace PKHeX.Core
+{
+ public class EntreeSlot
+ {
+ public int Species // bits 0-10
+ {
+ get => (int)(RawValue & 0x7FF) >> 0;
+ set => RawValue = (RawValue & 0xFFFF_F800) | ((uint)value << 0);
+ }
+ public int Move // bits 11-20
+ {
+ get => (int)(RawValue & 0x1F_F800) >> 10;
+ set => RawValue = (RawValue & 0xFFF0_02FF) | ((uint)value << 10);
+ }
+ public int Gender // bits 21-22
+ {
+ get => (int)(RawValue & 60_0000) >> 21;
+ set => RawValue = (RawValue & 0xFF9F_FFFF) | ((uint)value << 21);
+ }
+ public int Form // bits 23-27
+ {
+ get => (int)(RawValue & 0x0F80_0000) >> 23;
+ set => RawValue = (RawValue & 0xF07F_FFFF) | ((uint)value << 23);
+ }
+ public bool Invisible // bit 28
+ {
+ get => ((RawValue >> 28) & 1) == 1;
+ set => RawValue = (RawValue & 0xEFFFFFFF) | (value ? 0 : 1u << 28);
+ }
+ public int Animation // bits 29-31
+ {
+ get => (int)(RawValue >> 29);
+ set => RawValue = (RawValue << 3) >> 3 | (uint)(value << 29);
+ }
+
+ private readonly byte[] Data;
+ private readonly int Offset;
+
+ public uint RawValue
+ {
+ get => BitConverter.ToUInt32(Data, Offset);
+ set => BitConverter.GetBytes(value).CopyTo(Data, Offset);
+ }
+
+ public void Delete() => RawValue = 0;
+
+ public EntreeForestArea Area { get; internal set; }
+
+ public EntreeSlot(byte[] data, int ofs)
+ {
+ Data = data;
+ Offset = ofs;
+ }
+ }
+}
\ No newline at end of file