Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +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-08-27 11:33:21 +00:00
2022-06-18 18:04:24 +00:00
namespace PKHeX.Core ;
/// <summary>
/// Generation 1 <see cref="SaveFile"/> object.
/// </summary>
public sealed class SAV1 : SaveFile , ILangDeviantSave , IEventFlagArray
2016-08-27 11:33:21 +00:00
{
2022-06-18 18:04:24 +00:00
protected internal override string ShortSummary = > $"{OT} ({Version}) - {PlayTimeString}" ;
public override string Extension = > ".sav" ;
public bool IsVirtualConsole = > State . Exportable & & Metadata . FileName is { } s & & s . StartsWith ( "sav" , StringComparison . Ordinal ) & & s . Contains ( ".dat" ) ; // default to GB-Era for non-exportable
public int SaveRevision = > Japanese ? 0 : 1 ;
public string SaveRevisionString = > ( Japanese ? "J" : "U" ) + ( IsVirtualConsole ? "VC" : "GB" ) ;
public bool Japanese { get ; }
public bool Korean = > false ;
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +00:00
private readonly PersonalTable1 Table ;
public override IPersonalTable Personal = > Table ;
2022-06-18 18:04:24 +00:00
public override IReadOnlyList < ushort > HeldItems = > Array . Empty < ushort > ( ) ;
public override IReadOnlyList < string > PKMExtensions = > Array . FindAll ( PKM . Extensions , f = >
2016-08-27 11:33:21 +00:00
{
2022-06-18 18:04:24 +00:00
int gen = f [ ^ 1 ] - 0x30 ;
return gen is 1 or 2 ;
} ) ;
2020-09-26 20:30:17 +00:00
2022-06-18 18:04:24 +00:00
public SAV1 ( GameVersion version = GameVersion . RBY , bool japanese = false ) : base ( SaveUtil . SIZE_G1RAW )
{
Version = version ;
Japanese = japanese ;
Offsets = Japanese ? SAV1Offsets . JPN : SAV1Offsets . INT ;
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +00:00
Table = version = = GameVersion . YW ? PersonalTable . Y : PersonalTable . RB ;
2022-06-18 18:04:24 +00:00
Initialize ( version ) ;
ClearBoxes ( ) ;
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public SAV1 ( byte [ ] data , GameVersion versionOverride = GameVersion . Any ) : base ( data )
{
Japanese = SaveUtil . GetIsG1SAVJ ( Data ) ;
Offsets = Japanese ? SAV1Offsets . JPN : SAV1Offsets . INT ;
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
2022-06-18 18:04:24 +00:00
Version = versionOverride ! = GameVersion . Any ? versionOverride : SaveUtil . GetIsG1SAV ( data ) ;
Refactoring: Move Source (Legality) (#3560)
Rewrites a good amount of legality APIs pertaining to:
* Legal moves that can be learned
* Evolution chains & cross-generation paths
* Memory validation with forgotten moves
In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data.
The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space.
The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation.
* `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game.
* `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`).
* Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +00:00
Table = Version = = GameVersion . YW ? PersonalTable . Y : PersonalTable . RB ;
2022-06-18 18:04:24 +00:00
if ( Version = = GameVersion . Invalid )
return ;
2016-08-27 11:33:21 +00:00
2022-06-18 18:04:24 +00:00
Initialize ( versionOverride ) ;
}
2019-06-09 02:56:11 +00:00
2022-06-18 18:04:24 +00:00
private void Initialize ( GameVersion versionOverride )
{
// see if RBY can be differentiated
if ( Starter ! = 0 & & versionOverride is not ( GameVersion . RB or GameVersion . YW ) )
Version = Yellow ? GameVersion . YW : GameVersion . RB ;
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
2022-06-18 18:04:24 +00:00
Box = Data . Length ;
Array . Resize ( ref Data , Data . Length + SIZE_RESERVED ) ;
Party = GetPartyOffset ( 0 ) ;
2018-08-07 04:29:47 +00:00
2022-06-18 18:04:24 +00:00
// Stash boxes after the save file's end.
int stored = SIZE_STOREDBOX ;
var capacity = Japanese ? PokeListType . StoredJP : PokeListType . Stored ;
int baseDest = Data . Length - SIZE_RESERVED ;
for ( int box = 0 ; box < BoxCount ; box + + )
{
int boxOfs = GetBoxRawDataOffset ( box ) ;
UnpackBox ( boxOfs , baseDest , stored , box , capacity ) ;
2019-06-09 02:56:11 +00:00
}
2022-06-18 18:04:24 +00:00
if ( ( uint ) CurrentBox < BoxCount )
2019-06-09 02:56:11 +00:00
{
2022-06-18 18:04:24 +00:00
// overwrite previously cached box data.
UnpackBox ( Offsets . CurrentBox , baseDest , stored , CurrentBox , capacity ) ;
}
2016-08-28 10:18:22 +00:00
2022-06-18 18:04:24 +00:00
var party = GetData ( Offsets . Party , SIZE_STOREDPARTY ) ;
var partyPL = new PokeList1 ( party , PokeListType . Party , Japanese ) ;
for ( int i = 0 ; i < partyPL . Pokemon . Length ; i + + )
{
var dest = GetPartyOffset ( i ) ;
var pkDat = i < partyPL . Count
? new PokeList1 ( partyPL [ i ] ) . Write ( )
: new byte [ PokeList1 . GetDataLength ( PokeListType . Single , Japanese ) ] ;
pkDat . CopyTo ( Data , dest ) ;
}
2016-08-28 10:18:22 +00:00
2022-06-18 18:04:24 +00:00
Span < byte > rawDC = stackalloc byte [ 0x38 ] ;
Data . AsSpan ( Offsets . Daycare , rawDC . Length ) . CopyTo ( rawDC ) ;
byte [ ] TempDaycare = new byte [ PokeList1 . GetDataLength ( PokeListType . Single , Japanese ) ] ;
TempDaycare [ 0 ] = rawDC [ 0 ] ;
2016-08-28 10:18:22 +00:00
2022-06-18 18:04:24 +00:00
rawDC . Slice ( 1 , StringLength ) . CopyTo ( TempDaycare . AsSpan ( 2 + 1 + PokeCrypto . SIZE_1PARTY + StringLength ) ) ;
rawDC . Slice ( 1 + StringLength , StringLength ) . CopyTo ( TempDaycare . AsSpan ( 2 + 1 + PokeCrypto . SIZE_1PARTY ) ) ;
rawDC . Slice ( 1 + ( 2 * StringLength ) , PokeCrypto . SIZE_1STORED ) . CopyTo ( TempDaycare . AsSpan ( 2 + 1 ) ) ;
2022-01-03 05:35:59 +00:00
2022-06-18 18:04:24 +00:00
PokeList1 daycareList = new ( TempDaycare , PokeListType . Single , Japanese ) ;
daycareList . Write ( ) . CopyTo ( Data , GetPartyOffset ( 7 ) ) ;
DaycareOffset = GetPartyOffset ( 7 ) ;
2022-01-03 05:35:59 +00:00
2022-06-18 18:04:24 +00:00
// Enable Pokedex editing
PokeDex = 0 ;
}
2017-09-24 05:13:48 +00:00
2022-06-18 18:04:24 +00:00
private void UnpackBox ( int srcOfs , int destOfs , int boxSize , int boxIndex , PokeListType boxCapacity )
{
var boxData = GetData ( srcOfs , boxSize ) ;
var boxDest = destOfs + ( boxIndex * SIZE_BOX ) ;
var boxPL = new PokeList1 ( boxData , boxCapacity , Japanese ) ;
for ( int i = 0 ; i < boxPL . Pokemon . Length ; i + + )
{
var slotOfs = boxDest + ( i * SIZE_STORED ) ;
var slotData = Data . AsSpan ( slotOfs , SIZE_STORED ) ;
if ( i < boxPL . Count )
new PokeList1 ( boxPL [ i ] ) . Write ( ) . CopyTo ( slotData ) ;
else
slotData . Clear ( ) ;
2016-08-27 11:33:21 +00:00
}
2022-06-18 18:04:24 +00:00
}
2016-08-27 11:33:21 +00:00
2022-06-18 18:04:24 +00:00
private void PackBox ( int boxDest , int boxIndex , PokeListType boxCapacity )
{
var boxPL = new PokeList1 ( boxCapacity , Japanese ) ;
int slot = 0 ;
for ( int i = 0 ; i < boxPL . Pokemon . Length ; i + + )
2022-05-03 06:47:41 +00:00
{
2022-06-18 18:04:24 +00:00
var slotOfs = boxDest + ( i * SIZE_STORED ) ;
var slotData = Data . AsSpan ( slotOfs , SIZE_STORED ) ;
PK1 pk = ( PK1 ) GetPKM ( slotData . ToArray ( ) ) ;
if ( pk . Species > 0 )
boxPL [ slot + + ] = pk ;
2022-05-03 06:47:41 +00:00
}
2022-06-18 18:04:24 +00:00
// copy to box location
var boxData = boxPL . Write ( ) ;
int boxSrc = GetBoxRawDataOffset ( boxIndex ) ;
SetData ( Data , boxData , boxSrc ) ;
2022-05-03 06:47:41 +00:00
2022-06-18 18:04:24 +00:00
// copy to active loc if current box
if ( boxIndex = = CurrentBox )
SetData ( Data , boxData , Offsets . CurrentBox ) ;
}
2022-05-03 06:47:41 +00:00
2022-06-18 18:04:24 +00:00
private const int SIZE_RESERVED = 0x8000 ; // unpacked box data
private readonly SAV1Offsets Offsets ;
// Event Flags
public int EventFlagCount = > 0xA00 ; // 320 * 8
public bool GetEventFlag ( int flagNumber )
{
if ( ( uint ) flagNumber > = EventFlagCount )
throw new ArgumentOutOfRangeException ( nameof ( flagNumber ) , $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagCount})." ) ;
return GetFlag ( Offsets . EventFlag + ( flagNumber > > 3 ) , flagNumber & 7 ) ;
}
2022-05-03 06:47:41 +00:00
2022-06-18 18:04:24 +00:00
public void SetEventFlag ( int flagNumber , bool value )
{
if ( ( uint ) flagNumber > = EventFlagCount )
throw new ArgumentOutOfRangeException ( nameof ( flagNumber ) , $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagCount})." ) ;
SetFlag ( Offsets . EventFlag + ( flagNumber > > 3 ) , flagNumber & 7 , value ) ;
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
protected override byte [ ] GetFinalData ( )
{
var capacity = Japanese ? PokeListType . StoredJP : PokeListType . Stored ;
for ( int box = 0 ; box < BoxCount ; box + + )
2022-04-10 01:12:57 +00:00
{
2022-06-18 18:04:24 +00:00
var boxOfs = GetBoxOffset ( box ) ;
PackBox ( boxOfs , box , capacity ) ;
2022-04-10 01:12:57 +00:00
}
2022-06-18 18:04:24 +00:00
var partyPL = new PokeList1 ( PokeListType . Party , Japanese ) ;
int pSlot = 0 ;
for ( int i = 0 ; i < 6 ; i + + )
2022-04-10 01:12:57 +00:00
{
2022-06-18 18:04:24 +00:00
PK1 partyPK = ( PK1 ) GetPKM ( GetData ( GetPartyOffset ( i ) , SIZE_STORED ) ) ;
if ( partyPK . Species > 0 )
partyPL [ pSlot + + ] = partyPK ;
2022-04-10 01:12:57 +00:00
}
2022-06-18 18:04:24 +00:00
partyPL . Write ( ) . CopyTo ( Data , Offsets . Party ) ;
2017-09-24 05:13:48 +00:00
2022-06-18 18:04:24 +00:00
// Daycare is read-only, but in case it ever becomes editable, copy it back in.
Span < byte > rawDC = Data . AsSpan ( GetDaycareSlotOffset ( loc : 0 , slot : 0 ) , SIZE_STORED ) ;
Span < byte > dc = stackalloc byte [ 1 + ( 2 * StringLength ) + PokeCrypto . SIZE_1STORED ] ;
dc [ 0 ] = IsDaycareOccupied ( 0 , 0 ) = = true ? ( byte ) 1 : ( byte ) 0 ;
rawDC . Slice ( 2 + 1 + PokeCrypto . SIZE_1PARTY + StringLength , StringLength ) . CopyTo ( dc [ 1. . ] ) ;
rawDC . Slice ( 2 + 1 + PokeCrypto . SIZE_1PARTY , StringLength ) . CopyTo ( dc [ ( 1 + StringLength ) . . ] ) ;
rawDC . Slice ( 2 + 1 , PokeCrypto . SIZE_1STORED ) . CopyTo ( dc [ ( 1 + ( 2 * StringLength ) ) . . ] ) ;
dc . CopyTo ( Data . AsSpan ( Offsets . Daycare ) ) ;
2016-08-29 20:27:17 +00:00
2022-06-18 18:04:24 +00:00
SetChecksums ( ) ;
return Data . AsSpan ( ) [ . . ^ SIZE_RESERVED ] . ToArray ( ) ;
}
2016-08-27 11:33:21 +00:00
2022-06-18 18:04:24 +00:00
private int GetBoxRawDataOffset ( int box )
{
if ( box < BoxCount / 2 )
return 0x4000 + ( box * SIZE_STOREDBOX ) ;
return 0x6000 + ( ( box - ( BoxCount / 2 ) ) * SIZE_STOREDBOX ) ;
}
2018-08-06 00:28:54 +00:00
2022-06-18 18:04:24 +00:00
// Configuration
protected override SaveFile CloneInternal ( ) = > new SAV1 ( Write ( ) , Version ) ;
protected override int SIZE_STORED = > Japanese ? PokeCrypto . SIZE_1JLIST : PokeCrypto . SIZE_1ULIST ;
protected override int SIZE_PARTY = > Japanese ? PokeCrypto . SIZE_1JLIST : PokeCrypto . SIZE_1ULIST ;
private int SIZE_BOX = > BoxSlotCount * SIZE_STORED ;
private int SIZE_STOREDBOX = > PokeList1 . GetDataLength ( Japanese ? PokeListType . StoredJP : PokeListType . Stored , Japanese ) ;
private int SIZE_STOREDPARTY = > PokeList1 . GetDataLength ( PokeListType . Party , Japanese ) ;
public override PKM BlankPKM = > new PK1 ( Japanese ) ;
public override Type PKMType = > typeof ( PK1 ) ;
2022-08-27 06:43:36 +00:00
public override ushort MaxMoveID = > Legal . MaxMoveID_1 ;
public override ushort MaxSpeciesID = > Legal . MaxSpeciesID_1 ;
2022-06-18 18:04:24 +00:00
public override int MaxAbilityID = > Legal . MaxAbilityID_1 ;
public override int MaxItemID = > Legal . MaxItemID_1 ;
public override int MaxBallID = > 0 ; // unused
public override int MaxGameID = > 99 ; // unused
public override int MaxMoney = > 999999 ;
public override int MaxCoins = > 9999 ;
public override int BoxCount = > Japanese ? 8 : 12 ;
public override int MaxEV = > 65535 ;
public override int MaxIV = > 15 ;
public override int Generation = > 1 ;
public override EntityContext Context = > EntityContext . Gen1 ;
protected override int GiftCountMax = > 0 ;
public override int OTLength = > Japanese ? 5 : 7 ;
public override int NickLength = > Japanese ? 5 : 10 ;
public override int BoxSlotCount = > Japanese ? 30 : 20 ;
public override bool HasParty = > true ;
private int StringLength = > Japanese ? GBPKML . StringLengthJapanese : GBPKML . StringLengthNotJapan ;
public override bool IsPKMPresent ( ReadOnlySpan < byte > data ) = > EntityDetection . IsPresentGB ( data ) ;
// Checksums
protected override void SetChecksums ( ) = > Data [ Offsets . ChecksumOfs ] = GetRBYChecksum ( Offsets . OT , Offsets . ChecksumOfs ) ;
public override bool ChecksumsValid = > Data [ Offsets . ChecksumOfs ] = = GetRBYChecksum ( Offsets . OT , Offsets . ChecksumOfs ) ;
public override string ChecksumInfo = > ChecksumsValid ? "Checksum valid." : "Checksum invalid" ;
private byte GetRBYChecksum ( int start , int end )
{
byte chksum = 0 ;
for ( int i = start ; i < end ; i + + )
chksum + = Data [ i ] ;
chksum ^ = 0xFF ;
return chksum ;
}
2016-08-27 11:33:21 +00:00
2022-06-18 18:04:24 +00:00
// Trainer Info
public override GameVersion Version { get ; protected set ; }
2016-08-27 11:33:21 +00:00
2022-06-18 18:04:24 +00:00
public override string OT
{
get = > GetString ( Offsets . OT , OTLength ) ;
2022-09-12 18:54:35 +00:00
set = > SetString ( Data . AsSpan ( Offsets . OT , OTLength + 1 ) , value . AsSpan ( ) , OTLength , StringConverterOption . ClearZero ) ;
2022-06-18 18:04:24 +00:00
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public Span < byte > OT_Trash { get = > Data . AsSpan ( Offsets . OT , StringLength ) ; set { if ( value . Length = = StringLength ) value . CopyTo ( Data . AsSpan ( Offsets . OT ) ) ; } }
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public override int Gender
{
get = > 0 ;
set { }
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public override int TID
{
get = > ReadUInt16BigEndian ( Data . AsSpan ( Offsets . TID ) ) ;
set = > WriteUInt16BigEndian ( Data . AsSpan ( Offsets . TID ) , ( ushort ) value ) ;
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public override int SID { get = > 0 ; set { } }
2017-01-05 09:14:19 +00:00
2022-06-18 18:04:24 +00:00
public string Rival
{
get = > GetString ( Offsets . Rival , OTLength ) ;
set = > SetString ( Data . AsSpan ( Offsets . Rival , OTLength ) , value . AsSpan ( ) , OTLength , StringConverterOption . Clear50 ) ;
}
2021-10-10 23:11:46 +00:00
2022-06-18 18:04:24 +00:00
public Span < byte > Rival_Trash { get = > Data . AsSpan ( Offsets . Rival , StringLength ) ; set { if ( value . Length = = StringLength ) value . CopyTo ( Data . AsSpan ( Offsets . Rival ) ) ; } }
2021-10-10 23:11:46 +00:00
2022-06-18 18:04:24 +00:00
public bool Yellow = > Starter = = 0x54 ; // Pikachu
public int Starter = > Data [ Offsets . Starter ] ;
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public byte PikaFriendship
{
get = > Data [ Offsets . PikaFriendship ] ;
set = > Data [ Offsets . PikaFriendship ] = value ;
}
2018-02-21 23:22:43 +00:00
2022-06-18 18:04:24 +00:00
public int PikaBeachScore
{
get = > BinaryCodedDecimal . ToInt32LE ( Data . AsSpan ( Offsets . PikaBeachScore , 2 ) ) ;
set = > BinaryCodedDecimal . WriteBytesLE ( Data . AsSpan ( Offsets . PikaBeachScore , 2 ) , Math . Min ( 9999 , value ) ) ;
}
2019-12-21 23:17:05 +00:00
2022-06-18 18:04:24 +00:00
public override string PlayTimeString = > ! PlayedMaximum ? base . PlayTimeString : $"{base.PlayTimeString} {Checksums.CRC16_CCITT(Data):X4}" ;
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public override int PlayedHours
{
get = > Data [ Offsets . PlayTime + 0 ] ;
set
2016-08-27 11:33:21 +00:00
{
2022-06-18 18:04:24 +00:00
if ( value > = byte . MaxValue ) // Set 255:00:00.00 and flag
2018-02-21 23:22:43 +00:00
{
2022-06-18 18:04:24 +00:00
PlayedMaximum = true ;
value = byte . MaxValue ;
PlayedMinutes = PlayedSeconds = PlayedFrames = 0 ;
2018-02-21 23:22:43 +00:00
}
2022-06-18 18:04:24 +00:00
Data [ Offsets . PlayTime + 0 ] = ( byte ) value ;
2018-02-21 23:22:43 +00:00
}
2022-06-18 18:04:24 +00:00
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public bool PlayedMaximum
{
get = > Data [ Offsets . PlayTime + 1 ] ! = 0 ;
set = > Data [ Offsets . PlayTime + 1 ] = value ? ( byte ) 1 : ( byte ) 0 ;
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public override int PlayedMinutes
{
get = > Data [ Offsets . PlayTime + 2 ] ;
set = > Data [ Offsets . PlayTime + 2 ] = ( byte ) value ;
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public override int PlayedSeconds
{
get = > Data [ Offsets . PlayTime + 3 ] ;
set = > Data [ Offsets . PlayTime + 3 ] = ( byte ) value ;
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public int PlayedFrames
{
get = > Data [ Offsets . PlayTime + 4 ] ;
set = > Data [ Offsets . PlayTime + 4 ] = ( byte ) value ;
}
2016-08-27 11:33:21 +00:00
2022-06-18 18:04:24 +00:00
public int Badges
{
get = > Data [ Offsets . Badges ] ;
set { if ( value < 0 ) return ; Data [ Offsets . Badges ] = ( byte ) value ; }
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
private byte Options
{
get = > Data [ Offsets . Options ] ;
set = > Data [ Offsets . Options ] = value ;
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public bool BattleEffects
{
get = > ( Options & 0x80 ) = = 0 ;
set = > Options = ( byte ) ( ( Options & 0x7F ) | ( value ? 0 : 0x80 ) ) ;
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public bool BattleStyleSwitch
{
get = > ( Options & 0x40 ) = = 0 ;
set = > Options = ( byte ) ( ( Options & 0xBF ) | ( value ? 0 : 0x40 ) ) ;
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public int Sound
{
get = > ( Options & 0x30 ) > > 4 ;
set = > Options = ( byte ) ( ( Options & 0xCF ) | ( ( value & 3 ) < < 4 ) ) ;
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public int TextSpeed
{
get = > Options & 0x7 ;
set = > Options = ( byte ) ( ( Options & 0xF8 ) | ( value & 7 ) ) ;
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
// yellow only
public byte GBPrinterBrightness { get = > Data [ Offsets . PrinterBrightness ] ; set = > Data [ Offsets . PrinterBrightness ] = value ; }
2019-06-09 21:15:42 +00:00
2022-06-18 18:04:24 +00:00
public override uint Money
{
get = > ( uint ) BinaryCodedDecimal . ToInt32BE ( Data . AsSpan ( Offsets . Money , 3 ) ) ;
set
2016-08-27 11:33:21 +00:00
{
2022-06-18 18:04:24 +00:00
value = ( uint ) Math . Min ( value , MaxMoney ) ;
BinaryCodedDecimal . WriteBytesBE ( Data . AsSpan ( Offsets . Money , 3 ) , ( int ) value ) ;
2016-08-27 11:33:21 +00:00
}
2022-06-18 18:04:24 +00:00
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public uint Coin
{
get = > ( uint ) BinaryCodedDecimal . ToInt32BE ( Data . AsSpan ( Offsets . Coin , 2 ) ) ;
set
2016-08-27 11:33:21 +00:00
{
2022-06-18 18:04:24 +00:00
value = ( ushort ) Math . Min ( value , MaxCoins ) ;
BinaryCodedDecimal . WriteBytesBE ( Data . AsSpan ( Offsets . Coin , 2 ) , ( int ) value ) ;
2016-08-27 11:33:21 +00:00
}
2022-06-18 18:04:24 +00:00
}
2016-08-27 11:33:21 +00:00
2022-06-18 18:04:24 +00:00
private readonly ushort [ ] LegalItems = Legal . Pouch_Items_RBY ;
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public override IReadOnlyList < InventoryPouch > Inventory
{
get
2016-08-27 11:33:21 +00:00
{
2022-06-18 18:04:24 +00:00
ushort [ ] legalItems = LegalItems ;
InventoryPouch [ ] pouch =
2016-08-27 11:33:21 +00:00
{
2022-06-18 18:04:24 +00:00
new InventoryPouchGB ( InventoryType . Items , legalItems , 99 , Offsets . Items , 20 ) ,
new InventoryPouchGB ( InventoryType . PCItems , legalItems , 99 , Offsets . PCItems , 50 ) ,
} ;
return pouch . LoadAll ( Data ) ;
2016-08-27 11:33:21 +00:00
}
2022-06-18 18:04:24 +00:00
set = > value . SaveAll ( Data ) ;
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public override int GetDaycareSlotOffset ( int loc , int slot )
{
return DaycareOffset ;
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public override uint? GetDaycareEXP ( int loc , int slot )
{
return null ;
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public override bool? IsDaycareOccupied ( int loc , int slot )
{
if ( loc = = 0 & & slot = = 0 )
return Data [ Offsets . Daycare ] = = 0x01 ;
return null ;
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public override void SetDaycareEXP ( int loc , int slot , uint EXP )
{
// todo
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public override void SetDaycareOccupied ( int loc , int slot , bool occupied )
{
// todo
}
2016-08-27 11:33:21 +00:00
2022-06-18 18:04:24 +00:00
// Storage
public override int PartyCount
{
get = > Data [ Offsets . Party ] ;
protected set = > Data [ Offsets . Party ] = ( byte ) value ;
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public override int GetBoxOffset ( int box )
{
return Data . Length - SIZE_RESERVED + ( box * SIZE_BOX ) ;
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public override int GetPartyOffset ( int slot )
{
return Data . Length - SIZE_RESERVED + ( BoxCount * SIZE_BOX ) + ( slot * SIZE_STORED ) ;
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public override int CurrentBox
{
get = > Data [ Offsets . CurrentBoxIndex ] & 0x7F ;
set = > Data [ Offsets . CurrentBoxIndex ] = ( byte ) ( ( Data [ Offsets . CurrentBoxIndex ] & 0x80 ) | ( value & 0x7F ) ) ;
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public bool CurrentBoxChanged
{
get = > ( Data [ Offsets . CurrentBoxIndex ] & 0x80 ) ! = 0 ;
set = > Data [ Offsets . CurrentBoxIndex ] = ( byte ) ( ( Data [ Offsets . CurrentBoxIndex ] & 0x7F ) | ( byte ) ( value ? 0x80 : 0 ) ) ;
}
2022-05-03 06:47:41 +00:00
2022-06-18 18:04:24 +00:00
public override string GetBoxName ( int box )
{
return $"BOX {box + 1}" ;
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public override void SetBoxName ( int box , string value )
{
// Don't allow for custom box names
}
2016-08-28 10:18:22 +00:00
2022-06-18 18:04:24 +00:00
protected override PKM GetPKM ( byte [ ] data )
{
if ( data . Length = = SIZE_STORED )
return new PokeList1 ( data , PokeListType . Single , Japanese ) [ 0 ] ;
return new PK1 ( data ) ;
}
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
protected override byte [ ] DecryptPKM ( byte [ ] data )
{
return data ;
}
2016-08-27 11:33:21 +00:00
2022-06-18 18:04:24 +00:00
// Pokédex
protected override void SetDex ( PKM pk )
{
2022-08-27 06:43:36 +00:00
ushort species = pk . Species ;
2022-06-18 18:04:24 +00:00
if ( ! CanSetDex ( species ) )
return ;
2016-08-30 00:48:05 +00:00
2022-06-18 18:04:24 +00:00
SetCaught ( pk . Species , true ) ;
SetSeen ( pk . Species , true ) ;
}
2018-08-06 03:27:25 +00:00
2022-08-27 06:43:36 +00:00
private bool CanSetDex ( ushort species )
2022-06-18 18:04:24 +00:00
{
2022-08-30 22:00:45 +00:00
if ( species = = 0 )
2022-06-18 18:04:24 +00:00
return false ;
if ( species > MaxSpeciesID )
return false ;
if ( Version = = GameVersion . Invalid )
return false ;
return true ;
}
2018-08-06 03:27:25 +00:00
2022-08-27 06:43:36 +00:00
public override bool GetSeen ( ushort species ) = > GetDexFlag ( Offsets . DexSeen , species ) ;
public override bool GetCaught ( ushort species ) = > GetDexFlag ( Offsets . DexCaught , species ) ;
public override void SetSeen ( ushort species , bool seen ) = > SetDexFlag ( Offsets . DexSeen , species , seen ) ;
public override void SetCaught ( ushort species , bool caught ) = > SetDexFlag ( Offsets . DexCaught , species , caught ) ;
2018-08-06 03:27:25 +00:00
2022-08-27 06:43:36 +00:00
private bool GetDexFlag ( int region , ushort species )
2022-06-18 18:04:24 +00:00
{
int bit = species - 1 ;
int ofs = bit > > 3 ;
return GetFlag ( region + ofs , bit & 7 ) ;
}
2018-08-06 03:27:25 +00:00
2022-08-27 06:43:36 +00:00
private void SetDexFlag ( int region , ushort species , bool value )
2022-06-18 18:04:24 +00:00
{
int bit = species - 1 ;
int ofs = bit > > 3 ;
SetFlag ( region + ofs , bit & 7 , value ) ;
}
2017-04-09 21:06:50 +00:00
2022-06-18 18:04:24 +00:00
public override void WriteSlotFormatStored ( PKM pk , Span < byte > data , int offset )
{
// pk that have never been boxed have yet to save the 'current level' for box indication
// set this value at this time
( ( PK1 ) pk ) . Stat_LevelBox = pk . CurrentLevel ;
base . WriteSlotFormatStored ( pk , Data , offset ) ;
}
2019-11-16 01:34:18 +00:00
2022-06-18 18:04:24 +00:00
public override void WriteBoxSlot ( PKM pk , Span < byte > data , int offset )
{
// pk that have never been boxed have yet to save the 'current level' for box indication
// set this value at this time
( ( PK1 ) pk ) . Stat_LevelBox = pk . CurrentLevel ;
base . WriteBoxSlot ( pk , Data , offset ) ;
}
2018-08-19 20:35:20 +00:00
2022-06-18 18:04:24 +00:00
private const int SpawnFlagCount = 0xF0 ;
2018-08-06 03:27:25 +00:00
2022-06-18 18:04:24 +00:00
public bool [ ] EventSpawnFlags
{
get
2017-09-24 23:36:51 +00:00
{
2022-06-18 18:04:24 +00:00
// RB uses 0xE4 (0xE8) flags, Yellow uses 0xF0 flags. Just grab 0xF0
bool [ ] data = new bool [ SpawnFlagCount ] ;
for ( int i = 0 ; i < data . Length ; i + + )
data [ i ] = GetFlag ( Offsets . ObjectSpawnFlags + ( i > > 3 ) , i & 7 ) ;
return data ;
2017-09-24 23:36:51 +00:00
}
2022-06-18 18:04:24 +00:00
set
2017-04-09 21:06:50 +00:00
{
2022-06-18 18:04:24 +00:00
if ( value . Length ! = SpawnFlagCount )
return ;
for ( int i = 0 ; i < value . Length ; i + + )
SetFlag ( Offsets . ObjectSpawnFlags + ( i > > 3 ) , i & 7 , value [ i ] ) ;
2017-04-09 21:06:50 +00:00
}
2016-08-27 11:33:21 +00:00
}
2022-06-18 18:04:24 +00:00
public override string GetString ( ReadOnlySpan < byte > data ) = > StringConverter12 . GetString ( data , Japanese ) ;
public override int SetString ( Span < byte > destBuffer , ReadOnlySpan < char > value , int maxLength , StringConverterOption option )
{
return StringConverter12 . SetString ( destBuffer , value , maxLength , Japanese , option ) ;
}
2016-08-27 11:33:21 +00:00
}