2016-09-26 23:15:40 +00:00
|
|
|
|
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;
|
2022-01-03 05:35:59 +00:00
|
|
|
|
using static System.Buffers.Binary.BinaryPrimitives;
|
2016-09-26 23:15:40 +00:00
|
|
|
|
|
2017-01-08 07:54:09 +00:00
|
|
|
|
namespace PKHeX.Core
|
2016-09-26 23:15:40 +00:00
|
|
|
|
{
|
2017-10-24 06:12:58 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Generation 3 <see cref="SaveFile"/> object for Pokémon XD saves.
|
|
|
|
|
/// </summary>
|
2019-02-19 05:59:57 +00:00
|
|
|
|
public sealed class SAV3XD : SaveFile, IGCSaveFile
|
2016-09-26 23:15:40 +00:00
|
|
|
|
{
|
2020-12-05 13:36:23 +00:00
|
|
|
|
protected internal override string ShortSummary => $"{OT} ({Version}) #{SaveCount:0000}";
|
2019-02-19 05:59:57 +00:00
|
|
|
|
public override string Extension => this.GCExtension();
|
2022-02-10 00:48:55 +00:00
|
|
|
|
public SAV3GCMemoryCard? MemoryCard { get; init; }
|
2016-09-26 23:15:40 +00:00
|
|
|
|
|
2016-09-27 06:07:17 +00:00
|
|
|
|
private const int SLOT_SIZE = 0x28000;
|
2016-09-26 23:15:40 +00:00
|
|
|
|
private const int SLOT_START = 0x6000;
|
2016-09-27 06:07:17 +00:00
|
|
|
|
private const int SLOT_COUNT = 2;
|
2016-09-26 23:15:40 +00:00
|
|
|
|
|
2019-06-09 02:56:11 +00:00
|
|
|
|
private int SaveCount = -1;
|
|
|
|
|
private int SaveIndex = -1;
|
2019-11-28 22:00:55 +00:00
|
|
|
|
private int Trainer1;
|
2019-06-09 02:56:11 +00:00
|
|
|
|
private int Memo;
|
|
|
|
|
private int Shadow;
|
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
|
|
|
|
private readonly StrategyMemo StrategyMemo;
|
|
|
|
|
private readonly ShadowInfoTableXD ShadowInfo;
|
2018-09-07 03:35:55 +00:00
|
|
|
|
public int MaxShadowID => ShadowInfo.Count;
|
2019-09-03 02:30:58 +00:00
|
|
|
|
private int OFS_PouchHeldItem, OFS_PouchKeyItem, OFS_PouchBalls, OFS_PouchTMHM, OFS_PouchBerry, OFS_PouchCologne, OFS_PouchDisc;
|
2016-09-29 01:32:20 +00:00
|
|
|
|
private readonly int[] subOffsets = new int[16];
|
2022-02-10 00:48:55 +00:00
|
|
|
|
private readonly byte[] BAK;
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2019-06-09 02:56:11 +00:00
|
|
|
|
public SAV3XD() : base(SaveUtil.SIZE_G3XD)
|
2016-09-26 23:15:40 +00:00
|
|
|
|
{
|
2022-02-10 00:48:55 +00:00
|
|
|
|
BAK = Array.Empty<byte>();
|
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
|
|
|
|
// create fake objects
|
|
|
|
|
StrategyMemo = new StrategyMemo();
|
2021-09-04 01:16:09 +00:00
|
|
|
|
ShadowInfo = new ShadowInfoTableXD(false);
|
2019-06-09 02:56:11 +00:00
|
|
|
|
Initialize();
|
|
|
|
|
ClearBoxes();
|
|
|
|
|
}
|
2016-09-26 23:15:40 +00:00
|
|
|
|
|
2022-02-10 00:48:55 +00:00
|
|
|
|
public SAV3XD(byte[] data) : base(data)
|
2019-06-09 02:56:11 +00:00
|
|
|
|
{
|
2022-02-10 00:48:55 +00:00
|
|
|
|
BAK = data;
|
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
|
|
|
|
InitializeData(out StrategyMemo, out ShadowInfo);
|
2019-06-09 02:56:11 +00:00
|
|
|
|
Initialize();
|
|
|
|
|
}
|
2016-09-26 23:15:40 +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 => PersonalTable.RS;
|
|
|
|
|
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_XD;
|
|
|
|
|
|
|
|
|
|
private void InitializeData(out StrategyMemo memo, out ShadowInfoTableXD info)
|
2019-06-09 02:56:11 +00:00
|
|
|
|
{
|
2016-09-26 23:15:40 +00:00
|
|
|
|
// Scan all 3 save slots for the highest counter
|
|
|
|
|
for (int i = 0; i < SLOT_COUNT; i++)
|
|
|
|
|
{
|
2018-09-15 05:37:47 +00:00
|
|
|
|
int slotOffset = SLOT_START + (i * SLOT_SIZE);
|
2022-01-03 05:35:59 +00:00
|
|
|
|
int SaveCounter = ReadInt32BigEndian(Data.AsSpan(slotOffset + 4));
|
2016-09-26 23:15:40 +00:00
|
|
|
|
if (SaveCounter <= SaveCount)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
SaveCount = SaveCounter;
|
|
|
|
|
SaveIndex = i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Decrypt most recent save slot
|
2022-03-26 20:28:29 +00:00
|
|
|
|
Data = ReadSlot(Data, SaveIndex);
|
2016-09-26 23:15:40 +00:00
|
|
|
|
|
2016-09-27 06:07:17 +00:00
|
|
|
|
// Get Offset Info
|
2022-01-03 05:35:59 +00:00
|
|
|
|
Span<ushort> subLength = stackalloc ushort[16];
|
2016-09-27 06:07:17 +00:00
|
|
|
|
for (int i = 0; i < 16; i++)
|
|
|
|
|
{
|
2022-01-03 05:35:59 +00:00
|
|
|
|
subLength[i] = ReadUInt16BigEndian(Data.AsSpan(0x20 + (2 * i)));
|
|
|
|
|
subOffsets[i] = ReadUInt16BigEndian(Data.AsSpan(0x40 + (4 * i))) | ReadUInt16BigEndian(Data.AsSpan(0x40 + (4 * i) + 2)) << 16;
|
2016-09-27 06:07:17 +00:00
|
|
|
|
}
|
2019-06-09 02:56:11 +00:00
|
|
|
|
|
2016-09-27 06:07:17 +00:00
|
|
|
|
// Offsets are displaced by the 0xA8 savedata region
|
|
|
|
|
Trainer1 = subOffsets[1] + 0xA8;
|
|
|
|
|
Party = Trainer1 + 0x30;
|
|
|
|
|
Box = subOffsets[2] + 0xA8;
|
2019-10-19 03:42:03 +00:00
|
|
|
|
DaycareOffset = subOffsets[4] + 0xA8;
|
2016-10-01 02:05:35 +00:00
|
|
|
|
Memo = subOffsets[5] + 0xA8;
|
2016-10-01 23:52:29 +00:00
|
|
|
|
Shadow = subOffsets[7] + 0xA8;
|
2016-09-27 06:07:17 +00:00
|
|
|
|
// Purifier = subOffsets[14] + 0xA8;
|
|
|
|
|
|
2021-09-04 01:16:09 +00:00
|
|
|
|
bool jp = subLength[7] == 0x1E00;
|
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
|
|
|
|
memo = new StrategyMemo(Data, Memo, xd: true);
|
2022-01-08 01:20:32 +00:00
|
|
|
|
info = new ShadowInfoTableXD(Data.AsSpan(Shadow, subLength[7]), jp);
|
2019-06-09 02:56:11 +00:00
|
|
|
|
}
|
2016-09-27 06:07:17 +00:00
|
|
|
|
|
2022-03-26 20:28:29 +00:00
|
|
|
|
private static byte[] ReadSlot(Span<byte> data, int index)
|
|
|
|
|
{
|
|
|
|
|
int slotOffset = SLOT_START + (index * SLOT_SIZE);
|
|
|
|
|
var slot = data.Slice(slotOffset, SLOT_SIZE);
|
|
|
|
|
var result = new byte[SLOT_SIZE];
|
|
|
|
|
var destSpan = result.AsSpan();
|
|
|
|
|
|
|
|
|
|
// Decrypt Slot
|
|
|
|
|
Span<ushort> keys = stackalloc ushort[4];
|
|
|
|
|
GeniusCrypto.ReadKeys(slot.Slice(8, keys.Length * 2), keys);
|
|
|
|
|
Range r = new(0x10, 0x27FD8);
|
|
|
|
|
GeniusCrypto.Decrypt(slot[r], destSpan[r], keys); // body
|
|
|
|
|
slot[..0x10].CopyTo(destSpan[..0x10]); // checksums
|
|
|
|
|
slot[^0x18..].CopyTo(destSpan[^0x18..]); // tail end
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-09 02:56:11 +00:00
|
|
|
|
private void Initialize()
|
|
|
|
|
{
|
2016-09-27 06:07:17 +00:00
|
|
|
|
OFS_PouchHeldItem = Trainer1 + 0x4C8;
|
|
|
|
|
OFS_PouchKeyItem = Trainer1 + 0x540;
|
|
|
|
|
OFS_PouchBalls = Trainer1 + 0x5EC;
|
|
|
|
|
OFS_PouchTMHM = Trainer1 + 0x62C;
|
|
|
|
|
OFS_PouchBerry = Trainer1 + 0x72C;
|
|
|
|
|
OFS_PouchCologne = Trainer1 + 0x7E4;
|
|
|
|
|
OFS_PouchDisc = Trainer1 + 0x7F0;
|
2016-09-26 23:15:40 +00:00
|
|
|
|
|
2016-10-02 04:43:23 +00:00
|
|
|
|
// Since PartyCount is not stored in the save file,
|
|
|
|
|
// Count up how many party slots are active.
|
|
|
|
|
for (int i = 0; i < 6; i++)
|
2018-09-15 05:37:47 +00:00
|
|
|
|
{
|
2019-11-16 01:34:18 +00:00
|
|
|
|
if (GetPartySlot(Data, GetPartyOffset(i)).Species != 0)
|
2016-10-02 04:43:23 +00:00
|
|
|
|
PartyCount++;
|
2018-09-15 05:37:47 +00:00
|
|
|
|
}
|
2016-09-26 23:15:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-02-19 05:59:57 +00:00
|
|
|
|
protected override byte[] GetFinalData()
|
2019-03-08 02:05:55 +00:00
|
|
|
|
{
|
|
|
|
|
var newFile = GetInnerData();
|
|
|
|
|
|
|
|
|
|
// Return the gci if Memory Card is not being exported
|
2021-07-05 21:32:09 +00:00
|
|
|
|
if (MemoryCard is null)
|
2019-03-08 02:05:55 +00:00
|
|
|
|
return newFile;
|
|
|
|
|
|
2021-07-05 21:32:09 +00:00
|
|
|
|
MemoryCard.WriteSaveGameData(newFile);
|
|
|
|
|
return MemoryCard.Data;
|
2019-03-08 02:05:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private byte[] GetInnerData()
|
2016-09-26 23:15:40 +00:00
|
|
|
|
{
|
2016-10-01 02:05:35 +00:00
|
|
|
|
// Set Memo Back
|
2020-08-02 15:25:04 +00:00
|
|
|
|
StrategyMemo.Write(); // .CopyTo(Data, Memo);
|
2019-10-26 19:58:55 +00:00
|
|
|
|
ShadowInfo.Write().CopyTo(Data, Shadow);
|
2017-06-18 01:37:19 +00:00
|
|
|
|
SetChecksums();
|
2016-09-26 23:15:40 +00:00
|
|
|
|
|
2022-03-26 20:28:29 +00:00
|
|
|
|
// Put save slot back in original save data
|
|
|
|
|
var destOffset = SLOT_START + (SaveIndex * SLOT_SIZE);
|
|
|
|
|
byte[] dest = MemoryCard != null ? MemoryCard.ReadSaveGameData() : (byte[])BAK.Clone();
|
|
|
|
|
var destSpan = dest.AsSpan(destOffset, Data.Length);
|
|
|
|
|
|
2016-09-26 23:15:40 +00:00
|
|
|
|
// Get updated save slot data
|
2022-03-26 20:28:29 +00:00
|
|
|
|
Span<byte> slot = Data;
|
2022-01-03 05:35:59 +00:00
|
|
|
|
Span<ushort> keys = stackalloc ushort[4];
|
2022-03-26 20:28:29 +00:00
|
|
|
|
GeniusCrypto.ReadKeys(slot.Slice(8, keys.Length * 2), keys);
|
|
|
|
|
Range r = new(0x10, 0x27FD8);
|
|
|
|
|
GeniusCrypto.Encrypt(slot[r], destSpan[r], keys);
|
|
|
|
|
slot[..0x10].CopyTo(destSpan[..0x10]); // checksum/keys
|
|
|
|
|
slot[^0x18..].CopyTo(destSpan[^0x18..]); // tail end
|
|
|
|
|
return dest;
|
2016-09-26 23:15:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Configuration
|
2020-12-05 13:36:23 +00:00
|
|
|
|
protected override SaveFile CloneInternal()
|
2019-03-08 02:05:55 +00:00
|
|
|
|
{
|
|
|
|
|
var data = GetInnerData();
|
2022-02-10 00:48:55 +00:00
|
|
|
|
return new SAV3XD(data) { MemoryCard = MemoryCard };
|
2019-03-08 02:05:55 +00:00
|
|
|
|
}
|
2016-09-26 23:15:40 +00:00
|
|
|
|
|
2020-08-07 23:16:10 +00:00
|
|
|
|
protected override int SIZE_STORED => PokeCrypto.SIZE_3XSTORED;
|
2020-01-04 22:48:39 +00:00
|
|
|
|
protected override int SIZE_PARTY => PokeCrypto.SIZE_3XSTORED; // unused
|
2016-09-26 23:15:40 +00:00
|
|
|
|
public override PKM BlankPKM => new XK3();
|
2016-09-26 23:18:10 +00:00
|
|
|
|
public override Type PKMType => typeof(XK3);
|
2016-09-26 23:15:40 +00:00
|
|
|
|
|
2017-12-28 00:36:24 +00:00
|
|
|
|
public override int MaxMoveID => Legal.MaxMoveID_3;
|
2016-10-24 04:59:27 +00:00
|
|
|
|
public override int MaxSpeciesID => Legal.MaxSpeciesID_3;
|
2017-12-28 00:36:24 +00:00
|
|
|
|
public override int MaxAbilityID => Legal.MaxAbilityID_3;
|
|
|
|
|
public override int MaxBallID => Legal.MaxBallID_3;
|
|
|
|
|
public override int MaxItemID => Legal.MaxItemID_3_XD;
|
|
|
|
|
public override int MaxGameID => Legal.MaxGameID_3;
|
|
|
|
|
|
2016-09-26 23:15:40 +00:00
|
|
|
|
public override int MaxEV => 255;
|
|
|
|
|
public override int Generation => 3;
|
|
|
|
|
protected override int GiftCountMax => 1;
|
2017-04-10 04:53:53 +00:00
|
|
|
|
public override int OTLength => 7;
|
2016-09-26 23:15:40 +00:00
|
|
|
|
public override int NickLength => 10;
|
2021-04-28 18:58:26 +00:00
|
|
|
|
public override int MaxMoney => 9999999;
|
2016-09-26 23:15:40 +00:00
|
|
|
|
|
|
|
|
|
public override int BoxCount => 8;
|
|
|
|
|
|
2022-05-07 03:38:55 +00:00
|
|
|
|
public override bool IsPKMPresent(ReadOnlySpan<byte> data) => EntityDetection.IsPresentGC(data);
|
2018-03-18 23:22:21 +00:00
|
|
|
|
|
2016-09-26 23:15:40 +00:00
|
|
|
|
// Checksums
|
2017-06-18 01:37:19 +00:00
|
|
|
|
protected override void SetChecksums()
|
2016-09-26 23:15:40 +00:00
|
|
|
|
{
|
2017-06-18 01:37:19 +00:00
|
|
|
|
Data = SetChecksums(Data, subOffsets[0]);
|
2016-09-26 23:15:40 +00:00
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2016-09-29 01:32:20 +00:00
|
|
|
|
public override bool ChecksumsValid => !ChecksumInfo.Contains("Invalid");
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2016-09-26 23:15:40 +00:00
|
|
|
|
public override string ChecksumInfo
|
|
|
|
|
{
|
2016-09-29 01:32:20 +00:00
|
|
|
|
get
|
|
|
|
|
{
|
2017-06-18 01:37:19 +00:00
|
|
|
|
byte[] data = SetChecksums(Data, subOffsets[0]);
|
2016-09-29 01:32:20 +00:00
|
|
|
|
|
|
|
|
|
const int start = 0xA8; // 0x88 + 0x20
|
2022-01-03 05:35:59 +00:00
|
|
|
|
int oldHC = ReadInt32BigEndian(Data.AsSpan(start + subOffsets[0] + 0x38));
|
|
|
|
|
int newHC = ReadInt32BigEndian(data.AsSpan(start + subOffsets[0] + 0x38));
|
2016-09-29 01:32:20 +00:00
|
|
|
|
bool header = newHC == oldHC;
|
|
|
|
|
|
2021-05-10 08:01:45 +00:00
|
|
|
|
var oldCHK = Data.AsSpan(0x10, 0x10);
|
|
|
|
|
var newCHK = data.AsSpan(0x10, 0x10);
|
2016-09-29 01:32:20 +00:00
|
|
|
|
bool body = newCHK.SequenceEqual(oldCHK);
|
|
|
|
|
return $"Header Checksum {(header ? "V" : "Inv")}alid, Body Checksum {(body ? "V" : "Inv")}alid.";
|
|
|
|
|
}
|
2016-09-26 23:15:40 +00:00
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2017-06-18 01:37:19 +00:00
|
|
|
|
private static byte[] SetChecksums(byte[] input, int subOffset0)
|
2016-09-29 01:32:20 +00:00
|
|
|
|
{
|
2020-08-02 15:25:04 +00:00
|
|
|
|
if (input.Length != SLOT_SIZE)
|
2016-09-29 01:32:20 +00:00
|
|
|
|
throw new ArgumentException("Input should be a slot, not the entire save binary.");
|
|
|
|
|
|
|
|
|
|
byte[] data = (byte[])input.Clone();
|
|
|
|
|
const int start = 0xA8; // 0x88 + 0x20
|
2016-09-26 23:15:40 +00:00
|
|
|
|
|
2016-09-29 01:32:20 +00:00
|
|
|
|
// Header Checksum
|
|
|
|
|
int newHC = 0;
|
|
|
|
|
for (int i = 0; i < 8; i++)
|
|
|
|
|
newHC += data[i];
|
|
|
|
|
|
2022-01-03 05:35:59 +00:00
|
|
|
|
WriteInt32BigEndian(data.AsSpan(start + subOffset0 + 0x38), newHC);
|
2016-09-29 01:32:20 +00:00
|
|
|
|
|
|
|
|
|
// Body Checksum
|
2022-03-26 20:28:29 +00:00
|
|
|
|
data.AsSpan(0x10, 0x10).Clear(); // Clear old Checksum Data
|
2022-01-03 05:35:59 +00:00
|
|
|
|
Span<uint> checksum = stackalloc uint[4];
|
2016-09-29 01:32:20 +00:00
|
|
|
|
int dt = 8;
|
2021-05-10 08:01:45 +00:00
|
|
|
|
for (int i = 0; i < checksum.Length; i++)
|
2018-09-15 05:37:47 +00:00
|
|
|
|
{
|
2021-05-10 08:01:45 +00:00
|
|
|
|
uint val = 0;
|
|
|
|
|
var end = dt + 0x9FF4;
|
|
|
|
|
for (int j = dt; j < end; j += 2)
|
2022-01-03 05:35:59 +00:00
|
|
|
|
val += ReadUInt16BigEndian(data.AsSpan(j));
|
2021-05-10 08:01:45 +00:00
|
|
|
|
dt = end;
|
|
|
|
|
checksum[i] = val;
|
2018-09-15 05:37:47 +00:00
|
|
|
|
}
|
2016-09-29 01:32:20 +00:00
|
|
|
|
|
2022-01-03 05:35:59 +00:00
|
|
|
|
Span<ushort> newchks = stackalloc ushort[8];
|
2016-09-29 01:32:20 +00:00
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
|
|
|
{
|
|
|
|
|
newchks[i*2] = (ushort)(checksum[i] >> 16);
|
2018-09-15 05:37:47 +00:00
|
|
|
|
newchks[(i * 2) + 1] = (ushort)checksum[i];
|
2016-09-29 01:32:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < newchks.Length; i++)
|
2022-01-03 05:35:59 +00:00
|
|
|
|
{
|
|
|
|
|
var dest = data.AsSpan(0x10 + (2 * i));
|
|
|
|
|
var chk = newchks[newchks.Length - 1 - i];
|
|
|
|
|
WriteUInt16BigEndian(dest, chk);
|
|
|
|
|
}
|
2016-09-29 01:32:20 +00:00
|
|
|
|
|
|
|
|
|
return data;
|
|
|
|
|
}
|
2016-09-26 23:15:40 +00:00
|
|
|
|
// Trainer Info
|
2017-05-13 03:32:36 +00:00
|
|
|
|
public override GameVersion Version { get => GameVersion.XD; protected set { } }
|
2022-01-03 05:35:59 +00:00
|
|
|
|
public override string OT { get => GetString(Trainer1 + 0x00, 20); set => SetString(Data.AsSpan(Trainer1 + 0x00, 20), value.AsSpan(), 10, StringConverterOption.ClearZero); }
|
|
|
|
|
public override int SID { get => ReadUInt16BigEndian(Data.AsSpan(Trainer1 + 0x2C)); set => WriteUInt16BigEndian(Data.AsSpan(Trainer1 + 0x2C), (ushort)value); }
|
|
|
|
|
public override int TID { get => ReadUInt16BigEndian(Data.AsSpan(Trainer1 + 0x2E)); set => WriteUInt16BigEndian(Data.AsSpan(Trainer1 + 0x2E), (ushort)value); }
|
2017-05-13 03:32:36 +00:00
|
|
|
|
|
|
|
|
|
public override int Gender { get => Data[Trainer1 + 0x8E0]; set => Data[Trainer1 + 0x8E0] = (byte)value; }
|
2022-01-03 05:35:59 +00:00
|
|
|
|
public override uint Money { get => ReadUInt32BigEndian(Data.AsSpan(Trainer1 + 0x8E4)); set => WriteUInt32BigEndian(Data.AsSpan(Trainer1 + 0x8E4), value); }
|
|
|
|
|
public uint Coupons { get => ReadUInt32BigEndian(Data.AsSpan(Trainer1 + 0x8E8)); set => WriteUInt32BigEndian(Data.AsSpan(Trainer1 + 0x8E8), value); }
|
2016-09-26 23:15:40 +00:00
|
|
|
|
|
|
|
|
|
// Storage
|
2019-02-17 18:42:43 +00:00
|
|
|
|
public override int GetPartyOffset(int slot) => Party + (SIZE_STORED * slot);
|
|
|
|
|
private int GetBoxInfoOffset(int box) => Box + (((30 * SIZE_STORED) + 0x14) * box);
|
|
|
|
|
public override int GetBoxOffset(int box) => GetBoxInfoOffset(box) + 20;
|
|
|
|
|
public override string GetBoxName(int box) => GetString(GetBoxInfoOffset(box), 16);
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override void SetBoxName(int box, string value)
|
2016-09-26 23:15:40 +00:00
|
|
|
|
{
|
2022-01-03 05:35:59 +00:00
|
|
|
|
SetString(Data.AsSpan(GetBoxInfoOffset(box), 20), value.AsSpan(), 8, StringConverterOption.ClearZero);
|
2016-09-26 23:15:40 +00:00
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2019-03-30 02:43:33 +00:00
|
|
|
|
protected override PKM GetPKM(byte[] data)
|
2016-09-26 23:15:40 +00:00
|
|
|
|
{
|
2019-02-17 18:42:43 +00:00
|
|
|
|
if (data.Length != SIZE_STORED)
|
|
|
|
|
Array.Resize(ref data, SIZE_STORED);
|
|
|
|
|
return new XK3(data);
|
2016-09-26 23:15:40 +00:00
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2019-03-30 02:43:33 +00:00
|
|
|
|
protected override byte[] DecryptPKM(byte[] data) => data;
|
2019-11-16 01:34:18 +00:00
|
|
|
|
public override PKM GetPartySlot(byte[] data, int offset) => GetStoredSlot(data, offset);
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2019-11-16 01:34:18 +00:00
|
|
|
|
public override PKM GetStoredSlot(byte[] data, int offset)
|
2016-10-01 23:52:29 +00:00
|
|
|
|
{
|
|
|
|
|
// Get Shadow Data
|
2019-11-16 01:34:18 +00:00
|
|
|
|
var pk = (XK3)base.GetStoredSlot(data, offset);
|
2017-12-12 00:01:24 +00:00
|
|
|
|
if (pk.ShadowID > 0 && pk.ShadowID < ShadowInfo.Count)
|
2019-06-21 03:39:34 +00:00
|
|
|
|
pk.Purification = ShadowInfo[pk.ShadowID].Purification;
|
2016-10-01 23:52:29 +00:00
|
|
|
|
return pk;
|
|
|
|
|
}
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2021-02-09 04:26:53 +00:00
|
|
|
|
protected override void SetPKM(PKM pkm, bool isParty = false)
|
2016-10-01 23:52:29 +00:00
|
|
|
|
{
|
2020-12-22 01:12:39 +00:00
|
|
|
|
if (pkm is not XK3 pk)
|
2016-10-01 23:52:29 +00:00
|
|
|
|
return; // shouldn't ever hit
|
2018-05-12 15:13:39 +00:00
|
|
|
|
|
2016-12-27 05:26:45 +00:00
|
|
|
|
if (pk.CurrentRegion == 0)
|
|
|
|
|
pk.CurrentRegion = 2; // NTSC-U
|
|
|
|
|
if (pk.OriginalRegion == 0)
|
|
|
|
|
pk.OriginalRegion = 2; // NTSC-U
|
|
|
|
|
|
2016-10-01 23:52:29 +00:00
|
|
|
|
// Set Shadow Data back to save
|
|
|
|
|
if (pk.ShadowID <= 0 || pk.ShadowID >= ShadowInfo.Count)
|
|
|
|
|
return;
|
|
|
|
|
|
2019-06-21 03:39:34 +00:00
|
|
|
|
var entry = ShadowInfo[pk.ShadowID];
|
2016-10-01 23:52:29 +00:00
|
|
|
|
entry.Purification = pk.Purification;
|
|
|
|
|
entry.Species = pk.Species;
|
|
|
|
|
entry.PID = pk.PID;
|
2020-07-29 20:37:17 +00:00
|
|
|
|
entry.IV_HP = pk.IV_HP ;
|
|
|
|
|
entry.IV_ATK = pk.IV_ATK;
|
|
|
|
|
entry.IV_DEF = pk.IV_DEF;
|
|
|
|
|
entry.IV_SPA = pk.IV_SPA;
|
|
|
|
|
entry.IV_SPD = pk.IV_SPD;
|
|
|
|
|
entry.IV_SPE = pk.IV_SPE;
|
2016-10-01 23:52:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-06-18 01:37:19 +00:00
|
|
|
|
protected override void SetDex(PKM pkm)
|
2016-10-01 02:05:35 +00:00
|
|
|
|
{
|
2020-07-29 20:37:17 +00:00
|
|
|
|
/*
|
2021-06-05 00:29:55 +00:00
|
|
|
|
if (pkm.Species is 0 or > Legal.MaxSpeciesID_3)
|
|
|
|
|
return;
|
|
|
|
|
if (pkm.IsEgg)
|
|
|
|
|
return;
|
|
|
|
|
|
2016-10-01 02:05:35 +00:00
|
|
|
|
// Dex Related
|
|
|
|
|
var entry = StrategyMemo.GetEntry(pkm.Species);
|
|
|
|
|
if (entry.IsEmpty) // Populate
|
|
|
|
|
{
|
|
|
|
|
entry.Species = pkm.Species;
|
|
|
|
|
entry.PID = pkm.PID;
|
|
|
|
|
entry.TID = pkm.TID;
|
|
|
|
|
entry.SID = pkm.SID;
|
|
|
|
|
}
|
|
|
|
|
if (entry.Matches(pkm.Species, pkm.PID, pkm.TID, pkm.SID))
|
|
|
|
|
{
|
|
|
|
|
entry.Seen = true;
|
|
|
|
|
entry.Owned = true;
|
|
|
|
|
}
|
|
|
|
|
StrategyMemo.SetEntry(entry);
|
2020-07-29 20:37:17 +00:00
|
|
|
|
*/
|
2016-10-01 02:05:35 +00:00
|
|
|
|
}
|
2018-05-12 15:13:39 +00:00
|
|
|
|
|
2020-12-05 14:09:33 +00:00
|
|
|
|
public override IReadOnlyList<InventoryPouch> Inventory
|
2016-09-26 23:15:40 +00:00
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
InventoryPouch[] pouch =
|
|
|
|
|
{
|
2019-06-09 02:56:11 +00:00
|
|
|
|
new InventoryPouch3GC(InventoryType.Items, Legal.Pouch_Items_XD, 999, OFS_PouchHeldItem, 30), // 20 COLO, 30 XD
|
|
|
|
|
new InventoryPouch3GC(InventoryType.KeyItems, Legal.Pouch_Key_XD, 1, OFS_PouchKeyItem, 43),
|
|
|
|
|
new InventoryPouch3GC(InventoryType.Balls, Legal.Pouch_Ball_RS, 999, OFS_PouchBalls, 16),
|
|
|
|
|
new InventoryPouch3GC(InventoryType.TMHMs, Legal.Pouch_TM_RS, 999, OFS_PouchTMHM, 64),
|
|
|
|
|
new InventoryPouch3GC(InventoryType.Berries, Legal.Pouch_Berries_RS, 999, OFS_PouchBerry, 46),
|
|
|
|
|
new InventoryPouch3GC(InventoryType.Medicine, Legal.Pouch_Cologne_XD, 999, OFS_PouchCologne, 3), // Cologne
|
2021-08-20 20:49:20 +00:00
|
|
|
|
new InventoryPouch3GC(InventoryType.BattleItems, Legal.Pouch_Disc_XD, 1, OFS_PouchDisc, 60),
|
2016-09-26 23:15:40 +00:00
|
|
|
|
};
|
2019-03-30 23:10:14 +00:00
|
|
|
|
return pouch.LoadAll(Data);
|
2016-09-26 23:15:40 +00:00
|
|
|
|
}
|
2019-03-30 23:10:14 +00:00
|
|
|
|
set => value.SaveAll(Data);
|
2016-09-26 23:15:40 +00:00
|
|
|
|
}
|
2016-09-27 06:07:17 +00:00
|
|
|
|
|
|
|
|
|
// Daycare Structure:
|
|
|
|
|
// 0x00 -- Occupied
|
|
|
|
|
// 0x01 -- Deposited Level
|
|
|
|
|
// 0x02-0x03 -- unused?
|
|
|
|
|
// 0x04-0x07 -- Initial EXP
|
2019-10-19 03:42:03 +00:00
|
|
|
|
public override int GetDaycareSlotOffset(int loc, int slot) { return DaycareOffset + 8; }
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override uint? GetDaycareEXP(int loc, int slot) { return null; }
|
|
|
|
|
public override bool? IsDaycareOccupied(int loc, int slot) { return null; }
|
2019-02-02 18:19:41 +00:00
|
|
|
|
public override void SetDaycareEXP(int loc, int slot, uint EXP) { /* todo */ }
|
|
|
|
|
public override void SetDaycareOccupied(int loc, int slot, bool occupied) { /* todo */ }
|
2017-06-18 01:37:19 +00:00
|
|
|
|
|
2022-01-03 05:35:59 +00:00
|
|
|
|
public override string GetString(ReadOnlySpan<byte> data) => StringConverter3GC.GetString(data);
|
2018-09-15 05:37:47 +00:00
|
|
|
|
|
2022-01-03 05:35:59 +00:00
|
|
|
|
public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option)
|
2017-04-09 21:06:50 +00:00
|
|
|
|
{
|
2022-01-03 05:35:59 +00:00
|
|
|
|
return StringConverter3GC.SetString(destBuffer, value, maxLength, option);
|
2017-04-09 21:06:50 +00:00
|
|
|
|
}
|
2022-01-03 05:35:59 +00:00
|
|
|
|
}
|
2016-09-26 23:15:40 +00:00
|
|
|
|
}
|