From 642cd9a76a68f9afe0ce4ade93238b4fa1c1e189 Mon Sep 17 00:00:00 2001 From: hheik <4469778+hheik@users.noreply.github.com> Date: Sat, 1 Apr 2023 16:07:09 +0300 Subject: [PATCH] added pokemon data decryption and parsing --- src/ivpeek/battle.rs | 110 ++++++++++++++++++++++++++++++++++-- src/ivpeek/memory_reader.rs | 40 ++++++++++++- 2 files changed, 142 insertions(+), 8 deletions(-) diff --git a/src/ivpeek/battle.rs b/src/ivpeek/battle.rs index be6dffd..beffe48 100644 --- a/src/ivpeek/battle.rs +++ b/src/ivpeek/battle.rs @@ -1,10 +1,33 @@ use super::inspector::{Battle, PokemonInstance}; +use crate::pokemon::*; const PLAYER_PARTY_OFFSET: usize = 0x21E42C; const ENEMY_PARTY_OFFSET: usize = 0x258874; const POKEMON_SIZE: usize = 0xDC; 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) -> Result { let mut battle = Battle::default(); for i in 0..PARTY_MAX_SIZE { @@ -24,24 +47,99 @@ fn try_parse(data: &[u8], offset: usize) -> Option { return None; } let decrypted = decrypt_pokemon(encrypted).ok()?; + // println!("pokemon:\n{decrypted:?}"); let pokemon = parse_pokemon(&decrypted).ok()?; Some(pokemon) } fn decrypt_pokemon(encrypted: &[u8]) -> Result, String> { validate_size(encrypted)?; - let mut result = vec![0; POKEMON_SIZE]; - // NOTIMP - Ok(result) + const PRNG_ADD: u32 = 0x6073; + const PRNG_MULT: u32 = 0x41C64E6D; + + 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 { - validate_size(decrypted)?; +fn parse_pokemon(data: &[u8]) -> Result { + validate_size(data)?; + const SHUFFLE_AND: u32 = 0x3E000; + const SHUFFLE_RSHIFT: u32 = 0xD; + const BLOCK_SIZE: usize = 0x20; + 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) } +fn read_header(data: &[u8]) -> Result { + validate_size(data)?; + Ok(Header { + pid: read_long(data, 0), + checksum: read_short(data, 6), + }) +} + /// 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. /// This works with both encrypted and decryped forms, as the header is never encrypted. diff --git a/src/ivpeek/memory_reader.rs b/src/ivpeek/memory_reader.rs index 27c8bc1..f435012 100644 --- a/src/ivpeek/memory_reader.rs +++ b/src/ivpeek/memory_reader.rs @@ -7,6 +7,8 @@ use std::{ time::Duration, }; +use crate::{database::Database, pokemon::*}; + use super::{ battle::parse_battle_party, inspector::{Battle, Inspector}, @@ -32,10 +34,44 @@ struct EventQueue { updates: Arc>>, } -fn flush_updates(queue: Res, mut events: EventWriter) { +fn flush_updates( + queue: Res, + pokedex: Res>, + natures: Res>, + mut events: EventWriter, +) { match queue.updates.lock() { 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) } }