2018-07-21 04:32:33 +00:00
using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
using System.IO ;
using System.Linq ;
using static PKHeX . Core . MessageStrings ;
namespace PKHeX.Core
{
2019-07-14 22:06:45 +00:00
/// <summary>
/// Extension methods for <see cref="SaveFile"/> syntax sugar.
/// </summary>
2018-07-21 04:32:33 +00:00
public static class SaveExtensions
{
2021-10-22 05:13:21 +00:00
/// <summary>
/// Evaluates a <see cref="PKM"/> file for compatibility to the <see cref="SaveFile"/>.
/// </summary>
/// <param name="sav"><see cref="SaveFile"/> that is being checked.</param>
/// <param name="pk"><see cref="PKM"/> that is being tested for compatibility.</param>
public static IReadOnlyList < string > EvaluateCompatibility ( this SaveFile sav , PKM pk )
{
return sav . GetSaveFileErrata ( pk , GameInfo . Strings ) ;
}
2018-07-21 04:32:33 +00:00
/// <summary>
/// Checks a <see cref="PKM"/> file for compatibility to the <see cref="SaveFile"/>.
/// </summary>
2019-07-14 22:06:45 +00:00
/// <param name="sav"><see cref="SaveFile"/> that is being checked.</param>
2021-10-22 05:13:21 +00:00
/// <param name="pk"><see cref="PKM"/> that is being tested for compatibility.</param>
public static bool IsCompatiblePKM ( this SaveFile sav , PKM pk )
2018-07-21 04:32:33 +00:00
{
2021-10-22 05:13:21 +00:00
if ( sav . PKMType ! = pk . GetType ( ) )
return false ;
if ( sav is ILangDeviantSave il & & PKMConverter . IsIncompatibleGB ( pk , il . Japanese , pk . Japanese ) )
return false ;
return true ;
2018-07-21 04:32:33 +00:00
}
2021-10-22 05:13:21 +00:00
private static IReadOnlyList < string > GetSaveFileErrata ( this SaveFile sav , PKM pk , IBasicStrings strings )
2018-07-21 04:32:33 +00:00
{
var errata = new List < string > ( ) ;
2021-10-22 05:13:21 +00:00
ushort held = ( ushort ) pk . HeldItem ;
2020-01-25 20:16:45 +00:00
if ( sav . Generation > 1 & & held ! = 0 )
2018-07-21 04:32:33 +00:00
{
2020-01-25 20:16:45 +00:00
string? msg = null ;
if ( held > sav . MaxItemID )
msg = MsgIndexItemGame ;
2021-10-22 05:13:21 +00:00
else if ( ! pk . CanHoldItem ( sav . HeldItems ) )
2020-01-25 20:16:45 +00:00
msg = MsgIndexItemHeld ;
if ( msg ! = null )
{
2021-10-22 05:13:21 +00:00
var itemstr = GameInfo . Strings . GetItemStrings ( pk . Format , ( GameVersion ) pk . Version ) ;
2021-05-07 06:26:38 +00:00
errata . Add ( $"{msg} {(held >= itemstr.Length ? held.ToString() : itemstr[held])}" ) ;
2020-01-25 20:16:45 +00:00
}
2018-07-21 04:32:33 +00:00
}
2021-10-22 05:13:21 +00:00
if ( pk . Species > strings . Species . Count )
errata . Add ( $"{MsgIndexSpeciesRange} {pk.Species}" ) ;
else if ( sav . MaxSpeciesID < pk . Species )
errata . Add ( $"{MsgIndexSpeciesGame} {strings.Species[pk.Species]}" ) ;
2018-07-21 04:32:33 +00:00
2021-10-22 05:13:21 +00:00
if ( ! sav . Personal [ pk . Species ] . IsFormWithinRange ( pk . Form ) & & ! FormInfo . IsValidOutOfBoundsForm ( pk . Species , pk . Form , pk . Generation ) )
errata . Add ( string . Format ( LegalityCheckStrings . LFormInvalidRange , Math . Max ( 0 , sav . Personal [ pk . Species ] . FormCount - 1 ) , pk . Form ) ) ;
2018-07-21 04:32:33 +00:00
2021-10-22 05:13:21 +00:00
if ( pk . Moves . Any ( m = > m > strings . Move . Count ) )
errata . Add ( $"{MsgIndexMoveRange} {string.Join(" , ", pk.Moves.Where(m => m > strings.Move.Count).Select(m => m.ToString()))}" ) ;
else if ( pk . Moves . Any ( m = > m > sav . MaxMoveID ) )
errata . Add ( $"{MsgIndexMoveGame} {string.Join(" , ", pk.Moves.Where(m => m > sav.MaxMoveID).Select(m => strings.Move[m]))}" ) ;
2018-07-21 04:32:33 +00:00
2021-10-22 05:13:21 +00:00
if ( pk . Ability > strings . Ability . Count )
errata . Add ( $"{MsgIndexAbilityRange} {pk.Ability}" ) ;
else if ( pk . Ability > sav . MaxAbilityID )
errata . Add ( $"{MsgIndexAbilityGame} {strings.Ability[pk.Ability]}" ) ;
2018-07-21 04:32:33 +00:00
return errata ;
}
2018-12-29 01:58:13 +00:00
/// <summary>
2019-07-14 22:06:45 +00:00
/// Imports compatible <see cref="PKM"/> data to the <see cref="sav"/>, starting at the provided box.
2018-12-29 01:58:13 +00:00
/// </summary>
2019-07-14 22:06:45 +00:00
/// <param name="sav">Save File that will receive the <see cref="compat"/> data.</param>
/// <param name="compat">Compatible <see cref="PKM"/> data that can be set to the <see cref="sav"/> without conversion.</param>
2018-12-29 01:58:13 +00:00
/// <param name="overwrite">Overwrite existing full slots. If true, will only overwrite empty slots.</param>
/// <param name="boxStart">First box to start loading to. All prior boxes are not modified.</param>
/// <param name="noSetb">Bypass option to not modify <see cref="PKM"/> properties when setting to Save File.</param>
/// <returns>Count of injected <see cref="PKM"/>.</returns>
2019-07-14 22:06:45 +00:00
public static int ImportPKMs ( this SaveFile sav , IEnumerable < PKM > compat , bool overwrite = false , int boxStart = 0 , PKMImportSetting noSetb = PKMImportSetting . UseDefault )
2018-07-21 04:32:33 +00:00
{
2019-07-14 22:06:45 +00:00
int startCount = boxStart * sav . BoxSlotCount ;
int maxCount = sav . SlotCount ;
2018-12-29 01:58:13 +00:00
int index = startCount ;
2019-05-15 16:11:48 +00:00
int nonOverwriteImport = 0 ;
2018-07-21 04:32:33 +00:00
foreach ( var pk in compat )
{
2018-12-29 01:58:13 +00:00
if ( overwrite )
{
2019-07-14 22:06:45 +00:00
while ( sav . IsSlotOverwriteProtected ( index ) )
2018-12-29 01:58:13 +00:00
+ + index ;
2019-05-15 16:11:48 +00:00
2020-12-08 03:49:04 +00:00
// The above will return false if out of range. We need to double-check.
if ( index > = maxCount ) // Boxes full!
break ;
2019-07-14 22:06:45 +00:00
sav . SetBoxSlotAtIndex ( pk , index , noSetb ) ;
2018-12-29 01:58:13 +00:00
}
else
2018-07-21 04:32:33 +00:00
{
2019-07-14 22:06:45 +00:00
index = sav . NextOpenBoxSlot ( index - 1 ) ;
2018-12-29 01:58:13 +00:00
if ( index < 0 ) // Boxes full!
break ;
2018-07-21 04:32:33 +00:00
2019-07-14 22:06:45 +00:00
sav . SetBoxSlotAtIndex ( pk , index , noSetb ) ;
2019-05-15 16:11:48 +00:00
nonOverwriteImport + + ;
}
2018-07-21 04:32:33 +00:00
2018-12-29 01:58:13 +00:00
if ( + + index = = maxCount ) // Boxes full!
2018-07-21 04:32:33 +00:00
break ;
}
2019-07-14 22:06:45 +00:00
return overwrite ? index - startCount : nonOverwriteImport ; // actual imported count
2018-07-21 04:32:33 +00:00
}
2019-07-14 22:06:45 +00:00
public static IEnumerable < PKM > GetCompatible ( this SaveFile sav , IEnumerable < PKM > pks )
2018-07-21 04:32:33 +00:00
{
2019-07-14 22:06:45 +00:00
var savtype = sav . PKMType ;
2018-07-21 04:32:33 +00:00
foreach ( var temp in pks )
{
var pk = PKMConverter . ConvertToType ( temp , savtype , out string c ) ;
if ( pk = = null )
{
Debug . WriteLine ( c ) ;
continue ;
}
2020-10-04 21:42:48 +00:00
if ( sav is ILangDeviantSave il & & PKMConverter . IsIncompatibleGB ( temp , il . Japanese , pk . Japanese ) )
2018-07-24 22:49:00 +00:00
{
2019-07-14 22:06:45 +00:00
c = PKMConverter . GetIncompatibleGBMessage ( pk , il . Japanese ) ;
2018-07-24 22:49:00 +00:00
Debug . WriteLine ( c ) ;
continue ;
}
2021-10-22 05:13:21 +00:00
var compat = sav . EvaluateCompatibility ( pk ) ;
2018-07-21 04:32:33 +00:00
if ( compat . Count > 0 )
continue ;
yield return pk ;
}
}
2019-02-15 08:50:23 +00:00
/// <summary>
/// Gets a compatible <see cref="PKM"/> for editing with a new <see cref="SaveFile"/>.
/// </summary>
/// <param name="sav">SaveFile to receive the compatible <see cref="pk"/></param>
/// <param name="pk">Current Pokémon being edited</param>
/// <returns>Current Pokémon, assuming conversion is possible. If conversion is not possible, a blank <see cref="PKM"/> will be obtained from the <see cref="sav"/>.</returns>
2020-04-16 19:58:48 +00:00
public static PKM GetCompatiblePKM ( this SaveFile sav , PKM pk )
2019-02-15 08:50:23 +00:00
{
2020-04-16 19:58:48 +00:00
if ( pk . Format > = 3 | | sav . Generation > = 7 )
return PKMConverter . ConvertToType ( pk , sav . PKMType , out _ ) ? ? sav . BlankPKM ;
// gen1-2 compatibility check
if ( pk . Japanese ! = ( ( ILangDeviantSave ) sav ) . Japanese )
return sav . BlankPKM ;
if ( sav is SAV2 s2 & & s2 . Korean ! = pk . Korean )
2019-02-15 08:50:23 +00:00
return sav . BlankPKM ;
return PKMConverter . ConvertToType ( pk , sav . PKMType , out _ ) ? ? sav . BlankPKM ;
}
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
/// <summary>
/// Gets a blank file for the save file. If the template path exists, a template load will be attempted.
/// </summary>
/// <param name="sav">Save File to fetch a template for</param>
/// <returns>Template if it exists, or a blank <see cref="PKM"/> from the <see cref="sav"/></returns>
2020-12-08 03:49:04 +00:00
private static PKM LoadTemplateInternal ( this SaveFile sav ) = > sav . BlankPKM ;
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-02-15 08:50:23 +00:00
/// <summary>
/// Gets a blank file for the save file. If the template path exists, a template load will be attempted.
/// </summary>
/// <param name="sav">Save File to fetch a template for</param>
/// <param name="templatePath">Path to look for a template in</param>
/// <returns>Template if it exists, or a blank <see cref="PKM"/> from the <see cref="sav"/></returns>
2020-12-08 03:49:04 +00:00
public static PKM LoadTemplate ( this SaveFile sav , string? templatePath = null )
2019-02-15 08:50:23 +00:00
{
2020-12-08 03:49:04 +00:00
if ( templatePath = = null | | ! Directory . Exists ( templatePath ) )
return LoadTemplateInternal ( sav ) ;
2019-02-15 08:50:23 +00:00
var di = new DirectoryInfo ( templatePath ) ;
2021-06-24 16:16:36 +00:00
string path = Path . Combine ( templatePath , $"{di.Name}.{sav.PKMType.Name.ToLowerInvariant()}" ) ;
2019-02-15 08:50:23 +00:00
if ( ! File . Exists ( path ) | | ! PKX . IsPKM ( new FileInfo ( path ) . Length ) )
2020-12-08 03:49:04 +00:00
return LoadTemplateInternal ( sav ) ;
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
var pk = PKMConverter . GetPKMfromBytes ( File . ReadAllBytes ( path ) , prefer : sav . Generation ) ;
2021-08-06 00:05:39 +00:00
if ( pk ? . Species is not > 0 )
2020-12-08 03:49:04 +00:00
return LoadTemplateInternal ( sav ) ;
2019-02-15 08:50:23 +00:00
2020-12-08 03:49:04 +00:00
return PKMConverter . ConvertToType ( pk , sav . BlankPKM . GetType ( ) , out _ ) ? ? LoadTemplateInternal ( sav ) ;
2019-02-15 08:50:23 +00:00
}
2018-07-21 04:32:33 +00:00
}
}