PKHeX/PKHeX.Core/Saves/SAV2.cs

732 lines
27 KiB
C#
Raw Normal View History

using System;
PKHeX.Core Nullable cleanup (#2401) * Handle some nullable cases Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data) Make some classes have explicit constructors instead of { } initialization * Handle bits more obviously without null * Make SaveFile.BAK explicitly readonly again * merge constructor methods to have readonly fields * Inline some properties * More nullable handling * Rearrange box actions define straightforward classes to not have any null properties * Make extrabyte reference array immutable * Move tooltip creation to designer * Rearrange some logic to reduce nesting * Cache generated fonts * Split mystery gift album purpose * Handle more tooltips * Disallow null setters * Don't capture RNG object, only type enum * Unify learnset objects Now have readonly properties which are never null don't new() empty learnsets (>800 Learnset objects no longer created, total of 2400 objects since we also new() a move & level array) optimize g1/2 reader for early abort case * Access rewrite Initialize blocks in a separate object, and get via that object removes a couple hundred "might be null" warnings since blocks are now readonly getters some block references have been relocated, but interfaces should expose all that's needed put HoF6 controls in a groupbox, and disable * Readonly personal data * IVs non nullable for mystery gift * Explicitly initialize forced encounter moves * Make shadow objects readonly & non-null Put murkrow fix in binary data resource, instead of on startup * Assign dex form fetch on constructor Fixes legality parsing edge cases also handle cxd parse for valid; exit before exception is thrown in FrameGenerator * Remove unnecessary null checks * Keep empty value until init SetPouch sets the value to an actual one during load, but whatever * Readonly team lock data * Readonly locks Put locked encounters at bottom (favor unlocked) * Mail readonly data / offset Rearrange some call flow and pass defaults Add fake classes for SaveDataEditor mocking Always party size, no need to check twice in stat editor use a fake save file as initial data for savedata editor, and for gamedata (wow i found a usage) constrain eventwork editor to struct variable types (uint, int, etc), thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
using System.Collections.Generic;
namespace PKHeX.Core
{
/// <summary>
/// Generation 2 <see cref="SaveFile"/> object.
/// </summary>
public sealed class SAV2 : SaveFile, ILangDeviantSave
{
protected internal override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}";
public override string Extension => ".sav";
public int SaveRevision => Japanese ? 0 : !Korean ? 1 : 2;
public string SaveRevisionString => Japanese ? "J" : !Korean ? "U" : "K";
public bool Japanese { get; }
public bool Korean { get; }
2018-09-15 05:37:47 +00:00
PKHeX.Core Nullable cleanup (#2401) * Handle some nullable cases Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data) Make some classes have explicit constructors instead of { } initialization * Handle bits more obviously without null * Make SaveFile.BAK explicitly readonly again * merge constructor methods to have readonly fields * Inline some properties * More nullable handling * Rearrange box actions define straightforward classes to not have any null properties * Make extrabyte reference array immutable * Move tooltip creation to designer * Rearrange some logic to reduce nesting * Cache generated fonts * Split mystery gift album purpose * Handle more tooltips * Disallow null setters * Don't capture RNG object, only type enum * Unify learnset objects Now have readonly properties which are never null don't new() empty learnsets (>800 Learnset objects no longer created, total of 2400 objects since we also new() a move & level array) optimize g1/2 reader for early abort case * Access rewrite Initialize blocks in a separate object, and get via that object removes a couple hundred "might be null" warnings since blocks are now readonly getters some block references have been relocated, but interfaces should expose all that's needed put HoF6 controls in a groupbox, and disable * Readonly personal data * IVs non nullable for mystery gift * Explicitly initialize forced encounter moves * Make shadow objects readonly & non-null Put murkrow fix in binary data resource, instead of on startup * Assign dex form fetch on constructor Fixes legality parsing edge cases also handle cxd parse for valid; exit before exception is thrown in FrameGenerator * Remove unnecessary null checks * Keep empty value until init SetPouch sets the value to an actual one during load, but whatever * Readonly team lock data * Readonly locks Put locked encounters at bottom (favor unlocked) * Mail readonly data / offset Rearrange some call flow and pass defaults Add fake classes for SaveDataEditor mocking Always party size, no need to check twice in stat editor use a fake save file as initial data for savedata editor, and for gamedata (wow i found a usage) constrain eventwork editor to struct variable types (uint, int, etc), thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
public override PersonalTable Personal { get; }
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_GSC;
2021-08-06 05:39:38 +00:00
public override IReadOnlyList<string> PKMExtensions => Array.FindAll(PKM.Extensions, f =>
{
int gen = f[^1] - 0x30;
if (Korean)
return gen == 2;
return gen is 1 or 2;
2021-08-06 05:39:38 +00:00
});
2019-07-21 19:30:21 +00:00
public SAV2(GameVersion version = GameVersion.C, LanguageID lang = LanguageID.English) : base(SaveUtil.SIZE_G2RAW_J)
{
2019-07-21 19:30:21 +00:00
Version = version;
switch (lang)
{
case LanguageID.Japanese:
Japanese = true;
break;
case LanguageID.Korean:
Korean = true;
break;
// otherwise, both false
}
Offsets = new SAV2Offsets(this);
PKHeX.Core Nullable cleanup (#2401) * Handle some nullable cases Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data) Make some classes have explicit constructors instead of { } initialization * Handle bits more obviously without null * Make SaveFile.BAK explicitly readonly again * merge constructor methods to have readonly fields * Inline some properties * More nullable handling * Rearrange box actions define straightforward classes to not have any null properties * Make extrabyte reference array immutable * Move tooltip creation to designer * Rearrange some logic to reduce nesting * Cache generated fonts * Split mystery gift album purpose * Handle more tooltips * Disallow null setters * Don't capture RNG object, only type enum * Unify learnset objects Now have readonly properties which are never null don't new() empty learnsets (>800 Learnset objects no longer created, total of 2400 objects since we also new() a move & level array) optimize g1/2 reader for early abort case * Access rewrite Initialize blocks in a separate object, and get via that object removes a couple hundred "might be null" warnings since blocks are now readonly getters some block references have been relocated, but interfaces should expose all that's needed put HoF6 controls in a groupbox, and disable * Readonly personal data * IVs non nullable for mystery gift * Explicitly initialize forced encounter moves * Make shadow objects readonly & non-null Put murkrow fix in binary data resource, instead of on startup * Assign dex form fetch on constructor Fixes legality parsing edge cases also handle cxd parse for valid; exit before exception is thrown in FrameGenerator * Remove unnecessary null checks * Keep empty value until init SetPouch sets the value to an actual one during load, but whatever * Readonly team lock data * Readonly locks Put locked encounters at bottom (favor unlocked) * Mail readonly data / offset Rearrange some call flow and pass defaults Add fake classes for SaveDataEditor mocking Always party size, no need to check twice in stat editor use a fake save file as initial data for savedata editor, and for gamedata (wow i found a usage) constrain eventwork editor to struct variable types (uint, int, etc), thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
Personal = Version == GameVersion.GS ? PersonalTable.GS : PersonalTable.C;
Initialize();
ClearBoxes();
}
public SAV2(byte[] data, GameVersion versionOverride = GameVersion.Any) : base(data)
{
Version = versionOverride != GameVersion.Any ? versionOverride : SaveUtil.GetIsG2SAV(Data);
Japanese = SaveUtil.GetIsG2SAVJ(Data) != GameVersion.Invalid;
if (Version != GameVersion.C && !Japanese)
Korean = SaveUtil.GetIsG2SAVK(Data) != GameVersion.Invalid;
Offsets = new SAV2Offsets(this);
PKHeX.Core Nullable cleanup (#2401) * Handle some nullable cases Refactor MysteryGift into a second abstract class (backed by a byte array, or fake data) Make some classes have explicit constructors instead of { } initialization * Handle bits more obviously without null * Make SaveFile.BAK explicitly readonly again * merge constructor methods to have readonly fields * Inline some properties * More nullable handling * Rearrange box actions define straightforward classes to not have any null properties * Make extrabyte reference array immutable * Move tooltip creation to designer * Rearrange some logic to reduce nesting * Cache generated fonts * Split mystery gift album purpose * Handle more tooltips * Disallow null setters * Don't capture RNG object, only type enum * Unify learnset objects Now have readonly properties which are never null don't new() empty learnsets (>800 Learnset objects no longer created, total of 2400 objects since we also new() a move & level array) optimize g1/2 reader for early abort case * Access rewrite Initialize blocks in a separate object, and get via that object removes a couple hundred "might be null" warnings since blocks are now readonly getters some block references have been relocated, but interfaces should expose all that's needed put HoF6 controls in a groupbox, and disable * Readonly personal data * IVs non nullable for mystery gift * Explicitly initialize forced encounter moves * Make shadow objects readonly & non-null Put murkrow fix in binary data resource, instead of on startup * Assign dex form fetch on constructor Fixes legality parsing edge cases also handle cxd parse for valid; exit before exception is thrown in FrameGenerator * Remove unnecessary null checks * Keep empty value until init SetPouch sets the value to an actual one during load, but whatever * Readonly team lock data * Readonly locks Put locked encounters at bottom (favor unlocked) * Mail readonly data / offset Rearrange some call flow and pass defaults Add fake classes for SaveDataEditor mocking Always party size, no need to check twice in stat editor use a fake save file as initial data for savedata editor, and for gamedata (wow i found a usage) constrain eventwork editor to struct variable types (uint, int, etc), thus preventing null assignment errors
2019-10-17 01:47:31 +00:00
Personal = Version == GameVersion.GS ? PersonalTable.GS : PersonalTable.C;
Initialize();
}
private void Initialize()
{
Box = Data.Length;
Array.Resize(ref Data, Data.Length + SIZE_RESERVED);
Party = GetPartyOffset(0);
2018-05-12 15:13:39 +00:00
// Stash boxes after the save file's end.
int splitAtIndex = (Japanese ? 6 : 7);
int stored = SIZE_STOREDBOX;
int baseDest = Data.Length - SIZE_RESERVED;
var capacity = Japanese ? PokeListType.StoredJP : PokeListType.Stored;
for (int i = 0; i < BoxCount; i++)
{
int ofs = GetBoxRawDataOffset(i, splitAtIndex);
var box = GetData(ofs, stored);
var boxDest = baseDest + (i * SIZE_BOX);
var boxPL = new PokeList2(box, capacity, Japanese);
for (int j = 0; j < boxPL.Pokemon.Length; j++)
{
var dest = boxDest + (j * SIZE_STORED);
var pkDat = (j < boxPL.Count)
? new PokeList2(boxPL[j]).Write()
: new byte[PokeList2.GetDataLength(PokeListType.Single, Japanese)];
pkDat.CopyTo(Data, dest);
}
}
var current = GetData(Offsets.CurrentBox, stored);
var curBoxPL = new PokeList2(current, capacity, Japanese);
var curDest = baseDest + (CurrentBox * SIZE_BOX);
for (int i = 0; i < curBoxPL.Pokemon.Length; i++)
{
var dest = curDest + (i * SIZE_STORED);
var pkDat = i < curBoxPL.Count
? new PokeList2(curBoxPL[i]).Write()
: new byte[PokeList2.GetDataLength(PokeListType.Single, Japanese)];
pkDat.CopyTo(Data, dest);
}
var party = GetData(Offsets.Party, SIZE_STOREDPARTY);
var partyPL = new PokeList2(party, PokeListType.Party, Japanese);
for (int i = 0; i < partyPL.Pokemon.Length; i++)
{
var dest = GetPartyOffset(i);
var pkDat = i < partyPL.Count
? new PokeList2(partyPL[i]).Write()
: new byte[PokeList2.GetDataLength(PokeListType.Single, Japanese)];
pkDat.CopyTo(Data, dest);
}
if (Offsets.Daycare >= 0)
{
int offset = Offsets.Daycare;
DaycareFlags[0] = Data[offset];
offset++;
var pk1 = ReadPKMFromOffset(offset); // parent 1
var daycare1 = new PokeList2(pk1);
2018-09-15 05:37:47 +00:00
offset += (StringLength * 2) + 0x20; // nick/ot/pkm
DaycareFlags[1] = Data[offset];
offset++;
//byte steps = Data[offset];
offset++;
//byte BreedMotherOrNonDitto = Data[offset];
offset++;
var pk2 = ReadPKMFromOffset(offset); // parent 2
var daycare2 = new PokeList2(pk2);
offset += (StringLength * 2) + PokeCrypto.SIZE_2STORED; // nick/ot/pkm
var pk3 = ReadPKMFromOffset(offset); // egg!
pk3.IsEgg = true;
var daycare3 = new PokeList2(pk3);
daycare1.Write().CopyTo(Data, GetPartyOffset(7 + (0 * 2)));
daycare2.Write().CopyTo(Data, GetPartyOffset(7 + (1 * 2)));
daycare3.Write().CopyTo(Data, GetPartyOffset(7 + (2 * 2)));
DaycareOffset = Offsets.Daycare;
}
// Enable Pokedex editing
PokeDex = 0;
EventFlag = Offsets.EventFlag;
EventConst = Offsets.EventConst;
}
private PK2 ReadPKMFromOffset(int offset)
{
byte[] nick = new byte[StringLength];
byte[] ot = new byte[StringLength];
byte[] pk = new byte[PokeCrypto.SIZE_2STORED];
Array.Copy(Data, offset, nick, 0, nick.Length); offset += nick.Length;
Array.Copy(Data, offset, ot, 0, ot.Length); offset += ot.Length;
Array.Copy(Data, offset, pk, 0, pk.Length);
return new PK2(pk, jp: Japanese) { OT_Trash = ot, Nickname_Trash = nick };
}
private const int SIZE_RESERVED = 0x8000; // unpacked box data
private readonly SAV2Offsets Offsets;
private int GetBoxRawDataOffset(int i, int splitAtIndex)
{
if (i < splitAtIndex)
return 0x4000 + (i * (SIZE_STOREDBOX + 2));
return 0x6000 + ((i - splitAtIndex) * (SIZE_STOREDBOX + 2));
}
protected override byte[] GetFinalData()
{
int splitAtIndex = (Japanese ? 6 : 7);
for (int i = 0; i < BoxCount; i++)
{
var boxPL = new PokeList2(Japanese ? PokeListType.StoredJP : PokeListType.Stored, Japanese);
int slot = 0;
for (int j = 0; j < boxPL.Pokemon.Length; j++)
{
2018-09-15 05:37:47 +00:00
PK2 boxPK = (PK2) GetPKM(GetData(GetBoxOffset(i) + (j * SIZE_STORED), SIZE_STORED));
if (boxPK.Species > 0)
boxPL[slot++] = boxPK;
}
int src = GetBoxRawDataOffset(i, splitAtIndex);
boxPL.Write().CopyTo(Data, src);
if (i == CurrentBox)
boxPL.Write().CopyTo(Data, Offsets.CurrentBox);
}
var partyPL = new PokeList2(PokeListType.Party, Japanese);
int pSlot = 0;
for (int i = 0; i < 6; i++)
{
PK2 partyPK = (PK2)GetPKM(GetData(GetPartyOffset(i), SIZE_STORED));
if (partyPK.Species > 0)
partyPL[pSlot++] = partyPK;
}
partyPL.Write().CopyTo(Data, Offsets.Party);
SetChecksums();
if (Japanese)
{
switch (Version)
{
case GameVersion.GS: Array.Copy(Data, Offsets.Trainer1, Data, 0x7209, 0xC83); break;
case GameVersion.C: Array.Copy(Data, Offsets.Trainer1, Data, 0x7209, 0xADA); break;
}
}
else if (Korean)
{
// Calculate oddball checksum
ushort sum = 0;
ushort[][] offsetpairs =
{
new ushort[] {0x106B, 0x1533},
new ushort[] {0x1534, 0x1A12},
new ushort[] {0x1A13, 0x1C38},
new ushort[] {0x3DD8, 0x3F79},
new ushort[] {0x7E39, 0x7E6A},
};
foreach (ushort[] p in offsetpairs)
2018-09-15 05:37:47 +00:00
{
for (int i = p[0]; i < p[1]; i++)
sum += Data[i];
2018-09-15 05:37:47 +00:00
}
BitConverter.GetBytes(sum).CopyTo(Data, 0x7E6B);
}
else
2016-09-04 06:15:09 +00:00
{
switch (Version)
{
case GameVersion.GS:
Array.Copy(Data, 0x2009, Data, 0x15C7, 0x222F - 0x2009);
Array.Copy(Data, 0x222F, Data, 0x3D69, 0x23D9 - 0x222F);
Array.Copy(Data, 0x23D9, Data, 0x0C6B, 0x2856 - 0x23D9);
Array.Copy(Data, 0x2856, Data, 0x7E39, 0x288A - 0x2856);
Array.Copy(Data, 0x288A, Data, 0x10E8, 0x2D69 - 0x288A);
break;
case GameVersion.C:
Array.Copy(Data, 0x2009, Data, 0x1209, 0xB7A);
break;
}
2016-09-04 06:15:09 +00:00
}
byte[] outData = new byte[Data.Length - SIZE_RESERVED];
Array.Copy(Data, outData, outData.Length);
return outData;
}
// Configuration
protected override SaveFile CloneInternal() => new SAV2(Write(), Version);
protected override int SIZE_STORED => Japanese ? PokeCrypto.SIZE_2JLIST : PokeCrypto.SIZE_2ULIST;
protected override int SIZE_PARTY => Japanese ? PokeCrypto.SIZE_2JLIST : PokeCrypto.SIZE_2ULIST;
public override PKM BlankPKM => new PK2(jp: Japanese);
public override Type PKMType => typeof(PK2);
private int SIZE_BOX => BoxSlotCount*SIZE_STORED;
private int SIZE_STOREDBOX => PokeList2.GetDataLength(Japanese ? PokeListType.StoredJP : PokeListType.Stored, Japanese);
private int SIZE_STOREDPARTY => PokeList2.GetDataLength(PokeListType.Party, Japanese);
public override int MaxMoveID => Legal.MaxMoveID_2;
public override int MaxSpeciesID => Legal.MaxSpeciesID_2;
public override int MaxAbilityID => Legal.MaxAbilityID_2;
public override int MaxItemID => Legal.MaxItemID_2;
public override int MaxBallID => 0; // unused
public override int MaxGameID => 99; // unused
public override int MaxMoney => 999999;
public override int MaxCoins => 9999;
public override bool IsPKMPresent(byte[] data, int offset) => PKX.IsPKMPresentGB(data, offset);
protected override int EventConstMax => 0x100;
protected override int EventFlagMax => 2000;
public override int BoxCount => Japanese ? 9 : 14;
public override int MaxEV => 65535;
public override int MaxIV => 15;
public override int Generation => 2;
protected override int GiftCountMax => 0;
public override int OTLength => Japanese || Korean ? 5 : 7;
public override int NickLength => Japanese || Korean ? 5 : 10;
public override int BoxSlotCount => Japanese ? 30 : 20;
public override bool HasParty => true;
2017-12-27 23:52:29 +00:00
public override bool HasNamableBoxes => true;
private int StringLength => Japanese ? GBPKML.StringLengthJapanese : GBPKML.StringLengthNotJapan;
2018-05-12 15:13:39 +00:00
// Checksums
private ushort GetChecksum()
{
ushort sum = 0;
for (int i = Offsets.Trainer1; i <= Offsets.AccumulatedChecksumEnd; i++)
sum += Data[i];
return sum;
}
2018-09-15 05:37:47 +00:00
protected override void SetChecksums()
{
ushort accum = GetChecksum();
BitConverter.GetBytes(accum).CopyTo(Data, Offsets.OverallChecksumPosition);
BitConverter.GetBytes(accum).CopyTo(Data, Offsets.OverallChecksumPosition2);
}
2018-09-15 05:37:47 +00:00
public override bool ChecksumsValid
{
get
{
ushort accum = GetChecksum();
ushort actual = BitConverter.ToUInt16(Data, Offsets.OverallChecksumPosition);
return accum == actual;
}
}
2016-09-04 06:54:11 +00:00
public override string ChecksumInfo => ChecksumsValid ? "Checksum valid." : "Checksum invalid";
// Trainer Info
public override GameVersion Version { get; protected set; }
public override string OT
{
get => GetString(Offsets.Trainer1 + 2, (Korean ? 2 : 1) * OTLength);
set => SetString(value, (Korean ? 2 : 1) * OTLength).CopyTo(Data, Offsets.Trainer1 + 2);
}
2018-09-15 05:37:47 +00:00
public Span<byte> OT_Trash
{
get => Data.AsSpan(Offsets.Trainer1 + 2, StringLength);
set { if (value.Length == StringLength) value.CopyTo(Data.AsSpan(Offsets.Trainer1 + 2)); }
}
2018-09-15 05:37:47 +00:00
public override int Gender
{
get => Version == GameVersion.C ? Data[Offsets.Gender] : 0;
set
{
if (Version != GameVersion.C)
return;
Data[Offsets.Gender] = (byte) value;
Data[Offsets.Palette] = (byte) value;
}
}
2018-09-15 05:37:47 +00:00
public override int TID
{
get => BigEndian.ToUInt16(Data, Offsets.Trainer1);
set => BigEndian.GetBytes((ushort)value).CopyTo(Data, Offsets.Trainer1);
}
2018-09-15 05:37:47 +00:00
public override int SID { get => 0; set { } }
2018-09-15 05:37:47 +00:00
public override int PlayedHours
{
get => BigEndian.ToUInt16(Data, Offsets.TimePlayed);
set => BigEndian.GetBytes((ushort)value).CopyTo(Data, Offsets.TimePlayed);
}
2018-09-15 05:37:47 +00:00
public override int PlayedMinutes
{
get => Data[Offsets.TimePlayed + 2];
set => Data[Offsets.TimePlayed + 2] = (byte)value;
}
2018-09-15 05:37:47 +00:00
public override int PlayedSeconds
{
get => Data[Offsets.TimePlayed + 3];
set => Data[Offsets.TimePlayed + 3] = (byte)value;
}
public int Badges
{
get => BitConverter.ToUInt16(Data, Offsets.JohtoBadges);
set { if (value < 0) return; BitConverter.GetBytes((ushort)value).CopyTo(Data, Offsets.JohtoBadges); }
}
2018-09-15 05:37:47 +00:00
private byte Options
{
get => Data[Offsets.Options];
set => Data[Offsets.Options] = value;
}
2018-09-15 05:37:47 +00:00
public bool BattleEffects
{
get => (Options & 0x80) == 0;
set => Options = (byte)((Options & 0x7F) | (value ? 0 : 0x80));
}
2018-09-15 05:37:47 +00:00
public bool BattleStyleSwitch
{
get => (Options & 0x40) == 0;
set => Options = (byte)((Options & 0xBF) | (value ? 0 : 0x40));
}
2018-09-15 05:37:47 +00:00
public int Sound
{
get => (Options & 0x30) >> 4;
set => Options = (byte)((Options & 0xCF) | ((value != 0 ? 2 : 0) << 4)); // Stereo 2, Mono 0
}
2018-09-15 05:37:47 +00:00
public int TextSpeed
{
2017-12-27 23:52:29 +00:00
get => Options & 0x7;
set => Options = (byte)((Options & 0xF8) | (value & 7));
}
2018-09-15 05:37:47 +00:00
public bool SaveFileExists
{
get => Data[Offsets.Options + 1] == 1;
set => Data[Offsets.Options + 1] = value ? (byte)1 : (byte)0;
}
public int TextBoxFrame // 3bits
{
get => Data[Offsets.Options + 2] & 0b0000_0111;
set => Data[Offsets.Options + 2] = (byte)((Data[Offsets.Options + 2] & 0b1111_1000) | (value & 0b0000_0111));
}
public int TextBoxFlags { get => Data[Offsets.Options + 3]; set => Data[Offsets.Options + 3] = (byte)value; }
public bool TextBoxFrameDelay1 // bit 0
{
get => (TextBoxFlags & 0x01) == 0x01;
set => TextBoxFlags = (TextBoxFlags & ~0x01) | (value ? 0x01 : 0);
}
public bool TextBoxFrameDelayNone // bit 4
{
get => (TextBoxFlags & 0x10) == 0x10;
set => TextBoxFlags = (TextBoxFlags & ~0x10) | (value ? 0x10 : 0);
}
public byte GBPrinterBrightness { get => Data[Offsets.Options + 4]; set => Data[Offsets.Options + 4] = value; }
public bool MenuAccountOn
{
get => Data[Offsets.Options + 5] == 1;
set => Data[Offsets.Options + 5] = value ? (byte)1 : (byte)0;
}
public override uint Money
{
get => BigEndian.ToUInt32(Data, Offsets.Money - 1) & 0xFFFFFF;
set
{
byte[] data = BigEndian.GetBytes((uint) Math.Min(value, MaxMoney));
Array.Copy(data, 1, Data, Offsets.Money, 3);
}
}
2018-09-15 05:37:47 +00:00
public uint Coin
{
get => BigEndian.ToUInt16(Data, Offsets.Money + 7);
set
{
value = (ushort)Math.Min(value, MaxCoins);
BigEndian.GetBytes((ushort)value).CopyTo(Data, Offsets.Money + 7);
}
}
private static ushort[] LegalItems => Legal.Pouch_Items_GSC;
2019-11-16 21:15:09 +00:00
private ushort[] LegalKeyItems => Version == GameVersion.C? Legal.Pouch_Key_C : Legal.Pouch_Key_GS;
private static ushort[] LegalBalls => Legal.Pouch_Ball_GSC;
private static ushort[] LegalTMHMs => Legal.Pouch_TMHM_GSC;
2018-09-15 05:37:47 +00:00
public override IReadOnlyList<InventoryPouch> Inventory
{
get
{
InventoryPouch[] pouch =
{
new InventoryPouchGB(InventoryType.TMHMs, LegalTMHMs, 99, Offsets.PouchTMHM, 57),
new InventoryPouchGB(InventoryType.Items, LegalItems, 99, Offsets.PouchItem, 20),
new InventoryPouchGB(InventoryType.KeyItems, LegalKeyItems, 99, Offsets.PouchKey, 26),
new InventoryPouchGB(InventoryType.Balls, LegalBalls, 99, Offsets.PouchBall, 12),
new InventoryPouchGB(InventoryType.PCItems, ArrayUtil.ConcatAll(LegalItems, LegalKeyItems, LegalBalls, LegalTMHMs), 99, Offsets.PouchPC, 50)
};
2019-03-30 23:10:14 +00:00
return pouch.LoadAll(Data);
}
2019-03-30 23:10:14 +00:00
set => value.SaveAll(Data);
}
private readonly byte[] DaycareFlags = new byte[2];
2018-09-15 05:37:47 +00:00
public override int GetDaycareSlotOffset(int loc, int slot) => GetPartyOffset(7 + (slot * 2));
public override uint? GetDaycareEXP(int loc, int slot) => null;
public override bool? IsDaycareOccupied(int loc, int slot) => (DaycareFlags[slot] & 1) != 0;
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[Offsets.Party]; protected set => Data[Offsets.Party] = (byte)value;
}
2018-09-15 05:37:47 +00:00
public override int GetBoxOffset(int box)
{
2018-09-15 05:37:47 +00:00
return Data.Length - SIZE_RESERVED + (box * SIZE_BOX);
}
2018-09-15 05:37:47 +00:00
public override int GetPartyOffset(int slot)
{
2018-09-15 05:37:47 +00:00
return Data.Length - SIZE_RESERVED + (BoxCount * SIZE_BOX) + (slot * SIZE_STORED);
}
2018-09-15 05:37:47 +00:00
public override int CurrentBox
{
get => Data[Offsets.CurrentBoxIndex] & 0x7F; set => Data[Offsets.CurrentBoxIndex] = (byte)((Data[Offsets.OtherCurrentBox] & 0x80) | (value & 0x7F));
}
public override string GetBoxName(int box)
{
int len = Korean ? 17 : 9;
2018-09-15 05:37:47 +00:00
return GetString(Offsets.BoxNames + (box * len), len);
}
2018-09-15 05:37:47 +00:00
public override void SetBoxName(int box, string value)
{
2017-12-27 23:52:29 +00:00
int len = Korean ? 17 : 9;
var data = SetString(value, len, len, 0x50);
2018-09-15 05:37:47 +00:00
SetData(data, Offsets.BoxNames + (box * len));
}
2019-03-30 02:43:33 +00:00
protected override PKM GetPKM(byte[] data)
{
if (data.Length == SIZE_STORED)
return new PokeList2(data, PokeListType.Single, Japanese)[0];
return new PK2(data);
}
2018-09-15 05:37:47 +00:00
2019-03-30 02:43:33 +00:00
protected override byte[] DecryptPKM(byte[] data)
{
return data;
}
// Pokédex
protected override void SetDex(PKM pkm)
{
int species = pkm.Species;
2021-06-05 00:29:55 +00:00
if (species is 0 or > Legal.MaxSpeciesID_2)
return;
if (pkm.IsEgg)
return;
SetCaught(pkm.Species, true);
SetSeen(pkm.Species, true);
}
2018-09-15 05:37:47 +00:00
private void SetUnownFormFlags()
{
// Give all Unown caught to prevent a crash on pokedex view
for (int i = 1; i <= 26; i++)
Data[Offsets.PokedexSeen + 0x1F + i] = (byte)i;
if (UnownFirstSeen == 0) // Invalid
UnownFirstSeen = 1; // A
}
2018-09-15 05:37:47 +00:00
/// <summary>
/// Toggles the availability of Unown letter groups in the Wild
/// </summary>
/// <remarks>
/// Max value of 0x0F, 4 bitflags
/// 1 lsh 0: A, B, C, D, E, F, G, H, I, J, K
/// 1 lsh 1: L, M, N, O, P, Q, R
/// 1 lsh 2: S, T, U, V, W
/// 1 lsh 3: X, Y, Z
/// </remarks>
public int UnownUnlocked
{
get => Data[Offsets.PokedexSeen + 0x1F + 27];
set => Data[Offsets.PokedexSeen + 0x1F + 27] = (byte)value;
}
2018-09-15 05:37:47 +00:00
/// <summary>
/// Unlocks all Unown letters/forms in the wild.
/// </summary>
public void UnownUnlockAll() => UnownUnlocked = 0x0F; // all 4 bitflags
2018-09-15 05:37:47 +00:00
/// <summary>
/// Flag that determines if Unown Letters are available in the wild: A, B, C, D, E, F, G, H, I, J, K
/// </summary>
public bool UnownUnlocked0
{
get => (UnownUnlocked & 1 << 0) == 1 << 0;
2021-05-18 20:12:43 +00:00
set => UnownUnlocked = (UnownUnlocked & ~(1 << 0)) | ((value ? 1 : 0) << 0);
}
2018-09-15 05:37:47 +00:00
/// <summary>
/// Flag that determines if Unown Letters are available in the wild: L, M, N, O, P, Q, R
/// </summary>
public bool UnownUnlocked1
{
get => (UnownUnlocked & 1 << 1) == 1 << 1;
2021-05-18 20:12:43 +00:00
set => UnownUnlocked = (UnownUnlocked & ~(1 << 1)) | ((value ? 1 : 0) << 1);
}
2018-09-15 05:37:47 +00:00
/// <summary>
/// Flag that determines if Unown Letters are available in the wild: S, T, U, V, W
/// </summary>
public bool UnownUnlocked2
{
get => (UnownUnlocked & 1 << 2) == 1 << 2;
2021-05-18 20:12:43 +00:00
set => UnownUnlocked = (UnownUnlocked & ~(1 << 2)) | ((value ? 1 : 0) << 2);
}
2018-09-15 05:37:47 +00:00
/// <summary>
/// Flag that determines if Unown Letters are available in the wild: X, Y, Z
/// </summary>
public bool UnownUnlocked3
{
get => (UnownUnlocked & 1 << 3) == 1 << 3;
2021-05-18 20:12:43 +00:00
set => UnownUnlocked = (UnownUnlocked & ~(1 << 3)) | ((value ? 1 : 0) << 3);
}
2018-09-15 05:37:47 +00:00
/// <summary>
/// Chooses which Unown sprite to show in the regular Pokédex View
/// </summary>
public int UnownFirstSeen
{
get => Data[Offsets.PokedexSeen + 0x1F + 28];
set => Data[Offsets.PokedexSeen + 0x1F + 28] = (byte)value;
}
2018-09-15 05:37:47 +00:00
public override bool GetSeen(int species) => GetDexFlag(Offsets.PokedexSeen, species);
public override bool GetCaught(int species) => GetDexFlag(Offsets.PokedexCaught, species);
public override void SetSeen(int species, bool seen) => SetDexFlag(Offsets.PokedexSeen, species, seen);
public override void SetCaught(int species, bool caught)
{
SetDexFlag(Offsets.PokedexCaught, species, caught);
if (caught && species == (int)Species.Unown)
SetUnownFormFlags();
}
private bool GetDexFlag(int region, int species)
{
int bit = species - 1;
int ofs = bit >> 3;
return GetFlag(region + ofs, bit & 7);
}
2018-09-15 05:37:47 +00:00
private void SetDexFlag(int region, int species, bool value)
{
int bit = species - 1;
int ofs = bit >> 3;
SetFlag(region + ofs, bit & 7, value);
}
/// <summary>All Event Constant values for the save file</summary>
/// <remarks>These are all bytes</remarks>
public override ushort[] GetEventConsts()
{
ushort[] Constants = new ushort[EventConstMax];
for (int i = 0; i < Constants.Length; i++)
Constants[i] = Data[EventConst + i];
return Constants;
}
/// <summary>All Event Constant values for the save file</summary>
/// <remarks>These are all bytes</remarks>
public override void SetEventConsts(ushort[] value)
{
if (value.Length != EventConstMax)
return;
for (int i = 0; i < value.Length; i++)
Data[EventConst + i] = Math.Min(byte.MaxValue, (byte)value[i]);
}
2017-09-23 23:03:59 +00:00
// Misc
public ushort ResetKey => GetResetKey();
2018-09-15 05:37:47 +00:00
2017-09-23 23:03:59 +00:00
private ushort GetResetKey()
{
var val = (TID >> 8) + (TID & 0xFF) + ((Money >> 16) & 0xFF) + ((Money >> 8) & 0xFF) + (Money & 0xFF);
2021-08-06 05:39:38 +00:00
var ot = Data.AsSpan(Offsets.Trainer1 + 2, 5);
var sum = 0;
foreach (var b in ot)
{
if (b == StringConverter12.G1TerminatorCode)
break;
sum += b;
}
return (ushort)(val + sum);
2017-09-23 23:03:59 +00:00
}
2018-09-15 05:37:47 +00:00
/// <summary>
/// Sets the "Time Not Set" flag to the RTC Flag list.
/// </summary>
public void ResetRTC() => Data[Offsets.RTCFlags] |= 0x80;
public void UnlockAllDecorations()
{
for (int i = 676; i <= 721; i++)
SetEventFlag(i, true);
}
2017-09-23 23:03:59 +00:00
2018-12-05 06:00:57 +00:00
public override string GetString(byte[] data, int offset, int length)
{
if (Korean)
return StringConverter2KOR.GetString2KOR(data, offset, length);
return StringConverter12.GetString1(data, offset, length, Japanese);
}
2018-09-15 05:37:47 +00:00
public override byte[] SetString(string value, int maxLength, int PadToSize = 0, ushort PadWith = 0)
{
if (Korean)
return StringConverter2KOR.SetString2KOR(value, maxLength, PadToSize, (byte)PadWith);
return StringConverter12.SetString1(value, maxLength, Japanese, PadToSize, (byte)PadWith);
}
public bool IsGBMobileAvailable => Japanese && Version == GameVersion.C;
public bool IsGBMobileEnabled => Japanese && Enum.IsDefined(typeof(GBMobileCableColor), GBMobileCable);
public GBMobileCableColor GBMobileCable
{
get => (GBMobileCableColor) Data[0xE800];
set
{
Data[0xE800] = (byte)value;
Data[0x9000] = (byte)(0xFF - value);
}
}
}
public enum GBMobileCableColor : byte
{
None = 0,
Blue = 1,
Yellow = 2,
Green = 3,
Red = 4,
Debug = 0x81,
}
}