Split SAV3 into version classes

Begone are the version-switch cases for value fetching.
This commit is contained in:
Kurt 2021-03-15 23:51:58 -07:00
parent 6bce4eea14
commit 33e2c64721
14 changed files with 695 additions and 665 deletions

View file

@ -66,7 +66,7 @@ namespace PKHeX.Core
private static List<SlotInfoMisc> GetExtraSlots3(SAV3 sav)
{
if (!sav.FRLG)
if (sav is not SAV3FRLG)
return None;
return new List<SlotInfoMisc>
{

View file

@ -1,22 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
{
/// <summary>
/// Generation 3 <see cref="SaveFile"/> object.
/// </summary>
public sealed class SAV3 : SaveFile, ILangDeviantSave
public abstract class SAV3 : SaveFile, ILangDeviantSave
{
protected internal override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}";
public override string Extension => ".sav";
protected internal sealed override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}";
public sealed override string Extension => ".sav";
public int SaveRevision => Japanese ? 0 : 1;
public string SaveRevisionString => Japanese ? "J" : "U";
public bool Japanese { get; }
public bool Korean => false;
public bool IndeterminateGame => Version == GameVersion.Unknown;
// Similar to future games, the Generation 3 Mainline save files are comprised of two separate objects:
// Object 1 - Small Block, containing misc configuration data & the Pokédex.
@ -40,8 +38,8 @@ namespace PKHeX.Core
public readonly byte[] Small = new byte[SIZE_SMALL];
public readonly byte[] Large = new byte[SIZE_LARGE];
public readonly byte[] Storage = new byte[SIZE_PC];
protected override byte[] BoxBuffer => Storage;
protected override byte[] PartyBuffer => Large;
protected sealed override byte[] BoxBuffer => Storage;
protected sealed override byte[] PartyBuffer => Large;
// 0x83D0
private const int SIZE_PC = sizeof(int) // Current Box
@ -67,37 +65,15 @@ namespace PKHeX.Core
0x7d0 // D | PC Block 8
};
private PersonalTable _personal;
public override PersonalTable Personal => _personal;
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_RS;
public sealed override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_RS;
public SAV3(GameVersion version = GameVersion.FRLG, bool japanese = false)
protected SAV3(bool japanese)
{
Version = version switch
{
GameVersion.FR or GameVersion.LG => GameVersion.FRLG,
GameVersion.R or GameVersion.S => GameVersion.RS,
_ => version
};
_personal = SaveUtil.GetG3Personal(Version);
Japanese = japanese;
BlockOrder = Array.Empty<short>();
LegalKeyItems = Version switch
{
GameVersion.RS => Legal.Pouch_Key_RS,
GameVersion.E => Legal.Pouch_Key_E,
_ => Legal.Pouch_Key_FRLG
};
PokeDex = 0x18;
SeenFlagOffsets = Array.Empty<int>();
Initialize();
ClearBoxes();
}
public SAV3(byte[] data, GameVersion versionOverride = GameVersion.Any) : base(data)
protected SAV3(byte[] data) : base(data)
{
LoadBlocks(out BlockOrder);
@ -106,81 +82,10 @@ namespace PKHeX.Core
LoadBlocks(Large, 1, 5);
LoadBlocks(Storage, 5, BLOCK_COUNT);
Version = versionOverride != GameVersion.Any ? versionOverride : GetVersion(Small);
_personal = SaveUtil.GetG3Personal(Version);
// Japanese games are limited to 5 character OT names; any unused characters are 0xFF.
// 5 for JP, 7 for INT. There's always 1 terminator, thus we can check 0x6-0x7 being 0xFFFF = INT
// OT name is stored at the top of the first block.
Japanese = BitConverter.ToInt16(Small, 0x6) == 0;
LegalKeyItems = Version switch
{
GameVersion.RS => Legal.Pouch_Key_RS,
GameVersion.E => Legal.Pouch_Key_E,
_ => Legal.Pouch_Key_FRLG
};
PokeDex = 0x18;
SeenFlagOffsets = Version switch
{
GameVersion.RS => new[] { 0x938, 0x3A8C },
GameVersion.E => new[] { 0x988, 0x3B24 },
_ => new[] { 0x5F8, 0x3A18 }
};
Initialize();
}
private void Initialize()
{
Box = 0;
switch (Version)
{
case GameVersion.RS:
OFS_PCItem = 0x0498;
OFS_PouchHeldItem = 0x0560;
OFS_PouchKeyItem = 0x05B0;
OFS_PouchBalls = 0x0600;
OFS_PouchTMHM = 0x0640;
OFS_PouchBerry = 0x0740;
EventFlag = 0x1220;
EventConst = 0x1340;
OFS_Decorations = 0x26A0;
DaycareOffset = 0x2F9C;
break;
case GameVersion.E:
OFS_PCItem = 0x0498;
OFS_PouchHeldItem = 0x0560;
OFS_PouchKeyItem = 0x05D8;
OFS_PouchBalls = 0x0650;
OFS_PouchTMHM = 0x0690;
OFS_PouchBerry = 0x0790;
EventFlag = 0x1270;
EventConst = 0x139C;
OFS_Decorations = 0x2734;
DaycareOffset = 0x3030;
break;
case GameVersion.FRLG:
OFS_PCItem = 0x0298;
OFS_PouchHeldItem = 0x0310;
OFS_PouchKeyItem = 0x03B8;
OFS_PouchBalls = 0x0430;
OFS_PouchTMHM = 0x0464;
OFS_PouchBerry = 0x054C;
EventFlag = 0xEE0;
EventConst = 0x1000;
DaycareOffset = 0x2F80;
break;
default:
throw new ArgumentException(nameof(Version));
}
LoadEReaderBerryData();
// Sanity Check SeenFlagOffsets -- early saves may not have block 4 initialized yet
SeenFlagOffsets = SeenFlagOffsets.Where(z => z >= 0).ToArray();
}
private void LoadBlocks(byte[] dest, short start, short end)
@ -250,28 +155,7 @@ namespace PKHeX.Core
return count1 > count2 ? 0 : 1;
}
public static GameVersion GetVersion(byte[] data, int offset = 0)
{
uint GameCode = BitConverter.ToUInt32(data, offset + 0xAC);
switch (GameCode)
{
case 1: return GameVersion.FRLG; // fixed value
case 0: return GameVersion.RS; // no battle tower record data
case uint.MaxValue: return GameVersion.Unknown; // what a hack
default:
// Ruby doesn't set data as far down as Emerald.
// 00 FF 00 00 00 00 00 00 00 FF 00 00 00 00 00 00
// ^ byte pattern in Emerald saves, is all zero in Ruby/Sapphire as far as I can tell.
// Some saves have had data @ 0x550
if (BitConverter.ToUInt64(data, offset + 0xEE0) != 0)
return GameVersion.E;
if (BitConverter.ToUInt64(data, offset + 0xEE8) != 0)
return GameVersion.E;
return GameVersion.RS;
}
}
protected override byte[] GetFinalData()
protected sealed override byte[] GetFinalData()
{
// Copy Box data back
SaveBlocks(Small, 0, 1);
@ -284,41 +168,34 @@ namespace PKHeX.Core
private int ABO => ActiveSAV*SIZE_BLOCK*0xE;
private readonly short[] BlockOrder;
// Configuration
protected override SaveFile CloneInternal() => new SAV3(Write(), Version);
protected sealed override int SIZE_STORED => PokeCrypto.SIZE_3STORED;
protected sealed override int SIZE_PARTY => PokeCrypto.SIZE_3PARTY;
public sealed override PKM BlankPKM => new PK3();
public sealed override Type PKMType => typeof(PK3);
protected override int SIZE_STORED => PokeCrypto.SIZE_3STORED;
protected override int SIZE_PARTY => PokeCrypto.SIZE_3PARTY;
public override PKM BlankPKM => new PK3();
public override Type PKMType => typeof(PK3);
public sealed override int MaxMoveID => Legal.MaxMoveID_3;
public sealed override int MaxSpeciesID => Legal.MaxSpeciesID_3;
public sealed override int MaxAbilityID => Legal.MaxAbilityID_3;
public sealed override int MaxItemID => Legal.MaxItemID_3;
public sealed override int MaxBallID => Legal.MaxBallID_3;
public sealed override int MaxGameID => Legal.MaxGameID_3;
public override int MaxMoveID => Legal.MaxMoveID_3;
public override int MaxSpeciesID => Legal.MaxSpeciesID_3;
public override int MaxAbilityID => Legal.MaxAbilityID_3;
public override int MaxItemID => Legal.MaxItemID_3;
public override int MaxBallID => Legal.MaxBallID_3;
public override int MaxGameID => Legal.MaxGameID_3;
public sealed override int BoxCount => 14;
public sealed override int MaxEV => 255;
public sealed override int Generation => 3;
protected sealed override int GiftCountMax => 1;
public sealed override int OTLength => 7;
public sealed override int NickLength => 10;
public sealed override int MaxMoney => 999999;
public override int BoxCount => 14;
public override int MaxEV => 255;
public override int Generation => 3;
protected override int GiftCountMax => 1;
public override int OTLength => 7;
public override int NickLength => 10;
public override int MaxMoney => 999999;
protected override int EventFlagMax => 8 * (E ? 300 : 288); // 0x960 E, else 0x900
protected override int EventConstMax => 0x100;
public sealed override bool HasParty => true;
public bool E => Version == GameVersion.E;
public bool FRLG => Version == GameVersion.FRLG;
public bool RS => Version == GameVersion.RS;
public override bool HasParty => true;
public override bool IsPKMPresent(byte[] data, int offset) => PKX.IsPKMPresentGBA(data, offset);
public sealed override bool IsPKMPresent(byte[] data, int offset) => PKX.IsPKMPresentGBA(data, offset);
protected sealed override PKM GetPKM(byte[] data) => new PK3(data);
protected sealed override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray3(data);
// Checksums
protected override void SetChecksums()
protected sealed override void SetChecksums()
{
for (int i = 0; i < BLOCK_COUNT; i++)
{
@ -345,7 +222,7 @@ namespace PKHeX.Core
}
}
public override bool ChecksumsValid
public sealed override bool ChecksumsValid
{
get
{
@ -372,15 +249,15 @@ namespace PKHeX.Core
return chk == BitConverter.ToUInt16(Data, ofs + 0xFF4);
}
private bool IsChunkValid(int i)
private bool IsChunkValid(int chunk)
{
int ofs = ABO + (i * SIZE_BLOCK);
int len = chunkLength[BlockOrder[i]];
int ofs = ABO + (chunk * SIZE_BLOCK);
int len = chunkLength[BlockOrder[chunk]];
ushort chk = Checksums.CheckSum32(Data, ofs, len);
return chk == BitConverter.ToUInt16(Data, ofs + 0xFF6);
}
public override string ChecksumInfo
public sealed override string ChecksumInfo
{
get
{
@ -402,17 +279,9 @@ namespace PKHeX.Core
}
}
// Trainer Info
public override GameVersion Version { get; protected set; }
public abstract uint SecurityKey { get; set; }
public uint SecurityKey => Version switch
{
GameVersion.E => BitConverter.ToUInt32(Small, 0xAC),
GameVersion.FRLG => BitConverter.ToUInt32(Small, 0xF20),
_ => 0u
};
public override string OT
public sealed override string OT
{
get => GetString(Small, 0, 0x10);
set
@ -422,37 +291,37 @@ namespace PKHeX.Core
}
}
public override int Gender
public sealed override int Gender
{
get => Small[8];
set => Small[8] = (byte)value;
}
public override int TID
public sealed override int TID
{
get => BitConverter.ToUInt16(Small, 0xA);
set => BitConverter.GetBytes((ushort)value).CopyTo(Small, 0xA);
}
public override int SID
public sealed override int SID
{
get => BitConverter.ToUInt16(Small, 0xC);
set => BitConverter.GetBytes((ushort)value).CopyTo(Small, 0xC);
}
public override int PlayedHours
public sealed override int PlayedHours
{
get => BitConverter.ToUInt16(Small, 0xE);
set => BitConverter.GetBytes((ushort)value).CopyTo(Small, 0xE);
}
public override int PlayedMinutes
public sealed override int PlayedMinutes
{
get => Small[0x10];
set => Small[0x10] = (byte)value;
}
public override int PlayedSeconds
public sealed override int PlayedSeconds
{
get => Small[0x11];
set => Small[0x11] = (byte)value;
@ -464,7 +333,7 @@ namespace PKHeX.Core
set => Small[0x12] = (byte)value;
}
public override bool GetEventFlag(int flagNumber)
public sealed override bool GetEventFlag(int flagNumber)
{
if (flagNumber >= EventFlagMax)
throw new ArgumentException($"Event Flag to get ({flagNumber}) is greater than max ({EventFlagMax}).");
@ -473,7 +342,7 @@ namespace PKHeX.Core
return GetFlag(start + (flagNumber >> 3), flagNumber & 7);
}
public override void SetEventFlag(int flagNumber, bool value)
public sealed override void SetEventFlag(int flagNumber, bool value)
{
if (flagNumber >= EventFlagMax)
throw new ArgumentException($"Event Flag to set ({flagNumber}) is greater than max ({EventFlagMax}).");
@ -482,13 +351,13 @@ namespace PKHeX.Core
SetFlag(start + (flagNumber >> 3), flagNumber & 7, value);
}
public override bool GetFlag(int offset, int bitIndex) => FlagUtil.GetFlag(Large, offset, bitIndex);
public override void SetFlag(int offset, int bitIndex, bool value) => FlagUtil.SetFlag(Large, offset, bitIndex, value);
public sealed override bool GetFlag(int offset, int bitIndex) => FlagUtil.GetFlag(Large, offset, bitIndex);
public sealed override void SetFlag(int offset, int bitIndex, bool value) => FlagUtil.SetFlag(Large, offset, bitIndex, value);
public ushort GetEventConst(int index) => BitConverter.ToUInt16(Large, EventConst + (index * 2));
public void SetEventConst(int index, ushort value) => BitConverter.GetBytes(value).CopyTo(Large, EventConst + (index * 2));
public override ushort[] GetEventConsts()
public sealed override ushort[] GetEventConsts()
{
ushort[] Constants = new ushort[EventConstMax];
for (int i = 0; i < Constants.Length; i++)
@ -496,7 +365,7 @@ namespace PKHeX.Core
return Constants;
}
public override void SetEventConsts(ushort[] value)
public sealed override void SetEventConsts(ushort[] value)
{
if (value.Length != EventConstMax)
return;
@ -505,6 +374,9 @@ namespace PKHeX.Core
SetEventConst(i, value[i]);
}
protected abstract int BadgeFlagStart { get; }
public abstract uint Coin { get; set; }
public int Badges
{
get
@ -527,216 +399,47 @@ namespace PKHeX.Core
}
}
private int BadgeFlagStart
public sealed override IReadOnlyList<InventoryPouch> Inventory
{
get
{
if (Version == GameVersion.FRLG)
return 0x820;
if (Version == GameVersion.RS)
return 0x807;
return 0x867; // emerald
}
}
public override uint Money
{
get
{
switch (Version)
{
case GameVersion.RS:
case GameVersion.E: return BitConverter.ToUInt32(Large, 0x0490) ^ SecurityKey;
case GameVersion.FRLG: return BitConverter.ToUInt32(Large, 0x0290) ^ SecurityKey;
default: return 0;
}
}
set
{
switch (Version)
{
case GameVersion.RS:
case GameVersion.E: BitConverter.GetBytes(value ^ SecurityKey).CopyTo(Large, 0x0490); break;
case GameVersion.FRLG: BitConverter.GetBytes(value ^ SecurityKey).CopyTo(Large, 0x0290); break;
}
}
}
public uint Coin
{
get
{
switch (Version)
{
case GameVersion.RS:
case GameVersion.E: return (ushort)(BitConverter.ToUInt16(Large, 0x0494) ^ SecurityKey);
case GameVersion.FRLG: return (ushort)(BitConverter.ToUInt16(Large, 0x0294) ^ SecurityKey);
default: return 0;
}
}
set
{
if (value > 9999)
value = 9999;
switch (Version)
{
case GameVersion.RS:
case GameVersion.E: BitConverter.GetBytes((ushort)(value ^ SecurityKey)).CopyTo(Large, 0x0494); break;
case GameVersion.FRLG: BitConverter.GetBytes((ushort)(value ^ SecurityKey)).CopyTo(Large, 0x0294); break;
}
}
}
public uint BP
{
get => BitConverter.ToUInt16(Small, 0xEB8);
set
{
if (value > 9999)
value = 9999;
BitConverter.GetBytes((ushort)value).CopyTo(Small, 0xEB8);
}
}
public uint BPEarned
{
get => BitConverter.ToUInt16(Small, 0xEBA);
set
{
if (value > 65535)
value = 65535;
BitConverter.GetBytes((ushort)value).CopyTo(Small, 0xEBA);
}
}
public uint BerryPowder
{
get
{
if (Version != GameVersion.FRLG)
return 0;
return BitConverter.ToUInt32(Small, 0xAF8) ^ SecurityKey;
}
set
{
if (Version != GameVersion.FRLG)
return;
SetData(Small, BitConverter.GetBytes(value ^ SecurityKey), 0xAF8);
}
}
private readonly ushort[] LegalKeyItems;
private static ushort[] LegalItems => Legal.Pouch_Items_RS;
private static ushort[] LegalBalls => Legal.Pouch_Ball_RS;
private static ushort[] LegalTMHMs => Legal.Pouch_TMHM_RS;
private static ushort[] LegalBerries => Legal.Pouch_Berries_RS;
private int OFS_PCItem, OFS_PouchHeldItem, OFS_PouchKeyItem, OFS_PouchBalls, OFS_PouchTMHM, OFS_PouchBerry, OFS_Decorations;
public override IReadOnlyList<InventoryPouch> Inventory
{
get
{
int max = Version == GameVersion.FRLG ? 999 : 99;
var PCItems = new [] {LegalItems, LegalKeyItems, LegalBalls, LegalTMHMs, LegalBerries}.SelectMany(a => a).ToArray();
InventoryPouch[] pouch =
{
new InventoryPouch3(InventoryType.Items, LegalItems, max, OFS_PouchHeldItem, (OFS_PouchKeyItem - OFS_PouchHeldItem)/4),
new InventoryPouch3(InventoryType.KeyItems, LegalKeyItems, 1, OFS_PouchKeyItem, (OFS_PouchBalls - OFS_PouchKeyItem)/4),
new InventoryPouch3(InventoryType.Balls, LegalBalls, max, OFS_PouchBalls, (OFS_PouchTMHM - OFS_PouchBalls)/4),
new InventoryPouch3(InventoryType.TMHMs, LegalTMHMs, max, OFS_PouchTMHM, (OFS_PouchBerry - OFS_PouchTMHM)/4),
new InventoryPouch3(InventoryType.Berries, LegalBerries, 999, OFS_PouchBerry, Version == GameVersion.FRLG ? 43 : 46),
new InventoryPouch3(InventoryType.PCItems, PCItems, 999, OFS_PCItem, (OFS_PouchHeldItem - OFS_PCItem)/4),
};
var pouch = GetItems();
foreach (var p in pouch)
{
if (p.Type != InventoryType.PCItems)
((InventoryPouch3)p).SecurityKey = SecurityKey;
p.SecurityKey = SecurityKey;
}
return pouch.LoadAll(Large);
}
set => value.SaveAll(Large);
}
private int DaycareSlotSize => RS ? SIZE_STORED : SIZE_STORED + 0x3C; // 0x38 mail + 4 exp
public override int DaycareSeedSize => E ? 8 : 4; // 32bit, 16bit
public override uint? GetDaycareEXP(int loc, int slot) => BitConverter.ToUInt32(Large, GetDaycareEXPOffset(slot));
public override void SetDaycareEXP(int loc, int slot, uint EXP) => BitConverter.GetBytes(EXP).CopyTo(Large, GetDaycareEXPOffset(slot));
public override bool? IsDaycareOccupied(int loc, int slot) => IsPKMPresent(Large, GetDaycareSlotOffset(loc, slot));
public override void SetDaycareOccupied(int loc, int slot, bool occupied) { /* todo */ }
public override int GetDaycareSlotOffset(int loc, int slot) => DaycareOffset + (slot * DaycareSlotSize);
protected abstract InventoryPouch3[] GetItems();
private int EggEventFlag => GameVersion.FRLG.Contains(Version) ? 0x266 : 0x86;
public override bool? IsDaycareHasEgg(int loc) => GetEventFlag(EggEventFlag);
public override void SetDaycareHasEgg(int loc, bool hasEgg) => SetEventFlag(EggEventFlag, hasEgg);
protected abstract int DaycareSlotSize { get; }
private int GetDaycareEXPOffset(int slot)
{
if (Version == GameVersion.RS)
return GetDaycareSlotOffset(0, 2) + (2 * 0x38) + (4 * slot); // consecutive vals, after both consecutive slots & 2 mail
return GetDaycareSlotOffset(0, slot + 1) - 4; // @ end of each pkm slot
}
public sealed override uint? GetDaycareEXP(int loc, int slot) => BitConverter.ToUInt32(Large, GetDaycareEXPOffset(slot));
public sealed override void SetDaycareEXP(int loc, int slot, uint EXP) => BitConverter.GetBytes(EXP).CopyTo(Large, GetDaycareEXPOffset(slot));
public sealed override bool? IsDaycareOccupied(int loc, int slot) => IsPKMPresent(Large, GetDaycareSlotOffset(loc, slot));
public sealed override void SetDaycareOccupied(int loc, int slot, bool occupied) { /* todo */ }
public sealed override int GetDaycareSlotOffset(int loc, int slot) => DaycareOffset + (slot * DaycareSlotSize);
public override string GetDaycareRNGSeed(int loc)
{
if (Version == GameVersion.E)
return BitConverter.ToUInt32(Large, GetDaycareSlotOffset(0, 2)).ToString("X8"); // after the 2 slots, before the step counter
return BitConverter.ToUInt16(Large, GetDaycareEXPOffset(2)).ToString("X4"); // after the 2nd slot EXP, before the step counter
}
protected abstract int EggEventFlag { get; }
public sealed override bool? IsDaycareHasEgg(int loc) => GetEventFlag(EggEventFlag);
public sealed override void SetDaycareHasEgg(int loc, bool hasEgg) => SetEventFlag(EggEventFlag, hasEgg);
public override void SetDaycareRNGSeed(int loc, string seed)
{
if (Version == GameVersion.E) // egg pid
{
var val = Util.GetHexValue(seed);
BitConverter.GetBytes(val).CopyTo(Large, GetDaycareSlotOffset(0, 2));
}
// egg pid half
{
var val = (ushort)Util.GetHexValue(seed);
BitConverter.GetBytes(val).CopyTo(Large, GetDaycareEXPOffset(2));
}
}
protected abstract int GetDaycareEXPOffset(int slot);
// Storage
public override int PartyCount
{
get
{
int ofs = 0x34;
if (GameVersion.FRLG != Version)
ofs += 0x200;
return Large[ofs];
}
protected set
{
int ofs = 0x34;
if (GameVersion.FRLG != Version)
ofs += 0x200;
Large[ofs] = (byte)value;
}
}
#region Storage
public sealed override int GetBoxOffset(int box) => Box + 4 + (SIZE_STORED * box * COUNT_SLOTSPERBOX);
public override int GetBoxOffset(int box)
{
return Box + 4 + (SIZE_STORED * box * 30);
}
public override int GetPartyOffset(int slot)
{
int ofs = 0x38;
if (GameVersion.FRLG != Version)
ofs += 0x200;
return ofs + (SIZE_PARTY * slot);
}
public override int CurrentBox
public sealed override int CurrentBox
{
get => Storage[0];
set => Storage[0] = (byte)value;
}
public override int GetBoxWallpaper(int box)
public sealed override int GetBoxWallpaper(int box)
{
if (box > COUNT_BOX)
return box;
@ -746,7 +449,7 @@ namespace PKHeX.Core
private const int COUNT_BOXNAME = 8 + 1;
public override void SetBoxWallpaper(int box, int value)
public sealed override void SetBoxWallpaper(int box, int value)
{
if (box > COUNT_BOX)
return;
@ -754,39 +457,28 @@ namespace PKHeX.Core
Storage[offset] = (byte)value;
}
protected override int GetBoxWallpaperOffset(int box)
protected sealed override int GetBoxWallpaperOffset(int box)
{
int offset = GetBoxOffset(COUNT_BOX);
offset += (COUNT_BOX * COUNT_BOXNAME) + box;
return offset;
}
public override string GetBoxName(int box)
public sealed override string GetBoxName(int box)
{
int offset = GetBoxOffset(COUNT_BOX);
return StringConverter3.GetString3(Storage, offset + (box * COUNT_BOXNAME), COUNT_BOXNAME, Japanese);
}
public override void SetBoxName(int box, string value)
public sealed override void SetBoxName(int box, string value)
{
int offset = GetBoxOffset(COUNT_BOX);
SetString(value, COUNT_BOXNAME - 1).CopyTo(Storage, offset + (box * COUNT_BOXNAME));
}
#endregion
protected override PKM GetPKM(byte[] data)
{
return new PK3(data);
}
protected override byte[] DecryptPKM(byte[] data)
{
return PokeCrypto.DecryptArray3(data);
}
/// <summary> Mirrors of the Seen Flags (inside the Large block) </summary>
private int[] SeenFlagOffsets;
protected override void SetDex(PKM pkm)
#region Pokédex
protected sealed override void SetDex(PKM pkm)
{
int species = pkm.Species;
if (!CanSetDex(species))
@ -811,8 +503,6 @@ namespace PKHeX.Core
return false;
if (species > MaxSpeciesID)
return false;
if (Version == GameVersion.Invalid)
return false;
return true;
}
@ -820,7 +510,7 @@ namespace PKHeX.Core
public uint DexPIDSpinda { get => BitConverter.ToUInt32(Small, PokeDex + 0x8); set => BitConverter.GetBytes(value).CopyTo(Small, PokeDex + 0x8); }
public int DexUnownForm => PKX.GetUnownForm(DexPIDUnown);
public override bool GetCaught(int species)
public sealed override bool GetCaught(int species)
{
int bit = species - 1;
int ofs = bit >> 3;
@ -828,7 +518,7 @@ namespace PKHeX.Core
return FlagUtil.GetFlag(Small, caughtOffset + ofs, bit & 7);
}
public override void SetCaught(int species, bool caught)
public sealed override void SetCaught(int species, bool caught)
{
int bit = species - 1;
int ofs = bit >> 3;
@ -836,7 +526,7 @@ namespace PKHeX.Core
FlagUtil.SetFlag(Small, caughtOffset + ofs, bit & 7, caught);
}
public override bool GetSeen(int species)
public sealed override bool GetSeen(int species)
{
int bit = species - 1;
int ofs = bit >> 3;
@ -844,15 +534,18 @@ namespace PKHeX.Core
return FlagUtil.GetFlag(Small, seenOffset + ofs, bit & 7);
}
public override void SetSeen(int species, bool seen)
protected abstract int SeenOffset2 { get; }
protected abstract int SeenOffset3 { get; }
public sealed override void SetSeen(int species, bool seen)
{
int bit = species - 1;
int ofs = bit >> 3;
int seenOffset = PokeDex + 0x44;
FlagUtil.SetFlag(Small, seenOffset + ofs, bit & 7, seen);
foreach (int o in SeenFlagOffsets)
FlagUtil.SetFlag(Large, o + ofs, bit & 7, seen);
FlagUtil.SetFlag(Large, SeenOffset2 + ofs, bit & 7, seen);
FlagUtil.SetFlag(Large, SeenOffset3 + ofs, bit & 7, seen);
}
public byte PokedexSort
@ -879,201 +572,27 @@ namespace PKHeX.Core
set => Small[PokeDex + 0x03] = value;
}
private const int PokedexNationalUnlockRSE = 0xDA;
private const int PokedexNationalUnlockFRLG = 0xDA;
private const ushort PokedexNationalUnlockWorkRSE = 0x0302;
private const ushort PokedexNationalUnlockWorkFRLG = 0x6258;
protected const int PokedexNationalUnlockRSE = 0xDA;
protected const int PokedexNationalUnlockFRLG = 0xDA;
protected const ushort PokedexNationalUnlockWorkRSE = 0x0302;
protected const ushort PokedexNationalUnlockWorkFRLG = 0x6258;
public bool NationalDex
{
get
{
return Version switch // only check natdex status in Block0
{
// enable nat dex option magic value
GameVersion.RS or GameVersion.E => PokedexNationalMagicRSE == PokedexNationalUnlockRSE,
GameVersion.FRLG => PokedexNationalMagicFRLG == PokedexNationalUnlockFRLG,
_ => false
};
}
set
{
PokedexMode = value ? 1 : 0; // mode
switch (Version)
{
case GameVersion.RS:
PokedexNationalMagicRSE = value ? PokedexNationalUnlockRSE : 0; // magic
SetEventFlag(0x836, value);
SetEventConst(0x46, PokedexNationalUnlockWorkRSE);
break;
case GameVersion.E:
PokedexNationalMagicRSE = value ? PokedexNationalUnlockRSE : 0; // magic
SetEventFlag(0x896, value);
SetEventConst(0x46, PokedexNationalUnlockWorkRSE);
break;
case GameVersion.FRLG:
//PokedexNationalMagicRSE = value ? PokedexNationalUnlockRSE : 0; // magic
//SetEventFlag(0x838, value);
//SetEventConst(0x3C, PokedexNationalUnlockWorkRSE);
PokedexNationalMagicFRLG = value ? PokedexNationalUnlockFRLG : 0; // magic
SetEventFlag(0x840, value);
SetEventConst(0x4E, PokedexNationalUnlockWorkFRLG);
break;
}
}
}
public abstract bool NationalDex { get; set; }
#endregion
public override string GetString(byte[] data, int offset, int length) => StringConverter3.GetString3(data, offset, length, Japanese);
public sealed override string GetString(byte[] data, int offset, int length) => StringConverter3.GetString3(data, offset, length, Japanese);
public override byte[] SetString(string value, int maxLength, int PadToSize = 0, ushort PadWith = 0)
public sealed override byte[] SetString(string value, int maxLength, int PadToSize = 0, ushort PadWith = 0)
{
if (PadToSize == 0)
PadToSize = maxLength + 1;
return StringConverter3.SetString3(value, maxLength, Japanese, PadToSize, PadWith);
}
#region eBerry
// Offset and checksum code based from
// https://github.com/suloku/wc-tool by Suloku
private const int SIZE_EBERRY = 0x530;
private const int OFFSET_EBERRY = 0x2E80 + 0x2E0;
protected abstract int MailOffset { get; }
public int GetMailOffset(int index) => (index * Mail3.SIZE) + MailOffset;
private uint EBerryChecksum => BitConverter.ToUInt32(Large, OFFSET_EBERRY + SIZE_EBERRY - 4);
private bool IsEBerryChecksumValid { get; set; }
public string EBerryName
{
get
{
if (!GameVersion.RS.Contains(Version) || !IsEBerryChecksumValid)
return string.Empty;
return StringConverter3.GetString3(Large, OFFSET_EBERRY, 7, Japanese).Trim();
}
}
public bool IsEBerryIsEnigma => string.IsNullOrEmpty(EBerryName.Trim());
private void LoadEReaderBerryData()
{
if (!GameVersion.RS.Contains(Version))
return;
byte[] data = GetData(Large, OFFSET_EBERRY, SIZE_EBERRY - 4);
// 8 bytes are 0x00 for chk calculation
for (int i = 0; i < 8; i++)
data[0xC + i] = 0x00;
uint chk = (uint)data.Sum(z => z);
IsEBerryChecksumValid = EBerryChecksum == chk;
}
#endregion
public RTC3 ClockInitial
{
get
{
if (FRLG)
throw new ArgumentException(nameof(ClockInitial));
return new RTC3(GetData(Small, 0x98, RTC3.Size));
}
set
{
if (FRLG)
return;
SetData(Small, value.Data, 0x98);
}
}
public RTC3 ClockElapsed
{
get
{
if (FRLG)
throw new ArgumentException(nameof(ClockElapsed));
return new RTC3(GetData(Small, 0xA0, RTC3.Size));
}
set
{
if (FRLG)
return;
SetData(Small, value.Data, 0xA0);
}
}
public PokeBlock3Case PokeBlocks
{
get
{
var ofs = PokeBlockOffset;
if (ofs < 0)
throw new ArgumentException($"Game does not support {nameof(PokeBlocks)}.");
return new PokeBlock3Case(Large, ofs);
}
set => SetData(Large, value.Write(), PokeBlockOffset);
}
private int PokeBlockOffset
{
get
{
if (Version == GameVersion.E)
return 0x848;
if (Version == GameVersion.RS)
return 0x7F8;
return -1;
}
}
public int GetMailOffset(int index)
{
var offset = Version switch
{
GameVersion.RS => 0x2B4C,
GameVersion.E => 0x2BE0,
_ => 0x2CD0, // FRLG
};
return (index * Mail3.SIZE) + offset;
}
private int ExternalEventFlags => Version switch
{
GameVersion.RS => 0x312F,
GameVersion.E => 0x31C7,
_ => throw new IndexOutOfRangeException(),
};
public bool HasReceivedWishmkrJirachi
{
get => !GameVersion.FRLG.Contains(Version) && GetFlag(ExternalEventFlags + 2, 0);
set
{
if (!GameVersion.FRLG.Contains(Version))
SetFlag(ExternalEventFlags + 2, 0, value);
}
}
public bool ResetPersonal(GameVersion g)
{
if (g.GetGeneration() != 3)
return false;
_personal = SaveUtil.GetG3Personal(g);
return true;
}
public DecorationInventory3 Decorations
{
get
{
if (Version == GameVersion.FRLG)
throw new Exception();
return Large.Slice(OFS_Decorations, DecorationInventory3.SIZE).ToStructure<DecorationInventory3>();
}
set
{
if (Version == GameVersion.FRLG)
throw new Exception();
SetData(Large, value.ToBytes(), OFS_Decorations);
}
}
public abstract string EBerryName { get; }
public abstract bool IsEBerryEngima { get; }
}
}

181
PKHeX.Core/Saves/SAV3E.cs Normal file
View file

@ -0,0 +1,181 @@
using System;
namespace PKHeX.Core
{
public sealed class SAV3E : SAV3, IGen3Hoenn, IGen3Joyful
{
// Configuration
protected override SaveFile CloneInternal() => new SAV3E(Write());
public override GameVersion Version { get => GameVersion.E; protected set { } }
public override PersonalTable Personal => PersonalTable.E;
protected override int EventFlagMax => 8 * 300;
protected override int EventConstMax => 0x100;
protected override int DaycareSlotSize => SIZE_STORED + 0x3C; // 0x38 mail + 4 exp
public override int DaycareSeedSize => 8; // 32bit
protected override int EggEventFlag => 0x86;
protected override int BadgeFlagStart => 0x867;
public SAV3E(byte[] data) : base(data) => Initialize();
public SAV3E(bool japanese = false) : base(japanese) => Initialize();
private void Initialize()
{
// small
PokeDex = 0x18;
// large
EventFlag = 0x1270;
EventConst = 0x139C;
DaycareOffset = 0x3030;
// storage
Box = 0;
}
#region Small
public override bool NationalDex
{
get => PokedexNationalMagicRSE == PokedexNationalUnlockRSE;
set
{
PokedexMode = value ? 1 : 0; // mode
PokedexNationalMagicRSE = value ? PokedexNationalUnlockRSE : 0; // magic
SetEventFlag(0x896, value);
SetEventConst(0x46, PokedexNationalUnlockWorkRSE);
}
}
public override uint SecurityKey
{
get => BitConverter.ToUInt32(Small, 0xAC);
set => SetData(Small, BitConverter.GetBytes(value), 0xAC);
}
public RTC3 ClockInitial
{
get => new(GetData(Small, 0x98, RTC3.Size));
set => SetData(Small, value.Data, 0x98);
}
public RTC3 ClockElapsed
{
get => new(GetData(Small, 0xA0, RTC3.Size));
set => SetData(Small, value.Data, 0xA0);
}
public ushort JoyfulJumpInRow { get => BitConverter.ToUInt16(Small, 0x1FC); set => SetData(Small, BitConverter.GetBytes(Math.Min((ushort)9999, value)), 0x1FC); }
// u16 field2;
public ushort JoyfulJump5InRow { get => BitConverter.ToUInt16(Small, 0x200); set => SetData(Small, BitConverter.GetBytes(Math.Min((ushort)9999, value)), 0x200); }
public ushort JoyfulJumpGamesMaxPlayers { get => BitConverter.ToUInt16(Small, 0x202); set => SetData(Small, BitConverter.GetBytes(Math.Min((ushort)9999, value)), 0x202); }
// u32 field8;
public uint JoyfulJumpScore { get => BitConverter.ToUInt16(Small, 0x208); set => SetData(Small, BitConverter.GetBytes(Math.Min( 9999, value)), 0x208); }
public uint JoyfulBerriesScore { get => BitConverter.ToUInt16(Small, 0x20C); set => SetData(Small, BitConverter.GetBytes(Math.Min( 9999, value)), 0x20C); }
public ushort JoyfulBerriesInRow { get => BitConverter.ToUInt16(Small, 0x210); set => SetData(Small, BitConverter.GetBytes(Math.Min((ushort)9999, value)), 0x210); }
public ushort JoyfulBerries5InRow { get => BitConverter.ToUInt16(Small, 0x212); set => SetData(Small, BitConverter.GetBytes(Math.Min((ushort)9999, value)), 0x212); }
public uint BP
{
get => BitConverter.ToUInt16(Small, 0xEB8);
set
{
if (value > 9999)
value = 9999;
BitConverter.GetBytes((ushort)value).CopyTo(Small, 0xEB8);
}
}
public uint BPEarned
{
get => BitConverter.ToUInt16(Small, 0xEBA);
set
{
if (value > 65535)
value = 65535;
BitConverter.GetBytes((ushort)value).CopyTo(Small, 0xEBA);
}
}
#endregion
#region Large
public override int PartyCount { get => Large[0x34]; protected set => Large[0x34] = (byte)value; }
public override int GetPartyOffset(int slot) => 0x38 + (SIZE_PARTY * slot);
public override uint Money
{
get => BitConverter.ToUInt32(Large, 0x0490) ^ SecurityKey;
set => SetData(BitConverter.GetBytes(value ^ SecurityKey), 0x0490);
}
public override uint Coin
{
get => BitConverter.ToUInt16(Large, 0x0494) ^ SecurityKey;
set => SetData(BitConverter.GetBytes(value ^ SecurityKey), 0x0494);
}
private const int OFS_PCItem = 0x0498;
private const int OFS_PouchHeldItem = 0x0560;
private const int OFS_PouchKeyItem = 0x05D8;
private const int OFS_PouchBalls = 0x0650;
private const int OFS_PouchTMHM = 0x0690;
private const int OFS_PouchBerry = 0x0790;
protected override InventoryPouch3[] GetItems()
{
const int max = 99;
var PCItems = ArrayUtil.ConcatAll(Legal.Pouch_Items_RS, Legal.Pouch_Key_E, Legal.Pouch_Ball_RS, Legal.Pouch_HM_RS, Legal.Pouch_Berries_RS);
return new InventoryPouch3[]
{
new(InventoryType.Items, Legal.Pouch_Items_RS, max, OFS_PouchHeldItem, (OFS_PouchKeyItem - OFS_PouchHeldItem) / 4),
new(InventoryType.KeyItems, Legal.Pouch_Key_E, 1, OFS_PouchKeyItem, (OFS_PouchBalls - OFS_PouchKeyItem) / 4),
new(InventoryType.Balls, Legal.Pouch_Ball_RS, max, OFS_PouchBalls, (OFS_PouchTMHM - OFS_PouchBalls) / 4),
new(InventoryType.TMHMs, Legal.Pouch_HM_RS, max, OFS_PouchTMHM, (OFS_PouchBerry - OFS_PouchTMHM) / 4),
new(InventoryType.Berries, Legal.Pouch_Berries_RS, 999, OFS_PouchBerry, 46),
new(InventoryType.PCItems, PCItems, 999, OFS_PCItem, (OFS_PouchHeldItem - OFS_PCItem) / 4),
};
}
public PokeBlock3Case PokeBlocks
{
get => new(Large, 0x848);
set => SetData(Large, value.Write(), 0x848);
}
protected override int SeenOffset2 => 0x988;
public DecorationInventory3 Decorations
{
get => Large.Slice(0x2734, DecorationInventory3.SIZE).ToStructure<DecorationInventory3>();
set => SetData(Large, value.ToBytes(), 0x2734);
}
protected override int MailOffset => 0x2BE0;
protected override int GetDaycareEXPOffset(int slot) => GetDaycareSlotOffset(0, slot + 1) - 4; // @ end of each pkm slot
public override string GetDaycareRNGSeed(int loc) => BitConverter.ToUInt32(Large, GetDaycareSlotOffset(0, 2)).ToString("X8"); // after the 2 slots, before the step counter
public override void SetDaycareRNGSeed(int loc, string seed) => BitConverter.GetBytes(Util.GetHexValue(seed)).CopyTo(Large, GetDaycareEXPOffset(2));
private const int ExternalEventFlags = 0x31C7;
public bool HasReceivedWishmkrJirachi
{
get => GetFlag(ExternalEventFlags + 2, 0);
set => SetFlag(ExternalEventFlags + 2, 0, value);
}
#region eBerry
private const int OFFSET_EBERRY = 0x31F8;
private const int SIZE_EBERRY = 0x134;
public byte[] GetEReaderBerry() => Large.Slice(OFFSET_EBERRY, SIZE_EBERRY);
public void SetEReaderBerry(byte[] data) => SetData(Large, data, OFFSET_EBERRY);
public override string EBerryName => GetString(Large, OFFSET_EBERRY, 7);
public override bool IsEBerryEngima => Large[OFFSET_EBERRY] is 0 or 0xFF;
#endregion
protected override int SeenOffset3 => 0x3B24;
#endregion
}
}

View file

@ -0,0 +1,147 @@
using System;
namespace PKHeX.Core
{
public sealed class SAV3FRLG : SAV3, IGen3Joyful
{
// Configuration
protected override SaveFile CloneInternal() => new SAV3FRLG(Write());
public override GameVersion Version { get; protected set; } = GameVersion.FR; // allow mutation
private PersonalTable _personal = PersonalTable.FR;
public override PersonalTable Personal => _personal;
protected override int EventFlagMax => 8 * 288;
protected override int EventConstMax => 0x100;
protected override int DaycareSlotSize => SIZE_STORED + 0x3C; // 0x38 mail + 4 exp
public override int DaycareSeedSize => 4; // 16bit
protected override int EggEventFlag => 0x266;
protected override int BadgeFlagStart => 0x820;
public SAV3FRLG(byte[] data) : base(data) => Initialize();
public SAV3FRLG(bool japanese = false) : base(japanese) => Initialize();
private void Initialize()
{
// small
PokeDex = 0x18;
// large
EventFlag = 0xEE0;
EventConst = 0x1000;
DaycareOffset = 0x2F80;
// storage
Box = 0;
}
public bool ResetPersonal(GameVersion g)
{
if (g is not (GameVersion.FR or GameVersion.LG))
return false;
_personal = g == GameVersion.FR ? PersonalTable.FR : PersonalTable.LG;
return true;
}
#region Small
public override bool NationalDex
{
get => PokedexNationalMagicFRLG == PokedexNationalUnlockFRLG;
set
{
PokedexNationalMagicFRLG = value ? PokedexNationalUnlockFRLG : 0; // magic
SetEventFlag(0x840, value);
SetEventConst(0x4E, PokedexNationalUnlockWorkFRLG);
}
}
public ushort JoyfulJumpInRow { get => BitConverter.ToUInt16(Small, 0xB00); set => SetData(Small, BitConverter.GetBytes(Math.Min((ushort)9999, value)), 0xB00); }
// u16 field2;
public ushort JoyfulJump5InRow { get => BitConverter.ToUInt16(Small, 0xB04); set => SetData(Small, BitConverter.GetBytes(Math.Min((ushort)9999, value)), 0xB04); }
public ushort JoyfulJumpGamesMaxPlayers { get => BitConverter.ToUInt16(Small, 0xB06); set => SetData(Small, BitConverter.GetBytes(Math.Min((ushort)9999, value)), 0xB06); }
// u32 field8;
public uint JoyfulJumpScore { get => BitConverter.ToUInt16(Small, 0xB0C); set => SetData(Small, BitConverter.GetBytes(Math.Min( 9999, value)), 0xB0C); }
public uint JoyfulBerriesScore { get => BitConverter.ToUInt16(Small, 0xB10); set => SetData(Small, BitConverter.GetBytes(Math.Min( 9999, value)), 0xB10); }
public ushort JoyfulBerriesInRow { get => BitConverter.ToUInt16(Small, 0xB14); set => SetData(Small, BitConverter.GetBytes(Math.Min((ushort)9999, value)), 0xB14); }
public ushort JoyfulBerries5InRow { get => BitConverter.ToUInt16(Small, 0xB16); set => SetData(Small, BitConverter.GetBytes(Math.Min((ushort)9999, value)), 0xB16); }
public uint BerryPowder
{
get => BitConverter.ToUInt32(Small, 0xAF8) ^ SecurityKey;
set => SetData(Small, BitConverter.GetBytes(value ^ SecurityKey), 0xAF8);
}
public override uint SecurityKey
{
get => BitConverter.ToUInt32(Small, 0xF20);
set => SetData(Small, BitConverter.GetBytes(value), 0xF20);
}
#endregion
#region Large
public override int PartyCount { get => Large[0x234]; protected set => Large[0x234] = (byte)value; }
public override int GetPartyOffset(int slot) => 0x238 + (SIZE_PARTY * slot);
public override uint Money
{
get => BitConverter.ToUInt32(Large, 0x0290) ^ SecurityKey;
set => SetData(BitConverter.GetBytes(value ^ SecurityKey), 0x0290);
}
public override uint Coin
{
get => BitConverter.ToUInt16(Large, 0x0294) ^ SecurityKey;
set => SetData(BitConverter.GetBytes(value ^ SecurityKey), 0x0294);
}
private const int OFS_PCItem = 0x0298;
private const int OFS_PouchHeldItem = 0x0310;
private const int OFS_PouchKeyItem = 0x03B8;
private const int OFS_PouchBalls = 0x0430;
private const int OFS_PouchTMHM = 0x0464;
private const int OFS_PouchBerry = 0x054C;
protected override InventoryPouch3[] GetItems()
{
const int max = 999;
var PCItems = ArrayUtil.ConcatAll(Legal.Pouch_Items_RS, Legal.Pouch_Key_FRLG, Legal.Pouch_Ball_RS, Legal.Pouch_HM_RS, Legal.Pouch_Berries_RS);
return new InventoryPouch3[]
{
new(InventoryType.Items, Legal.Pouch_Items_RS, max, OFS_PouchHeldItem, (OFS_PouchKeyItem - OFS_PouchHeldItem) / 4),
new(InventoryType.KeyItems, Legal.Pouch_Key_FRLG, 1, OFS_PouchKeyItem, (OFS_PouchBalls - OFS_PouchKeyItem) / 4),
new(InventoryType.Balls, Legal.Pouch_Ball_RS, max, OFS_PouchBalls, (OFS_PouchTMHM - OFS_PouchBalls) / 4),
new(InventoryType.TMHMs, Legal.Pouch_HM_RS, max, OFS_PouchTMHM, (OFS_PouchBerry - OFS_PouchTMHM) / 4),
new(InventoryType.Berries, Legal.Pouch_Berries_RS, 999, OFS_PouchBerry, 43),
new(InventoryType.PCItems, PCItems, 999, OFS_PCItem, (OFS_PouchHeldItem - OFS_PCItem) / 4),
};
}
protected override int SeenOffset2 => 0x5F8;
protected override int MailOffset => 0x2CD0;
protected override int GetDaycareEXPOffset(int slot) => GetDaycareSlotOffset(0, slot + 1) - 4; // @ end of each pkm slot
public override string GetDaycareRNGSeed(int loc) => BitConverter.ToUInt16(Large, GetDaycareEXPOffset(2)).ToString("X4"); // after the 2nd slot EXP, before the step counter
public override void SetDaycareRNGSeed(int loc, string seed) => BitConverter.GetBytes((ushort)Util.GetHexValue(seed)).CopyTo(Large, GetDaycareEXPOffset(2));
#region eBerry
private const int OFFSET_EBERRY = 0x30EC;
private const int SIZE_EBERRY = 0x134;
public byte[] GetEReaderBerry() => Large.Slice(OFFSET_EBERRY, SIZE_EBERRY);
public void SetEReaderBerry(byte[] data) => SetData(Large, data, OFFSET_EBERRY);
public override string EBerryName => GetString(Large, OFFSET_EBERRY, 7);
public override bool IsEBerryEngima => Large[OFFSET_EBERRY] is 0 or 0xFF;
#endregion
protected override int SeenOffset3 => 0x3A18;
public string RivalName
{
get => GetString(Large, 0x3A4C, 8);
set => SetData(SetString(value, 7), 0x3A4C);
}
#endregion
}
}

144
PKHeX.Core/Saves/SAV3RS.cs Normal file
View file

@ -0,0 +1,144 @@
using System;
namespace PKHeX.Core
{
public sealed class SAV3RS : SAV3, IGen3Hoenn
{
// Configuration
protected override SaveFile CloneInternal() => new SAV3RS(Write());
public override GameVersion Version { get => GameVersion.RS; protected set { } }
public override PersonalTable Personal => PersonalTable.RS;
protected override int EventFlagMax => 8 * 288;
protected override int EventConstMax => 0x100;
protected override int DaycareSlotSize => SIZE_STORED;
public override int DaycareSeedSize => 4; // 16bit
protected override int EggEventFlag => 0x86;
protected override int BadgeFlagStart => 0x807;
public SAV3RS(byte[] data) : base(data) => Initialize();
public SAV3RS(bool japanese = false) : base(japanese) => Initialize();
private void Initialize()
{
// small
PokeDex = 0x18;
// large
EventFlag = 0x1220;
EventConst = 0x1340;
DaycareOffset = 0x2F9C;
// storage
Box = 0;
}
#region Small
public override bool NationalDex
{
get => PokedexNationalMagicRSE == PokedexNationalUnlockRSE;
set
{
PokedexMode = value ? 1 : 0; // mode
PokedexNationalMagicRSE = value ? PokedexNationalUnlockRSE : 0; // magic
SetEventFlag(0x836, value);
SetEventConst(0x46, PokedexNationalUnlockWorkRSE);
}
}
public override uint SecurityKey { get => 0; set { } }
public RTC3 ClockInitial
{
get => new(GetData(Small, 0x98, RTC3.Size));
set => SetData(Small, value.Data, 0x98);
}
public RTC3 ClockElapsed
{
get => new(GetData(Small, 0xA0, RTC3.Size));
set => SetData(Small, value.Data, 0xA0);
}
#endregion
#region Large
public override int PartyCount { get => Large[0x34]; protected set => Large[0x34] = (byte)value; }
public override int GetPartyOffset(int slot) => 0x38 + (SIZE_PARTY * slot);
public override uint Money
{
get => BitConverter.ToUInt32(Large, 0x0490);
set => SetData(BitConverter.GetBytes(value), 0x0490);
}
public override uint Coin
{
get => BitConverter.ToUInt16(Large, 0x0494);
set => SetData(BitConverter.GetBytes(value), 0x0494);
}
private const int OFS_PCItem = 0x0498;
private const int OFS_PouchHeldItem = 0x0560;
private const int OFS_PouchKeyItem = 0x05B0;
private const int OFS_PouchBalls = 0x0600;
private const int OFS_PouchTMHM = 0x0640;
private const int OFS_PouchBerry = 0x0740;
protected override InventoryPouch3[] GetItems()
{
const int max = 99;
var PCItems = ArrayUtil.ConcatAll(Legal.Pouch_Items_RS, Legal.Pouch_Key_RS, Legal.Pouch_Ball_RS, Legal.Pouch_HM_RS, Legal.Pouch_Berries_RS);
return new InventoryPouch3[]
{
new(InventoryType.Items, Legal.Pouch_Items_RS, max, OFS_PouchHeldItem, (OFS_PouchKeyItem - OFS_PouchHeldItem) / 4),
new(InventoryType.KeyItems, Legal.Pouch_Key_RS, 1, OFS_PouchKeyItem, (OFS_PouchBalls - OFS_PouchKeyItem) / 4),
new(InventoryType.Balls, Legal.Pouch_Ball_RS, max, OFS_PouchBalls, (OFS_PouchTMHM - OFS_PouchBalls) / 4),
new(InventoryType.TMHMs, Legal.Pouch_HM_RS, max, OFS_PouchTMHM, (OFS_PouchBerry - OFS_PouchTMHM) / 4),
new(InventoryType.Berries, Legal.Pouch_Berries_RS, 999, OFS_PouchBerry, 46),
new(InventoryType.PCItems, PCItems, 999, OFS_PCItem, (OFS_PouchHeldItem - OFS_PCItem) / 4),
};
}
public PokeBlock3Case PokeBlocks
{
get => new(Large, 0x7F8);
set => SetData(Large, value.Write(), 0x7F8);
}
protected override int SeenOffset2 => 0x938;
public DecorationInventory3 Decorations
{
get => Large.Slice(0x26A0, DecorationInventory3.SIZE).ToStructure<DecorationInventory3>();
set => SetData(Large, value.ToBytes(), 0x26A0);
}
protected override int MailOffset => 0x2B4C;
protected override int GetDaycareEXPOffset(int slot) => GetDaycareSlotOffset(0, 2) + (2 * 0x38) + (4 * slot); // consecutive vals, after both consecutive slots & 2 mail
public override string GetDaycareRNGSeed(int loc) => BitConverter.ToUInt16(Large, GetDaycareEXPOffset(2)).ToString("X4");
public override void SetDaycareRNGSeed(int loc, string seed) => BitConverter.GetBytes((ushort)Util.GetHexValue(seed)).CopyTo(Large, GetDaycareEXPOffset(2));
private const int ExternalEventFlags = 0x312F;
public bool HasReceivedWishmkrJirachi
{
get => GetFlag(ExternalEventFlags + 2, 0);
set => SetFlag(ExternalEventFlags + 2, 0, value);
}
#region eBerry
private const int OFFSET_EBERRY = 0x3160;
private const int SIZE_EBERRY = 0x530;
public byte[] GetEReaderBerry() => Large.Slice(OFFSET_EBERRY, SIZE_EBERRY);
public void SetEReaderBerry(byte[] data) => SetData(Large, data, OFFSET_EBERRY);
public override string EBerryName => GetString(Large, OFFSET_EBERRY, 7);
public override bool IsEBerryEngima => Large[OFFSET_EBERRY] is 0 or 0xFF;
#endregion
protected override int SeenOffset3 => 0x3A8C;
#endregion
}
}

View file

@ -19,13 +19,16 @@ namespace PKHeX.Core
private static int Language { get; set; }
/// <summary>
/// Checks if the most recently loaded Generation 3 Save File has a proper Enigma Berry that matches known distributions.
/// </summary>
public static EReaderBerryMatch GetStatus()
{
if (IsEnigma) // no e-Reader Berry data provided, can't hold berry.
return NoData;
var matchUSA = EReaderBerriesNames_USA.Contains(Name);
if (matchUSA)
var name = Name;
if (EReaderBerriesNames_USA.Contains(name))
{
return Language switch
{
@ -34,9 +37,7 @@ namespace PKHeX.Core
_ => InvalidUSA
};
}
var matchJP = EReaderBerriesNames_JP.Contains(Name);
if (matchJP)
if (EReaderBerriesNames_JP.Contains(name))
{
return Language switch
{
@ -45,7 +46,6 @@ namespace PKHeX.Core
_ => InvalidJPN
};
}
return NoMatch;
}
@ -81,9 +81,17 @@ namespace PKHeX.Core
public static void LoadFrom(SAV3 sav3)
{
IsEnigma = sav3.IsEBerryIsEnigma;
Name = sav3.EBerryName;
Language = sav3.Japanese ? (int)LanguageID.Japanese : (int)LanguageID.English;
if (sav3.IsEBerryEngima)
{
IsEnigma = true;
Name = string.Empty;
}
else
{
IsEnigma = false;
Name = sav3.EBerryName;
}
}
}

View file

@ -0,0 +1,14 @@
namespace PKHeX.Core
{
/// <summary>
/// Properties common to RS &amp; Emerald save files.
/// </summary>
public interface IGen3Hoenn
{
RTC3 ClockInitial { get; set; }
RTC3 ClockElapsed { get; set; }
PokeBlock3Case PokeBlocks { get; set; }
DecorationInventory3 Decorations { get; set; }
bool HasReceivedWishmkrJirachi { get; set; }
}
}

View file

@ -0,0 +1,14 @@
namespace PKHeX.Core
{
public interface IGen3Joyful
{
ushort JoyfulJumpInRow { get; set; }
ushort JoyfulJump5InRow { get; set; }
ushort JoyfulJumpGamesMaxPlayers { get; set; }
uint JoyfulJumpScore { get; set; }
uint JoyfulBerriesScore { get; set; }
ushort JoyfulBerriesInRow { get; set; }
ushort JoyfulBerries5InRow { get; set; }
}
}

View file

@ -289,11 +289,40 @@ namespace PKHeX.Core
if (Array.TrueForAll(order, v => v == 0)) // all blocks are 0
continue;
// Detect RS/E/FRLG
return SAV3.GetVersion(data, (blockSize * block0) + ofs);
return GetVersionG3SAV(data, (blockSize * block0) + ofs);
}
return Invalid;
}
/// <summary>
/// Checks the input <see cref="data"/> to see which game is for this file.
/// </summary>
/// <param name="data">Data to check</param>
/// <param name="offset">Offset for the start of the first Small chunk.</param>
/// <returns>RS, E, or FR/LG.</returns>
private static GameVersion GetVersionG3SAV(byte[] data, int offset = 0)
{
// 0xAC
// RS: Battle Tower Data, which will never match the FR/LG fixed value.
// E: Encryption Key
// FR/LG @ 0xAC has a fixed value (01 00 00 00)
// RS has battle tower data (variable)
uint _0xAC = BitConverter.ToUInt32(data, offset + 0xAC);
switch (_0xAC)
{
case 1: return FRLG; // fixed value
case 0: return RS; // save has no battle tower record data
default:
// RS data structure only extends 0x890 bytes; check if any data is present afterwards.
for (int i = 0x890; i < 0xF2C; i += 4)
{
if (BitConverter.ToUInt64(data, offset + i) != 0)
return E;
}
return RS;
}
}
/// <summary>Checks to see if the data belongs to a Gen3 Box RS save</summary>
/// <param name="data">Save data of which to determine the type</param>
/// <returns>Version Identifier or Invalid if type cannot be determined.</returns>
@ -547,7 +576,10 @@ namespace PKHeX.Core
// Main Games
RBY => new SAV1(data, type),
GS or C => new SAV2(data, type),
RS or E or FRLG => new SAV3(data, type),
RS => new SAV3RS(data),
E => new SAV3E(data),
FRLG => new SAV3FRLG(data),
DP => new SAV4DP(data),
Pt => new SAV4Pt(data),
@ -684,10 +716,9 @@ namespace PKHeX.Core
C or GSC => new SAV2(version: C, lang: language),
Stadium2 => new SAV2Stadium(language == LanguageID.Japanese),
R or S or E or FR or LG => new SAV3(version: game, language == LanguageID.Japanese),
RS => new SAV3(version: R, language == LanguageID.Japanese),
RSE => new SAV3(version: E, language == LanguageID.Japanese),
FRLG => new SAV3(version: FR, language == LanguageID.Japanese),
R or S or RS => new SAV3RS(language == LanguageID.Japanese),
E or RSE => new SAV3E(language == LanguageID.Japanese),
FR or LG or FRLG => new SAV3FRLG(language == LanguageID.Japanese),
CXD or COLO => new SAV3Colosseum(),
XD => new SAV3XD(),
@ -778,13 +809,9 @@ namespace PKHeX.Core
/// <returns>New <see cref="SaveFile"/> object.</returns>
public static SAV3 GetG3SaveOverride(SaveFile sav, GameVersion ver) => ver switch // Reset save file info
{
R => new SAV3(sav.State.BAK, RS),
S => new SAV3(sav.State.BAK, RS),
RS => new SAV3(sav.State.BAK, RS),
E => new SAV3(sav.State.BAK, E),
FRLG => new SAV3(sav.State.BAK, FRLG),
FR => new SAV3(sav.State.BAK, FRLG),
LG => new SAV3(sav.State.BAK, FRLG),
R or S or RS => new SAV3RS(sav.State.BAK),
E => new SAV3E(sav.State.BAK),
FR or LG or FRLG => new SAV3FRLG(sav.State.BAK),
_ => throw new ArgumentException(nameof(ver))
};

View file

@ -1076,7 +1076,7 @@ namespace PKHeX.WinForms.Controls
B_OpenHoneyTreeEditor.Visible = B_OpenUGSEditor.Visible = sav is SAV4Sinnoh;
B_OpenApricorn.Visible = sav is SAV4HGSS;
B_OpenRTCEditor.Visible = sav.Generation == 2 || (sav is SAV3 s3 && (s3.RS || s3.E));
B_OpenRTCEditor.Visible = sav.Generation == 2 || sav is IGen3Hoenn;
B_MailBox.Visible = sav is SAV2 or SAV3 or SAV4 or SAV5;
B_Raids.Visible = sav is SAV8SWSH;

View file

@ -867,7 +867,7 @@ namespace PKHeX.WinForms
if (sav.State.Exportable && sav is SAV3 s3)
{
if (s3.IndeterminateGame || ModifierKeys == Keys.Control)
if (ModifierKeys == Keys.Control)
{
var g = new[] { GameVersion.R, GameVersion.S, GameVersion.E, GameVersion.FR, GameVersion.LG };
var games = g.Select(z => GameInfo.VersionDataSource.First(v => v.Value == (int)z));
@ -876,14 +876,14 @@ namespace PKHeX.WinForms
dialog.ShowDialog();
sav = SaveUtil.GetG3SaveOverride(sav, dialog.Result);
if (sav.Version == GameVersion.FRLG)
if (s3 is SAV3FRLG frlg)
{
bool result = s3.ResetPersonal(dialog.Result);
bool result = frlg.ResetPersonal(dialog.Result);
if (!result)
return false;
}
}
else if (sav.Version == GameVersion.FRLG) // IndeterminateSubVersion
else if (s3 is SAV3FRLG frlg) // IndeterminateSubVersion
{
string fr = GameInfo.GetVersionName(GameVersion.FR);
string lg = GameInfo.GetVersionName(GameVersion.LG);
@ -893,7 +893,7 @@ namespace PKHeX.WinForms
var msg = string.Format(dual, "3", fr, lg);
using var dialog = new SAV_GameSelect(games, msg, MsgFileLoadSaveSelectVersion);
dialog.ShowDialog();
bool result = s3.ResetPersonal(dialog.Result);
bool result = frlg.ResetPersonal(dialog.Result);
if (!result)
return false;
}

View file

@ -19,12 +19,12 @@ namespace PKHeX.WinForms
LoadRecords();
if (SAV.FRLG || SAV.E)
ReadJoyful();
if (SAV is IGen3Joyful j)
ReadJoyful(j);
else
tabControl1.Controls.Remove(TAB_Joyful);
if (SAV.E)
if (SAV is SAV3E)
{
ReadFerry();
ReadBattleFrontier();
@ -35,9 +35,9 @@ namespace PKHeX.WinForms
tabControl1.Controls.Remove(TAB_BF);
}
if (SAV.FRLG)
if (SAV is SAV3FRLG frlg)
{
TB_RivalName.Text = SAV.GetString(SAV.Large, 0x3A4C, 8);
TB_RivalName.Text = frlg.RivalName;
// Trainer Card Species
ComboBox[] cba = { CB_TCM1, CB_TCM2, CB_TCM3, CB_TCM4, CB_TCM5, CB_TCM6 };
@ -59,22 +59,22 @@ namespace PKHeX.WinForms
private void B_Save_Click(object sender, EventArgs e)
{
if (tabControl1.Controls.Contains(TAB_Joyful))
SaveJoyful();
if (tabControl1.Controls.Contains(TAB_Joyful) && SAV is IGen3Joyful j)
SaveJoyful(j);
if (tabControl1.Controls.Contains(TAB_Ferry))
SaveFerry();
if (tabControl1.Controls.Contains(TAB_BF))
SaveBattleFrontier();
if (SAV.FRLG)
if (SAV is SAV3FRLG frlg)
{
SAV.SetData(SAV.Large, SAV.SetString(TB_RivalName.Text, TB_RivalName.MaxLength), 0x3A4C);
frlg.RivalName = TB_RivalName.Text;
ComboBox[] cba = { CB_TCM1, CB_TCM2, CB_TCM3, CB_TCM4, CB_TCM5, CB_TCM6 };
for (int i = 0; i < cba.Length; i++)
SAV.SetEventConst(0x43 + i, (ushort)(int)cba[i].SelectedValue);
}
if (!SAV.RS)
SAV.BP = (ushort)NUD_BP.Value;
if (SAV is SAV3E se)
se.BP = (ushort)NUD_BP.Value;
SAV.Coin = (ushort)NUD_Coins.Value;
Origin.CopyChangesFrom(SAV);
@ -84,48 +84,24 @@ namespace PKHeX.WinForms
private void B_Cancel_Click(object sender, EventArgs e) => Close();
#region Joyful
private int JUMPS_IN_ROW, JUMPS_SCORE, JUMPS_5_IN_ROW;
private int BERRIES_IN_ROW, BERRIES_SCORE, BERRIES_5_IN_ROW;
private void ReadJoyful()
private void ReadJoyful(IGen3Joyful j)
{
switch (SAV.Version)
{
case GameVersion.E:
JUMPS_IN_ROW = 0x1fc;
JUMPS_SCORE = 0x208;
JUMPS_5_IN_ROW = 0x200;
BERRIES_IN_ROW = 0x210;
BERRIES_SCORE = 0x20c;
BERRIES_5_IN_ROW = 0x212;
break;
case GameVersion.FRLG:
JUMPS_IN_ROW = 0xB00;
JUMPS_SCORE = 0xB0C;
JUMPS_5_IN_ROW = 0xB04;
BERRIES_IN_ROW = 0xB14;
BERRIES_SCORE = 0xB10;
BERRIES_5_IN_ROW = 0xB16;
break;
}
TB_J1.Text = Math.Min((ushort)9999, BitConverter.ToUInt16(SAV.Small, JUMPS_IN_ROW)).ToString();
TB_J2.Text = Math.Min((ushort)9999, BitConverter.ToUInt16(SAV.Small, JUMPS_SCORE)).ToString();
TB_J3.Text = Math.Min((ushort)9999, BitConverter.ToUInt16(SAV.Small, JUMPS_5_IN_ROW)).ToString();
TB_B1.Text = Math.Min((ushort)9999, BitConverter.ToUInt16(SAV.Small, BERRIES_IN_ROW)).ToString();
TB_B2.Text = Math.Min((ushort)9999, BitConverter.ToUInt16(SAV.Small, BERRIES_SCORE)).ToString();
TB_B3.Text = Math.Min((ushort)9999, BitConverter.ToUInt16(SAV.Small, BERRIES_5_IN_ROW)).ToString();
TB_J1.Text = Math.Min((ushort)9999, j.JoyfulJumpInRow).ToString();
TB_J2.Text = Math.Min( 9999, j.JoyfulJumpScore).ToString();
TB_J3.Text = Math.Min((ushort)9999, j.JoyfulJump5InRow).ToString();
TB_B1.Text = Math.Min((ushort)9999, j.JoyfulBerriesInRow).ToString();
TB_B2.Text = Math.Min( 9999, j.JoyfulBerriesScore).ToString();
TB_B3.Text = Math.Min((ushort)9999, j.JoyfulBerries5InRow).ToString();
}
private void SaveJoyful()
private void SaveJoyful(IGen3Joyful j)
{
BitConverter.GetBytes((ushort)Util.ToUInt32(TB_J1.Text)).CopyTo(SAV.Small, JUMPS_IN_ROW);
BitConverter.GetBytes((ushort)Util.ToUInt32(TB_J2.Text)).CopyTo(SAV.Small, JUMPS_SCORE);
BitConverter.GetBytes((ushort)Util.ToUInt32(TB_J3.Text)).CopyTo(SAV.Small, JUMPS_5_IN_ROW);
BitConverter.GetBytes((ushort)Util.ToUInt32(TB_B1.Text)).CopyTo(SAV.Small, BERRIES_IN_ROW);
BitConverter.GetBytes((ushort)Util.ToUInt32(TB_B2.Text)).CopyTo(SAV.Small, BERRIES_SCORE);
BitConverter.GetBytes((ushort)Util.ToUInt32(TB_B3.Text)).CopyTo(SAV.Small, BERRIES_5_IN_ROW);
j.JoyfulJumpInRow = (ushort)Util.ToUInt32(TB_J1.Text);
j.JoyfulJumpScore = (ushort)Util.ToUInt32(TB_J2.Text);
j.JoyfulJump5InRow = (ushort)Util.ToUInt32(TB_J3.Text);
j.JoyfulBerriesInRow = (ushort)Util.ToUInt32(TB_B1.Text);
j.JoyfulBerriesScore = (ushort)Util.ToUInt32(TB_B2.Text);
j.JoyfulBerries5InRow = (ushort)Util.ToUInt32(TB_B3.Text);
}
#endregion
@ -468,11 +444,11 @@ namespace PKHeX.WinForms
LoadFame(val);
};
if (!SAV.RS)
if (SAV is SAV3E em)
{
NUD_BP.Value = Math.Min(NUD_BP.Maximum, SAV.BP);
NUD_BPEarned.Value = SAV.BPEarned;
NUD_BPEarned.ValueChanged += (s, e) => SAV.BPEarned = (uint)NUD_BPEarned.Value;
NUD_BP.Value = Math.Min(NUD_BP.Maximum, em.BP);
NUD_BPEarned.Value = em.BPEarned;
NUD_BPEarned.ValueChanged += (s, e) => em.BPEarned = (uint)NUD_BPEarned.Value;
}
else
{

View file

@ -7,13 +7,13 @@ namespace PKHeX.WinForms
public partial class SAV_RTC3 : Form
{
private readonly SaveFile Origin;
private readonly SAV3 SAV;
private readonly IGen3Hoenn SAV;
public SAV_RTC3(SaveFile sav)
{
InitializeComponent();
WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage);
SAV = (SAV3)(Origin = sav).Clone();
SAV = (IGen3Hoenn)(Origin = sav).Clone();
ClockInitial = SAV.ClockInitial;
ClockElapsed = SAV.ClockElapsed;
@ -56,7 +56,7 @@ namespace PKHeX.WinForms
SAV.ClockInitial = ClockInitial;
SAV.ClockElapsed = ClockElapsed;
Origin.CopyChangesFrom(SAV);
Origin.CopyChangesFrom((SaveFile)SAV);
Close();
}

View file

@ -176,7 +176,7 @@ namespace PKHeX.Tests.Simulator
var pk7 = new PK7 { Species = set.Species, Form = set.Form, Moves = set.Moves, CurrentLevel = set.Level };
var encs = EncounterMovesetGenerator.GenerateEncounters(pk7, set.Moves);
var tr3 = encs.First(z => z is EncounterTrade3);
var pk3 = tr3.ConvertToPKM(new SAV3());
var pk3 = tr3.ConvertToPKM(new SAV3FRLG());
var la = new LegalityAnalysis(pk3);
la.Valid.Should().BeTrue();