added pokemon data decryption and parsing
parent
d26da8776b
commit
642cd9a76a
|
|
@ -1,10 +1,33 @@
|
||||||
use super::inspector::{Battle, PokemonInstance};
|
use super::inspector::{Battle, PokemonInstance};
|
||||||
|
use crate::pokemon::*;
|
||||||
|
|
||||||
const PLAYER_PARTY_OFFSET: usize = 0x21E42C;
|
const PLAYER_PARTY_OFFSET: usize = 0x21E42C;
|
||||||
const ENEMY_PARTY_OFFSET: usize = 0x258874;
|
const ENEMY_PARTY_OFFSET: usize = 0x258874;
|
||||||
const POKEMON_SIZE: usize = 0xDC;
|
const POKEMON_SIZE: usize = 0xDC;
|
||||||
const PARTY_MAX_SIZE: usize = 6;
|
const PARTY_MAX_SIZE: usize = 6;
|
||||||
|
|
||||||
|
/// All the block offsets for table A (growth) based on pokemon's shift value
|
||||||
|
const OFFSET_TABLE_A: [usize; 24] = [
|
||||||
|
0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 2, 3, 1, 1, 2, 3, 2, 3, 1, 1, 2, 3, 2, 3,
|
||||||
|
];
|
||||||
|
/// All the block offsets for table B (attack) based on pokemon's shift value
|
||||||
|
const OFFSET_TABLE_B: [usize; 24] = [
|
||||||
|
1, 1, 2, 3, 2, 3, 0, 0, 0, 0, 0, 0, 2, 3, 1, 1, 3, 2, 2, 3, 1, 1, 3, 2,
|
||||||
|
];
|
||||||
|
/// All the block offsets for table C (effort) based on pokemon's shift value
|
||||||
|
const OFFSET_TABLE_C: [usize; 24] = [
|
||||||
|
2, 3, 1, 1, 3, 2, 2, 3, 1, 1, 3, 2, 0, 0, 0, 0, 0, 0, 3, 2, 3, 2, 1, 1,
|
||||||
|
];
|
||||||
|
/// All the block offsets for table D (misc) based on pokemon's shift value
|
||||||
|
const OFFSET_TABLE_D: [usize; 24] = [
|
||||||
|
3, 2, 3, 2, 1, 1, 3, 2, 3, 2, 1, 1, 3, 2, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
struct Header {
|
||||||
|
pid: u32,
|
||||||
|
checksum: u16,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_battle_party(data: &Vec<u8>) -> Result<Battle, String> {
|
pub fn parse_battle_party(data: &Vec<u8>) -> Result<Battle, String> {
|
||||||
let mut battle = Battle::default();
|
let mut battle = Battle::default();
|
||||||
for i in 0..PARTY_MAX_SIZE {
|
for i in 0..PARTY_MAX_SIZE {
|
||||||
|
|
@ -24,24 +47,99 @@ fn try_parse(data: &[u8], offset: usize) -> Option<PokemonInstance> {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let decrypted = decrypt_pokemon(encrypted).ok()?;
|
let decrypted = decrypt_pokemon(encrypted).ok()?;
|
||||||
|
// println!("pokemon:\n{decrypted:?}");
|
||||||
let pokemon = parse_pokemon(&decrypted).ok()?;
|
let pokemon = parse_pokemon(&decrypted).ok()?;
|
||||||
Some(pokemon)
|
Some(pokemon)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decrypt_pokemon(encrypted: &[u8]) -> Result<Vec<u8>, String> {
|
fn decrypt_pokemon(encrypted: &[u8]) -> Result<Vec<u8>, String> {
|
||||||
validate_size(encrypted)?;
|
validate_size(encrypted)?;
|
||||||
let mut result = vec![0; POKEMON_SIZE];
|
const PRNG_ADD: u32 = 0x6073;
|
||||||
// NOTIMP
|
const PRNG_MULT: u32 = 0x41C64E6D;
|
||||||
Ok(result)
|
|
||||||
|
let mut decrypted = vec![0; POKEMON_SIZE];
|
||||||
|
let header = read_header(encrypted)?;
|
||||||
|
|
||||||
|
// First 8 bytes are not encrypted
|
||||||
|
for i in 0x00..0x08 {
|
||||||
|
decrypted[i] = encrypted[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut prng = header.checksum as u32;
|
||||||
|
for i in (0x08..0x88).step_by(2) {
|
||||||
|
prng = prng.wrapping_mul(PRNG_MULT).wrapping_add(PRNG_ADD);
|
||||||
|
let decrypted_bytes = (read_short(encrypted, i) ^ (prng >> 16) as u16).to_le_bytes();
|
||||||
|
decrypted[i] = decrypted_bytes[0];
|
||||||
|
decrypted[i + 1] = decrypted_bytes[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut prng = header.pid as u32;
|
||||||
|
// for i in (0x88..0xEC).step_by(2) {
|
||||||
|
for i in (0x88..0xDC).step_by(2) {
|
||||||
|
prng = prng.wrapping_mul(PRNG_MULT).wrapping_add(PRNG_ADD);
|
||||||
|
let decrypted_bytes = (read_short(encrypted, i) ^ (prng >> 16) as u16).to_le_bytes();
|
||||||
|
decrypted[i] = decrypted_bytes[0];
|
||||||
|
decrypted[i + 1] = decrypted_bytes[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(decrypted)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_pokemon(decrypted: &[u8]) -> Result<PokemonInstance, String> {
|
fn parse_pokemon(data: &[u8]) -> Result<PokemonInstance, String> {
|
||||||
validate_size(decrypted)?;
|
validate_size(data)?;
|
||||||
|
const SHUFFLE_AND: u32 = 0x3E000;
|
||||||
|
const SHUFFLE_RSHIFT: u32 = 0xD;
|
||||||
|
const BLOCK_SIZE: usize = 0x20;
|
||||||
|
|
||||||
let mut instance = PokemonInstance::default();
|
let mut instance = PokemonInstance::default();
|
||||||
// NOTIMP
|
|
||||||
|
let header = read_header(data)?;
|
||||||
|
let shift = ((header.pid & SHUFFLE_AND) >> SHUFFLE_RSHIFT) % 24;
|
||||||
|
let offset_a: usize = OFFSET_TABLE_A[shift as usize] * BLOCK_SIZE + 0x08;
|
||||||
|
let offset_b: usize = OFFSET_TABLE_B[shift as usize] * BLOCK_SIZE + 0x08;
|
||||||
|
let offset_c: usize = OFFSET_TABLE_C[shift as usize] * BLOCK_SIZE + 0x08;
|
||||||
|
let offset_d: usize = OFFSET_TABLE_D[shift as usize] * BLOCK_SIZE + 0x08;
|
||||||
|
|
||||||
|
instance.nature = Some(Nature {
|
||||||
|
pid: (header.pid % 25) as u8,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
instance.pokemon.national = read_short(data, offset_a + 0x00);
|
||||||
|
|
||||||
|
let ivs = read_long(data, offset_b + 0x10);
|
||||||
|
instance
|
||||||
|
.ivs
|
||||||
|
.insert(crate::pokemon::BaseStat::Hp, ((ivs >> 0) & 31) as u8);
|
||||||
|
instance
|
||||||
|
.ivs
|
||||||
|
.insert(crate::pokemon::BaseStat::Attack, ((ivs >> 5) & 31) as u8);
|
||||||
|
instance
|
||||||
|
.ivs
|
||||||
|
.insert(crate::pokemon::BaseStat::Defense, ((ivs >> 10) & 31) as u8);
|
||||||
|
instance
|
||||||
|
.ivs
|
||||||
|
.insert(crate::pokemon::BaseStat::Speed, ((ivs >> 15) & 31) as u8);
|
||||||
|
instance.ivs.insert(
|
||||||
|
crate::pokemon::BaseStat::SpecialAttack,
|
||||||
|
((ivs >> 20) & 31) as u8,
|
||||||
|
);
|
||||||
|
instance.ivs.insert(
|
||||||
|
crate::pokemon::BaseStat::SpecialDefense,
|
||||||
|
((ivs >> 25) & 31) as u8,
|
||||||
|
);
|
||||||
|
|
||||||
Ok(instance)
|
Ok(instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_header(data: &[u8]) -> Result<Header, String> {
|
||||||
|
validate_size(data)?;
|
||||||
|
Ok(Header {
|
||||||
|
pid: read_long(data, 0),
|
||||||
|
checksum: read_short(data, 6),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if the raw data contains a pokemon.
|
/// Check if the raw data contains a pokemon.
|
||||||
/// If the header section (8 bytes) are all 0x00, then the data doens't contain a pokemon.
|
/// If the header section (8 bytes) are all 0x00, then the data doens't contain a pokemon.
|
||||||
/// This works with both encrypted and decryped forms, as the header is never encrypted.
|
/// This works with both encrypted and decryped forms, as the header is never encrypted.
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ use std::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::{database::Database, pokemon::*};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
battle::parse_battle_party,
|
battle::parse_battle_party,
|
||||||
inspector::{Battle, Inspector},
|
inspector::{Battle, Inspector},
|
||||||
|
|
@ -32,10 +34,44 @@ struct EventQueue {
|
||||||
updates: Arc<Mutex<Vec<ParsedBattleEvent>>>,
|
updates: Arc<Mutex<Vec<ParsedBattleEvent>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush_updates(queue: Res<EventQueue>, mut events: EventWriter<ParsedBattleEvent>) {
|
fn flush_updates(
|
||||||
|
queue: Res<EventQueue>,
|
||||||
|
pokedex: Res<Database<Pokemon>>,
|
||||||
|
natures: Res<Database<Nature>>,
|
||||||
|
mut events: EventWriter<ParsedBattleEvent>,
|
||||||
|
) {
|
||||||
match queue.updates.lock() {
|
match queue.updates.lock() {
|
||||||
Ok(mut updates) => {
|
Ok(mut updates) => {
|
||||||
for update in updates.drain(..) {
|
for mut update in updates.drain(..) {
|
||||||
|
for mut instance in update
|
||||||
|
.0
|
||||||
|
.player_party
|
||||||
|
.iter_mut()
|
||||||
|
.chain(update.0.enemy_party.iter_mut())
|
||||||
|
{
|
||||||
|
// Fill pokemon data
|
||||||
|
if let Some(pokemon) = pokedex
|
||||||
|
.map
|
||||||
|
.values()
|
||||||
|
.find(|pokemon| pokemon.national == instance.pokemon.national)
|
||||||
|
{
|
||||||
|
instance.pokemon = pokemon.clone();
|
||||||
|
} else {
|
||||||
|
warn!("National dex not found: {}", instance.pokemon.national);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill nature data
|
||||||
|
if let Some(instance_nature) = instance.nature.clone() {
|
||||||
|
if let Some(nature) = natures
|
||||||
|
.map
|
||||||
|
.values()
|
||||||
|
.find(|nature| nature.pid == instance_nature.pid)
|
||||||
|
{
|
||||||
|
instance.nature = Some(nature.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
events.send(update)
|
events.send(update)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue