diff --git a/PKHeX.Core/Saves/SAV3E.cs b/PKHeX.Core/Saves/SAV3E.cs
index 55d001f76..d8f3ed7e4 100644
--- a/PKHeX.Core/Saves/SAV3E.cs
+++ b/PKHeX.Core/Saves/SAV3E.cs
@@ -128,6 +128,9 @@ public override uint Coin
private const int OFS_PouchBalls = 0x0650;
private const int OFS_PouchTMHM = 0x0690;
private const int OFS_PouchBerry = 0x0790;
+ private const int OFS_BerryBlenderRecord = 0x9BC;
+ private const int OFS_TrendyWord = 0x2E20;
+ private const int OFS_TrainerHillRecord = 0x3718;
protected override InventoryPouch3[] GetItems()
{
@@ -185,6 +188,50 @@ public int SwarmIndex
protected override int ExternalEventData => 0x31B3;
+ ///
+ /// Max RPM for 2, 3 and 4 players. Each value unit represents 0.01 RPM. Value 0 if no record.
+ ///
+ /// 2 players: index 0, 3 players: index 1, 4 players: index 2
+ public const int BerryBlenderRPMRecordCount = 3;
+
+ private Span GetBlenderRPMSpan(int index)
+ {
+ if ((uint)index >= BerryBlenderRPMRecordCount)
+ throw new ArgumentOutOfRangeException(nameof(index));
+ return Large.AsSpan(OFS_BerryBlenderRecord + (index * 2));
+ }
+
+ public ushort GetBerryBlenderRPMRecord(int index) => ReadUInt16LittleEndian(GetBlenderRPMSpan(index));
+
+ public void SetBerryBlenderRPMRecord(int index, ushort value)
+ {
+ WriteUInt16LittleEndian(GetBlenderRPMSpan(index), value);
+ State.Edited = true;
+ }
+
+ public bool GetTrendyWordUnlocked(TrendyWord3E word)
+ {
+ return GetFlag(OFS_TrendyWord + ((byte)word >> 3), (byte)word & 7);
+ }
+
+ public void SetTrendyWordUnlocked(TrendyWord3E word, bool value)
+ {
+ SetFlag(OFS_TrendyWord + ((byte)word >> 3), (byte)word & 7, value);
+ State.Edited = true;
+ }
+
+ /** Each value unit represents 1/60th of a second. Value 0 if no record. */
+ public uint GetTrainerHillRecord(TrainerHillMode3E mode)
+ {
+ return ReadUInt32LittleEndian(Large.AsSpan(OFS_TrainerHillRecord + (byte)mode * 4));
+ }
+
+ public void SetTrainerHillRecord(TrainerHillMode3E mode, uint value)
+ {
+ WriteUInt32LittleEndian(Large.AsSpan(OFS_TrainerHillRecord + (byte)mode * 4), value);
+ State.Edited = true;
+ }
+
#region eBerry
private const int OFFSET_EBERRY = 0x31F8;
private const int SIZE_EBERRY = 0x34;
diff --git a/PKHeX.Core/Saves/SAV4Pt.cs b/PKHeX.Core/Saves/SAV4Pt.cs
index 9a1608f40..4c616017d 100644
--- a/PKHeX.Core/Saves/SAV4Pt.cs
+++ b/PKHeX.Core/Saves/SAV4Pt.cs
@@ -29,7 +29,8 @@ public SAV4Pt(byte[] data) : base(data, GeneralSize, StorageSize, GeneralSize)
public const int GeneralSize = 0xCF2C;
private const int StorageSize = 0x121E4; // Start 0xCF2C, +4 starts box data
- private const int VillaFurnitureStart = 0x111F;
+
+ public const byte BACKDROP_POSITION_IF_NOT_UNLOCKED = 0x12;
protected override BlockInfo4[] ExtraBlocks =>
[
@@ -51,6 +52,12 @@ private void Initialize()
protected override int EventFlag => 0xFEC;
public override BattleFrontierFacility4 MaxFacility => BattleFrontierFacility4.Arcade;
+ private const int OFS_AccessoryMultiCount = 0x4E38; // 4 bits each
+ private const int OFS_AccessorySingleCount = 0x4E58; // 1 bit each
+ private const int OFS_Backdrop = 0x4E60;
+ private const int OFS_ToughWord = 0xCEB4;
+ private const int OFS_VillaFurniture = 0x111F;
+
private void GetSAVOffsets()
{
AdventureInfo = 0;
@@ -78,6 +85,8 @@ private void GetSAVOffsets()
}
#region Storage
+ private const int OFS_Wallpaper = 0x121C6;
+
private static int AdjustWallpaper(int value, int shift)
{
// Pt's Special Wallpapers 1-8 are shifted by +0x8
@@ -102,6 +111,25 @@ public override void SetBoxWallpaper(int box, int value)
value = AdjustWallpaper(value, 0x08);
Storage[GetBoxWallpaperOffset(box)] = (byte)value;
}
+
+ public bool GetWallpaperUnlocked(Wallpaper4Pt wallpaperId)
+ {
+ if (wallpaperId < Wallpaper4Pt.Distortion)
+ return true;
+
+ var unlockableWallpaperIdx = wallpaperId - Wallpaper4Pt.Distortion;
+ return FlagUtil.GetFlag(Storage, OFS_Wallpaper + (unlockableWallpaperIdx >> 3), unlockableWallpaperIdx & 7);
+ }
+
+ public void SetWallpaperUnlocked(Wallpaper4Pt wallpaperId, bool value)
+ {
+ if (wallpaperId < Wallpaper4Pt.Distortion)
+ return; // Always unlocked
+
+ var unlockableWallpaperIdx = wallpaperId - Wallpaper4Pt.Distortion;
+ FlagUtil.SetFlag(Storage, OFS_Wallpaper + (unlockableWallpaperIdx >> 3), unlockableWallpaperIdx & 7, value);
+ State.Edited = true;
+ }
#endregion
public override IReadOnlyList Inventory
@@ -169,41 +197,112 @@ private Roamer4 GetRoamer(int index)
return new Roamer4(mem);
}
- public bool GetVillaFurniturePurchased(VillaFurniture index)
+ public byte GetAccessoryOwnedCount(Accessory4 accessory)
{
- if (index > VillaFurniture.MAX)
- throw new ArgumentOutOfRangeException(nameof(index));
- return FlagUtil.GetFlag(General, VillaFurnitureStart + ((byte)index >> 3), (byte)index & 7);
+ if (accessory < Accessory4.ColoredParasol)
+ {
+ byte enumIdx = (byte)accessory;
+ byte val = General[OFS_AccessoryMultiCount + (enumIdx / 2)];
+ if (enumIdx % 2 == 0)
+ return (byte)(val & 0x0F);
+ return (byte)(val >> 4);
+ }
+
+ // Otherwise, it's a single-count accessory
+ var flagIdx = accessory - Accessory4.ColoredParasol;
+ if (GetFlag(OFS_AccessorySingleCount + (flagIdx >> 3), flagIdx & 7))
+ return 1;
+ return 0;
}
- public void SetVillaFurniturePurchased(VillaFurniture index, bool value = true)
+ public void SetAccessoryOwnedCount(Accessory4 accessory, byte count)
{
- if (index > VillaFurniture.MAX)
+ if (accessory < Accessory4.ColoredParasol)
+ {
+ if (count > 9)
+ count = 9;
+
+ var enumIdx = (byte)accessory;
+ var addr = OFS_AccessoryMultiCount + (enumIdx / 2);
+
+ if (enumIdx % 2 == 0)
+ {
+ General[addr] &= 0xF0; // Reset old count to 0
+ General[addr] |= count; // Set new count
+ }
+ else
+ {
+ General[addr] &= 0x0F; // Reset old count to 0
+ General[addr] |= (byte)(count << 4); // Set new count
+ }
+ }
+ else
+ {
+ var flagIdx = accessory - Accessory4.ColoredParasol;
+ SetFlag(OFS_AccessorySingleCount + (flagIdx >> 3), flagIdx & 7, count != 0);
+ }
+
+ State.Edited = true;
+ }
+
+ public byte GetBackdropPosition(Backdrop4 backdrop)
+ {
+ if (backdrop > Backdrop4.Theater)
+ throw new ArgumentOutOfRangeException(nameof(backdrop), backdrop, null);
+ return General[OFS_Backdrop + (byte)backdrop];
+ }
+
+ public bool GetBackdropUnlocked(Backdrop4 backdrop)
+ {
+ return GetBackdropPosition(backdrop) != BACKDROP_POSITION_IF_NOT_UNLOCKED;
+ }
+
+ public void RemoveBackdrop(Backdrop4 backdrop) => SetBackdropPosition(backdrop, BACKDROP_POSITION_IF_NOT_UNLOCKED);
+
+ ///
+ /// Sets the position of a backdrop.
+ ///
+ ///
+ /// Every unlocked backdrop must have a different position.
+ /// Use to remove a backdrop.
+ ///
+ public void SetBackdropPosition(Backdrop4 backdrop, byte position)
+ {
+ if (backdrop > Backdrop4.Theater)
+ throw new ArgumentOutOfRangeException(nameof(backdrop), backdrop, null);
+ if (position > BACKDROP_POSITION_IF_NOT_UNLOCKED)
+ position = BACKDROP_POSITION_IF_NOT_UNLOCKED;
+ General[OFS_Backdrop + (byte)backdrop] = position;
+ State.Edited = true;
+ }
+
+ public bool GetToughWordUnlocked(ToughWord4 word)
+ {
+ if (word > ToughWord4.REMSleep)
+ throw new ArgumentOutOfRangeException(nameof(word), word, null);
+ return GetFlag(OFS_ToughWord + ((byte)word >> 3), (byte)word & 7);
+ }
+
+ public void SetToughWordUnlocked(ToughWord4 word, bool value)
+ {
+ if (word > ToughWord4.REMSleep)
+ throw new ArgumentOutOfRangeException(nameof(word), word, null);
+ SetFlag(OFS_ToughWord + ((byte)word >> 3), (byte)word & 7, value);
+ State.Edited = true;
+ }
+
+ public bool GetVillaFurniturePurchased(VillaFurniture4 index)
+ {
+ if (index > VillaFurniture4.Chandelier)
throw new ArgumentOutOfRangeException(nameof(index));
- FlagUtil.SetFlag(General, VillaFurnitureStart + ((byte)index >> 3), (byte)index & 7, value);
+ return GetFlag(OFS_VillaFurniture + ((byte)index >> 3), (byte)index & 7);
+ }
+
+ public void SetVillaFurniturePurchased(VillaFurniture4 index, bool value = true)
+ {
+ if (index > VillaFurniture4.Chandelier)
+ throw new ArgumentOutOfRangeException(nameof(index));
+ SetFlag(OFS_VillaFurniture + ((byte)index >> 3), (byte)index & 7, value);
+ State.Edited = true;
}
}
-
-public enum VillaFurniture
-{
- BigSofa,
- SmallSofa,
- Bed,
- NightTable,
- TV,
- AudioSystem,
- Bookshelf,
- Rack,
- Houseplant,
- PCDesk,
- MusicBox,
- PokemonBust1,
- PokemonBust2,
- Piano,
- GuestSet,
- WallClock,
- Masterpiece,
- TeaSet,
- Chandelier,
- MAX = Chandelier,
-}
diff --git a/PKHeX.Core/Saves/Substructures/Gen3/TrainerHillMode3E.cs b/PKHeX.Core/Saves/Substructures/Gen3/TrainerHillMode3E.cs
new file mode 100644
index 000000000..a186510b7
--- /dev/null
+++ b/PKHeX.Core/Saves/Substructures/Gen3/TrainerHillMode3E.cs
@@ -0,0 +1,9 @@
+namespace PKHeX.Core;
+
+public enum TrainerHillMode3E : byte
+{
+ Normal = 0,
+ Variety = 1,
+ Unique = 2,
+ Expert = 3,
+}
diff --git a/PKHeX.Core/Saves/Substructures/Gen3/TrendyWord3E.cs b/PKHeX.Core/Saves/Substructures/Gen3/TrendyWord3E.cs
new file mode 100644
index 000000000..c14b4c568
--- /dev/null
+++ b/PKHeX.Core/Saves/Substructures/Gen3/TrendyWord3E.cs
@@ -0,0 +1,38 @@
+namespace PKHeX.Core;
+
+public enum TrendyWord3E : byte
+{
+ KTHXBYE = 0,
+ YESSIR = 1,
+ AVANTGARDE = 2,
+ COUPLE = 3,
+ MUCHOBLIGED = 4,
+ YEEHAW = 5,
+ MEGA = 6,
+ ONEHITKO = 7,
+ DESTINY = 8,
+ CANCEL = 9,
+ NEW = 10,
+ FLATTEN = 11,
+ KIDDING = 12,
+ LOSER = 13,
+ LOSING = 14,
+ HAPPENING = 15,
+ HIPAND = 16,
+ SHAKE = 17,
+ SHADY = 18,
+ UPBEAT = 19,
+ MODERN = 20,
+ SMELLYA = 21,
+ BANG = 22,
+ KNOCKOUT = 23,
+ HASSLE = 24,
+ WINNER = 25,
+ FEVER = 26,
+ WANNABE = 27,
+ BABY = 28,
+ HEART = 29,
+ OLD = 30,
+ YOUNG = 31,
+ UGLY = 32,
+}
diff --git a/PKHeX.Core/Saves/Substructures/Gen4/Accessory4.cs b/PKHeX.Core/Saves/Substructures/Gen4/Accessory4.cs
new file mode 100644
index 000000000..a76a2bd91
--- /dev/null
+++ b/PKHeX.Core/Saves/Substructures/Gen4/Accessory4.cs
@@ -0,0 +1,107 @@
+namespace PKHeX.Core;
+
+public enum Accessory4 : byte
+{
+ WhiteFluff = 0,
+ YellowFluff = 1,
+ PinkFluff = 2,
+ BrownFluff = 3,
+ BlackFluff = 4,
+ OrangeFluff = 5,
+ RoundPebble = 6,
+ GlitterBoulder = 7,
+ SnaggyPebble = 8,
+ JaggedBoulder = 9,
+ BlackPebble = 10,
+ MiniPebble = 11,
+ PinkScale = 12,
+ BlueScale = 13,
+ GreenScale = 14,
+ PurpleScale = 15,
+ BigScale = 16,
+ NarrowScale = 17,
+ BlueFeather = 18,
+ RedFeather = 19,
+ YellowFeather = 20,
+ WhiteFeather = 21,
+ BlackMoustache = 22,
+ WhiteMoustache = 23,
+ BlackBeard = 24,
+ WhiteBeard = 25,
+ SmallLeaf = 26,
+ BigLeaf = 27,
+ NarrowLeaf = 28,
+ ShedClaw = 29,
+ ShedHorn = 30,
+ ThinMushroom = 31,
+ ThickMushroom = 32,
+ Stump = 33,
+ PrettyDewdrop = 34,
+ SnowCrystal = 35,
+ Sparks = 36,
+ ShimmeringFire = 37,
+ MysticFire = 38,
+ Determination = 39,
+ PeculiarSpoon = 40,
+ PuffySmoke = 41,
+ PoisonExtract = 42,
+ WealthyCoin = 43,
+ EerieThing = 44,
+ Spring = 45,
+ Seashell = 46,
+ HummingNote = 47,
+ ShinyPowder = 48,
+ GlitterPowder = 49,
+ RedFlower = 50,
+ PinkFlower = 51,
+ WhiteFlower = 52,
+ BlueFlower = 53,
+ OrangeFlower = 54,
+ YellowFlower = 55,
+ GooglySpecs = 56,
+ BlackSpecs = 57,
+ GorgeousSpecs = 58,
+ SweetCandy = 59,
+ Confetti = 60,
+
+ // For accessories below this point, only 1 copy can be owned at once
+ ColoredParasol = 61,
+ OldUmbrella = 62,
+ Spotlight = 63,
+ Cape = 64,
+ StandingMike = 65,
+ Surfboard = 66,
+ Carpet = 67,
+ RetroPipe = 68,
+ FluffyBed = 69,
+ MirrorBall = 70,
+ PhotoBoard = 71,
+ PinkBarrette = 72,
+ RedBarrette = 73,
+ BlueBarrette = 74,
+ YellowBarrette = 75,
+ GreenBarrette = 76,
+ PinkBalloon = 77,
+ RedBalloons = 78,
+ BlueBalloons = 79,
+ YellowBalloon = 80,
+ GreenBalloons = 81,
+ LaceHeadress = 82,
+ TopHat = 83,
+ SilkVeil = 84,
+ HeroicHeadband = 85,
+ ProfessorHat = 86,
+ FlowerStage = 87,
+ GoldPedestal = 88,
+ GlassStage = 89,
+ AwardPodium = 90,
+ CubeStage = 91,
+ TURTWIGMask = 92,
+ CHIMCHARMask = 93,
+ PIPLUPMask = 94,
+ BigTree = 95,
+ Flag = 96,
+ Crown = 97,
+ Tiara = 98,
+ Comet = 99,
+}
diff --git a/PKHeX.Core/Saves/Substructures/Gen4/Backdrop4.cs b/PKHeX.Core/Saves/Substructures/Gen4/Backdrop4.cs
new file mode 100644
index 000000000..e452c04f5
--- /dev/null
+++ b/PKHeX.Core/Saves/Substructures/Gen4/Backdrop4.cs
@@ -0,0 +1,23 @@
+namespace PKHeX.Core;
+
+public enum Backdrop4 : byte
+{
+ DressUp = 0,
+ Ranch = 1,
+ CityatNight = 2,
+ SnowyTown = 3,
+ Fiery = 4,
+ OuterSpace = 5,
+ Desert = 6,
+ CumulusCloud = 7,
+ FlowerPatch = 8,
+ FutureRoom = 9,
+ OpenSea = 10,
+ TotalDarkness = 11,
+ TatamiRoom = 12,
+ GingerbreadRoom = 13,
+ Seafloor = 14,
+ Underground = 15,
+ Sky = 16,
+ Theater = 17,
+}
diff --git a/PKHeX.Core/Saves/Substructures/Gen4/ToughWord4.cs b/PKHeX.Core/Saves/Substructures/Gen4/ToughWord4.cs
new file mode 100644
index 000000000..544a8ed64
--- /dev/null
+++ b/PKHeX.Core/Saves/Substructures/Gen4/ToughWord4.cs
@@ -0,0 +1,37 @@
+namespace PKHeX.Core;
+
+public enum ToughWord4 : byte
+{
+ EarthTones = 0,
+ Implant = 1,
+ GoldenRatio = 2,
+ Omnibus = 3,
+ Starboard = 4,
+ MoneyRate = 5,
+ Resolution = 6,
+ Cadenza = 7,
+ Education = 8,
+ Cubism = 9,
+ CrossStitch = 10,
+ Artery = 11,
+ BoneDensity = 12,
+ Gommage = 13,
+ Streaming = 14,
+ Conductivity = 15,
+ Copyright = 16,
+ TwoStep = 17,
+ Contour = 18,
+ Neutrino = 19,
+ Howling = 20,
+ Spreadsheet = 21,
+ GMT = 22,
+ Irritability = 23,
+ Fractals = 24,
+ Flambe = 25,
+ StockPrices = 26,
+ PHBalance = 27,
+ Vector = 28,
+ Polyphenol = 29,
+ Ubiquitous = 30,
+ REMSleep = 31,
+}
diff --git a/PKHeX.Core/Saves/Substructures/Gen4/VillaFurniture4.cs b/PKHeX.Core/Saves/Substructures/Gen4/VillaFurniture4.cs
new file mode 100644
index 000000000..ec3955869
--- /dev/null
+++ b/PKHeX.Core/Saves/Substructures/Gen4/VillaFurniture4.cs
@@ -0,0 +1,24 @@
+namespace PKHeX.Core;
+
+public enum VillaFurniture4 : byte
+{
+ BigSofa = 0,
+ SmallSofa = 1,
+ Bed = 2,
+ NightTable = 3,
+ TV = 4,
+ AudioSystem = 5,
+ Bookshelf = 6,
+ Rack = 7,
+ Houseplant = 8,
+ PCDesk = 9,
+ MusicBox = 10,
+ PokemonBust1 = 11,
+ PokemonBust2 = 12,
+ Piano = 13,
+ GuestSet = 14,
+ WallClock = 15,
+ Masterpiece = 16,
+ TeaSet = 17,
+ Chandelier = 18,
+}
diff --git a/PKHeX.Core/Saves/Substructures/Gen4/Wallpaper4Pt.cs b/PKHeX.Core/Saves/Substructures/Gen4/Wallpaper4Pt.cs
new file mode 100644
index 000000000..9bf26c6b1
--- /dev/null
+++ b/PKHeX.Core/Saves/Substructures/Gen4/Wallpaper4Pt.cs
@@ -0,0 +1,29 @@
+namespace PKHeX.Core;
+
+public enum Wallpaper4Pt : byte
+{
+ Forest = 0,
+ City = 1,
+ Desert = 2,
+ Savanna = 3,
+ Crag = 4,
+ Volcano = 5,
+ Snow = 6,
+ Cave = 7,
+ Beach = 8,
+ Seafloor = 9,
+ River = 10,
+ Sky = 11,
+ Checks = 12,
+ PokeCenter = 13,
+ Machine = 14,
+ Simple = 15,
+ Distortion = 16,
+ Contest = 17,
+ Nostalgic = 18,
+ Croagunk = 19,
+ trio = 20,
+ PikaPika = 21,
+ Legend = 22,
+ Team_Galactic = 23,
+}