2016-06-20 04:22:43 +00:00
using System ;
2016-08-05 04:12:16 +00:00
using System.Collections.Generic ;
2016-08-05 03:48:52 +00:00
using System.IO ;
2016-06-20 04:22:43 +00:00
using System.Linq ;
2016-06-20 05:11:53 +00:00
using System.Text ;
2016-06-20 04:22:43 +00:00
2018-09-07 03:35:55 +00:00
using static PKHeX . Core . MessageStrings ;
2017-01-08 07:54:09 +00:00
namespace PKHeX.Core
2016-06-20 04:22:43 +00:00
{
2017-10-24 06:12:58 +00:00
/// <summary>
/// Logic for <see cref="SaveFile"/> data loading and manipulation.
/// </summary>
2016-06-26 18:46:08 +00:00
public static class SaveUtil
2016-06-20 04:22:43 +00:00
{
2017-01-08 07:54:09 +00:00
public const int BEEF = 0x42454546 ;
2019-09-23 23:56:47 +00:00
public const int SIZE_G8SWSH = 0x845FED ; // todo
2018-11-14 03:18:29 +00:00
public const int SIZE_G7GG = 0x100000 ;
2017-11-08 08:34:32 +00:00
public const int SIZE_G7USUM = 0x6CC00 ;
2017-01-08 07:54:09 +00:00
public const int SIZE_G7SM = 0x6BE00 ;
public const int SIZE_G6XY = 0x65600 ;
public const int SIZE_G6ORAS = 0x76000 ;
public const int SIZE_G6ORASDEMO = 0x5A00 ;
public const int SIZE_G5RAW = 0x80000 ;
public const int SIZE_G5BW = 0x24000 ;
public const int SIZE_G5B2W2 = 0x26000 ;
public const int SIZE_G4BR = 0x380000 ;
public const int SIZE_G4RAW = 0x80000 ;
public const int SIZE_G3BOX = 0x76000 ;
public const int SIZE_G3COLO = 0x60000 ;
public const int SIZE_G3XD = 0x56000 ;
2019-03-16 19:01:21 +00:00
public const int SIZE_G3BOXGCI = SIZE_G3BOX + 0x40 ; // GCI data
public const int SIZE_G3COLOGCI = SIZE_G3COLO + 0x40 ; // GCI data
public const int SIZE_G3XDGCI = SIZE_G3XD + 0x40 ; // GCI data
2017-01-08 07:54:09 +00:00
public const int SIZE_G3RAW = 0x20000 ;
public const int SIZE_G3RAWHALF = 0x10000 ;
public const int SIZE_G2RAW_U = 0x8000 ;
2018-01-26 02:30:09 +00:00
public const int SIZE_G2VC_U = 0x8010 ;
2017-01-08 07:54:09 +00:00
public const int SIZE_G2BAT_U = 0x802C ;
2017-09-09 04:53:08 +00:00
public const int SIZE_G2EMU_U = 0x8030 ;
2017-01-08 07:54:09 +00:00
public const int SIZE_G2RAW_J = 0x10000 ;
2018-01-26 02:30:09 +00:00
public const int SIZE_G2VC_J = 0x10010 ;
2017-01-08 07:54:09 +00:00
public const int SIZE_G2BAT_J = 0x1002C ;
2017-09-09 04:53:08 +00:00
public const int SIZE_G2EMU_J = 0x10030 ;
2017-01-08 07:54:09 +00:00
public const int SIZE_G1RAW = 0x8000 ;
public const int SIZE_G1BAT = 0x802C ;
2018-02-24 21:03:32 +00:00
// Bank Binaries
public const int SIZE_G7BANK = 0xACA48 ;
2019-01-03 05:56:33 +00:00
public const int SIZE_G4BANK = 0x405C4 ;
2019-01-15 05:31:53 +00:00
public const int SIZE_G4RANCH = 0x54000 ;
2019-01-25 23:24:01 +00:00
public const int SIZE_G4RANCH_PLAT = 0x7C000 ;
2018-07-29 20:27:48 +00:00
2018-01-26 02:30:09 +00:00
private static readonly HashSet < int > SIZES_2 = new HashSet < int >
{
SIZE_G2RAW_U , SIZE_G2VC_U , SIZE_G2BAT_U , SIZE_G2EMU_U , SIZE_G2RAW_J , SIZE_G2BAT_J , SIZE_G2EMU_J , SIZE_G2VC_J ,
} ;
2018-07-29 20:27:48 +00:00
2018-01-26 02:30:09 +00:00
private static readonly HashSet < int > SIZES = new HashSet < int > ( SIZES_2 )
2017-08-25 01:49:09 +00:00
{
2019-09-23 23:56:47 +00:00
SIZE_G8SWSH ,
2018-11-14 03:18:29 +00:00
SIZE_G7SM , SIZE_G7USUM , SIZE_G7GG ,
2017-08-25 01:49:09 +00:00
SIZE_G6XY , SIZE_G6ORAS , SIZE_G6ORASDEMO ,
SIZE_G5RAW , SIZE_G5BW , SIZE_G5B2W2 ,
SIZE_G4BR , SIZE_G4RAW ,
SIZE_G3BOX , SIZE_G3BOXGCI , SIZE_G3COLO , SIZE_G3COLOGCI , SIZE_G3XD , SIZE_G3XDGCI , SIZE_G3RAW , SIZE_G3RAWHALF ,
2018-01-26 02:30:09 +00:00
// SIZES_2 covers gen2 sizes since there's so many
2018-02-24 21:03:32 +00:00
SIZE_G1RAW , SIZE_G1BAT ,
2019-01-25 23:24:01 +00:00
SIZE_G7BANK , SIZE_G4BANK , SIZE_G4RANCH , SIZE_G4RANCH_PLAT ,
2017-08-25 01:49:09 +00:00
} ;
2018-07-29 20:27:48 +00:00
2018-02-23 06:42:50 +00:00
private static readonly byte [ ] FOOTER_DSV = Encoding . ASCII . GetBytes ( "|-DESMUME SAVE-|" ) ;
2017-09-16 18:38:58 +00:00
internal static readonly string [ ] HEADER_COLO = { "GC6J" , "GC6E" , "GC6P" } ; // NTSC-J, NTSC-U, PAL
internal static readonly string [ ] HEADER_XD = { "GXXJ" , "GXXE" , "GXXP" } ; // NTSC-J, NTSC-U, PAL
internal static readonly string [ ] HEADER_RSBOX = { "GPXJ" , "GPXE" , "GPXP" } ; // NTSC-J, NTSC-U, PAL
2016-06-20 04:22:43 +00:00
2019-03-31 04:28:32 +00:00
/// <summary>Determines the type of the provided save data.</summary>
/// <param name="data">Save data of which to determine the origins of</param>
2016-06-28 06:03:57 +00:00
/// <returns>Version Identifier or Invalid if type cannot be determined.</returns>
2019-06-09 02:56:11 +00:00
private static GameVersion GetSAVType ( byte [ ] data )
2016-06-20 04:22:43 +00:00
{
2019-06-09 02:56:11 +00:00
GameVersion ver ;
if ( ( ver = GetIsG1SAV ( data ) ) ! = GameVersion . Invalid )
return ver ;
if ( ( ver = GetIsG2SAV ( data ) ) ! = GameVersion . Invalid )
return ver ;
if ( ( ver = GetIsG3SAV ( data ) ) ! = GameVersion . Invalid )
return ver ;
if ( ( ver = GetIsG4SAV ( data ) ) ! = GameVersion . Invalid )
return ver ;
if ( ( ver = GetIsG5SAV ( data ) ) ! = GameVersion . Invalid )
return ver ;
if ( ( ver = GetIsG6SAV ( data ) ) ! = GameVersion . Invalid )
return ver ;
if ( ( ver = GetIsG7SAV ( data ) ) ! = GameVersion . Invalid )
return ver ;
2019-09-23 23:56:47 +00:00
if ( ( ver = GetIsG8SAV ( data ) ) ! = GameVersion . Invalid )
return ver ;
2016-10-12 02:11:24 +00:00
2018-11-14 03:18:29 +00:00
if ( GetIsBelugaSAV ( data ) ! = GameVersion . Invalid )
return GameVersion . GG ;
2017-06-18 01:37:19 +00:00
if ( GetIsG3COLOSAV ( data ) ! = GameVersion . Invalid )
2016-10-12 02:11:24 +00:00
return GameVersion . COLO ;
2017-06-18 01:37:19 +00:00
if ( GetIsG3XDSAV ( data ) ! = GameVersion . Invalid )
2016-10-12 02:11:24 +00:00
return GameVersion . XD ;
2017-06-18 01:37:19 +00:00
if ( GetIsG3BOXSAV ( data ) ! = GameVersion . Invalid )
2016-10-12 02:11:24 +00:00
return GameVersion . RSBOX ;
2017-06-18 01:37:19 +00:00
if ( GetIsG4BRSAV ( data ) ! = GameVersion . Invalid )
2016-10-12 02:11:24 +00:00
return GameVersion . BATREV ;
2019-01-03 05:56:33 +00:00
if ( GetIsBank7 ( data ) ) // pokebank
2019-06-09 02:56:11 +00:00
return GameVersion . Gen7 ;
2019-01-03 05:56:33 +00:00
if ( GetIsBank4 ( data ) ) // pokestock
2019-06-09 02:56:11 +00:00
return GameVersion . Gen4 ;
2019-01-03 05:56:33 +00:00
if ( GetIsBank3 ( data ) ) // pokestock
2019-06-09 02:56:11 +00:00
return GameVersion . Gen3 ;
2019-01-15 05:31:53 +00:00
if ( GetIsRanch4 ( data ) ) // ranch
2019-06-09 02:56:11 +00:00
return GameVersion . DPPt ;
2018-02-24 21:03:32 +00:00
2016-10-12 02:11:24 +00:00
return GameVersion . Invalid ;
2016-06-20 04:22:43 +00:00
}
2018-07-29 20:27:48 +00:00
2017-09-28 06:22:41 +00:00
/// <summary>
2019-03-31 04:28:32 +00:00
/// Determines if a Gen1/2 Pokémon List is Invalid
2017-09-28 06:22:41 +00:00
/// </summary>
/// <param name="data">Save data</param>
/// <param name="offset">Offset the list starts at</param>
/// <param name="listCount">Max count of Pokémon in the list</param>
/// <returns>True if a valid list, False otherwise</returns>
private static bool IsG12ListValid ( byte [ ] data , int offset , int listCount )
{
byte num_entries = data [ offset ] ;
return num_entries < = listCount & & data [ offset + 1 + num_entries ] = = 0xFF ;
}
2018-07-29 20:27:48 +00:00
2019-03-31 04:28:32 +00:00
/// <summary>Checks to see if the data belongs to a Gen1 save</summary>
2016-08-27 11:33:21 +00:00
/// <param name="data">Save data of which to determine the type</param>
/// <returns>Version Identifier or Invalid if type cannot be determined.</returns>
2017-06-18 01:37:19 +00:00
internal static GameVersion GetIsG1SAV ( byte [ ] data )
2016-08-27 11:33:21 +00:00
{
if ( data . Length ! = SIZE_G1RAW & & data . Length ! = SIZE_G1BAT )
return GameVersion . Invalid ;
// Check if it's not an american save or a japanese save
2017-06-18 01:37:19 +00:00
if ( ! ( GetIsG1SAVU ( data ) | | GetIsG1SAVJ ( data ) ) )
2016-08-27 11:33:21 +00:00
return GameVersion . Invalid ;
// I can't actually detect which game version, because it's not stored anywhere.
// If you can think of anything to do here, please implement :)
return GameVersion . RBY ;
}
2018-07-29 20:27:48 +00:00
2019-03-31 04:28:32 +00:00
/// <summary>Checks to see if the data belongs to an International Gen1 save</summary>
2016-08-27 11:33:21 +00:00
/// <param name="data">Save data of which to determine the region</param>
2019-03-31 04:28:32 +00:00
/// <returns>True if a valid International save, False otherwise.</returns>
2017-06-18 01:37:19 +00:00
private static bool GetIsG1SAVU ( byte [ ] data )
2016-08-27 11:33:21 +00:00
{
2017-09-28 06:22:41 +00:00
return IsG12ListValid ( data , 0x2F2C , 20 ) & & IsG12ListValid ( data , 0x30C0 , 20 ) ;
2016-08-27 11:33:21 +00:00
}
2018-07-29 20:27:48 +00:00
2019-03-31 04:28:32 +00:00
/// <summary>Checks to see if the data belongs to a Japanese Gen1 save</summary>
2016-08-27 11:33:21 +00:00
/// <param name="data">Save data of which to determine the region</param>
2019-03-31 04:28:32 +00:00
/// <returns>True if a valid Japanese save, False otherwise.</returns>
2017-06-18 01:37:19 +00:00
internal static bool GetIsG1SAVJ ( byte [ ] data )
2016-08-27 11:33:21 +00:00
{
2017-09-28 06:22:41 +00:00
return IsG12ListValid ( data , 0x2ED5 , 30 ) & & IsG12ListValid ( data , 0x302D , 30 ) ;
2016-08-27 11:33:21 +00:00
}
2018-07-29 20:27:48 +00:00
2019-03-31 04:28:32 +00:00
/// <summary>Checks to see if the data belongs to a Gen2 save</summary>
2016-09-02 21:20:39 +00:00
/// <param name="data">Save data of which to determine the type</param>
/// <returns>Version Identifier or Invalid if type cannot be determined.</returns>
2017-06-18 01:37:19 +00:00
internal static GameVersion GetIsG2SAV ( byte [ ] data )
2016-09-02 21:20:39 +00:00
{
2018-01-26 02:30:09 +00:00
if ( ! SIZES_2 . Contains ( data . Length ) )
2016-09-02 21:20:39 +00:00
return GameVersion . Invalid ;
2019-03-31 04:28:32 +00:00
// Check if it's not an International, Japanese, or Korean save file
2017-09-11 02:56:21 +00:00
GameVersion result ;
if ( ( result = GetIsG2SAVU ( data ) ) ! = GameVersion . Invalid )
return result ;
if ( ( result = GetIsG2SAVJ ( data ) ) ! = GameVersion . Invalid )
return result ;
if ( ( result = GetIsG2SAVK ( data ) ) ! = GameVersion . Invalid )
return result ;
2016-09-02 21:20:39 +00:00
return GameVersion . Invalid ;
}
2018-07-29 20:27:48 +00:00
2019-03-31 04:28:32 +00:00
/// <summary>Checks to see if the data belongs to an International (not Japanese or Korean) Gen2 save</summary>
2016-09-02 21:20:39 +00:00
/// <param name="data">Save data of which to determine the region</param>
2019-03-31 04:28:32 +00:00
/// <returns>True if a valid International save, False otherwise.</returns>
2017-06-18 01:37:19 +00:00
private static GameVersion GetIsG2SAVU ( byte [ ] data )
2016-09-02 21:20:39 +00:00
{
2017-10-17 00:33:09 +00:00
if ( IsG12ListValid ( data , 0x288A , 20 ) & & IsG12ListValid ( data , 0x2D6C , 20 ) )
2016-09-02 21:20:39 +00:00
return GameVersion . GS ;
2017-10-17 00:33:09 +00:00
if ( IsG12ListValid ( data , 0x2865 , 20 ) & & IsG12ListValid ( data , 0x2D10 , 20 ) )
2016-09-02 21:20:39 +00:00
return GameVersion . C ;
return GameVersion . Invalid ;
}
2018-07-29 20:27:48 +00:00
2019-03-31 04:28:32 +00:00
/// <summary>Checks to see if the data belongs to a Japanese Gen2 save</summary>
2016-09-02 21:20:39 +00:00
/// <param name="data">Save data of which to determine the region</param>
2017-09-28 06:22:41 +00:00
/// <returns>True if a valid Japanese save, False otherwise.</returns>
2017-06-18 01:37:19 +00:00
internal static GameVersion GetIsG2SAVJ ( byte [ ] data )
2016-09-02 21:20:39 +00:00
{
2017-10-17 00:33:09 +00:00
if ( ! IsG12ListValid ( data , 0x2D10 , 30 ) )
2017-09-28 06:22:41 +00:00
return GameVersion . Invalid ;
2017-10-17 00:33:09 +00:00
if ( IsG12ListValid ( data , 0x283E , 30 ) )
2016-09-02 21:20:39 +00:00
return GameVersion . GS ;
2017-10-17 00:33:09 +00:00
if ( IsG12ListValid ( data , 0x281A , 30 ) )
2016-09-02 21:20:39 +00:00
return GameVersion . C ;
return GameVersion . Invalid ;
}
2018-07-29 20:27:48 +00:00
2019-03-31 04:28:32 +00:00
/// <summary>Checks to see if the data belongs to a Korean Gen2 save</summary>
2017-09-11 02:56:21 +00:00
/// <param name="data">Save data of which to determine the region</param>
2017-09-28 06:22:41 +00:00
/// <returns>True if a valid Korean save, False otherwise.</returns>
2017-09-11 02:56:21 +00:00
internal static GameVersion GetIsG2SAVK ( byte [ ] data )
{
2017-10-17 00:33:09 +00:00
if ( IsG12ListValid ( data , 0x2DAE , 20 ) & & IsG12ListValid ( data , 0x28CC , 20 ) )
2017-09-28 06:22:41 +00:00
return GameVersion . GS ;
return GameVersion . Invalid ;
2017-09-11 02:56:21 +00:00
}
2018-07-29 20:27:48 +00:00
2019-03-31 04:28:32 +00:00
/// <summary>Checks to see if the data belongs to a Gen3 save</summary>
2016-06-28 06:03:57 +00:00
/// <param name="data">Save data of which to determine the type</param>
/// <returns>Version Identifier or Invalid if type cannot be determined.</returns>
2017-06-18 01:37:19 +00:00
internal static GameVersion GetIsG3SAV ( byte [ ] data )
2016-06-26 21:23:41 +00:00
{
if ( data . Length ! = SIZE_G3RAW & & data . Length ! = SIZE_G3RAWHALF )
2016-06-28 06:03:57 +00:00
return GameVersion . Invalid ;
2016-06-26 21:23:41 +00:00
2016-10-04 04:57:50 +00:00
// check the save file(s)
int count = data . Length / SIZE_G3RAWHALF ;
for ( int s = 0 ; s < count ; s + + )
{
2019-01-04 08:07:37 +00:00
const int blockcount = 14 ;
const int blocksize = 0x1000 ;
int ofs = blockcount * blocksize * s ;
int [ ] BlockOrder = new int [ blockcount ] ;
for ( int i = 0 ; i < BlockOrder . Length ; i + + )
BlockOrder [ i ] = BitConverter . ToUInt16 ( data , ( i * blocksize ) + 0xFF4 + ofs ) ;
if ( Array . FindIndex ( BlockOrder , i = > i > 0xD ) > = 0 ) // invalid block ID
2016-10-04 04:57:50 +00:00
continue ;
2016-06-26 21:23:41 +00:00
2016-10-04 04:57:50 +00:00
int Block0 = Array . IndexOf ( BlockOrder , 0 ) ;
2016-10-06 04:25:36 +00:00
// Sometimes not all blocks are present (start of game), yielding multiple block0's.
// Real 0th block comes before block1.
if ( BlockOrder [ 0 ] = = 1 & & Block0 ! = BlockOrder . Length - 1 )
continue ;
2019-01-04 08:07:37 +00:00
if ( Array . FindIndex ( BlockOrder , v = > v ! = 0 ) < 0 ) // all blocks are 0
2017-04-12 19:59:50 +00:00
continue ;
2019-01-04 08:07:37 +00:00
// Detect RS/E/FRLG
return SAV3 . GetVersion ( data , ( blocksize * Block0 ) + ofs ) ;
2016-06-28 06:03:57 +00:00
}
2016-10-04 04:57:50 +00:00
return GameVersion . Invalid ;
2016-06-26 21:23:41 +00:00
}
2018-07-29 20:27:48 +00:00
2019-03-31 04:28:32 +00:00
/// <summary>Checks to see if the data belongs to a Gen3 Box RS save</summary>
2016-09-19 05:47:31 +00:00
/// <param name="data">Save data of which to determine the type</param>
/// <returns>Version Identifier or Invalid if type cannot be determined.</returns>
2017-06-18 01:37:19 +00:00
internal static GameVersion GetIsG3BOXSAV ( byte [ ] data )
2016-09-19 05:47:31 +00:00
{
2017-10-18 06:19:34 +00:00
if ( data . Length ! = SIZE_G3BOX & & data . Length ! = SIZE_G3BOXGCI )
2016-09-19 05:47:31 +00:00
return GameVersion . Invalid ;
2017-06-11 07:23:04 +00:00
byte [ ] sav = data ;
2016-09-19 05:47:31 +00:00
// Verify first checksum
2017-09-28 06:22:41 +00:00
ushort chk = 0 ; // initial value
2017-06-11 07:23:04 +00:00
var ofs = data . Length - SIZE_G3BOX + 0x2000 ;
2017-09-28 06:22:41 +00:00
for ( int i = 0x4 ; i < 0x1FFC ; i + = 2 )
chk + = BigEndian . ToUInt16 ( sav , ofs + i ) ;
ushort chkA = chk ;
2016-09-19 05:47:31 +00:00
ushort chkB = ( ushort ) ( 0xF004 - chkA ) ;
2017-09-28 06:22:41 +00:00
ushort CHK_A = BigEndian . ToUInt16 ( sav , ofs + 0 ) ;
ushort CHK_B = BigEndian . ToUInt16 ( sav , ofs + 2 ) ;
2016-09-19 05:47:31 +00:00
return CHK_A = = chkA & & CHK_B = = chkB ? GameVersion . RSBOX : GameVersion . Invalid ;
}
2018-07-29 20:27:48 +00:00
2019-03-31 04:28:32 +00:00
/// <summary>Checks to see if the data belongs to a Colosseum save</summary>
2016-09-26 23:15:40 +00:00
/// <param name="data">Save data of which to determine the type</param>
/// <returns>Version Identifier or Invalid if type cannot be determined.</returns>
2017-06-18 01:37:19 +00:00
internal static GameVersion GetIsG3COLOSAV ( byte [ ] data )
2016-09-26 23:15:40 +00:00
{
2017-10-18 06:19:34 +00:00
if ( data . Length ! = SIZE_G3COLO & & data . Length ! = SIZE_G3COLOGCI )
2016-09-26 23:15:40 +00:00
return GameVersion . Invalid ;
// Check the intro bytes for each save slot
int offset = data . Length - SIZE_G3COLO ;
for ( int i = 0 ; i < 3 ; i + + )
{
2018-07-29 20:27:48 +00:00
var ofs = 0x6000 + offset + ( 0x1E000 * i ) ;
2017-09-28 06:22:41 +00:00
if ( BitConverter . ToUInt32 ( data , ofs ) ! = 0x00000101 )
2016-09-27 06:07:17 +00:00
return GameVersion . Invalid ;
}
return GameVersion . COLO ;
}
2018-07-29 20:27:48 +00:00
2019-03-31 04:28:32 +00:00
/// <summary>Checks to see if the data belongs to a Gen3 XD save</summary>
2016-09-27 06:07:17 +00:00
/// <param name="data">Save data of which to determine the type</param>
/// <returns>Version Identifier or Invalid if type cannot be determined.</returns>
2017-06-18 01:37:19 +00:00
internal static GameVersion GetIsG3XDSAV ( byte [ ] data )
2016-09-27 06:07:17 +00:00
{
2017-10-18 06:19:34 +00:00
if ( data . Length ! = SIZE_G3XD & & data . Length ! = SIZE_G3XDGCI )
2016-09-27 06:07:17 +00:00
return GameVersion . Invalid ;
// Check the intro bytes for each save slot
int offset = data . Length - SIZE_G3XD ;
for ( int i = 0 ; i < 2 ; i + + )
{
2018-07-29 20:27:48 +00:00
var ofs = 0x6000 + offset + ( 0x28000 * i ) ;
2017-09-28 06:22:41 +00:00
if ( ( BitConverter . ToUInt32 ( data , ofs ) & 0xFFFE _FFFF ) ! = 0x00000101 )
return GameVersion . Invalid ;
2016-09-26 23:15:40 +00:00
}
2017-09-28 06:22:41 +00:00
return GameVersion . XD ;
2016-09-26 23:15:40 +00:00
}
2018-07-29 20:27:48 +00:00
2019-03-31 04:28:32 +00:00
/// <summary>Checks to see if the data belongs to a Gen4 save</summary>
2016-06-26 18:46:08 +00:00
/// <param name="data">Save data of which to determine the type</param>
2016-06-28 06:03:57 +00:00
/// <returns>Version Identifier or Invalid if type cannot be determined.</returns>
2017-06-18 01:37:19 +00:00
internal static GameVersion GetIsG4SAV ( byte [ ] data )
2016-06-20 04:22:43 +00:00
{
if ( data . Length ! = SIZE_G4RAW )
2016-06-28 06:03:57 +00:00
return GameVersion . Invalid ;
2018-05-12 15:13:39 +00:00
2019-01-04 07:37:49 +00:00
// The block footers contain a u32 'size' followed by a u32 binary-coded-decimal timestamp(?)
// Korean savegames have a different timestamp from other localizations.
bool validSequence ( int offset )
2017-12-29 18:40:00 +00:00
{
2019-01-04 07:37:49 +00:00
var size = BitConverter . ToUInt32 ( data , offset - 0xC ) ;
if ( size ! = ( offset & 0xFFFF ) )
return false ;
var sdk = BitConverter . ToUInt32 ( data , offset - 0x8 ) ;
2019-01-04 08:07:37 +00:00
const int DATE_INT = 0x20060623 ;
const int DATE_KO = 0x20070903 ;
return sdk = = DATE_INT | | sdk = = DATE_KO ;
2017-12-29 18:40:00 +00:00
}
2019-01-04 07:37:49 +00:00
// Check the other save -- first save is done to the latter half of the binary.
// The second save should be all that is needed to check.
if ( validSequence ( 0x4C100 ) )
2016-06-28 06:03:57 +00:00
return GameVersion . DP ;
2019-01-04 07:37:49 +00:00
if ( validSequence ( 0x4CF2C ) )
2016-06-28 06:03:57 +00:00
return GameVersion . Pt ;
2019-01-04 07:37:49 +00:00
if ( validSequence ( 0x4F628 ) )
2016-09-13 01:01:26 +00:00
return GameVersion . HGSS ;
2016-06-28 06:03:57 +00:00
return GameVersion . Invalid ;
2016-06-20 04:22:43 +00:00
}
2018-07-29 20:27:48 +00:00
2019-03-31 04:28:32 +00:00
/// <summary>Checks to see if the data belongs to a Gen4 Battle Revolution save</summary>
2016-09-26 23:14:11 +00:00
/// <param name="data">Save data of which to determine the type</param>
/// <returns>Version Identifier or Invalid if type cannot be determined.</returns>
2017-06-18 01:37:19 +00:00
internal static GameVersion GetIsG4BRSAV ( byte [ ] data )
2016-09-26 23:14:11 +00:00
{
if ( data . Length ! = SIZE_G4BR )
return GameVersion . Invalid ;
byte [ ] sav = SAV4BR . DecryptPBRSaveData ( data ) ;
2017-10-18 06:19:34 +00:00
return SAV4BR . IsChecksumsValid ( sav ) ? GameVersion . BATREV : GameVersion . Invalid ;
2016-09-26 23:14:11 +00:00
}
2018-07-29 20:27:48 +00:00
2019-03-31 04:28:32 +00:00
/// <summary>Checks to see if the data belongs to a Gen5 save</summary>
2016-06-28 06:03:57 +00:00
/// <param name="data">Save data of which to determine the type</param>
/// <returns>Version Identifier or Invalid if type cannot be determined.</returns>
2017-06-18 01:37:19 +00:00
internal static GameVersion GetIsG5SAV ( byte [ ] data )
2016-06-20 04:22:43 +00:00
{
if ( data . Length ! = SIZE_G5RAW )
2016-06-28 06:03:57 +00:00
return GameVersion . Invalid ;
2016-06-20 04:22:43 +00:00
2019-01-04 08:07:37 +00:00
// check the checksum block validity; nobody would normally modify this region
2016-06-20 04:22:43 +00:00
ushort chk1 = BitConverter . ToUInt16 ( data , SIZE_G5BW - 0x100 + 0x8C + 0xE ) ;
2019-03-16 19:01:21 +00:00
ushort actual1 = Checksums . CRC16_CCITT ( data , SIZE_G5BW - 0x100 , 0x8C ) ;
2016-06-20 04:22:43 +00:00
if ( chk1 = = actual1 )
2016-06-28 06:03:57 +00:00
return GameVersion . BW ;
2016-06-20 04:22:43 +00:00
ushort chk2 = BitConverter . ToUInt16 ( data , SIZE_G5B2W2 - 0x100 + 0x94 + 0xE ) ;
2019-03-16 19:01:21 +00:00
ushort actual2 = Checksums . CRC16_CCITT ( data , SIZE_G5B2W2 - 0x100 , 0x94 ) ;
2016-06-20 04:22:43 +00:00
if ( chk2 = = actual2 )
2016-06-28 06:03:57 +00:00
return GameVersion . B2W2 ;
return GameVersion . Invalid ;
2016-06-20 04:22:43 +00:00
}
2018-07-29 20:27:48 +00:00
2019-03-31 04:28:32 +00:00
/// <summary>Checks to see if the data belongs to a Gen6 save</summary>
2016-06-28 06:03:57 +00:00
/// <param name="data">Save data of which to determine the type</param>
/// <returns>Version Identifier or Invalid if type cannot be determined.</returns>
2017-06-18 01:37:19 +00:00
private static GameVersion GetIsG6SAV ( byte [ ] data )
2016-06-20 04:22:43 +00:00
{
2019-01-04 08:07:37 +00:00
if ( data . Length ! = SIZE_G6XY & & data . Length ! = SIZE_G6ORAS & & data . Length ! = SIZE_G6ORASDEMO )
2016-06-28 06:03:57 +00:00
return GameVersion . Invalid ;
2016-06-20 04:22:43 +00:00
if ( BitConverter . ToUInt32 ( data , data . Length - 0x1F0 ) ! = BEEF )
2016-06-28 06:03:57 +00:00
return GameVersion . Invalid ;
2016-06-20 04:22:43 +00:00
2019-03-16 19:01:21 +00:00
if ( data . Length = = SIZE_G6XY )
return GameVersion . XY ;
if ( data . Length = = SIZE_G6ORAS )
return GameVersion . ORAS ;
return GameVersion . ORASDEMO ; // least likely
2016-06-20 04:22:43 +00:00
}
2018-07-29 20:27:48 +00:00
2019-03-31 04:28:32 +00:00
/// <summary>Checks to see if the data belongs to a Gen7 save</summary>
2017-01-14 21:10:36 +00:00
/// <param name="data">Save data of which to determine the type</param>
/// <returns>Version Identifier or Invalid if type cannot be determined.</returns>
2017-06-18 01:37:19 +00:00
private static GameVersion GetIsG7SAV ( byte [ ] data )
2016-10-12 02:11:24 +00:00
{
2017-10-18 06:19:34 +00:00
if ( data . Length ! = SIZE_G7SM & & data . Length ! = SIZE_G7USUM )
2016-10-20 01:19:01 +00:00
return GameVersion . Invalid ;
2017-10-18 06:19:34 +00:00
if ( BitConverter . ToUInt32 ( data , data . Length - 0x1F0 ) ! = BEEF )
return GameVersion . Invalid ;
2019-03-16 19:01:21 +00:00
return data . Length = = SIZE_G7SM ? GameVersion . SM : GameVersion . USUM ;
2016-10-12 02:11:24 +00:00
}
2016-06-26 18:46:08 +00:00
2018-11-14 03:18:29 +00:00
/// <summary>Determines if the input data belongs to a <see cref="SAV7b"/> save</summary>
/// <param name="data">Save data of which to determine the type</param>
/// <returns>Version Identifier or Invalid if type cannot be determined.</returns>
private static GameVersion GetIsBelugaSAV ( byte [ ] data )
{
if ( data . Length ! = SIZE_G7GG )
return GameVersion . Invalid ;
const int actualLength = 0xB8800 ;
if ( BitConverter . ToUInt32 ( data , actualLength - 0x1F0 ) ! = BEEF ) // beef table start
return GameVersion . Invalid ;
if ( BitConverter . ToUInt16 ( data , actualLength - 0x200 + 0xB0 ) ! = 0x13 ) // check a block number to double check
return GameVersion . Invalid ;
return GameVersion . GG ;
}
2019-09-23 23:56:47 +00:00
/// <summary>Checks to see if the data belongs to a Gen7 save</summary>
/// <param name="data">Save data of which to determine the type</param>
/// <returns>Version Identifier or Invalid if type cannot be determined.</returns>
private static GameVersion GetIsG8SAV ( byte [ ] data )
{
if ( data . Length ! = SIZE_G8SWSH )
return GameVersion . Invalid ;
if ( BitConverter . ToUInt32 ( data , data . Length - 0x1F0 ) ! = BEEF )
return GameVersion . Invalid ;
return GameVersion . SWSH ;
}
2018-02-24 21:03:32 +00:00
private static bool GetIsBank7 ( byte [ ] data ) = > data . Length = = SIZE_G7BANK & & data [ 0 ] ! = 0 ;
2019-01-03 05:56:33 +00:00
private static bool GetIsBank4 ( byte [ ] data ) = > data . Length = = SIZE_G4BANK & & BitConverter . ToUInt32 ( data , 0x3FC00 ) ! = 0 ; // box name present
private static bool GetIsBank3 ( byte [ ] data ) = > data . Length = = SIZE_G4BANK & & BitConverter . ToUInt32 ( data , 0x3FC00 ) = = 0 ; // size collision with ^
2019-01-25 23:24:01 +00:00
private static bool GetIsRanch4 ( byte [ ] data ) = > ( data . Length = = SIZE_G4RANCH & & BigEndian . ToUInt32 ( data , 0x22AC ) ! = 0 ) | | ( data . Length = = SIZE_G4RANCH_PLAT & & BigEndian . ToUInt32 ( data , 0x268C ) ! = 0 ) ;
2016-08-27 20:05:01 +00:00
2018-07-17 04:00:43 +00:00
/// <summary>Creates an instance of a SaveFile using the given save data.</summary>
/// <param name="path">File location from which to create a SaveFile.</param>
/// <returns>An appropriate type of save file for the given data, or null if the save data is invalid.</returns>
2018-07-15 20:35:58 +00:00
public static SaveFile GetVariantSAV ( string path )
{
var data = File . ReadAllBytes ( path ) ;
var sav = GetVariantSAV ( data ) ;
2018-07-17 04:00:43 +00:00
sav ? . SetFileInfo ( path ) ;
2018-07-15 20:35:58 +00:00
return sav ;
}
2016-06-28 06:03:57 +00:00
/// <summary>Creates an instance of a SaveFile using the given save data.</summary>
2016-06-26 18:46:08 +00:00
/// <param name="data">Save data from which to create a SaveFile.</param>
/// <returns>An appropriate type of save file for the given data, or null if the save data is invalid.</returns>
2017-06-18 01:37:19 +00:00
public static SaveFile GetVariantSAV ( byte [ ] data )
2016-06-20 04:22:43 +00:00
{
2016-10-12 02:11:24 +00:00
// Pre-check for header/footer signatures
2019-01-21 05:55:28 +00:00
CheckHeaderFooter ( ref data , out var header , out var footer ) ;
2018-08-03 03:11:42 +00:00
var sav = GetVariantSAVInternal ( data ) ;
if ( sav = = null )
return null ;
sav . Header = header ;
sav . Footer = footer ;
return sav ;
}
2016-10-12 02:11:24 +00:00
2018-08-03 03:11:42 +00:00
private static SaveFile GetVariantSAVInternal ( byte [ ] data )
{
2019-06-09 02:56:11 +00:00
switch ( GetSAVType ( data ) )
2016-06-20 04:22:43 +00:00
{
2016-10-12 02:11:24 +00:00
// Main Games
2019-06-09 02:56:11 +00:00
case GameVersion . RBY : return new SAV1 ( data ) ;
case GameVersion . GS :
case GameVersion . C : return new SAV2 ( data ) ;
case GameVersion . RS :
case GameVersion . E :
case GameVersion . FRLG : return new SAV3 ( data ) ;
2019-09-03 02:30:58 +00:00
case GameVersion . DP : return new SAV4DP ( data ) ;
case GameVersion . Pt : return new SAV4Pt ( data ) ;
case GameVersion . HGSS : return new SAV4HGSS ( data ) ;
2019-06-09 02:56:11 +00:00
case GameVersion . BW : return new SAV5BW ( data ) ;
case GameVersion . B2W2 : return new SAV5B2W2 ( data ) ;
case GameVersion . XY : return new SAV6XY ( data ) ;
case GameVersion . ORAS : return new SAV6AO ( data ) ;
case GameVersion . ORASDEMO : return new SAV6AODemo ( data ) ;
case GameVersion . SM :
return new SAV7SM ( data ) ;
case GameVersion . USUM :
return new SAV7USUM ( data ) ;
2016-10-12 02:11:24 +00:00
2019-09-23 23:56:47 +00:00
case GameVersion . SWSH :
return new SAV8SWSH ( data ) ;
2016-10-12 02:11:24 +00:00
// Side Games
2018-08-03 03:11:42 +00:00
case GameVersion . COLO : return new SAV3Colosseum ( data ) ;
case GameVersion . XD : return new SAV3XD ( data ) ;
case GameVersion . RSBOX : return new SAV3RSBox ( data ) ;
case GameVersion . BATREV : return new SAV4BR ( data ) ;
2018-11-14 03:18:29 +00:00
case GameVersion . GG : return new SAV7b ( data ) ;
2018-02-24 21:03:32 +00:00
// Bulk Storage
2019-06-09 02:56:11 +00:00
case GameVersion . Gen3 : return new Bank3 ( data ) ;
case GameVersion . DPPt : return new SAV4Ranch ( data ) ;
case GameVersion . Gen4 : return new Bank4 ( data ) ;
case GameVersion . Gen7 : return Bank7 . GetBank7 ( data ) ;
2018-05-12 15:13:39 +00:00
2016-10-12 02:11:24 +00:00
// No pattern matched
default : return null ;
2016-06-20 04:22:43 +00:00
}
}
2018-07-29 20:27:48 +00:00
2017-06-18 01:37:19 +00:00
public static SaveFile GetVariantSAV ( SAV3GCMemoryCard MC )
2017-04-02 14:53:46 +00:00
{
// Pre-check for header/footer signatures
SaveFile sav ;
2017-04-02 20:42:42 +00:00
byte [ ] data = MC . SelectedSaveData ;
2019-01-21 05:55:28 +00:00
CheckHeaderFooter ( ref data , out var header , out var footer ) ;
2017-04-02 14:53:46 +00:00
switch ( MC . SelectedGameVersion )
{
// Side Games
2017-04-21 02:51:35 +00:00
case GameVersion . COLO : sav = new SAV3Colosseum ( data , MC ) ; break ;
2017-04-02 14:53:46 +00:00
case GameVersion . XD : sav = new SAV3XD ( data , MC ) ; break ;
case GameVersion . RSBOX : sav = new SAV3RSBox ( data , MC ) ; break ;
// No pattern matched
default : return null ;
}
sav . Header = header ;
sav . Footer = footer ;
return sav ;
}
2016-06-26 18:46:08 +00:00
2017-01-14 21:10:36 +00:00
/// <summary>
/// Creates an instance of a SaveFile with a blank base.
/// </summary>
/// <param name="Game">Version to create the save file for.</param>
/// <param name="OT">Trainer Name</param>
2019-02-24 21:57:10 +00:00
/// <returns>Blank save file from the requested game, null if no game exists for that <see cref="GameVersion"/>.</returns>
2017-06-18 01:37:19 +00:00
public static SaveFile GetBlankSAV ( GameVersion Game , string OT )
2017-01-14 21:10:36 +00:00
{
2017-06-18 01:37:19 +00:00
var SAV = GetBlankSAV ( Game ) ;
2017-01-14 21:10:36 +00:00
if ( SAV = = null )
return null ;
SAV . Game = ( int ) Game ;
SAV . OT = OT ;
// Secondary Properties may not be used but can be filled in as template.
SAV . TID = 12345 ;
SAV . SID = 54321 ;
2017-10-23 04:01:08 +00:00
SAV . Language = ( int ) LanguageID . English ; // English
2017-01-14 21:10:36 +00:00
SAV . Country = 49 ; // USA
SAV . SubRegion = 7 ; // CA
SAV . ConsoleRegion = 1 ; // Americas
return SAV ;
}
2018-07-29 20:27:48 +00:00
2017-01-14 21:10:36 +00:00
/// <summary>
/// Creates an instance of a SaveFile with a blank base.
/// </summary>
/// <param name="Game">Version to create the save file for.</param>
2019-02-24 21:57:10 +00:00
/// <returns>Blank save file from the requested game, null if no game exists for that <see cref="GameVersion"/>.</returns>
2017-06-18 01:37:19 +00:00
private static SaveFile GetBlankSAV ( GameVersion Game )
2017-01-14 21:10:36 +00:00
{
switch ( Game )
{
2019-01-22 04:06:02 +00:00
case GameVersion . RD : case GameVersion . BU : case GameVersion . GN : case GameVersion . YW :
2017-01-14 21:10:36 +00:00
case GameVersion . RBY :
2019-07-21 19:30:21 +00:00
return new SAV1 ( version : Game ) ;
2017-01-14 21:10:36 +00:00
2019-07-14 22:13:13 +00:00
case GameVersion . GS : case GameVersion . GD : case GameVersion . SV :
2019-07-21 19:30:21 +00:00
return new SAV2 ( version : GameVersion . GS ) ;
2019-07-14 22:13:13 +00:00
case GameVersion . GSC : case GameVersion . C :
2019-07-21 19:30:21 +00:00
return new SAV2 ( version : GameVersion . C ) ;
2017-01-14 21:10:36 +00:00
case GameVersion . R : case GameVersion . S : case GameVersion . E : case GameVersion . FR : case GameVersion . LG :
2019-06-09 02:56:11 +00:00
return new SAV3 ( version : Game ) ;
2019-01-05 23:42:20 +00:00
case GameVersion . FRLG :
2019-06-09 02:56:11 +00:00
return new SAV3 ( version : GameVersion . FR ) ;
2019-01-05 23:42:20 +00:00
case GameVersion . RS :
2019-06-09 02:56:11 +00:00
return new SAV3 ( version : GameVersion . R ) ;
2019-01-05 23:42:20 +00:00
case GameVersion . RSE :
2019-06-09 02:56:11 +00:00
return new SAV3 ( version : GameVersion . E ) ;
2019-01-05 23:42:20 +00:00
case GameVersion . CXD :
2017-01-14 21:10:36 +00:00
case GameVersion . COLO :
return new SAV3Colosseum ( ) ;
case GameVersion . XD :
return new SAV3XD ( ) ;
case GameVersion . RSBOX :
return new SAV3RSBox ( ) ;
case GameVersion . D : case GameVersion . P : case GameVersion . DP :
2019-01-05 23:42:20 +00:00
case GameVersion . DPPt :
2019-09-03 02:30:58 +00:00
return new SAV4DP ( ) ;
2017-01-14 21:10:36 +00:00
case GameVersion . Pt :
2019-09-03 02:30:58 +00:00
return new SAV4Pt ( ) ;
2017-01-14 21:10:36 +00:00
case GameVersion . HG : case GameVersion . SS : case GameVersion . HGSS :
2019-09-03 02:30:58 +00:00
return new SAV4HGSS ( ) ;
2017-01-14 21:10:36 +00:00
case GameVersion . B : case GameVersion . W : case GameVersion . BW :
2019-06-09 02:56:11 +00:00
return new SAV5BW ( ) ;
2017-01-14 21:10:36 +00:00
case GameVersion . B2 : case GameVersion . W2 : case GameVersion . B2W2 :
2019-06-09 02:56:11 +00:00
return new SAV5B2W2 ( ) ;
2017-01-14 21:10:36 +00:00
case GameVersion . X : case GameVersion . Y : case GameVersion . XY :
2019-06-09 02:56:11 +00:00
return new SAV6XY ( ) ;
2017-01-14 21:10:36 +00:00
case GameVersion . ORASDEMO :
2019-06-09 02:56:11 +00:00
return new SAV6AODemo ( ) ;
2017-01-14 21:10:36 +00:00
case GameVersion . OR : case GameVersion . AS : case GameVersion . ORAS :
2019-06-09 02:56:11 +00:00
return new SAV6AO ( ) ;
2017-01-14 21:10:36 +00:00
case GameVersion . SN : case GameVersion . MN : case GameVersion . SM :
2019-06-09 02:56:11 +00:00
return new SAV7SM ( ) ;
2017-09-20 04:35:30 +00:00
case GameVersion . US : case GameVersion . UM : case GameVersion . USUM :
2019-06-09 02:56:11 +00:00
return new SAV7USUM ( ) ;
2019-01-05 23:42:20 +00:00
case GameVersion . GO :
2018-11-14 03:18:29 +00:00
case GameVersion . GP : case GameVersion . GE : case GameVersion . GG :
2019-06-09 02:56:11 +00:00
return new SAV7b ( ) ;
2017-01-14 21:10:36 +00:00
2019-09-23 23:56:47 +00:00
case GameVersion . SW : case GameVersion . SH : case GameVersion . SWSH :
return new SAV8SWSH ( ) ;
2017-01-14 21:10:36 +00:00
default :
return null ;
}
}
2018-07-29 20:27:48 +00:00
2017-01-14 21:10:36 +00:00
/// <summary>
/// Creates an instance of a SaveFile with a blank base.
/// </summary>
/// <param name="generation">Generation of the Save File.</param>
/// <param name="OT">Trainer Name</param>
/// <returns>Save File for that generation.</returns>
2017-06-18 01:37:19 +00:00
public static SaveFile GetBlankSAV ( int generation , string OT )
2017-01-14 21:10:36 +00:00
{
2017-06-18 01:37:19 +00:00
var ver = GameUtil . GetVersion ( generation ) ;
return GetBlankSAV ( ver , OT ) ;
2017-01-14 21:10:36 +00:00
}
2016-08-05 03:48:52 +00:00
/// <summary>
2018-07-15 20:35:58 +00:00
/// Retrieves possible save file paths from the provided <see cref="folderPath"/>.
2016-08-05 03:48:52 +00:00
/// </summary>
/// <param name="folderPath">Folder to look within</param>
/// <param name="deep">Search all subfolders</param>
2017-09-16 18:38:58 +00:00
/// <param name="result">If this function returns true, full path of all <see cref="SaveFile"/> that match criteria. If this function returns false, the error message, or null if the directory could not be found</param>
2017-01-08 08:14:34 +00:00
/// <returns>Boolean indicating whether or not operation was successful.</returns>
2017-06-18 01:37:19 +00:00
public static bool GetSavesFromFolder ( string folderPath , bool deep , out IEnumerable < string > result )
2016-08-05 03:48:52 +00:00
{
if ( ! Directory . Exists ( folderPath ) )
2017-01-08 07:54:09 +00:00
{
result = null ;
return false ;
}
2016-12-03 17:32:18 +00:00
try
{
var searchOption = deep ? SearchOption . AllDirectories : SearchOption . TopDirectoryOnly ;
2019-08-14 23:45:21 +00:00
// force evaluation so that an invalid path will throw before we return true/false.
// EnumerateFiles throws an exception while iterating, which won't be caught by the trycatch here.
var files = Directory . GetFiles ( folderPath , "*" , searchOption ) ;
2019-10-08 01:40:09 +00:00
static int safelen ( string file )
2018-05-22 00:02:48 +00:00
{
try { return ( int ) new FileInfo ( file ) . Length ; }
catch { return - 1 ; } // Bad File / Locked
}
result = files . Where ( f = > IsSizeValid ( safelen ( f ) ) ) ;
2017-01-08 07:54:09 +00:00
return true ;
2016-12-03 17:32:18 +00:00
}
catch ( ArgumentException )
{
2018-09-07 03:35:55 +00:00
result = new [ ] { MsgFileLoadFailAuto + Environment . NewLine + folderPath , MsgFileLoadFailAutoAdvise + Environment . NewLine + MsgFileLoadFailAutoCause } ;
2017-01-08 07:54:09 +00:00
return false ;
2016-12-03 17:32:18 +00:00
}
2016-08-05 03:48:52 +00:00
}
2016-06-26 18:46:08 +00:00
/// <summary>
2016-10-12 02:11:24 +00:00
/// Determines whether the save data size is valid for autodetecting saves.
2016-06-26 18:46:08 +00:00
/// </summary>
/// <param name="size">Size in bytes of the save data</param>
/// <returns>A boolean indicating whether or not the save data size is valid.</returns>
2019-01-23 03:32:22 +00:00
public static bool IsSizeValid ( int size ) = > SIZES . Contains ( size ) ;
2016-06-20 04:22:43 +00:00
2019-01-21 05:55:28 +00:00
/// <summary>
/// Checks the provided <see cref="input"/> and pulls out any <see cref="header"/> and/or <see cref="footer"/> arrays.
/// </summary>
/// <param name="input">Input byte array to strip</param>
/// <param name="header">Header data</param>
/// <param name="footer">Footer data</param>
private static void CheckHeaderFooter ( ref byte [ ] input , out byte [ ] header , out byte [ ] footer )
2016-09-24 01:47:03 +00:00
{
2019-01-21 05:55:28 +00:00
header = Array . Empty < byte > ( ) ; footer = Array . Empty < byte > ( ) ;
2019-02-19 05:59:57 +00:00
if ( ( input . Length & 0xFF ) = = 0 ) // catch most non-header/footers
return ;
2016-09-24 01:47:03 +00:00
if ( input . Length > SIZE_G4RAW ) // DeSmuME Gen4/5 DSV
{
2018-03-22 00:01:07 +00:00
if ( input . Length = = 0x800A4 ) // Action Replay
{
2019-10-08 01:40:09 +00:00
header = input . Slice ( 0 , 0xA4 ) ;
input = input . SliceEnd ( 0xA4 ) ;
2018-03-22 00:01:07 +00:00
return ;
}
2018-10-13 15:02:55 +00:00
int start = input . Length - FOOTER_DSV . Length ;
for ( int i = 0 ; i < FOOTER_DSV . Length ; i + + )
{
if ( FOOTER_DSV [ i ] ! = input [ start + i ] )
return ;
}
2019-10-08 01:40:09 +00:00
footer = input . SliceEnd ( SIZE_G4RAW ) ;
input = input . Slice ( 0 , SIZE_G4RAW ) ;
2016-09-24 01:47:03 +00:00
}
2017-09-28 06:22:41 +00:00
else if ( input . Length = = SIZE_G3BOXGCI )
2016-09-24 01:47:03 +00:00
{
2017-09-28 06:22:41 +00:00
if ( ! IsGameMatchHeader ( HEADER_RSBOX , input ) )
return ; // not gci
2019-10-08 01:40:09 +00:00
header = input . Slice ( 0 , SIZE_G3BOXGCI - SIZE_G3BOX ) ;
input = input . SliceEnd ( header . Length ) ;
2016-09-24 01:47:03 +00:00
}
2017-09-28 06:22:41 +00:00
else if ( input . Length = = SIZE_G3COLOGCI )
2016-09-26 23:15:40 +00:00
{
2017-09-28 06:22:41 +00:00
if ( ! IsGameMatchHeader ( HEADER_COLO , input ) )
return ; // not gci
2019-10-08 01:40:09 +00:00
header = input . Slice ( 0 , SIZE_G3COLOGCI - SIZE_G3COLO ) ;
input = input . SliceEnd ( header . Length ) ;
2016-09-26 23:15:40 +00:00
}
2017-09-28 06:22:41 +00:00
else if ( input . Length = = SIZE_G3XDGCI )
2016-09-26 23:15:40 +00:00
{
2017-09-28 06:22:41 +00:00
if ( ! IsGameMatchHeader ( HEADER_XD , input ) )
return ; // not gci
2019-10-08 01:40:09 +00:00
header = input . Slice ( 0 , SIZE_G3XDGCI - SIZE_G3XD ) ;
input = input . SliceEnd ( header . Length ) ;
2018-10-13 15:02:55 +00:00
}
2019-10-08 01:40:09 +00:00
static bool IsGameMatchHeader ( IEnumerable < string > headers , byte [ ] data ) = > headers . Contains ( Encoding . ASCII . GetString ( data , 0 , 4 ) ) ;
2016-09-24 01:47:03 +00:00
}
2016-06-20 04:22:43 +00:00
2018-09-03 01:02:25 +00:00
/// <summary>
/// Force loads the provided <see cref="sav"/> to the requested <see cref="ver"/>.
/// </summary>
/// <param name="sav">SaveFile data to force</param>
/// <param name="ver">Version to retrieve for</param>
/// <returns>New <see cref="SaveFile"/> object.</returns>
2019-07-06 05:02:29 +00:00
public static SAV3 GetG3SaveOverride ( SaveFile sav , GameVersion ver )
2018-09-03 01:02:25 +00:00
{
switch ( ver ) // Reset save file info
{
case GameVersion . R :
case GameVersion . S :
case GameVersion . RS :
return new SAV3 ( sav . BAK , GameVersion . RS ) ;
case GameVersion . E :
return new SAV3 ( sav . BAK , GameVersion . E ) ;
case GameVersion . FRLG :
case GameVersion . FR :
case GameVersion . LG :
return new SAV3 ( sav . BAK , GameVersion . FRLG ) ;
default :
return null ;
}
}
/// <summary>
/// Gets the <see cref="PersonalTable"/> for a Gen3 save file.
/// </summary>
/// <param name="ver">Version to retrieve for</param>
/// <returns>Reference to the <see cref="PersonalTable"/>.</returns>
public static PersonalTable GetG3Personal ( GameVersion ver )
{
switch ( ver )
{
case GameVersion . FRLG :
case GameVersion . FR :
return PersonalTable . FR ;
case GameVersion . LG :
return PersonalTable . LG ;
case GameVersion . E :
return PersonalTable . E ;
case GameVersion . R :
case GameVersion . S :
case GameVersion . RS :
return PersonalTable . RS ;
default :
return null ;
}
}
2016-06-20 04:22:43 +00:00
}
}