PKHeX/PKHeX.Core/Saves/SAV1.cs
Alexander Hoischen 517441a982 Implement IsDaycareOccupied for SAV1 (#2036)
This commit implements IsDaycareOccupied for generation 1 saves. The
byte which tracks if the daycare is occupied should normally only be
0x01 or 0x00, so I decided that all other values mean that the daycare
is probably corrupted and thus not occupied. SetDaycareOccupied is not
implemented, since I am not sure what other flags the game normally sets
in this scenario and daycare is read-only anyway.

The GUI displays the Pokemon's index to the right for some reason, but I
don't have saves to test this for other generations, so it might be
unrelated to this change.
2018-06-29 16:18:42 -07:00

540 lines
21 KiB
C#

using System;
using System.Linq;
namespace PKHeX.Core
{
/// <summary>
/// Generation 1 <see cref="SaveFile"/> object.
/// </summary>
public sealed class SAV1 : SaveFile
{
public override string BAKName => $"{FileName} [{OT} ({Version}) - {PlayTimeString}].bak";
public override string Filter => "SAV File|*.sav|All Files|*.*";
public override string Extension => ".sav";
public override string[] PKMExtensions => PKM.Extensions.Where(f =>
{
int gen = f.Last() - 0x30;
return 1 <= gen && gen <= 2;
}).ToArray();
public SAV1(byte[] data = null, GameVersion versionOverride = GameVersion.Any)
{
Data = data ?? new byte[SaveUtil.SIZE_G1RAW];
BAK = (byte[])Data.Clone();
Exportable = !IsRangeEmpty(0, Data.Length);
if (data == null)
Version = GameVersion.RBY;
else if (versionOverride != GameVersion.Any)
Version = versionOverride;
else Version = SaveUtil.GetIsG1SAV(Data);
if (Version == GameVersion.Invalid)
return;
if (Starter != 0)
Version = Yellow ? GameVersion.YW : GameVersion.RB;
Box = Data.Length;
Array.Resize(ref Data, Data.Length + SIZE_RESERVED);
Party = GetPartyOffset(0);
Japanese = SaveUtil.GetIsG1SAVJ(Data);
Personal = PersonalTable.Y;
// Stash boxes after the save file's end.
byte[] TempBox = new byte[SIZE_STOREDBOX];
for (int i = 0; i < BoxCount; i++)
{
if (i < BoxCount / 2)
Array.Copy(Data, 0x4000 + i * TempBox.Length, TempBox, 0, TempBox.Length);
else
Array.Copy(Data, 0x6000 + (i - BoxCount / 2) * TempBox.Length, TempBox, 0, TempBox.Length);
PokemonList1 PL1 = new PokemonList1(TempBox, Japanese ? PokemonList1.CapacityType.StoredJP : PokemonList1.CapacityType.Stored, Japanese);
for (int j = 0; j < PL1.Pokemon.Length; j++)
{
if (j < PL1.Count)
{
byte[] pkDat = new PokemonList1(PL1[j]).GetBytes();
pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + i * SIZE_BOX + j * SIZE_STORED);
}
else
{
byte[] pkDat = new byte[PokemonList1.GetDataLength(PokemonList1.CapacityType.Single, Japanese)];
pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + i * SIZE_BOX + j * SIZE_STORED);
}
}
}
Array.Copy(Data, Japanese ? 0x302D : 0x30C0, TempBox, 0, TempBox.Length);
PokemonList1 curBoxPL = new PokemonList1(TempBox, Japanese ? PokemonList1.CapacityType.StoredJP : PokemonList1.CapacityType.Stored, Japanese);
for (int i = 0; i < curBoxPL.Pokemon.Length; i++)
{
if (i < curBoxPL.Count)
{
byte[] pkDat = new PokemonList1(curBoxPL[i]).GetBytes();
pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + CurrentBox * SIZE_BOX + i * SIZE_STORED);
}
else
{
byte[] pkDat = new byte[PokemonList1.GetDataLength(PokemonList1.CapacityType.Single, Japanese)];
pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + CurrentBox * SIZE_BOX + i * SIZE_STORED);
}
}
byte[] TempParty = new byte[PokemonList1.GetDataLength(PokemonList1.CapacityType.Party, Japanese)];
Array.Copy(Data, Japanese ? 0x2ED5 : 0x2F2C, TempParty, 0, TempParty.Length);
PokemonList1 partyList = new PokemonList1(TempParty, PokemonList1.CapacityType.Party, Japanese);
for (int i = 0; i < partyList.Pokemon.Length; i++)
{
if (i < partyList.Count)
{
byte[] pkDat = new PokemonList1(partyList[i]).GetBytes();
pkDat.CopyTo(Data, GetPartyOffset(i));
}
else
{
byte[] pkDat = new byte[PokemonList1.GetDataLength(PokemonList1.CapacityType.Single, Japanese)];
pkDat.CopyTo(Data, GetPartyOffset(i));
}
}
byte[] rawDC = new byte[0x38];
Array.Copy(Data, Japanese ? 0x2CA7 : 0x2CF4, rawDC, 0, rawDC.Length);
byte[] TempDaycare = new byte[PokemonList1.GetDataLength(PokemonList1.CapacityType.Single, Japanese)];
TempDaycare[0] = rawDC[0];
Array.Copy(rawDC, 1, TempDaycare, 2 + 1 + PKX.SIZE_1PARTY + StringLength, StringLength);
Array.Copy(rawDC, 1 + StringLength, TempDaycare, 2 + 1 + PKX.SIZE_1PARTY, StringLength);
Array.Copy(rawDC, 1 + 2 * StringLength, TempDaycare, 2 + 1, PKX.SIZE_1STORED);
PokemonList1 daycareList = new PokemonList1(TempDaycare, PokemonList1.CapacityType.Single, Japanese);
daycareList.GetBytes().CopyTo(Data, GetPartyOffset(7));
Daycare = GetPartyOffset(7);
EventFlag = Japanese ? 0x29E9 : 0x29F3;
ObjectSpawnFlags = Japanese ? 0x2848 : 0x2852; // 2 bytes after Coin
// Enable Pokedex editing
PokeDex = 0;
if (!Exportable)
ClearBoxes();
}
// Event Flags
protected override int EventFlagMax => EventFlag > 0 ? 0xA00 : int.MinValue; // 320 * 8
protected override int EventConstMax => 0;
private readonly int ObjectSpawnFlags;
private const int SIZE_RESERVED = 0x8000; // unpacked box data
protected override byte[] Write(bool DSV)
{
for (int i = 0; i < BoxCount; i++)
{
PokemonList1 boxPL = new PokemonList1(Japanese ? PokemonList1.CapacityType.StoredJP : PokemonList1.CapacityType.Stored, Japanese);
int slot = 0;
for (int j = 0; j < boxPL.Pokemon.Length; j++)
{
PK1 boxPK = (PK1) GetPKM(GetData(GetBoxOffset(i) + j*SIZE_STORED, SIZE_STORED));
if (boxPK.Species > 0)
boxPL[slot++] = boxPK;
}
if (i < BoxCount / 2)
boxPL.GetBytes().CopyTo(Data, 0x4000 + i * SIZE_STOREDBOX);
else
boxPL.GetBytes().CopyTo(Data, 0x6000 + (i - BoxCount / 2) * SIZE_STOREDBOX);
if (i == CurrentBox)
boxPL.GetBytes().CopyTo(Data, Japanese ? 0x302D : 0x30C0);
}
PokemonList1 partyPL = new PokemonList1(PokemonList1.CapacityType.Party, Japanese);
int pSlot = 0;
for (int i = 0; i < 6; i++)
{
PK1 partyPK = (PK1)GetPKM(GetData(GetPartyOffset(i), SIZE_STORED));
if (partyPK.Species > 0)
partyPL[pSlot++] = partyPK;
}
partyPL.GetBytes().CopyTo(Data, Japanese ? 0x2ED5 : 0x2F2C);
// Daycare is read-only, but in case it ever becomes editable, copy it back in.
byte[] rawDC = GetData(GetDaycareSlotOffset(loc: 0, slot: 0), SIZE_STORED);
byte[] dc = new byte[1 + 2*StringLength + PKX.SIZE_1STORED];
dc[0] = rawDC[0];
Array.Copy(rawDC, 2 + 1 + PKX.SIZE_1PARTY + StringLength, dc, 1, StringLength);
Array.Copy(rawDC, 2 + 1 + PKX.SIZE_1PARTY, dc, 1 + StringLength, StringLength);
Array.Copy(rawDC, 2 + 1, dc, 1 + 2*StringLength, PKX.SIZE_1STORED);
dc.CopyTo(Data, Japanese ? 0x2CA7 : 0x2CF4);
SetChecksums();
byte[] outData = new byte[Data.Length - SIZE_RESERVED];
Array.Copy(Data, outData, outData.Length);
return outData;
}
// Configuration
public override SaveFile Clone() { return new SAV1(Write(DSV: false)); }
public override int SIZE_STORED => Japanese ? PKX.SIZE_1JLIST : PKX.SIZE_1ULIST;
protected override int SIZE_PARTY => Japanese ? PKX.SIZE_1JLIST : PKX.SIZE_1ULIST;
private int SIZE_BOX => BoxSlotCount*SIZE_STORED;
private int SIZE_STOREDBOX => PokemonList1.GetDataLength(Japanese ? PokemonList1.CapacityType.StoredJP : PokemonList1.CapacityType.Stored, Japanese);
public override PKM BlankPKM => new PK1(null, null, Japanese);
public override Type PKMType => typeof(PK1);
public override int MaxMoveID => Legal.MaxMoveID_1;
public override int MaxSpeciesID => Legal.MaxSpeciesID_1;
public override int MaxAbilityID => Legal.MaxAbilityID_1;
public override int MaxItemID => Legal.MaxItemID_1;
public override int MaxBallID => 0; // unused
public override int MaxGameID => 99; // unused
public override int MaxMoney => 999999;
public override int MaxCoins => 9999;
public override int BoxCount => Japanese ? 8 : 12;
public override int MaxEV => 65535;
public override int MaxIV => 15;
public override int Generation => 1;
protected override int GiftCountMax => 0;
public override int OTLength => Japanese ? 5 : 7;
public override int NickLength => Japanese ? 5 : 10;
public override int BoxSlotCount => Japanese ? 30 : 20;
public override bool HasParty => true;
private int StringLength => Japanese ? PK1.STRLEN_J : PK1.STRLEN_U;
public override bool IsPKMPresent(int Offset) => PKX.IsPKMPresentGB(Data, Offset);
// Checksums
protected override void SetChecksums()
{
int CHECKSUM_OFS = Japanese ? 0x3594 : 0x3523;
Data[CHECKSUM_OFS] = 0;
uint chksum = 0;
for (int i = 0x2598; i < CHECKSUM_OFS; i++)
{
chksum += Data[i];
}
chksum = ~chksum;
chksum &= 0xFF;
Data[CHECKSUM_OFS] = (byte)chksum;
}
public override bool ChecksumsValid
{
get
{
int CHECKSUM_OFS = Japanese ? 0x3594 : 0x3523;
byte temp = Data[CHECKSUM_OFS]; // cache current chk
SetChecksums(); // chksum is recalculated (after being set to 0 to perform check)
byte chk = Data[CHECKSUM_OFS]; // correct checksum
Data[CHECKSUM_OFS] = temp; // restore old chk
return temp == chk;
}
}
public override string ChecksumInfo => ChecksumsValid ? "Checksum valid." : "Checksum invalid";
// Trainer Info
public override GameVersion Version { get; protected set; }
public override string OT
{
get => GetString(0x2598, OTLength);
set => SetString(value, OTLength).CopyTo(Data, 0x2598);
}
public byte[] OT_Trash { get => GetData(0x2598, StringLength); set { if (value?.Length == StringLength) SetData(value, 0x2598); } }
public override int Gender
{
get => 0;
set { }
}
public override int TID
{
get => BigEndian.ToUInt16(Data, Japanese ? 0x25FB : 0x2605);
set => BigEndian.GetBytes((ushort)value).CopyTo(Data, Japanese ? 0x25FB : 0x2605);
}
public override int SID { get => 0; set { } }
public bool Yellow => Starter == 0x54; // Pikachu
public int Starter => Data[Japanese ? 0x29B9 : 0x29C3];
public byte PikaFriendship
{
get => Data[Japanese ? 0x2712 : 0x271C];
set => Data[Japanese ? 0x2712 : 0x271C] = value;
}
private int PlayedTimeOffset => Japanese ? 0x2CA0 : 0x2CED;
protected override string PlayTimeString => !PlayedMaximum ? base.PlayTimeString : $"{base.PlayTimeString} {SaveUtil.CRC16_CCITT(Data):X4}";
public override int PlayedHours
{
get => Data[PlayedTimeOffset + 0];
set
{
if (value >= byte.MaxValue) // Set 255:00:00.00 and flag
{
PlayedMaximum = true;
value = byte.MaxValue;
PlayedMinutes = PlayedSeconds = PlayedFrames = 0;
}
Data[PlayedTimeOffset + 0] = (byte) value;
}
}
public bool PlayedMaximum
{
get => Data[PlayedTimeOffset + 1] != 0;
set => Data[PlayedTimeOffset + 1] = (byte)(value ? 1 : 0);
}
public override int PlayedMinutes
{
get => Data[PlayedTimeOffset + 2];
set => Data[PlayedTimeOffset + 2] = (byte)value;
}
public override int PlayedSeconds
{
get => Data[PlayedTimeOffset + 3];
set => Data[PlayedTimeOffset + 3] = (byte)value;
}
public int PlayedFrames
{
get => Data[PlayedTimeOffset + 4];
set => Data[PlayedTimeOffset + 4] = (byte)value;
}
public int Badges
{
get => Data[Japanese ? 0x25F8 : 0x2602];
set { if (value < 0) return; Data[Japanese ? 0x25F8 : 0x2602] = (byte)value; }
}
private byte Options
{
get => Data[Japanese ? 0x25F7 : 0x2601];
set => Data[Japanese ? 0x25F7 : 0x2601] = value;
}
public bool BattleEffects
{
get => (Options & 0x80) == 0;
set => Options = (byte)((Options & 0x7F) | (value ? 0 : 0x80));
}
public bool BattleStyleSwitch
{
get => (Options & 0x40) == 0;
set => Options = (byte)((Options & 0xBF) | (value ? 0 : 0x40));
}
public int Sound
{
get => (Options & 0x30) >> 4;
set
{
var new_sound = value;
if (new_sound > 3)
new_sound = 3;
if (new_sound < 0)
new_sound = 0;
Options = (byte)((Options & 0xCF) | (new_sound << 4));
}
}
public int TextSpeed
{
get => Options & 0x7;
set
{
var new_speed = value;
if (new_speed > 7)
new_speed = 7;
if (new_speed < 0)
new_speed = 0;
Options = (byte)((Options & 0xF8) | new_speed);
}
}
public override uint Money
{
get => (uint)BigEndian.BCDToInt32(Data, Japanese ? 0x25EE : 0x25F3, 3);
set
{
value = (uint)Math.Min(value, MaxMoney);
BigEndian.Int32ToBCD((int)value, 3).CopyTo(Data, Japanese ? 0x25EE : 0x25F3);
}
}
public uint Coin
{
get => (uint)BigEndian.BCDToInt32(Data, Japanese ? 0x2846 : 0x2850, 2);
set
{
value = (ushort)Math.Min(value, MaxCoins);
BigEndian.Int32ToBCD((int)value, 2).CopyTo(Data, Japanese ? 0x2846 : 0x2850);
}
}
private readonly ushort[] LegalItems = Legal.Pouch_Items_RBY;
public override InventoryPouch[] Inventory
{
get
{
ushort[] legalItems = LegalItems;
InventoryPouch[] pouch =
{
new InventoryPouch(InventoryType.Items, legalItems, 99, Japanese ? 0x25C4 : 0x25C9, 20),
new InventoryPouch(InventoryType.PCItems, legalItems, 99, Japanese ? 0x27DC : 0x27E6, 50)
};
foreach (var p in pouch)
{
p.GetPouchG1(Data);
}
return pouch;
}
set
{
foreach (var p in value)
{
int ofs = 0;
for (int i = 0; i < p.Count; i++)
{
while (p.Items[ofs].Count == 0)
ofs++;
p.Items[i] = p.Items[ofs++];
}
while (ofs < p.Items.Length)
p.Items[ofs++] = new InventoryItem();
p.SetPouchG1(Data);
}
}
}
public override int GetDaycareSlotOffset(int loc, int slot)
{
return Daycare;
}
public override uint? GetDaycareEXP(int loc, int slot)
{
return null;
}
public override bool? IsDaycareOccupied(int loc, int slot)
{
if (loc == 0 && slot == 0)
return Data[Japanese ? 0x2CA7 : 0x2CF4] == 0x01;
else
return null;
}
public override void SetDaycareEXP(int loc, int slot, uint EXP)
{
}
public override void SetDaycareOccupied(int loc, int slot, bool occupied)
{
}
// Storage
public override int PartyCount
{
get => Data[Japanese ? 0x2ED5 : 0x2F2C];
protected set => Data[Japanese ? 0x2ED5 : 0x2F2C] = (byte)value;
}
public override int GetBoxOffset(int box)
{
return Data.Length - SIZE_RESERVED + box * SIZE_BOX;
}
public override int GetPartyOffset(int slot)
{
return Data.Length - SIZE_RESERVED + BoxCount * SIZE_BOX + slot * SIZE_STORED;
}
public override int CurrentBox
{
get => Data[Japanese ? 0x2842 : 0x284C] & 0x7F;
set => Data[Japanese ? 0x2842 : 0x284C] = (byte)((Data[Japanese ? 0x2842 : 0x284C] & 0x80) | (value & 0x7F));
}
public override string GetBoxName(int box)
{
return $"BOX {box + 1}";
}
public override void SetBoxName(int box, string value)
{
// Don't allow for custom box names
}
public override PKM GetPKM(byte[] data)
{
if (data.Length == SIZE_STORED)
return new PokemonList1(data, PokemonList1.CapacityType.Single, Japanese)[0];
return new PK1(data);
}
public override byte[] DecryptPKM(byte[] data)
{
return data;
}
// Pokédex
private int PokedexSeenOffset => Japanese ? 0x25B1 : 0x25B6;
private int PokedexCaughtOffset => Japanese ? 0x259E : 0x25A3;
protected override void SetDex(PKM pkm)
{
int species = pkm.Species;
if (!CanSetDex(species))
return;
SetCaught(pkm.Species, true);
SetSeen(pkm.Species, true);
}
private bool CanSetDex(int species)
{
if (species <= 0)
return false;
if (species > MaxSpeciesID)
return false;
if (Version == GameVersion.Invalid)
return false;
return true;
}
public override void SetSeen(int species, bool seen)
{
int bit = species - 1;
int ofs = bit >> 3;
SetFlag(PokedexSeenOffset + ofs, bit & 7, seen);
}
public override void SetCaught(int species, bool caught)
{
int bit = species - 1;
int ofs = bit >> 3;
SetFlag(PokedexCaughtOffset + ofs, bit & 7, caught);
}
public override bool GetSeen(int species)
{
int bit = species - 1;
int ofs = bit >> 3;
return GetFlag(PokedexSeenOffset + ofs, bit & 7);
}
public override bool GetCaught(int species)
{
int bit = species - 1;
int ofs = bit >> 3;
return GetFlag(PokedexCaughtOffset + ofs, bit & 7);
}
private const int SpawnFlagCount = 0xF0;
public bool[] EventSpawnFlags
{
get
{
// RB uses 0xE4 (0xE8) flags, Yellow uses 0xF0 flags. Just grab 0xF0
bool[] data = new bool[SpawnFlagCount];
for (int i = 0; i < data.Length; i++)
data[i] = GetFlag(ObjectSpawnFlags + i >> 3, i & 7);
return data;
}
set
{
if (value?.Length != SpawnFlagCount)
return;
for (int i = 0; i < value.Length; i++)
SetFlag(ObjectSpawnFlags + i >> 3, i & 7, value[i]);
}
}
public override string GetString(int Offset, int Length) => StringConverter.GetString1(Data, Offset, Length, Japanese);
public override byte[] SetString(string value, int maxLength, int PadToSize = 0, ushort PadWith = 0)
{
if (PadToSize == 0)
PadToSize = maxLength + 1;
return StringConverter.SetString1(value, maxLength, Japanese, PadToSize, PadWith);
}
}
}