2016-06-20 04:22:43 +00:00
using System ;
2017-09-29 05:20:27 +00:00
using System.Collections.Generic ;
2017-07-02 02:43:51 +00:00
using System.Diagnostics ;
2016-06-20 04:22:43 +00:00
using System.Linq ;
2017-01-08 07:54:09 +00:00
namespace PKHeX.Core
2016-06-20 04:22:43 +00:00
{
2017-10-24 06:12:58 +00:00
/// <summary>
/// Base Class for Save Files
/// </summary>
2020-09-09 19:47:24 +00:00
public abstract class SaveFile : ITrainerInfo , IGameValueLimit , IBoxDetailWallpaper , IBoxDetailName
2016-06-20 04:22:43 +00:00
{
// General Object Properties
public byte [ ] Data ;
2020-12-05 13:36:23 +00:00
public SaveFileState State { get ; }
public SaveFileMetadata Metadata { get ; private set ; }
2019-09-03 02:30:58 +00:00
2020-09-30 19:44:50 +00:00
protected SaveFile ( byte [ ] data , byte [ ] bak , bool exportable = true )
2019-09-03 02:30:58 +00:00
{
Data = data ;
2020-12-05 13:36:23 +00:00
State = new SaveFileState ( bak , exportable ) ;
Metadata = new SaveFileMetadata ( this ) ;
2019-09-03 02:30:58 +00:00
}
2020-12-05 13:36:23 +00:00
protected SaveFile ( byte [ ] data , bool exportable = true ) : this ( data , ( byte [ ] ) data . Clone ( ) , exportable )
{
}
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
2020-12-05 13:36:23 +00:00
protected SaveFile ( int size = 0 )
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
{
2020-12-05 13:36:23 +00:00
Data = size = = 0 ? Array . Empty < byte > ( ) : new byte [ size ] ;
State = new SaveFileState ( Array . Empty < byte > ( ) , false ) ;
Metadata = new SaveFileMetadata ( 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
}
2020-12-05 13:36:23 +00:00
protected internal abstract string ShortSummary { get ; }
public abstract string Extension { get ; }
protected abstract SaveFile CloneInternal ( ) ;
public SaveFile Clone ( )
2019-09-03 02:30:58 +00:00
{
2020-12-05 13:36:23 +00:00
var sav = CloneInternal ( ) ;
sav . Metadata = Metadata ;
return sav ;
2019-09-03 02:30:58 +00:00
}
2018-07-16 00:48:31 +00:00
public virtual string PlayTimeString = > $"{PlayedHours}ː {PlayedMinutes:00}ː {PlayedSeconds:00}" ; // not :
2018-08-03 03:11:42 +00:00
2020-08-18 22:39:45 +00:00
public virtual IReadOnlyList < string > PKMExtensions = > PKM . Extensions . Where ( f = >
2017-01-05 06:22:50 +00:00
{
int gen = f . Last ( ) - 0x30 ;
2017-01-27 17:13:42 +00:00
return 3 < = gen & & gen < = Generation ;
2017-01-05 06:22:50 +00:00
} ) . ToArray ( ) ;
2017-06-18 01:37:19 +00:00
2016-06-20 04:22:43 +00:00
// General SAV Properties
2019-02-19 05:59:57 +00:00
public byte [ ] Write ( ExportFlags flags = ExportFlags . None )
2017-04-02 14:53:46 +00:00
{
2019-02-19 05:59:57 +00:00
byte [ ] data = GetFinalData ( ) ;
2020-12-05 13:36:23 +00:00
return Metadata . Finalize ( data , flags ) ;
2017-04-02 14:53:46 +00:00
}
2018-08-03 03:11:42 +00:00
2019-02-19 05:59:57 +00:00
protected virtual byte [ ] GetFinalData ( )
2016-06-20 04:22:43 +00:00
{
2017-06-18 01:37:19 +00:00
SetChecksums ( ) ;
2016-06-20 04:22:43 +00:00
return Data ;
}
2018-08-03 03:11:42 +00:00
2019-09-03 02:30:58 +00:00
#region Metadata & Limits
2017-09-29 05:20:27 +00:00
public virtual string MiscSaveInfo ( ) = > string . Empty ;
2016-06-28 06:03:57 +00:00
public virtual GameVersion Version { get ; protected set ; }
2016-06-20 04:22:43 +00:00
public abstract bool ChecksumsValid { get ; }
public abstract string ChecksumInfo { get ; }
public abstract int Generation { get ; }
2019-09-03 02:30:58 +00:00
#endregion
#region Savedata Container Handling
2019-11-16 01:34:18 +00:00
public byte [ ] GetData ( int offset , int length ) = > GetData ( Data , offset , length ) ;
2020-04-14 17:52:19 +00:00
protected static byte [ ] GetData ( byte [ ] data , int offset , int length ) = > data . Slice ( offset , length ) ;
2019-09-03 02:30:58 +00:00
public void SetData ( byte [ ] input , int offset ) = > SetData ( Data , input , offset ) ;
public void SetData ( byte [ ] dest , byte [ ] input , int offset )
{
input . CopyTo ( dest , offset ) ;
2020-12-05 13:36:23 +00:00
State . Edited = true ;
2019-09-03 02:30:58 +00:00
}
public abstract string GetString ( byte [ ] data , int offset , int length ) ;
public string GetString ( int offset , int length ) = > GetString ( Data , offset , length ) ;
public abstract byte [ ] SetString ( string value , int maxLength , int PadToSize = 0 , ushort PadWith = 0 ) ;
#endregion
public virtual void CopyChangesFrom ( SaveFile sav ) = > SetData ( sav . Data , 0 ) ;
// Offsets
2016-06-20 04:22:43 +00:00
2019-09-03 02:30:58 +00:00
#region Stored PKM Limits
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 abstract PersonalTable Personal { get ; }
2019-09-03 02:30:58 +00:00
public abstract int OTLength { get ; }
public abstract int NickLength { get ; }
2017-06-18 01:37:19 +00:00
public abstract int MaxMoveID { get ; }
public abstract int MaxSpeciesID { get ; }
public abstract int MaxAbilityID { get ; }
public abstract int MaxItemID { get ; }
public abstract int MaxBallID { get ; }
public abstract int MaxGameID { get ; }
2018-06-15 23:00:28 +00:00
public virtual int MinGameID = > 0 ;
2019-09-03 02:30:58 +00:00
#endregion
2016-06-21 01:58:06 +00:00
2019-09-03 02:30:58 +00:00
#region Event Work
2020-12-05 14:09:33 +00:00
public virtual bool HasEvents = > GetEventFlags ( ) . Length ! = 0 ;
2019-09-03 02:30:58 +00:00
protected virtual int EventFlagMax { get ; } = int . MinValue ;
protected virtual int EventConstMax { get ; } = int . MinValue ;
protected int EventFlag { get ; set ; } = int . MinValue ;
protected int EventConst { get ; set ; } = int . MinValue ;
2019-01-12 06:25:48 +00:00
2017-09-14 03:37:18 +00:00
/// <summary> All Event Flag values for the savegame </summary>
2020-12-05 14:09:33 +00:00
public bool [ ] GetEventFlags ( )
2016-06-20 04:22:43 +00:00
{
2020-12-05 14:09:33 +00:00
if ( EventFlagMax < 0 )
return Array . Empty < bool > ( ) ;
2016-06-20 04:22:43 +00:00
2020-12-05 14:09:33 +00:00
bool [ ] result = new bool [ EventFlagMax ] ;
for ( int i = 0 ; i < result . Length ; i + + )
result [ i ] = GetEventFlag ( i ) ;
return result ;
}
/// <summary> All Event Flag values for the savegame </summary>
public void SetEventFlags ( bool [ ] value )
{
if ( EventFlagMax < 0 )
return ;
if ( value . Length ! = EventFlagMax )
return ;
for ( int i = 0 ; i < value . Length ; i + + )
SetEventFlag ( i , value [ i ] ) ;
2016-06-20 04:22:43 +00:00
}
2018-08-03 03:11:42 +00:00
2017-09-14 03:37:18 +00:00
/// <summary> All Event Constant values for the savegame </summary>
2020-12-05 14:09:33 +00:00
public virtual ushort [ ] GetEventConsts ( )
2016-06-20 04:22:43 +00:00
{
2020-12-05 14:09:33 +00:00
if ( EventConstMax < = 0 | | Data . Length = = 0 )
return Array . Empty < ushort > ( ) ;
2016-06-20 04:22:43 +00:00
2020-12-05 14:09:33 +00:00
ushort [ ] Constants = new ushort [ EventConstMax ] ;
for ( int i = 0 ; i < Constants . Length ; i + + )
Constants [ i ] = BitConverter . ToUInt16 ( Data , EventConst + ( i * 2 ) ) ;
return Constants ;
}
2016-06-20 04:22:43 +00:00
2020-12-05 14:09:33 +00:00
/// <summary> All Event Constant values for the savegame </summary>
public virtual void SetEventConsts ( ushort [ ] value )
{
if ( EventConstMax < = 0 )
return ;
if ( value . Length ! = EventConstMax )
return ;
for ( int i = 0 ; i < value . Length ; i + + )
BitConverter . GetBytes ( value [ i ] ) . CopyTo ( Data , EventConst + ( i * 2 ) ) ;
2016-06-20 04:22:43 +00:00
}
2018-08-03 03:11:42 +00:00
2017-09-14 03:37:18 +00:00
/// <summary>
/// Gets the <see cref="bool"/> status of a desired Event Flag
/// </summary>
/// <param name="flagNumber">Event Flag to check</param>
/// <returns>Flag is Set (true) or not Set (false)</returns>
2018-09-27 04:05:06 +00:00
public virtual bool GetEventFlag ( int flagNumber )
2017-09-14 03:37:18 +00:00
{
2018-09-28 03:01:34 +00:00
if ( flagNumber > = EventFlagMax )
2017-09-14 03:37:18 +00:00
throw new ArgumentException ( $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagMax})." ) ;
2017-09-24 23:36:51 +00:00
return GetFlag ( EventFlag + ( flagNumber > > 3 ) , flagNumber & 7 ) ;
2017-09-14 03:37:18 +00:00
}
/// <summary>
/// Sets the <see cref="bool"/> status of a desired Event Flag
/// </summary>
/// <param name="flagNumber">Event Flag to check</param>
/// <param name="value">Event Flag status to set</param>
/// <remarks>Flag is Set (true) or not Set (false)</remarks>
2018-09-27 04:05:06 +00:00
public virtual void SetEventFlag ( int flagNumber , bool value )
2017-09-14 03:37:18 +00:00
{
2018-09-28 03:01:34 +00:00
if ( flagNumber > = EventFlagMax )
2017-09-14 03:37:18 +00:00
throw new ArgumentException ( $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagMax})." ) ;
2017-09-24 23:36:51 +00:00
SetFlag ( EventFlag + ( flagNumber > > 3 ) , flagNumber & 7 , value ) ;
}
2018-08-03 03:11:42 +00:00
2017-10-19 04:43:42 +00:00
/// <summary>
/// Gets the <see cref="bool"/> status of the Flag at the specified offset and index.
/// </summary>
/// <param name="offset">Offset to read from</param>
/// <param name="bitIndex">Bit index to read</param>
/// <returns>Flag is Set (true) or not Set (false)</returns>
2019-09-03 02:30:58 +00:00
public virtual bool GetFlag ( int offset , int bitIndex ) = > FlagUtil . GetFlag ( Data , offset , bitIndex ) ;
2018-08-03 03:11:42 +00:00
2017-10-19 04:43:42 +00:00
/// <summary>
/// Sets the <see cref="bool"/> status of the Flag at the specified offset and index.
/// </summary>
/// <param name="offset">Offset to read from</param>
/// <param name="bitIndex">Bit index to read</param>
/// <param name="value">Flag status to set</param>
/// <remarks>Flag is Set (true) or not Set (false)</remarks>
2019-09-03 02:30:58 +00:00
public virtual void SetFlag ( int offset , int bitIndex , bool value ) = > FlagUtil . SetFlag ( Data , offset , bitIndex , value ) ;
#endregion
2016-06-20 04:22:43 +00:00
2020-12-05 14:09:33 +00:00
public virtual IReadOnlyList < InventoryPouch > Inventory { get = > Array . Empty < InventoryPouch > ( ) ; set { } }
2019-09-03 02:30:58 +00:00
#region Mystery Gift
protected virtual int GiftCountMax { get ; } = int . MinValue ;
protected virtual int GiftFlagMax { get ; } = 0x800 ;
protected int WondercardData { get ; set ; } = int . MinValue ;
public bool HasWondercards = > WondercardData > - 1 ;
2019-02-02 07:26:43 +00:00
protected virtual bool [ ] MysteryGiftReceivedFlags { get = > Array . Empty < bool > ( ) ; set { } }
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
protected virtual DataMysteryGift [ ] MysteryGiftCards { get = > Array . Empty < DataMysteryGift > ( ) ; set { } }
2018-08-03 03:11:42 +00:00
2016-06-20 04:22:43 +00:00
public virtual MysteryGiftAlbum GiftAlbum
{
2020-12-22 01:17:56 +00:00
get = > new ( MysteryGiftCards , MysteryGiftReceivedFlags ) ;
2016-06-20 04:22:43 +00:00
set
{
MysteryGiftReceivedFlags = value . Flags ;
MysteryGiftCards = value . Gifts ;
}
}
2019-09-03 02:30:58 +00:00
#endregion
2016-06-20 04:22:43 +00:00
2019-09-03 02:30:58 +00:00
#region Player Info
2016-09-19 05:47:31 +00:00
public virtual int Gender { get ; set ; }
2017-05-13 03:32:36 +00:00
public virtual int Language { get = > - 1 ; set { } }
public virtual int Game { get = > - 1 ; set { } }
2018-04-28 18:06:58 +00:00
public virtual int TID { get ; set ; }
public virtual int SID { get ; set ; }
2017-05-19 00:51:08 +00:00
public virtual string OT { get ; set ; } = "PKHeX" ;
2016-09-19 05:47:31 +00:00
public virtual int PlayedHours { get ; set ; }
public virtual int PlayedMinutes { get ; set ; }
public virtual int PlayedSeconds { get ; set ; }
2019-03-05 23:55:26 +00:00
public virtual uint SecondsToStart { get ; set ; }
public virtual uint SecondsToFame { get ; set ; }
2016-09-19 05:47:31 +00:00
public virtual uint Money { get ; set ; }
2016-06-20 04:22:43 +00:00
public abstract int BoxCount { get ; }
2019-01-15 05:31:53 +00:00
public virtual int SlotCount = > BoxCount * BoxSlotCount ;
2017-05-13 03:32:36 +00:00
public virtual int MultiplayerSpriteID { get = > 0 ; set { } }
2019-06-20 00:49:50 +00:00
public int TrainerID7 { get = > ( int ) ( ( uint ) ( TID | ( SID < < 16 ) ) % 1000000 ) ; set = > SetID7 ( TrainerSID7 , value ) ; }
public int TrainerSID7 { get = > ( int ) ( ( uint ) ( TID | ( SID < < 16 ) ) / 1000000 ) ; set = > SetID7 ( value , TrainerID7 ) ; }
2019-09-03 02:30:58 +00:00
public virtual int MaxMoney = > 9999999 ;
public virtual int MaxCoins = > 9999 ;
2019-06-20 00:49:50 +00:00
public int DisplayTID
{
get = > Generation > = 7 ? TrainerID7 : TID ;
set { if ( Generation > = 7 ) TrainerID7 = value ; else TID = value ; }
}
public int DisplaySID
{
get = > Generation > = 7 ? TrainerSID7 : SID ;
set { if ( Generation > = 7 ) TrainerSID7 = value ; else SID = value ; }
}
2019-09-03 02:30:58 +00:00
#endregion
2019-06-20 00:49:50 +00:00
private void SetID7 ( int sid7 , int tid7 )
{
var oid = ( sid7 * 1_000_000 ) + ( tid7 % 1_000_000 ) ;
TID = ( ushort ) oid ;
SID = oid > > 16 ;
}
2019-09-03 02:30:58 +00:00
#region Party
public virtual int PartyCount { get ; protected set ; }
protected int Party { get ; set ; } = int . MinValue ;
public virtual bool HasParty = > Party > - 1 ;
public abstract int GetPartyOffset ( int slot ) ;
2017-06-22 03:24:42 +00:00
public bool IsPartyAllEggs ( params int [ ] except )
{
if ( ! HasParty )
return false ;
var party = PartyData ;
2019-09-03 02:30:58 +00:00
return party . Count = = party . Where ( t = > t . Species ! = 0 ) . Where ( ( t , i ) = > t . IsEgg | | except . Contains ( i ) ) . Count ( ) ;
}
public IList < PKM > PartyData
{
get
{
PKM [ ] data = new PKM [ PartyCount ] ;
for ( int i = 0 ; i < data . Length ; i + + )
2019-11-16 01:34:18 +00:00
data [ i ] = GetPartySlot ( PartyBuffer , GetPartyOffset ( i ) ) ;
2019-09-03 02:30:58 +00:00
return data ;
}
set
{
if ( value . Count = = 0 | | value . Count > 6 )
throw new ArgumentException ( $"Expected 1-6, got {value.Count}" ) ;
if ( value . Any ( pk = > PKMType ! = pk . GetType ( ) ) )
throw new ArgumentException ( $"Not {PKMType} array." ) ;
if ( value [ 0 ] . Species = = 0 )
Debug . WriteLine ( $"Empty first slot, received {value.Count}." ) ;
int ctr = 0 ;
foreach ( var exist in value . Where ( pk = > pk . Species ! = 0 ) )
2019-11-16 01:34:18 +00:00
SetPartySlot ( exist , PartyBuffer , GetPartyOffset ( ctr + + ) ) ;
2019-09-03 02:30:58 +00:00
for ( int i = ctr ; i < 6 ; i + + )
2019-11-16 01:34:18 +00:00
SetPartySlot ( BlankPKM , PartyBuffer , GetPartyOffset ( i ) ) ;
2019-09-03 02:30:58 +00:00
}
2017-06-22 03:24:42 +00:00
}
2019-09-03 02:30:58 +00:00
#endregion
2016-06-20 05:11:53 +00:00
2016-06-20 04:22:43 +00:00
// Varied Methods
2017-06-18 01:37:19 +00:00
protected abstract void SetChecksums ( ) ;
2016-06-20 04:22:43 +00:00
2019-09-03 02:30:58 +00:00
#region Daycare
2019-10-19 03:42:03 +00:00
public bool HasDaycare = > DaycareOffset > - 1 ;
protected int DaycareOffset { get ; set ; } = int . MinValue ;
2019-09-03 02:30:58 +00:00
public virtual int DaycareSeedSize { get ; } = 0 ;
2016-06-20 04:22:43 +00:00
public int DaycareIndex = 0 ;
2016-10-23 19:45:27 +00:00
public virtual bool HasTwoDaycares = > false ;
2017-10-18 06:19:34 +00:00
public virtual int GetDaycareSlotOffset ( int loc , int slot ) = > - 1 ;
public virtual uint? GetDaycareEXP ( int loc , int slot ) = > null ;
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 virtual string GetDaycareRNGSeed ( int loc ) = > string . Empty ;
2017-10-18 06:19:34 +00:00
public virtual bool? IsDaycareHasEgg ( int loc ) = > null ;
public virtual bool? IsDaycareOccupied ( int loc , int slot ) = > null ;
2016-09-19 05:47:31 +00:00
2017-06-18 01:37:19 +00:00
public virtual void SetDaycareEXP ( int loc , int slot , uint EXP ) { }
public virtual void SetDaycareRNGSeed ( int loc , string seed ) { }
public virtual void SetDaycareHasEgg ( int loc , bool hasEgg ) { }
public virtual void SetDaycareOccupied ( int loc , int slot , bool occupied ) { }
2019-09-03 02:30:58 +00:00
#endregion
2016-06-20 04:22:43 +00:00
2019-11-16 01:34:18 +00:00
public PKM GetPartySlotAtIndex ( int index ) = > GetPartySlot ( PartyBuffer , GetPartyOffset ( index ) ) ;
2018-08-03 03:11:42 +00:00
2019-09-03 02:30:58 +00:00
public void SetPartySlotAtIndex ( PKM pkm , int index , PKMImportSetting trade = PKMImportSetting . UseDefault , PKMImportSetting dex = PKMImportSetting . UseDefault )
2017-01-07 05:22:42 +00:00
{
2019-09-03 02:30:58 +00:00
// update party count
if ( index < = - 1 )
throw new ArgumentException ( "Invalid Party offset provided; unable to resolve party slot index." ) ;
2017-01-07 05:22:42 +00:00
2019-09-03 02:30:58 +00:00
if ( pkm . Species ! = 0 )
{
if ( PartyCount < = index )
PartyCount = index + 1 ;
}
else if ( PartyCount > index )
{
PartyCount = index ;
}
2017-01-07 05:22:42 +00:00
2019-09-03 02:30:58 +00:00
int offset = GetPartyOffset ( index ) ;
2019-11-16 01:34:18 +00:00
SetPartySlot ( pkm , PartyBuffer , offset , trade , dex ) ;
}
public void SetSlotFormatParty ( PKM pkm , byte [ ] data , int offset , PKMImportSetting trade = PKMImportSetting . UseDefault , PKMImportSetting dex = PKMImportSetting . UseDefault )
{
if ( pkm . GetType ( ) ! = PKMType )
throw new ArgumentException ( $"PKM Format needs to be {PKMType} when setting to this Save File." ) ;
UpdatePKM ( pkm , trade , dex ) ;
SetPartyValues ( pkm , isParty : true ) ;
WritePartySlot ( pkm , data , offset ) ;
2019-09-03 02:30:58 +00:00
}
2018-05-12 15:13:39 +00:00
2019-11-16 01:34:18 +00:00
public void SetPartySlot ( PKM pkm , byte [ ] data , int offset , PKMImportSetting trade = PKMImportSetting . UseDefault , PKMImportSetting dex = PKMImportSetting . UseDefault )
2019-09-03 02:30:58 +00:00
{
if ( pkm . GetType ( ) ! = PKMType )
throw new ArgumentException ( $"PKM Format needs to be {PKMType} when setting to this Save File." ) ;
UpdatePKM ( pkm , trade , dex ) ;
SetPartyValues ( pkm , isParty : true ) ;
2019-11-16 01:34:18 +00:00
WritePartySlot ( pkm , data , offset ) ;
2019-09-03 02:30:58 +00:00
}
2019-11-16 01:34:18 +00:00
public void SetSlotFormatStored ( PKM pkm , byte [ ] data , int offset , PKMImportSetting trade = PKMImportSetting . UseDefault , PKMImportSetting dex = PKMImportSetting . UseDefault )
2019-09-03 02:30:58 +00:00
{
if ( pkm . GetType ( ) ! = PKMType )
throw new ArgumentException ( $"PKM Format needs to be {PKMType} when setting to this Save File." ) ;
UpdatePKM ( pkm , trade , dex ) ;
SetPartyValues ( pkm , isParty : false ) ;
2019-11-16 01:34:18 +00:00
WriteSlotFormatStored ( pkm , data , offset ) ;
2019-09-03 02:30:58 +00:00
}
2019-11-16 01:34:18 +00:00
public void SetBoxSlot ( PKM pkm , byte [ ] data , int offset , PKMImportSetting trade = PKMImportSetting . UseDefault , PKMImportSetting dex = PKMImportSetting . UseDefault )
2019-09-03 02:30:58 +00:00
{
if ( pkm . GetType ( ) ! = PKMType )
throw new ArgumentException ( $"PKM Format needs to be {PKMType} when setting to this Save File." ) ;
UpdatePKM ( pkm , trade , dex ) ;
SetPartyValues ( pkm , isParty : false ) ;
2019-11-16 01:34:18 +00:00
WriteBoxSlot ( pkm , data , offset ) ;
2019-09-03 02:30:58 +00:00
}
public void DeletePartySlot ( int slot )
{
2019-10-19 04:10:39 +00:00
int newEmpty = PartyCount - 1 ;
if ( ( uint ) slot > newEmpty ) // beyond party range (or empty data already present)
2019-09-03 02:30:58 +00:00
return ;
// Move all party slots down one
2019-10-19 04:10:39 +00:00
for ( int i = slot + 1 ; i < = newEmpty ; i + + ) // Slide slots down
2017-01-07 05:22:42 +00:00
{
2019-10-19 04:10:39 +00:00
var current = GetPartySlotAtIndex ( i ) ;
SetPartySlotAtIndex ( current , i - 1 , PKMImportSetting . Skip , PKMImportSetting . Skip ) ;
2017-01-07 05:22:42 +00:00
}
2019-10-19 04:10:39 +00:00
SetPartySlotAtIndex ( BlankPKM , newEmpty , PKMImportSetting . Skip , PKMImportSetting . Skip ) ;
// PartyCount will automatically update via above call. Do not adjust.
2019-09-03 02:30:58 +00:00
}
2018-11-11 05:04:24 +00:00
2019-09-03 02:30:58 +00:00
#region Slot Storing
public static PKMImportSetting SetUpdateDex { protected get ; set ; } = PKMImportSetting . Update ;
public static PKMImportSetting SetUpdatePKM { protected get ; set ; } = PKMImportSetting . Update ;
public abstract Type PKMType { get ; }
protected abstract PKM GetPKM ( byte [ ] data ) ;
protected abstract byte [ ] DecryptPKM ( byte [ ] data ) ;
public abstract PKM BlankPKM { get ; }
2020-08-07 23:16:10 +00:00
protected abstract int SIZE_STORED { get ; }
2019-09-03 02:30:58 +00:00
protected abstract int SIZE_PARTY { get ; }
2020-08-07 23:16:10 +00:00
public virtual int SIZE_BOXSLOT = > SIZE_STORED ;
2019-09-03 02:30:58 +00:00
public abstract int MaxEV { get ; }
public virtual int MaxIV = > 31 ;
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 abstract IReadOnlyList < ushort > HeldItems { get ; }
2019-11-16 01:34:18 +00:00
protected virtual byte [ ] BoxBuffer = > Data ;
protected virtual byte [ ] PartyBuffer = > Data ;
2019-09-03 02:30:58 +00:00
public virtual bool IsPKMPresent ( byte [ ] data , int offset ) = > PKX . IsPKMPresent ( data , offset ) ;
public virtual PKM GetDecryptedPKM ( byte [ ] data ) = > GetPKM ( DecryptPKM ( data ) ) ;
2019-11-16 01:34:18 +00:00
public virtual PKM GetPartySlot ( byte [ ] data , int offset ) = > GetDecryptedPKM ( GetData ( data , offset , SIZE_PARTY ) ) ;
public virtual PKM GetStoredSlot ( byte [ ] data , int offset ) = > GetDecryptedPKM ( GetData ( data , offset , SIZE_STORED ) ) ;
public virtual PKM GetBoxSlot ( int offset ) = > GetStoredSlot ( BoxBuffer , offset ) ;
public virtual byte [ ] GetDataForFormatStored ( PKM pkm ) = > pkm . EncryptedBoxData ;
public virtual byte [ ] GetDataForFormatParty ( PKM pkm ) = > pkm . EncryptedPartyData ;
public virtual byte [ ] GetDataForParty ( PKM pkm ) = > pkm . EncryptedPartyData ;
public virtual byte [ ] GetDataForBox ( PKM pkm ) = > pkm . EncryptedBoxData ;
public virtual void WriteSlotFormatStored ( PKM pkm , byte [ ] data , int offset ) = > SetData ( data , GetDataForFormatStored ( pkm ) , offset ) ;
public virtual void WriteSlotFormatParty ( PKM pkm , byte [ ] data , int offset ) = > SetData ( data , GetDataForFormatParty ( pkm ) , offset ) ;
public virtual void WritePartySlot ( PKM pkm , byte [ ] data , int offset ) = > SetData ( data , GetDataForParty ( pkm ) , offset ) ;
public virtual void WriteBoxSlot ( PKM pkm , byte [ ] data , int offset ) = > SetData ( data , GetDataForBox ( pkm ) , offset ) ;
2019-09-03 02:30:58 +00:00
protected virtual void SetPartyValues ( PKM pkm , bool isParty )
{
if ( ! isParty )
return ;
if ( pkm . PartyStatsPresent ) // Stats already present
return ;
pkm . SetStats ( pkm . GetStats ( pkm . PersonalInfo ) ) ;
pkm . Stat_Level = pkm . CurrentLevel ;
2017-01-07 05:22:42 +00:00
}
2018-08-03 03:11:42 +00:00
2020-10-31 05:44:08 +00:00
/// <summary>
/// Conditions a <see cref="pkm"/> for this save file as if it was traded to it.
/// </summary>
/// <param name="pkm">Entity to adapt</param>
/// <param name="trade">Setting on whether or not to adapt</param>
public void AdaptPKM ( PKM pkm , PKMImportSetting trade = PKMImportSetting . UseDefault )
2017-01-07 05:22:42 +00:00
{
2019-09-03 02:30:58 +00:00
if ( GetTradeUpdateSetting ( trade ) )
SetPKM ( pkm ) ;
2020-10-31 05:44:08 +00:00
}
protected void UpdatePKM ( PKM pkm , PKMImportSetting trade , PKMImportSetting dex )
{
AdaptPKM ( pkm , trade ) ;
2019-09-03 02:30:58 +00:00
if ( GetDexUpdateSetting ( dex ) )
SetDex ( pkm ) ;
}
2017-01-07 05:22:42 +00:00
2019-09-03 02:30:58 +00:00
private static bool GetTradeUpdateSetting ( PKMImportSetting trade = PKMImportSetting . UseDefault )
{
if ( trade = = PKMImportSetting . UseDefault )
trade = SetUpdatePKM ;
return trade = = PKMImportSetting . Update ;
}
2017-01-07 05:22:42 +00:00
2019-09-03 02:30:58 +00:00
private static bool GetDexUpdateSetting ( PKMImportSetting trade = PKMImportSetting . UseDefault )
{
if ( trade = = PKMImportSetting . UseDefault )
trade = SetUpdateDex ;
return trade = = PKMImportSetting . Update ;
}
2017-01-07 05:22:42 +00:00
2019-09-03 02:30:58 +00:00
protected virtual void SetPKM ( PKM pkm ) { }
protected virtual void SetDex ( PKM pkm ) { }
#endregion
2017-01-07 05:22:42 +00:00
2019-09-03 02:30:58 +00:00
#region Pokédex
public int PokeDex { get ; protected set ; } = int . MinValue ;
public virtual bool HasPokeDex = > PokeDex > - 1 ;
public virtual bool GetSeen ( int species ) = > false ;
public virtual void SetSeen ( int species , bool seen ) { }
public virtual bool GetCaught ( int species ) = > false ;
public virtual void SetCaught ( int species , bool caught ) { }
public int SeenCount = > HasPokeDex ? Enumerable . Range ( 1 , MaxSpeciesID ) . Count ( GetSeen ) : 0 ;
public int CaughtCount = > HasPokeDex ? Enumerable . Range ( 1 , MaxSpeciesID ) . Count ( GetCaught ) : 0 ;
public decimal PercentSeen = > ( decimal ) SeenCount / MaxSpeciesID ;
public decimal PercentCaught = > ( decimal ) CaughtCount / MaxSpeciesID ;
#endregion
2018-11-11 05:04:24 +00:00
2019-09-03 02:30:58 +00:00
public bool HasBox = > Box > - 1 ;
public virtual int BoxSlotCount = > 30 ;
public virtual int BoxesUnlocked { get = > - 1 ; set { } }
public virtual byte [ ] BoxFlags { get = > Array . Empty < byte > ( ) ; set { } }
public virtual int CurrentBox { get ; set ; }
2018-11-11 05:04:24 +00:00
2019-09-03 02:30:58 +00:00
#region BoxData
protected int Box { get ; set ; } = int . MinValue ;
public IList < PKM > BoxData
{
get
{
PKM [ ] data = new PKM [ BoxCount * BoxSlotCount ] ;
for ( int box = 0 ; box < BoxCount ; box + + )
AddBoxData ( data , box , box * BoxSlotCount ) ;
return data ;
}
set
{
if ( value . Count ! = BoxCount * BoxSlotCount )
throw new ArgumentException ( $"Expected {BoxCount * BoxSlotCount}, got {value.Count}" ) ;
if ( value . Any ( pk = > PKMType ! = pk . GetType ( ) ) )
throw new ArgumentException ( $"Not {PKMType} array." ) ;
for ( int b = 0 ; b < BoxCount ; b + + )
SetBoxData ( value , b , b * BoxSlotCount ) ;
}
2017-01-07 05:22:42 +00:00
}
2018-08-03 03:11:42 +00:00
2019-09-03 02:30:58 +00:00
public int SetBoxData ( IList < PKM > value , int box , int index = 0 )
2017-10-18 06:19:34 +00:00
{
2019-09-03 02:30:58 +00:00
int skipped = 0 ;
for ( int slot = 0 ; slot < BoxSlotCount ; slot + + )
{
var pk = value [ index + slot ] ;
if ( ! pk . StorageFlags . IsOverwriteProtected ( ) )
SetBoxSlotAtIndex ( pk , box , slot ) ;
else
+ + skipped ;
}
return skipped ;
2017-10-18 06:19:34 +00:00
}
2016-10-29 18:32:21 +00:00
2019-09-03 02:30:58 +00:00
public PKM [ ] GetBoxData ( int box )
{
var data = new PKM [ BoxSlotCount ] ;
AddBoxData ( data , box , 0 ) ;
return data ;
}
2018-08-03 03:11:42 +00:00
2019-09-03 02:30:58 +00:00
public void AddBoxData ( IList < PKM > data , int box , int index )
2016-10-29 18:32:21 +00:00
{
2019-09-03 02:30:58 +00:00
var boxName = GetBoxName ( box ) ;
for ( int slot = 0 ; slot < BoxSlotCount ; slot + + )
{
int i = slot + index ;
data [ i ] = GetBoxSlotAtIndex ( box , slot ) ;
data [ i ] . Identifier = $"{boxName}:{slot + 1:00}" ;
data [ i ] . Box = box + 1 ;
data [ i ] . Slot = slot + 1 ;
data [ i ] . StorageFlags = GetSlotFlags ( box , slot ) ;
}
2016-10-29 18:32:21 +00:00
}
2019-09-03 02:30:58 +00:00
#endregion
2018-08-03 03:11:42 +00:00
2019-09-03 02:30:58 +00:00
#region Storage Health & Metadata
protected int [ ] TeamSlots = Array . Empty < int > ( ) ;
protected virtual IList < int > [ ] SlotPointers = > new [ ] { TeamSlots } ;
public virtual StorageSlotFlag GetSlotFlags ( int index ) = > StorageSlotFlag . None ;
public StorageSlotFlag GetSlotFlags ( int box , int slot ) = > GetSlotFlags ( ( box * BoxSlotCount ) + slot ) ;
public bool IsSlotLocked ( int box , int slot ) = > GetSlotFlags ( box , slot ) . HasFlagFast ( StorageSlotFlag . Locked ) ;
public bool IsSlotLocked ( int index ) = > GetSlotFlags ( index ) . HasFlagFast ( StorageSlotFlag . Locked ) ;
public bool IsSlotOverwriteProtected ( int box , int slot ) = > GetSlotFlags ( box , slot ) . IsOverwriteProtected ( ) ;
public bool IsSlotOverwriteProtected ( int index ) = > GetSlotFlags ( index ) . IsOverwriteProtected ( ) ;
public bool IsSlotOverwriteProtected ( PKM pkm ) = > GetSlotFlags ( pkm . Box , pkm . Slot ) . IsOverwriteProtected ( ) ;
private const int StorageFullValue = - 1 ;
public bool IsStorageFull = > NextOpenBoxSlot ( ) = = StorageFullValue ;
public int NextOpenBoxSlot ( int lastKnownOccupied = - 1 )
2016-10-29 18:32:21 +00:00
{
2019-11-16 01:34:18 +00:00
var storage = BoxBuffer ;
2019-09-03 02:30:58 +00:00
int count = BoxSlotCount * BoxCount ;
for ( int i = lastKnownOccupied + 1 ; i < count ; i + + )
{
int offset = GetBoxSlotOffset ( i ) ;
if ( ! IsPKMPresent ( storage , offset ) )
return i ;
}
return StorageFullValue ;
}
protected virtual bool IsSlotSwapProtected ( int box , int slot ) = > false ;
private bool IsRegionOverwriteProtected ( int min , int max )
{
return SlotPointers . SelectMany ( z = > z )
. Where ( z = > GetSlotFlags ( z ) . IsOverwriteProtected ( ) )
. Any ( slot = > ArrayUtil . WithinRange ( slot , min , max ) ) ;
}
public bool IsAnySlotLockedInBox ( int BoxStart , int BoxEnd )
{
return SlotPointers . SelectMany ( z = > z )
. Where ( z = > GetSlotFlags ( z ) . HasFlagFast ( StorageSlotFlag . Locked ) )
. Any ( slot = > ArrayUtil . WithinRange ( slot , BoxStart * BoxSlotCount , ( BoxEnd + 1 ) * BoxSlotCount ) ) ;
2016-10-29 18:32:21 +00:00
}
2019-09-03 02:30:58 +00:00
#endregion
#region Storage Offsets and Indexing
public abstract int GetBoxOffset ( int box ) ;
2020-08-07 23:16:10 +00:00
public int GetBoxSlotOffset ( int box , int slot ) = > GetBoxOffset ( box ) + ( slot * SIZE_BOXSLOT ) ;
2019-09-21 17:42:54 +00:00
public PKM GetBoxSlotAtIndex ( int box , int slot ) = > GetBoxSlot ( GetBoxSlotOffset ( box , slot ) ) ;
2016-09-19 05:47:31 +00:00
2019-09-03 02:30:58 +00:00
public void GetBoxSlotFromIndex ( int index , out int box , out int slot )
2018-05-20 03:48:03 +00:00
{
box = index / BoxSlotCount ;
if ( box > = BoxCount )
throw new ArgumentOutOfRangeException ( nameof ( index ) ) ;
slot = index % BoxSlotCount ;
}
public PKM GetBoxSlotAtIndex ( int index )
{
GetBoxSlotFromIndex ( index , out int box , out int slot ) ;
return GetBoxSlotAtIndex ( box , slot ) ;
}
2018-08-03 03:11:42 +00:00
2018-05-20 03:48:03 +00:00
public int GetBoxSlotOffset ( int index )
2016-06-20 04:22:43 +00:00
{
2018-05-20 03:48:03 +00:00
GetBoxSlotFromIndex ( index , out int box , out int slot ) ;
return GetBoxSlotOffset ( box , slot ) ;
2016-06-20 04:22:43 +00:00
}
2018-05-20 03:48:03 +00:00
2019-03-30 00:52:26 +00:00
public void SetBoxSlotAtIndex ( PKM pkm , int box , int slot , PKMImportSetting trade = PKMImportSetting . UseDefault , PKMImportSetting dex = PKMImportSetting . UseDefault )
2019-11-16 01:34:18 +00:00
= > SetBoxSlot ( pkm , BoxBuffer , GetBoxSlotOffset ( box , slot ) , trade , dex ) ;
2019-03-30 00:52:26 +00:00
public void SetBoxSlotAtIndex ( PKM pkm , int index , PKMImportSetting trade = PKMImportSetting . UseDefault , PKMImportSetting dex = PKMImportSetting . UseDefault )
2019-11-16 01:34:18 +00:00
= > SetBoxSlot ( pkm , BoxBuffer , GetBoxSlotOffset ( index ) , trade , dex ) ;
2019-09-03 02:30:58 +00:00
#endregion
2019-03-30 00:52:26 +00:00
2019-09-03 02:30:58 +00:00
#region Storage Manipulations
2019-09-13 02:01:06 +00:00
2019-09-03 02:30:58 +00:00
public bool MoveBox ( int box , int insertBeforeBox )
{
if ( box = = insertBeforeBox ) // no movement required
return true ;
if ( box > = BoxCount | | insertBeforeBox > = BoxCount ) // invalid box positions
return false ;
2018-05-20 03:48:03 +00:00
2019-11-16 01:34:18 +00:00
MoveBox ( box , insertBeforeBox , BoxBuffer ) ;
2019-09-03 02:30:58 +00:00
return true ;
}
2018-08-03 03:11:42 +00:00
2019-09-03 02:30:58 +00:00
private void MoveBox ( int box , int insertBeforeBox , byte [ ] storage )
2016-06-20 04:22:43 +00:00
{
2019-09-03 02:30:58 +00:00
int pos1 = BoxSlotCount * box ;
int pos2 = BoxSlotCount * insertBeforeBox ;
int min = Math . Min ( pos1 , pos2 ) ;
int max = Math . Max ( pos1 , pos2 ) ;
2016-06-20 04:22:43 +00:00
2020-08-07 23:16:10 +00:00
int len = BoxSlotCount * SIZE_BOXSLOT ;
2019-09-03 02:30:58 +00:00
byte [ ] boxdata = storage . Slice ( GetBoxOffset ( 0 ) , len * BoxCount ) ; // get all boxes
string [ ] boxNames = new int [ BoxCount ] . Select ( ( _ , i ) = > GetBoxName ( i ) ) . ToArray ( ) ;
int [ ] boxWallpapers = new int [ BoxCount ] . Select ( ( _ , i ) = > GetBoxWallpaper ( i ) ) . ToArray ( ) ;
2018-05-12 15:13:39 +00:00
2019-09-03 02:30:58 +00:00
min / = BoxSlotCount ;
max / = BoxSlotCount ;
// move all boxes within range to final spot
for ( int i = min , ctr = min ; i < max ; i + + )
2018-08-03 03:11:42 +00:00
{
2019-09-03 02:30:58 +00:00
int b = insertBeforeBox ; // if box is the moved box, move to insertion point, else move to unused box.
if ( i ! = box )
{
if ( insertBeforeBox = = ctr )
+ + ctr ;
b = ctr + + ;
}
2016-07-05 03:04:43 +00:00
2019-09-03 02:30:58 +00:00
Buffer . BlockCopy ( boxdata , len * i , storage , GetBoxOffset ( b ) , len ) ;
SetBoxName ( b , boxNames [ i ] ) ;
SetBoxWallpaper ( b , boxWallpapers [ i ] ) ;
}
2018-08-03 03:11:42 +00:00
2019-09-03 02:30:58 +00:00
SlotPointerUtil . UpdateMove ( box , insertBeforeBox , BoxSlotCount , SlotPointers ) ;
2019-03-30 00:52:26 +00:00
}
2019-09-03 02:30:58 +00:00
public bool SwapBox ( int box1 , int box2 )
2019-03-30 00:52:26 +00:00
{
2019-09-03 02:30:58 +00:00
if ( box1 = = box2 ) // no movement required
return true ;
if ( box1 > = BoxCount | | box2 > = BoxCount ) // invalid box positions
return false ;
2019-03-30 00:52:26 +00:00
2019-09-03 02:30:58 +00:00
if ( ! IsBoxAbleToMove ( box1 ) | | ! IsBoxAbleToMove ( box2 ) )
return false ;
2019-03-30 00:52:26 +00:00
2019-11-16 01:34:18 +00:00
SwapBox ( box1 , box2 , BoxBuffer ) ;
2019-09-03 02:30:58 +00:00
return true ;
2017-10-18 06:19:34 +00:00
}
2018-08-03 03:11:42 +00:00
2019-09-03 02:30:58 +00:00
private void SwapBox ( int box1 , int box2 , byte [ ] boxData )
2016-06-20 04:22:43 +00:00
{
2019-09-03 02:30:58 +00:00
int b1o = GetBoxOffset ( box1 ) ;
int b2o = GetBoxOffset ( box2 ) ;
2020-08-07 23:16:10 +00:00
int len = BoxSlotCount * SIZE_BOXSLOT ;
2019-09-03 02:30:58 +00:00
byte [ ] b1 = new byte [ len ] ;
Buffer . BlockCopy ( boxData , b1o , b1 , 0 , len ) ;
Buffer . BlockCopy ( boxData , b2o , boxData , b1o , len ) ;
Buffer . BlockCopy ( b1 , 0 , boxData , b2o , len ) ;
2018-08-03 03:11:42 +00:00
2019-09-03 02:30:58 +00:00
// Name
string b1n = GetBoxName ( box1 ) ;
SetBoxName ( box1 , GetBoxName ( box2 ) ) ;
SetBoxName ( box2 , b1n ) ;
2018-08-03 03:11:42 +00:00
2019-09-03 02:30:58 +00:00
// Wallpaper
int b1w = GetBoxWallpaper ( box1 ) ;
SetBoxWallpaper ( box1 , GetBoxWallpaper ( box2 ) ) ;
SetBoxWallpaper ( box2 , b1w ) ;
2018-09-25 02:43:59 +00:00
2019-09-03 02:30:58 +00:00
// Pointers
SlotPointerUtil . UpdateSwap ( box1 , box2 , BoxSlotCount , SlotPointers ) ;
2018-09-25 02:43:59 +00:00
}
2018-08-03 03:11:42 +00:00
2019-09-03 02:30:58 +00:00
private bool IsBoxAbleToMove ( int box )
2016-12-16 07:17:17 +00:00
{
2019-09-03 02:30:58 +00:00
int min = BoxSlotCount * box ;
int max = min + BoxSlotCount ;
return ! IsRegionOverwriteProtected ( min , max ) ;
2016-12-16 07:17:17 +00:00
}
2018-08-03 03:11:42 +00:00
2019-02-21 03:39:49 +00:00
/// <summary>
/// Sorts all <see cref="PKM"/> present within the range specified by <see cref="BoxStart"/> and <see cref="BoxEnd"/> with the provied <see cref="sortMethod"/>.
/// </summary>
/// <param name="BoxStart">Starting box; if not provided, will iterate from the first box.</param>
/// <param name="BoxEnd">Ending box; if not provided, will iterate to the end.</param>
/// <param name="sortMethod">Sorting logic required to order a <see cref="PKM"/> with respect to its peers; if not provided, will use a default sorting method.</param>
/// <param name="reverse">Reverse the sorting order</param>
/// <returns>Count of repositioned <see cref="PKM"/> slots.</returns>
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 int SortBoxes ( int BoxStart = 0 , int BoxEnd = - 1 , Func < IEnumerable < PKM > , IEnumerable < PKM > > ? sortMethod = null , bool reverse = false )
2016-06-20 04:22:43 +00:00
{
2017-09-29 05:20:27 +00:00
var BD = BoxData ;
int start = BoxSlotCount * BoxStart ;
var Section = BD . Skip ( start ) ;
2018-04-24 02:11:55 +00:00
if ( BoxEnd > = BoxStart )
Section = Section . Take ( BoxSlotCount * ( BoxEnd - BoxStart + 1 ) ) ;
2016-06-20 04:22:43 +00:00
2019-04-30 00:20:16 +00:00
Func < PKM , bool > skip = IsSlotOverwriteProtected ;
Section = Section . Where ( z = > ! skip ( z ) ) ;
2018-04-21 22:07:58 +00:00
var Sorted = ( sortMethod ? ? PKMSorting . OrderBySpecies ) ( Section ) ;
2018-05-01 04:39:12 +00:00
if ( reverse )
Sorted = Sorted . ReverseSort ( ) ;
2016-06-20 04:22:43 +00:00
2018-11-11 05:04:24 +00:00
var result = Sorted . ToArray ( ) ;
var boxclone = new PKM [ BD . Count ] ;
BD . CopyTo ( boxclone , 0 ) ;
2019-02-21 01:59:54 +00:00
int count = result . CopyTo ( boxclone , skip , start ) ;
2018-11-11 05:04:24 +00:00
SlotPointerUtil . UpdateRepointFrom ( boxclone , BD , 0 , SlotPointers ) ;
2019-01-09 02:31:14 +00:00
2020-04-14 17:46:21 +00:00
for ( int i = 0 ; i < boxclone . Length ; i + + )
{
var pk = boxclone [ i ] ;
SetBoxSlotAtIndex ( pk , i , PKMImportSetting . Skip , PKMImportSetting . Skip ) ;
}
2019-02-21 01:59:54 +00:00
return count ;
2016-06-20 04:22:43 +00:00
}
2018-08-03 03:11:42 +00:00
2019-09-03 02:30:58 +00:00
/// <summary>
/// Compresses the <see cref="BoxData"/> by pulling out the empty storage slots and putting them at the end, retaining all existing data.
/// </summary>
/// <param name="storedCount">Count of actual <see cref="PKM"/> stored.</param>
/// <param name="slotPointers">Important slot pointers that need to be repointed if a slot moves.</param>
/// <returns>True if <see cref="BoxData"/> was updated, false if no update done.</returns>
2019-11-16 01:34:18 +00:00
public bool CompressStorage ( out int storedCount , params IList < int > [ ] slotPointers ) = > this . CompressStorage ( BoxBuffer , out storedCount , slotPointers ) ;
2019-09-03 02:30:58 +00:00
2019-02-21 03:39:49 +00:00
/// <summary>
/// Removes all <see cref="PKM"/> present within the range specified by <see cref="BoxStart"/> and <see cref="BoxEnd"/> if the provied <see cref="deleteCriteria"/> is satisfied.
/// </summary>
/// <param name="BoxStart">Starting box; if not provided, will iterate from the first box.</param>
/// <param name="BoxEnd">Ending box; if not provided, will iterate to the end.</param>
/// <param name="deleteCriteria">Criteria required to be satisfied for a <see cref="PKM"/> to be deleted; if not provided, will clear if possible.</param>
/// <returns>Count of deleted <see cref="PKM"/> slots.</returns>
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 int ClearBoxes ( int BoxStart = 0 , int BoxEnd = - 1 , Func < PKM , bool > ? deleteCriteria = null )
2016-06-20 04:22:43 +00:00
{
2019-11-16 01:34:18 +00:00
var storage = BoxBuffer ;
2019-09-03 02:30:58 +00:00
2016-06-20 04:22:43 +00:00
if ( BoxEnd < 0 )
2018-04-24 02:11:55 +00:00
BoxEnd = BoxCount - 1 ;
2017-03-04 17:38:39 +00:00
2019-11-16 05:01:22 +00:00
var blank = GetDataForBox ( BlankPKM ) ;
2019-02-21 01:59:54 +00:00
int deleted = 0 ;
2018-04-24 02:11:55 +00:00
for ( int i = BoxStart ; i < = BoxEnd ; i + + )
2016-06-20 04:22:43 +00:00
{
2016-08-28 10:18:22 +00:00
for ( int p = 0 ; p < BoxSlotCount ; p + + )
2018-05-01 00:29:13 +00:00
{
2018-09-25 02:43:59 +00:00
if ( IsSlotOverwriteProtected ( i , p ) )
2018-05-18 05:43:07 +00:00
continue ;
2019-02-02 07:08:03 +00:00
var ofs = GetBoxSlotOffset ( i , p ) ;
2019-09-03 02:30:58 +00:00
if ( ! IsPKMPresent ( storage , ofs ) )
2019-02-21 01:59:54 +00:00
continue ;
2018-05-01 00:29:13 +00:00
if ( deleteCriteria ! = null )
{
2019-09-21 17:42:54 +00:00
var pk = GetBoxSlotAtIndex ( i , p ) ;
2018-05-01 00:29:13 +00:00
if ( ! deleteCriteria ( pk ) )
continue ;
}
2019-09-13 02:01:06 +00:00
SetData ( storage , blank , ofs ) ;
2019-02-21 01:59:54 +00:00
+ + deleted ;
2018-05-01 00:29:13 +00:00
}
2016-06-20 04:22:43 +00:00
}
2019-02-21 01:59:54 +00:00
return deleted ;
2016-06-20 04:22:43 +00:00
}
2018-08-03 03:11:42 +00:00
2019-02-21 03:39:49 +00:00
/// <summary>
/// Modifies all <see cref="PKM"/> present within the range specified by <see cref="BoxStart"/> and <see cref="BoxEnd"/> with the modification routine provided by <see cref="action"/>.
/// </summary>
/// <param name="action">Modification to perform on a <see cref="PKM"/></param>
/// <param name="BoxStart">Starting box; if not provided, will iterate from the first box.</param>
/// <param name="BoxEnd">Ending box; if not provided, will iterate to the end.</param>
/// <returns>Count of modified <see cref="PKM"/> slots.</returns>
2019-02-21 01:59:54 +00:00
public int ModifyBoxes ( Action < PKM > action , int BoxStart = 0 , int BoxEnd = - 1 )
2018-04-22 19:43:18 +00:00
{
if ( BoxEnd < 0 )
2018-04-24 02:11:55 +00:00
BoxEnd = BoxCount - 1 ;
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
2019-11-16 01:34:18 +00:00
var storage = BoxBuffer ;
2019-02-21 01:59:54 +00:00
int modified = 0 ;
2018-04-24 02:11:55 +00:00
for ( int b = BoxStart ; b < = BoxEnd ; b + + )
2018-04-22 19:43:18 +00:00
{
2018-08-03 03:11:42 +00:00
for ( int s = 0 ; s < BoxSlotCount ; s + + )
{
2018-09-25 02:43:59 +00:00
if ( IsSlotOverwriteProtected ( b , s ) )
2018-08-03 03:11:42 +00:00
continue ;
2019-02-21 03:39:49 +00:00
var ofs = GetBoxSlotOffset ( b , s ) ;
2019-09-03 02:30:58 +00:00
if ( ! IsPKMPresent ( storage , ofs ) )
2019-02-21 03:39:49 +00:00
continue ;
2019-09-03 02:30:58 +00:00
var pk = GetBoxSlotAtIndex ( b , s ) ;
2019-02-21 03:39:49 +00:00
action ( pk ) ;
2019-02-21 01:59:54 +00:00
+ + modified ;
2019-11-16 01:34:18 +00:00
SetBoxSlot ( pk , storage , ofs , PKMImportSetting . Skip , PKMImportSetting . Skip ) ;
2018-08-03 03:11:42 +00:00
}
2018-04-22 19:43:18 +00:00
}
2019-02-21 01:59:54 +00:00
return modified ;
2018-04-22 19:43:18 +00:00
}
2019-09-03 02:30:58 +00:00
#endregion
#region Storage Name & Decoration
public virtual bool HasBoxWallpapers = > GetBoxWallpaperOffset ( 0 ) > - 1 ;
public virtual bool HasNamableBoxes = > HasBoxWallpapers ;
public abstract string GetBoxName ( int box ) ;
public abstract void SetBoxName ( int box , string value ) ;
protected virtual int GetBoxWallpaperOffset ( int box ) = > - 1 ;
public virtual int GetBoxWallpaper ( int box )
{
int offset = GetBoxWallpaperOffset ( box ) ;
if ( offset < 0 | | box > BoxCount )
return box ;
return Data [ offset ] ;
}
public virtual void SetBoxWallpaper ( int box , int value )
{
int offset = GetBoxWallpaperOffset ( box ) ;
if ( offset < 0 | | box > BoxCount )
return ;
Data [ offset ] = ( byte ) value ;
}
#endregion
2016-06-20 04:22:43 +00:00
2019-09-03 02:30:58 +00:00
#region Box Binaries
2019-11-17 22:04:04 +00:00
public byte [ ] GetPCBinary ( ) = > BoxData . SelectMany ( GetDataForBox ) . ToArray ( ) ;
public byte [ ] GetBoxBinary ( int box ) = > GetBoxData ( box ) . SelectMany ( GetDataForBox ) . ToArray ( ) ;
2018-08-03 03:11:42 +00:00
2017-06-18 01:37:19 +00:00
public bool SetPCBinary ( byte [ ] data )
2016-06-20 04:22:43 +00:00
{
2018-12-05 06:59:28 +00:00
if ( IsRegionOverwriteProtected ( 0 , SlotCount ) )
2016-12-16 07:17:17 +00:00
return false ;
2016-06-20 04:22:43 +00:00
2019-11-16 05:01:22 +00:00
int expectLength = SlotCount * GetDataForBox ( BlankPKM ) . Length ;
2019-09-03 02:30:58 +00:00
return SetConcatenatedBinary ( data , expectLength ) ;
2016-06-20 04:22:43 +00:00
}
2018-08-03 03:11:42 +00:00
2017-06-18 01:37:19 +00:00
public bool SetBoxBinary ( byte [ ] data , int box )
2016-06-20 04:22:43 +00:00
{
2017-09-29 05:20:27 +00:00
int start = box * BoxSlotCount ;
int end = start + BoxSlotCount ;
2018-12-05 06:59:28 +00:00
if ( IsRegionOverwriteProtected ( start , end ) )
2016-12-16 07:17:17 +00:00
return false ;
2016-06-20 04:22:43 +00:00
2019-11-16 05:01:22 +00:00
int expectLength = BoxSlotCount * GetDataForBox ( BlankPKM ) . Length ;
2019-09-23 22:12:03 +00:00
return SetConcatenatedBinary ( data , expectLength , start ) ;
2018-03-26 02:05:49 +00:00
}
2018-08-03 03:11:42 +00:00
2019-09-23 22:12:03 +00:00
private bool SetConcatenatedBinary ( byte [ ] data , int expectLength , int start = 0 )
2016-06-20 04:22:43 +00:00
{
2019-09-03 02:30:58 +00:00
if ( data . Length ! = expectLength )
return false ;
2018-08-03 03:11:42 +00:00
2019-09-03 02:30:58 +00:00
var BD = BoxData ;
2019-11-16 05:01:22 +00:00
var entryLength = GetDataForBox ( BlankPKM ) . Length ;
2019-09-10 07:21:51 +00:00
var pkdata = ArrayUtil . EnumerateSplit ( data , entryLength ) ;
2019-09-23 22:12:03 +00:00
pkdata . Select ( GetPKM ) . CopyTo ( BD , IsSlotOverwriteProtected , start ) ;
2019-09-03 02:30:58 +00:00
BoxData = BD ;
return true ;
2016-06-20 04:22:43 +00:00
}
2019-09-03 02:30:58 +00:00
#endregion
}
2018-08-03 03:11:42 +00:00
2019-09-03 02:30:58 +00:00
public static class StorageUtil
{
public static bool CompressStorage ( this SaveFile sav , byte [ ] storage , out int storedCount , IList < int > [ ] slotPointers )
2018-11-01 22:38:09 +00:00
{
// keep track of empty slots, and only write them at the end if slots were shifted (no need otherwise).
var empty = new List < byte [ ] > ( ) ;
bool shiftedSlots = false ;
ushort ctr = 0 ;
2020-08-07 23:16:10 +00:00
int size = sav . SIZE_BOXSLOT ;
2019-09-03 02:30:58 +00:00
int count = sav . BoxSlotCount * sav . BoxCount ;
2018-11-01 22:38:09 +00:00
for ( int i = 0 ; i < count ; i + + )
{
2019-09-03 02:30:58 +00:00
int offset = sav . GetBoxSlotOffset ( i ) ;
if ( sav . IsPKMPresent ( storage , offset ) )
2018-11-01 22:38:09 +00:00
{
if ( ctr ! = i ) // copy required
{
shiftedSlots = true ; // appending empty slots afterwards is now required since a rewrite was done
2019-09-03 02:30:58 +00:00
int destOfs = sav . GetBoxSlotOffset ( ctr ) ;
Buffer . BlockCopy ( storage , offset , storage , destOfs , size ) ;
2018-11-11 05:04:24 +00:00
SlotPointerUtil . UpdateRepointFrom ( ctr , i , slotPointers ) ;
2018-11-01 22:38:09 +00:00
}
2019-09-03 02:30:58 +00:00
2018-11-01 22:38:09 +00:00
ctr + + ;
continue ;
}
// pop out an empty slot; save all unused data & preserve order
byte [ ] data = new byte [ size ] ;
2019-09-03 02:30:58 +00:00
Buffer . BlockCopy ( storage , offset , data , 0 , size ) ;
2018-11-01 22:38:09 +00:00
empty . Add ( data ) ;
}
storedCount = ctr ;
if ( ! shiftedSlots )
return false ;
for ( int i = ctr ; i < count ; i + + )
{
var data = empty [ i - ctr ] ;
2019-09-03 02:30:58 +00:00
int offset = sav . GetBoxSlotOffset ( i ) ;
data . CopyTo ( storage , offset ) ;
2018-11-01 22:38:09 +00:00
}
2019-09-03 02:30:58 +00:00
2018-11-01 22:38:09 +00:00
return true ;
}
2016-06-20 04:22:43 +00:00
}
}