First draft of Gen4 pkm parsing.

This commit is contained in:
Greg Edwards
2015-02-06 21:17:25 -05:00
parent 4a1a85904f
commit 79e2b923f1
16 changed files with 616 additions and 85 deletions

View File

@@ -252,18 +252,18 @@ namespace VeekunImport
int[] fieldsInt = fields.Select(s => Convert.ToInt32(s)).ToArray();
FormStats f = new FormStats(null, fieldsInt[0], (Generations)generation, fieldsInt[1], fieldsInt[2],
new StatValues(fieldsInt[3],
new IntStatValues(fieldsInt[3],
fieldsInt[4],
fieldsInt[5],
fieldsInt[6],
fieldsInt[7],
fieldsInt[8]),
new StatValues(fieldsInt[9],
fieldsInt[10],
fieldsInt[11],
fieldsInt[12],
fieldsInt[13],
fieldsInt[14])
new ByteStatValues((byte)fieldsInt[9],
(byte)fieldsInt[10],
(byte)fieldsInt[11],
(byte)fieldsInt[12],
(byte)fieldsInt[13],
(byte)fieldsInt[14])
);
db.PokedexInsertFormStats(f);

View File

@@ -76,12 +76,20 @@
<Compile Include="Structures\BattleVideoRecord5.cs" />
<Compile Include="Structures\BinarySerializableBase.cs" />
<Compile Include="Structures\BoxRecord4.cs" />
<Compile Include="Structures\ByteStatValues.cs" />
<Compile Include="Structures\ContestStatValues.cs" />
<Compile Include="Structures\DressupRecord4.cs" />
<Compile Include="Structures\GtsRecord5.cs" />
<Compile Include="Structures\Enums.cs" />
<Compile Include="Structures\GtsRecord4.cs" />
<Compile Include="Structures\IvStatValues.cs" />
<Compile Include="Structures\MoveSlot.cs" />
<Compile Include="Structures\MusicalRecord5.cs" />
<Compile Include="Structures\Pokemon4.cs" />
<Compile Include="Structures\PokemonBase.cs" />
<Compile Include="Structures\IntStatValues.cs" />
<Compile Include="Structures\StatValues.cs" />
<Compile Include="Structures\StatValuesBase.cs" />
<Compile Include="Structures\TrainerProfile4.cs" />
<Compile Include="Structures\TrainerProfile5.cs" />
<Compile Include="Support\AssertHelper.cs" />

View File

@@ -11,7 +11,7 @@ namespace PkmnFoundations.Pokedex
public class FormStats : PokedexRecordBase
{
public FormStats(Pokedex pokedex, int form_id, Generations min_generation,
int type1, int type2, StatValues base_stats, StatValues reward_evs)
int type1, int type2, IntStatValues base_stats, ByteStatValues reward_evs)
: base(pokedex)
{
m_form_pair = new LazyKeyValuePair<int, Form>(k => k == 0 ? null : m_pokedex.Forms(k), v => v.Value);
@@ -36,7 +36,7 @@ namespace PkmnFoundations.Pokedex
(Generations)Convert.ToInt32(reader["MinGeneration"]),
Convert.ToInt32(reader["Type1"]),
Convert.ToInt32(reader["Type2"]),
new StatValues(
new IntStatValues(
Convert.ToInt32(reader["BaseHP"]),
Convert.ToInt32(reader["BaseAttack"]),
Convert.ToInt32(reader["BaseDefense"]),
@@ -44,21 +44,21 @@ namespace PkmnFoundations.Pokedex
Convert.ToInt32(reader["BaseSpAttack"]),
Convert.ToInt32(reader["BaseSpDefense"])
),
new StatValues(
Convert.ToInt32(reader["RewardHP"]),
Convert.ToInt32(reader["RewardAttack"]),
Convert.ToInt32(reader["RewardDefense"]),
Convert.ToInt32(reader["RewardSpeed"]),
Convert.ToInt32(reader["RewardSpAttack"]),
Convert.ToInt32(reader["RewardSpDefense"])
new ByteStatValues(
Convert.ToByte(reader["RewardHP"]),
Convert.ToByte(reader["RewardAttack"]),
Convert.ToByte(reader["RewardDefense"]),
Convert.ToByte(reader["RewardSpeed"]),
Convert.ToByte(reader["RewardSpAttack"]),
Convert.ToByte(reader["RewardSpDefense"])
)
)
{
}
public Generations MinGeneration { get; private set; }
public StatValues BaseStats { get; private set; }
public StatValues RewardEvs { get; private set; }
public IntStatValues BaseStats { get; private set; }
public ByteStatValues RewardEvs { get; private set; }
private LazyKeyValuePair<int, Form> m_form_pair;
private LazyKeyValuePair<int, PkmnFoundations.Pokedex.Type> m_type1_pair;

View File

@@ -30,7 +30,7 @@ namespace PkmnFoundations.Structures
public uint Personality;
public uint IVs;
public byte[] EVs;
public byte Unknown1;
public byte Unknown1; // probably a bitmask of applied PP ups
public Languages Language;
public byte Ability;
public byte Happiness;

View File

@@ -55,7 +55,7 @@ namespace PkmnFoundations.Structures
public uint Personality;
public uint IVs;
public byte[] EVs;
public byte Unknown1;
public byte Unknown1; // probably a bitmask of applied PP ups
public Languages Language;
public byte Ability;
public byte Happiness;

View File

@@ -12,6 +12,8 @@ namespace PkmnFoundations.Structures
/// </summary>
public abstract class BinarySerializableBase : ISerializable
{
// xxx: ISerializable may be useless or even non-idiomatic on this class.
public BinarySerializableBase()
{
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PkmnFoundations.Structures
{
public class ByteStatValues : StatValues<byte>
{
public ByteStatValues(byte hp, byte attack, byte defense, byte speed, byte special_attack, byte special_defense)
: base(hp, attack, defense, special_attack, special_attack, special_defense)
{
}
protected ByteStatValues(byte[] s) : base(s)
{
}
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PkmnFoundations.Structures
{
public class ConditionValues : StatValuesBase<byte>
{
public ConditionValues(byte cool, byte beauty, byte cute, byte smart, byte tough, byte sheen)
: base(cool, beauty, cute, smart, tough, sheen)
{
}
public byte Cool { get { return Stats[0]; } set { Stats[0] = value; } }
public byte Beauty { get { return Stats[1]; } set { Stats[1] = value; } }
public byte Cute { get { return Stats[2]; } set { Stats[2] = value; } }
public byte Smart { get { return Stats[3]; } set { Stats[3] = value; } }
public byte Tough { get { return Stats[4]; } set { Stats[4] = value; } }
public byte Sheen { get { return Stats[5]; } set { Stats[5] = value; } }
public static int ConditionsIndex(Conditions condition)
{
return (int)condition - 1;
}
public virtual byte this[Conditions stat]
{
get
{
int index = ConditionsIndex(stat);
if (index < 0 || index >= 6) throw new ArgumentException();
return Stats[index];
}
set
{
int index = ConditionsIndex(stat);
if (index < 0 || index >= 6) throw new ArgumentException();
Stats[index] = value;
}
}
}
}

View File

@@ -91,6 +91,16 @@ namespace PkmnFoundations.Structures
SpecialDefense = 6
}
public enum Conditions
{
Cool = 1,
Beauty = 2,
Cute = 3,
Smart = 4,
Tough = 5,
Sheen = 6,
}
public enum DamageClass
{
None = 0,
@@ -173,4 +183,13 @@ namespace PkmnFoundations.Structures
UserAndAllies = 13,
AllPokemon = 14
}
public enum Natures : uint
{
Hardy = 0, Lonely = 1, Brave = 2, Adamant = 3, Naughty = 4,
Bold = 5, Docile = 6, Relaxed = 7, Impish = 8, Lax = 9,
Timid = 10, Hasty = 11, Serious = 12, Jolly = 13, Naive = 14,
Modest = 15, Mild = 16, Quiet = 17, Bashful = 18, Rash = 19,
Calm = 20, Gentle = 21, Sassy = 22, Careful = 23, Quirky = 24
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PkmnFoundations.Structures
{
public class IntStatValues : StatValues<int>
{
public IntStatValues(int hp, int attack, int defense, int speed, int special_attack, int special_defense)
: base(hp, attack, defense, special_attack, special_attack, special_defense)
{
}
}
}

View File

@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PkmnFoundations.Structures
{
public class IvStatValues : ByteStatValues
{
public IvStatValues(byte hp, byte attack, byte defense, byte speed, byte special_attack, byte special_defense)
: base(hp, attack, defense, special_attack, special_attack, special_defense)
{
}
public IvStatValues(int ivs) : base(new byte[6])
{
for (int x = 0; x < 6; x++)
{
Stats[x] = (byte)(ivs & 31);
ivs >>= 5;
}
}
public override byte this[Stats stat]
{
get
{
return base[stat];
}
set
{
if (value > 31) throw new ArgumentOutOfRangeException();
base[stat] = value;
}
}
public int ToInt32()
{
int shift = 0;
int result = 0;
foreach (byte iv in Stats)
{
result |= iv << shift;
shift += 5;
}
return result;
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PkmnFoundations.Pokedex;
namespace PkmnFoundations.Structures
{
public struct MoveSlot
{
public MoveSlot(Pokedex.Pokedex pokedex, int moveId, byte ppUps, byte remainingPp) : this()
{
m_pokedex = pokedex;
MoveID = moveId;
PPUps = ppUps;
RemainingPP = remainingPp;
}
private Pokedex.Pokedex m_pokedex;
public int MoveID { get; set; }
public byte PPUps { get; set; } // todo: validate range
public byte RemainingPP { get; set; } // todo: validate range (against pokedex data and pp ups)
// todo: should have a MoveID/Move LazyKeyValuePair.
public Move Move { get { return m_pokedex.Moves(MoveID); } }
public int PP { get { return Move.PP * (5 + PPUps) / 5; } }
}
}

View File

@@ -0,0 +1,260 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using PkmnFoundations.Support;
namespace PkmnFoundations.Structures
{
public class Pokemon4 : PokemonBase
{
public Pokemon4(Pokedex.Pokedex pokedex) : base(pokedex)
{
}
public Pokemon4(Pokedex.Pokedex pokedex, BinaryReader data) : base(pokedex, data)
{
}
public Pokemon4(Pokedex.Pokedex pokedex, byte[] data) : base(pokedex, data)
{
}
public Pokemon4(Pokedex.Pokedex pokedex, byte[] data, int offset) : base(pokedex, data, offset)
{
}
protected override void Load(BinaryReader reader)
{
// header (unencrypted)
Personality = reader.ReadUInt32();
ushort zero = reader.ReadUInt16();
ushort checksum = reader.ReadUInt16();
// read out the main payload, apply xor decryption
byte[][] blocks = new byte[4][];
int rand = (int)checksum;
for (int x = 0; x < 4; x++)
{
byte[] block = blocks[x] = reader.ReadBytes(32);
// todo: extract
for (int pos = 0; pos < 32; pos += 2)
{
rand = DecryptRNG(rand);
block[pos] ^= (byte)(rand >> 24);
block[pos + 1] ^= (byte)(rand >> 16);
}
}
// shuffle blocks to their correct order
List<int> blockSequence = Invert(BlockScramble(Personality));
AssertHelper.Equals(blockSequence.Count, 4);
{
byte[][] blocks2 = new byte[4][];
for (int x = 0; x < 4; x++)
blocks2[x] = blocks[blockSequence[x]];
blocks = blocks2;
}
int ribbons1, ribbons2, ribbons3;
{
byte[] block = blocks[0];
SpeciesID = BitConverter.ToUInt16(block, 0);
HeldItemID = BitConverter.ToUInt16(block, 2);
TrainerID = BitConverter.ToUInt32(block, 4);
Experience = BitConverter.ToInt32(block, 8);
Happiness = block[12];
AbilityID = block[13];
Markings = (Markings)block[14];
Language = (Languages)block[15];
EVs = new ByteStatValues(block[16],
block[17],
block[18],
block[19],
block[20],
block[21]);
ContestStats = new ConditionValues(block[22],
block[23],
block[24],
block[25],
block[26],
block[27]);
ribbons1 = BitConverter.ToInt32(block, 28);
}
{
byte[] block = blocks[1];
Moves[0] = new MoveSlot(m_pokedex, BitConverter.ToUInt16(block, 0), block[8], block[12]);
Moves[1] = new MoveSlot(m_pokedex, BitConverter.ToUInt16(block, 2), block[9], block[13]);
Moves[2] = new MoveSlot(m_pokedex, BitConverter.ToUInt16(block, 4), block[10], block[14]);
Moves[3] = new MoveSlot(m_pokedex, BitConverter.ToUInt16(block, 6), block[11], block[15]);
int ivs = BitConverter.ToInt32(block, 16);
IVs = new IvStatValues(ivs & 0x3fffffff);
IsEgg = (ivs & 0x40000000) != 0;
HasNickname = (ivs & 0x80000000) != 0;
ribbons2 = BitConverter.ToInt32(block, 20);
byte forme = block[24];
FatefulEncounter = (forme & 0x01) != 0;
Female = (forme & 0x02) != 0;
Genderless = (forme & 0x04) != 0;
FormID = forme >> 3;
// todo: parse this in a meaningful way.
ShinyLeaves = block[25];
Unknown1 = BitConverter.ToUInt16(block, 26);
// todo: doing trainer memos the right way is a pretty large task
// involving new database work.
TrainerMemoExtended = BitConverter.ToInt32(block, 28);
}
{
byte[] block = blocks[2];
NicknameEncoded = new EncodedString4(block, 0, 22);
Unknown2 = block[22];
Version = (Versions)block[23];
ribbons3 = BitConverter.ToInt32(block, 24);
Unknown3 = BitConverter.ToInt32(block, 28);
}
{
byte[] block = blocks[3];
TrainerNameEncoded = new EncodedString4(block, 0, 16);
// todo: parse dates
EggDate = new byte[3];
Array.Copy(block, 16, EggDate, 0, 3);
Date = new byte[3];
Array.Copy(block, 19, Date, 0, 3);
TrainerMemo = BitConverter.ToInt32(block, 22);
PokerusStatus = block[26];
PokeBallID_DP = block[27];
byte encounter_level = block[28];
EncounterLevel = (byte)(encounter_level & 0x7f);
TrainerFemale = (encounter_level & 0x80) != 0;
EncounterType = block[29];
PokeBallID = block[30];
Unknown4 = block[31];
}
// todo: split this class into separate box/party versions.
{
rand = (int)Personality;
byte[] block = reader.ReadBytes(100);
for (int pos = 0; pos < 100; pos += 2)
{
rand = DecryptRNG(rand);
block[pos] ^= (byte)(rand >> 24);
block[pos + 1] ^= (byte)(rand >> 16);
}
StatusAffliction = block[0];
Unknown5 = block[1];
Unknown6 = BitConverter.ToUInt16(block, 2);
Level = block[4];
CapsuleIndex = block[5];
HP = BitConverter.ToUInt16(block, 6);
Stats = new IntStatValues(BitConverter.ToUInt16(block, 8),
BitConverter.ToUInt16(block, 10),
BitConverter.ToUInt16(block, 12),
BitConverter.ToUInt16(block, 14),
BitConverter.ToUInt16(block, 16),
BitConverter.ToUInt16(block, 18));
Unknown7 = new byte[56];
Array.Copy(block, 20, Unknown7, 0, 56);
Seals = new byte[24];
Array.Copy(block, 76, Seals, 0, 24);
}
Ribbons1 = ribbons1;
Ribbons2 = ribbons2;
Ribbons3 = ribbons3;
}
protected override void Save(BinaryWriter writer)
{
throw new NotImplementedException();
}
public int Experience { get; set; }
public Markings Markings { get; set; }
public ConditionValues ContestStats { get; set; }
public bool IsEgg { get; set; }
public bool HasNickname { get; set; }
public bool FatefulEncounter { get; set; } // aka. obedience flag
public bool Female { get; set; } // only part of gender calculations
public bool Genderless { get; set; }
public byte ShinyLeaves { get; set; }
public ushort Unknown1 { get; set; }
public int TrainerMemoExtended { get; set; } // trainer memo for PtHGSS
public EncodedString4 NicknameEncoded { get; set; }
public override string Nickname
{
get
{
return (NicknameEncoded == null) ? null : NicknameEncoded.Text;
}
set
{
if (NicknameEncoded == null) NicknameEncoded = new EncodedString4(value, 22);
else NicknameEncoded.Text = value;
}
}
public byte Unknown2 { get; set; }
public Versions Version { get; set; }
public int Unknown3 { get; set; }
public EncodedString4 TrainerNameEncoded { get; set; }
public byte[] EggDate { get; set; }
public byte[] Date { get; set; }
public int TrainerMemo { get; set; }
public byte PokerusStatus { get; set; }
public byte PokeBallID_DP { get; set; }
public byte PokeBallID { get; set; }
public byte EncounterLevel { get; set; }
public bool TrainerFemale { get; set; }
public byte EncounterType { get; set; }
public byte Unknown4 { get; set; }
public int Ribbons1 { get; set; }
public int Ribbons2 { get; set; }
public int Ribbons3 { get; set; }
public byte StatusAffliction { get; set; }
public byte Unknown5 { get; set; }
public ushort Unknown6 { get; set; }
public byte CapsuleIndex { get; set; }
public ushort HP { get; set; }
public IntStatValues Stats { get; set; } // cached stats (only refreshes per level on gen4)
public byte[] Unknown7 { get; set; }
public byte[] Seals { get; set; }
private static int DecryptRNG(int prev)
{
return prev * 0x41c64e6d + 0x6073;
}
public override int Size
{
get { return 236; }
}
}
}

View File

@@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace PkmnFoundations.Structures
{
public abstract class PokemonBase : BinarySerializableBase
{
public PokemonBase(Pokedex.Pokedex pokedex)
: base()
{
m_pokedex = pokedex;
}
public PokemonBase(Pokedex.Pokedex pokedex, BinaryReader data)
: base(data)
{
m_pokedex = pokedex;
}
public PokemonBase(Pokedex.Pokedex pokedex, byte[] data)
: base(data)
{
m_pokedex = pokedex;
}
public PokemonBase(Pokedex.Pokedex pokedex, byte[] data, int offset)
: base(data, offset)
{
m_pokedex = pokedex;
}
protected Pokedex.Pokedex m_pokedex;
private MoveSlot[] m_moves = new MoveSlot[4];
// todo: use LazyKeyValuePairs for the ID fields.
public int SpeciesID { get; set; }
public int FormID { get; set; }
public int HeldItemID { get; set; }
public MoveSlot[] Moves { get { return m_moves; } }
public uint TrainerID { get; set; }
public uint Personality { get; set; }
public Natures Nature { get { return (Natures)(Personality % 25u); } }
public byte Level { get; set; }
public byte Happiness { get; set; }
public int AbilityID { get; set; }
public Languages Language { get; set; }
public IvStatValues IVs { get; set; }
public ByteStatValues EVs { get; set; }
public abstract String Nickname { get; set; }
protected static List<int> BlockScramble(uint personality)
{
int x = 4; // todo: this can be an argument but YAGNI
int xFactorial = 24;
//if (x < 0) throw new ArgumentOutOfRangeException();
//if (x == 0) return new int[0];
int index;
List<int> remaining = Enumerable.Range(0, x).ToList();
List<int> result = new List<int>(x);
while (x > 0)
{
int xMinusOneFactorial = xFactorial / x;
index = (int)((personality % xFactorial) / xMinusOneFactorial);
result.Add(remaining[index]);
remaining.RemoveAt(index);
x--;
xFactorial = xMinusOneFactorial;
}
return result;
}
protected static List<int> Invert(List<int> arg)
{
// todo: this is of general utility and should go in a utility class.
int max = arg.Max();
List<int> result = new List<int>(max);
for (int x = 0; x < max; x++)
result.Add(arg.IndexOf(x));
return result;
}
}
}

View File

@@ -5,83 +5,45 @@ using System.Text;
namespace PkmnFoundations.Structures
{
public struct StatValues
public class StatValues<T> : StatValuesBase<T> where T : struct
{
public StatValues(int hp, int attack, int defense, int speed, int special_attack, int special_defense) : this()
public StatValues(T hp, T attack, T defense, T speed, T special_attack, T special_defense)
: base(hp, attack, defense, speed, special_attack, special_defense)
{
Hp = hp;
Attack = attack;
Defense = defense;
Speed = speed;
SpecialAttack = special_attack;
SpecialDefense = special_defense;
}
public int Hp { get; set; }
public int Attack { get; set; }
public int Defense { get; set; }
public int Speed { get; set; }
public int SpecialAttack { get; set; }
public int SpecialDefense { get; set; }
protected StatValues(T[] s) : base(s)
{
if (s.Length != 6) throw new ArgumentException();
}
public int this[Stats stat]
public T Hp { get { return Stats[0]; } set { Stats[0] = value; } }
public T Attack { get { return Stats[1]; } set { Stats[1] = value; } }
public T Defense { get { return Stats[2]; } set { Stats[2] = value; } }
public T Speed { get { return Stats[3]; } set { Stats[3] = value; } }
public T SpecialAttack { get { return Stats[4]; } set { Stats[4] = value; } }
public T SpecialDefense { get { return Stats[5]; } set { Stats[5] = value; } }
public static int StatsIndex(Stats stat)
{
return (int)stat - 1;
}
public virtual T this[Stats stat]
{
get
{
switch (stat)
{
case Stats.Hp:
return Hp;
case Stats.Attack:
return Attack;
case Stats.Defense:
return Defense;
case Stats.Speed:
return Speed;
case Stats.SpecialAttack:
return SpecialAttack;
case Stats.SpecialDefense:
return SpecialDefense;
default:
throw new ArgumentException();
}
int index = StatsIndex(stat);
if (index < 0 || index >= 6) throw new ArgumentException();
return Stats[index];
}
set
{
switch (stat)
{
case Stats.Hp:
Hp = value;
break;
case Stats.Attack:
Attack = value;
break;
case Stats.Defense:
Defense = value;
break;
case Stats.Speed:
Speed = value;
break;
case Stats.SpecialAttack:
SpecialAttack = value;
break;
case Stats.SpecialDefense:
SpecialDefense = value;
break;
default:
throw new ArgumentException();
}
int index = StatsIndex(stat);
if (index < 0 || index >= 6) throw new ArgumentException();
Stats[index] = value;
}
}
public int[] ToArray()
{
return new int[] { Hp, Attack, Defense, Speed, SpecialAttack, SpecialDefense };
}
public byte[] ToByteArray()
{
return new byte[] { (byte)Hp, (byte)Attack, (byte)Defense, (byte)Speed, (byte)SpecialAttack, (byte)SpecialDefense };
}
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PkmnFoundations.Structures
{
public abstract class StatValuesBase<T> where T : struct
{
protected StatValuesBase(T[] s)
{
Stats = s;
}
protected StatValuesBase(T s0, T s1, T s2, T s3, T s4, T s5)
: this(new T[6] { s0, s1, s2, s3, s4, s5 })
{
}
protected T[] Stats { get; private set; }
public T[] ToArray()
{
return Stats.ToArray();
}
}
}