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