From 3af7790bdc3ffd1650f53b64922ffb3232a63678 Mon Sep 17 00:00:00 2001 From: Andrew Benington Date: Mon, 23 Mar 2026 23:31:14 -0500 Subject: [PATCH 1/5] start work on original datta backup --- .../pokemon-files/src/util/pkmInterface.ts | 2 + pkm_rs/src/pkm/ohpkm/v2.rs | 128 +++++++-- pkm_rs/src/pkm/ohpkm/v2_sections.rs | 2 + .../pkm/ohpkm/v2_sections/original_backup.rs | 258 ++++++++++++++++++ pkm_rs/src/pkm/result.rs | 10 +- src/core/pkm/OHPKM.ts | 44 +++ 6 files changed, 416 insertions(+), 28 deletions(-) create mode 100644 pkm_rs/src/pkm/ohpkm/v2_sections/original_backup.rs diff --git a/packages/pokemon-files/src/util/pkmInterface.ts b/packages/pokemon-files/src/util/pkmInterface.ts index 533578b60..79ed40bb4 100644 --- a/packages/pokemon-files/src/util/pkmInterface.ts +++ b/packages/pokemon-files/src/util/pkmInterface.ts @@ -173,6 +173,8 @@ export interface AllPKMFields { metadata?: FormeMetadata + originalBytes?: ArrayBuffer + toBytes: ((_options?: types.ToBytesOptions) => ArrayBuffer) | (() => ArrayBuffer) extraDisplayFields?: () => Record } diff --git a/pkm_rs/src/pkm/ohpkm/v2.rs b/pkm_rs/src/pkm/ohpkm/v2.rs index 772fcd46f..b086045e7 100644 --- a/pkm_rs/src/pkm/ohpkm/v2.rs +++ b/pkm_rs/src/pkm/ohpkm/v2.rs @@ -2,6 +2,7 @@ use crate::pkm::ohpkm::OhpkmV1; use crate::pkm::ohpkm::sectioned_data::{DataSection, SectionTag, SectionedData}; #[cfg(feature = "wasm")] use crate::pkm::ohpkm::v2_sections::MonTag; +use crate::pkm::ohpkm::v2_sections::original_backup; use crate::pkm::ohpkm::v2_sections::{ BdspData, GameboyData, Gen45Data, Gen67Data, LegendsArceusData, MainDataV2, MonTags, MostRecentSave, Notes, PastHandlerData, PluginData, ScarletVioletData, SwordShieldData, @@ -101,38 +102,40 @@ fn rgb_to_display_color(rgb: [u8; 3]) -> String { #[derive(Clone, Copy, PartialEq, Eq, Hash, Display)] #[repr(u16)] pub enum SectionTagV2 { - MainData, - GameboyData, - Gen45Data, - Gen67Data, - SwordShield, - BdspTmFlags, - LegendsArceus, - ScarletViolet, - PastHandler, - PluginData, - Notes, - MostRecentSave, - Tag, + MainData = 0x00, + GameboyData = 0x01, + Gen45Data = 0x02, + Gen67Data = 0x03, + SwordShield = 0x04, + BdspTmFlags = 0x05, + LegendsArceus = 0x06, + ScarletViolet = 0x07, + PastHandler = 0x08, + PluginData = 0x09, + Notes = 0x0A, + MostRecentSave = 0x0B, + Tag = 0x0C, + OriginalBackup = 0x0D, } impl SectionTagV2 { pub const fn new(tag: u16) -> Option { match tag { - 0 => Some(Self::MainData), - 1 => Some(Self::GameboyData), - 2 => Some(Self::Gen45Data), - 3 => Some(Self::Gen67Data), - 4 => Some(Self::SwordShield), - 5 => Some(Self::BdspTmFlags), - 6 => Some(Self::LegendsArceus), - 7 => Some(Self::ScarletViolet), - 8 => Some(Self::PastHandler), - 9 => Some(Self::PluginData), - 10 => Some(Self::Notes), - 11 => Some(Self::MostRecentSave), - 12 => Some(Self::Tag), - 13.. => None, + 0x00 => Some(Self::MainData), + 0x01 => Some(Self::GameboyData), + 0x02 => Some(Self::Gen45Data), + 0x03 => Some(Self::Gen67Data), + 0x04 => Some(Self::SwordShield), + 0x05 => Some(Self::BdspTmFlags), + 0x06 => Some(Self::LegendsArceus), + 0x07 => Some(Self::ScarletViolet), + 0x08 => Some(Self::PastHandler), + 0x09 => Some(Self::PluginData), + 0x0A => Some(Self::Notes), + 0x0B => Some(Self::MostRecentSave), + 0x0C => Some(Self::Tag), + 0x0D => Some(Self::OriginalBackup), + _ => None, } } @@ -151,6 +154,7 @@ impl SectionTagV2 { Self::Notes => 0, Self::MostRecentSave => 31, Self::Tag => 0, + Self::OriginalBackup => 2, // Size of the tag } } } @@ -188,6 +192,7 @@ pub struct OhpkmV2 { notes: Option, most_recent_save: Option, tags: Option, + original_data: Option, } impl OhpkmV2 { @@ -206,8 +211,10 @@ impl OhpkmV2 { notes: None, most_recent_save: None, tags: None, + original_data: None, }) } + pub fn from_bytes(bytes: &[u8]) -> Result { let sectioned_data = SectionedData::::from_bytes(bytes)?; @@ -232,6 +239,45 @@ impl OhpkmV2 { notes: Notes::extract_from(§ioned_data)?, most_recent_save: MostRecentSave::extract_from(§ioned_data)?, tags: MonTags::extract_from(§ioned_data)?, + original_data: original_backup::OriginalBackup::extract_from(§ioned_data)?, + }; + + Ok(result) + } + + pub fn from_bytes_fixing_errors(bytes: &[u8]) -> Result { + let sectioned_data = SectionedData::::from_bytes(bytes)?; + + if sectioned_data.magic_number != MAGIC_NUMBER { + return Err(Error::other("Bad magic number")); + } else if sectioned_data.version != 2 { + return Err(Error::other("Bad version number")); + } + + let result = Self { + main_data: MainDataV2::extract_from(§ioned_data)? + .ok_or(Error::other("Main data not present in OHPKM V2 file"))?, + gameboy_data: GameboyData::extract_from(§ioned_data).ok().flatten(), + gen45_data: Gen45Data::extract_from(§ioned_data).ok().flatten(), + gen67_data: Gen67Data::extract_from(§ioned_data).ok().flatten(), + swsh_data: SwordShieldData::extract_from(§ioned_data) + .ok() + .flatten(), + bdsp_data: BdspData::extract_from(§ioned_data).ok().flatten(), + la_data: LegendsArceusData::extract_from(§ioned_data) + .ok() + .flatten(), + sv_data: ScarletVioletData::extract_from(§ioned_data) + .ok() + .flatten(), + handler_data: PastHandlerData::extract_all_from(§ioned_data).unwrap_or_default(), + plugin_data: PluginData::extract_from(§ioned_data).ok().flatten(), + notes: Notes::extract_from(§ioned_data).ok().flatten(), + most_recent_save: MostRecentSave::extract_from(§ioned_data).ok().flatten(), + tags: MonTags::extract_from(§ioned_data).ok().flatten(), + original_data: original_backup::OriginalBackup::extract_from(§ioned_data) + .ok() + .flatten(), }; Ok(result) @@ -252,6 +298,7 @@ impl OhpkmV2 { notes: None, most_recent_save: None, tags: None, + original_data: None, } } @@ -291,6 +338,14 @@ impl OhpkmV2 { Ok(Self::default()) } } + #[wasm_bindgen(js_name = "fromByteVectorFixingErrors")] + pub fn from_byte_vector_fixing_errors(bytes: &[u8]) -> JsResult { + if !bytes.is_empty() { + Self::from_bytes(bytes).map_err(|e| JsValue::from_str(&e.to_string())) + } else { + Ok(Self::default()) + } + } #[wasm_bindgen(getter = openhomeId)] pub fn openhome_id(&self) -> String { @@ -1830,6 +1885,25 @@ impl OhpkmV2 { self.most_recent_save.clone() } + // Original Data + #[wasm_bindgen(getter = originalData)] + pub fn original_data(&self) -> Option> { + Some(self.original_data?.to_byte_vector()) + } + + // Original Data + #[wasm_bindgen(js_name = trySetOriginalData)] + pub fn try_set_original_data( + &mut self, + tag: original_backup::Tag, + data: Vec, + ) -> Result<()> { + let backup = original_backup::OriginalBackup::new(tag, &data)?; + + self.original_data = Some(backup); + Ok(()) + } + // Calculated #[wasm_bindgen(js_name = isShinyWasm)] pub fn is_shiny(&self) -> bool { diff --git a/pkm_rs/src/pkm/ohpkm/v2_sections.rs b/pkm_rs/src/pkm/ohpkm/v2_sections.rs index c16b7fa1f..6bad96c16 100644 --- a/pkm_rs/src/pkm/ohpkm/v2_sections.rs +++ b/pkm_rs/src/pkm/ohpkm/v2_sections.rs @@ -21,6 +21,8 @@ use pkm_rs_types::{Gender, OriginGame, PokeDate, ShinyLeaves, TrainerMemory}; use serde::{Deserialize, Serialize}; use std::num::NonZeroU16; +pub mod original_backup; + #[cfg(feature = "wasm")] use pkm_rs_resources::species::NatDexIndex; diff --git a/pkm_rs/src/pkm/ohpkm/v2_sections/original_backup.rs b/pkm_rs/src/pkm/ohpkm/v2_sections/original_backup.rs new file mode 100644 index 000000000..c062034fd --- /dev/null +++ b/pkm_rs/src/pkm/ohpkm/v2_sections/original_backup.rs @@ -0,0 +1,258 @@ +use crate::pkm::{ + Error, Result, + ohpkm::{SectionTagV2, sectioned_data::DataSection}, +}; +use strum_macros::Display; + +const PK1_PARTY_SIZE: usize = 66; +const PK2_PARTY_SIZE: usize = 73; +const PK3_PARTY_SIZE: usize = 100; +const PK4_PARTY_SIZE: usize = 236; +const PK5_PARTY_SIZE: usize = 236; +const PK6_PARTY_SIZE: usize = 260; +const PK7_PARTY_SIZE: usize = 260; +const PB7_SIZE: usize = 260; +const PK8_SIZE: usize = 344; +const PA8_SIZE: usize = 360; +const PB8_SIZE: usize = 344; +const PK9_SIZE: usize = 344; +const PA9_SIZE: usize = 344; + +const PK3CFRU_PARTY_SIZE: usize = 58; +const PB8LUMI_SIZE: usize = 344; + +#[cfg(feature = "wasm")] +use wasm_bindgen::prelude::*; + +#[cfg_attr(feature = "wasm", wasm_bindgen)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Display)] +#[repr(u16)] +pub enum Tag { + Pk1 = 1, + Pk2 = 2, + Pk3 = 3, + Pk4 = 4, + Pk5 = 5, + Pk6 = 6, + Pk7 = 7, + Pb7 = 8, + Pk8 = 9, + Pa8 = 10, + Pb8 = 11, + Pk9 = 12, + Pa9 = 13, + + Pk3Rr = 0xfffe, + Pk3Ub = 0xfffd, + Pa8Lumi = 0xfffc, +} + +impl Tag { + pub const fn to_le_bytes(self) -> [u8; 2] { + (self as u16).to_le_bytes() + } +} + +impl TryFrom for Tag { + type Error = Error; + + fn try_from(value: u16) -> Result { + match value { + 1 => Ok(Self::Pk1), + 2 => Ok(Self::Pk2), + 3 => Ok(Self::Pk3), + 4 => Ok(Self::Pk4), + 5 => Ok(Self::Pk5), + 6 => Ok(Self::Pk6), + 7 => Ok(Self::Pk7), + 8 => Ok(Self::Pb7), + 9 => Ok(Self::Pk8), + 10 => Ok(Self::Pa8), + 11 => Ok(Self::Pb8), + 12 => Ok(Self::Pk9), + 13 => Ok(Self::Pa9), + + 0xfffe => Ok(Self::Pk3Rr), + 0xfffd => Ok(Self::Pk3Ub), + 0xfffc => Ok(Self::Pa8Lumi), + + other => Err(Error::TagError { + tag_type: "OriginalBackup", + value: other, + }), + } + } +} + +impl Tag { + pub const fn data_size(&self) -> usize { + match self { + Self::Pk1 => PK1_PARTY_SIZE, + Self::Pk2 => PK2_PARTY_SIZE, + Self::Pk3 => PK3_PARTY_SIZE, + Self::Pk4 => PK4_PARTY_SIZE, + Self::Pk5 => PK5_PARTY_SIZE, + Self::Pk6 => PK6_PARTY_SIZE, + Self::Pk7 => PK7_PARTY_SIZE, + Self::Pb7 => PB7_SIZE, + Self::Pk8 => PK8_SIZE, + Self::Pa8 => PA8_SIZE, + Self::Pb8 => PB8_SIZE, + Self::Pk9 => PK9_SIZE, + Self::Pa9 => PA9_SIZE, + + Self::Pk3Rr => PK3CFRU_PARTY_SIZE, + Self::Pk3Ub => PK3CFRU_PARTY_SIZE, + Self::Pa8Lumi => PB8LUMI_SIZE, + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Display, Debug)] +pub enum OriginalBackup { + Pk1([u8; PK1_PARTY_SIZE]), + Pk2([u8; PK2_PARTY_SIZE]), + Pk3([u8; PK3_PARTY_SIZE]), + Pk4([u8; PK4_PARTY_SIZE]), + Pk5([u8; PK5_PARTY_SIZE]), + Pk6([u8; PK6_PARTY_SIZE]), + Pk7([u8; PK7_PARTY_SIZE]), + Pb7([u8; PB7_SIZE]), + Pk8([u8; PK8_SIZE]), + Pa8([u8; PA8_SIZE]), + Pb8([u8; PB8_SIZE]), + Pk9([u8; PK9_SIZE]), + Pa9([u8; PA9_SIZE]), + + Pk3Rr([u8; PK3CFRU_PARTY_SIZE]), + Pk3Ub([u8; PK3CFRU_PARTY_SIZE]), + Pb8Lumi([u8; PB8_SIZE]), +} + +const LENGTH_CHECKED_MESSAGE: &str = "data length checked above"; + +impl OriginalBackup { + const fn data_as_bytes(&self) -> &[u8] { + let bytes: &[u8] = match self { + Self::Pk1(bytes) => bytes, + Self::Pk2(bytes) => bytes, + Self::Pk3(bytes) => bytes, + Self::Pk4(bytes) => bytes, + Self::Pk5(bytes) => bytes, + Self::Pk6(bytes) => bytes, + Self::Pk7(bytes) => bytes, + Self::Pb7(bytes) => bytes, + Self::Pk8(bytes) => bytes, + Self::Pa8(bytes) => bytes, + Self::Pb8(bytes) => bytes, + Self::Pk9(bytes) => bytes, + Self::Pa9(bytes) => bytes, + + Self::Pk3Rr(bytes) => bytes, + Self::Pk3Ub(bytes) => bytes, + Self::Pb8Lumi(bytes) => bytes, + }; + bytes + } + + const fn tag(&self) -> Tag { + match self { + Self::Pk1(_) => Tag::Pk1, + Self::Pk2(_) => Tag::Pk2, + Self::Pk3(_) => Tag::Pk3, + Self::Pk4(_) => Tag::Pk4, + Self::Pk5(_) => Tag::Pk5, + Self::Pk6(_) => Tag::Pk6, + Self::Pk7(_) => Tag::Pk7, + Self::Pb7(_) => Tag::Pb7, + Self::Pk8(_) => Tag::Pk8, + Self::Pa8(_) => Tag::Pa8, + Self::Pb8(_) => Tag::Pb8, + Self::Pk9(_) => Tag::Pk9, + Self::Pa9(_) => Tag::Pa9, + + Self::Pk3Rr(_) => Tag::Pk3Rr, + Self::Pk3Ub(_) => Tag::Pk3Ub, + Self::Pb8Lumi(_) => Tag::Pa8Lumi, + } + } + + pub fn new(tag: Tag, data: &[u8]) -> Result { + if data.len() != tag.data_size() { + return Err(Error::BufferSize { + field: format!("OriginalBackup({})", tag), + expected: tag.data_size(), + received: data.len(), + }); + } + + match tag { + Tag::Pk1 => Ok(Self::Pk1(*data.as_array().expect(LENGTH_CHECKED_MESSAGE))), + Tag::Pk2 => Ok(Self::Pk2(*data.as_array().expect(LENGTH_CHECKED_MESSAGE))), + Tag::Pk3 => Ok(Self::Pk3(*data.as_array().expect(LENGTH_CHECKED_MESSAGE))), + Tag::Pk4 => Ok(Self::Pk4(*data.as_array().expect(LENGTH_CHECKED_MESSAGE))), + Tag::Pk5 => Ok(Self::Pk5(*data.as_array().expect(LENGTH_CHECKED_MESSAGE))), + Tag::Pk6 => Ok(Self::Pk6(*data.as_array().expect(LENGTH_CHECKED_MESSAGE))), + Tag::Pk7 => Ok(Self::Pk7(*data.as_array().expect(LENGTH_CHECKED_MESSAGE))), + Tag::Pb7 => Ok(Self::Pb7(*data.as_array().expect(LENGTH_CHECKED_MESSAGE))), + Tag::Pk8 => Ok(Self::Pk8(*data.as_array().expect(LENGTH_CHECKED_MESSAGE))), + Tag::Pa8 => Ok(Self::Pa8(*data.as_array().expect(LENGTH_CHECKED_MESSAGE))), + Tag::Pb8 => Ok(Self::Pb8(*data.as_array().expect(LENGTH_CHECKED_MESSAGE))), + Tag::Pk9 => Ok(Self::Pk9(*data.as_array().expect(LENGTH_CHECKED_MESSAGE))), + Tag::Pa9 => Ok(Self::Pa9(*data.as_array().expect(LENGTH_CHECKED_MESSAGE))), + + Tag::Pk3Rr => Ok(Self::Pk3Rr(*data.as_array().expect(LENGTH_CHECKED_MESSAGE))), + Tag::Pk3Ub => Ok(Self::Pk3Ub(*data.as_array().expect(LENGTH_CHECKED_MESSAGE))), + Tag::Pa8Lumi => Ok(Self::Pb8Lumi( + *data.as_array().expect(LENGTH_CHECKED_MESSAGE), + )), + } + } + + #[cfg(feature = "wasm")] + pub fn to_byte_vector(self) -> Vec { + let mut bytes = Vec::with_capacity(2 + self.tag().data_size()); + bytes.extend_from_slice(&self.tag().to_le_bytes()); + bytes.extend_from_slice(self.data_as_bytes()); + bytes + } +} + +impl DataSection for OriginalBackup { + type TagType = SectionTagV2; + const TAG: Self::TagType = SectionTagV2::OriginalBackup; + + type ErrorType = Error; + + fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() < 2 { + return Err(Error::BufferSize { + field: String::from("OriginalBackup"), + expected: 2, + received: bytes.len(), + }); + } + let value = u16::from_le_bytes(bytes[0..2].try_into().expect(LENGTH_CHECKED_MESSAGE)); + + let Ok(tag) = Tag::try_from(value) else { + return Err(Error::TagError { + tag_type: "OriginalBackup", + value, + }); + }; + + Self::new(tag, &bytes[2..]) + } + + fn to_bytes(&self) -> Result> { + let mut bytes = Vec::with_capacity(2 + self.tag().data_size()); + bytes.extend_from_slice(&self.tag().to_le_bytes()); + bytes.extend_from_slice(self.data_as_bytes()); + Ok(bytes) + } + + fn is_empty(&self) -> bool { + // if this section exists, it should always have data, so it is never empty + false + } +} diff --git a/pkm_rs/src/pkm/result.rs b/pkm_rs/src/pkm/result.rs index 7ec6948a5..b0c79c0f3 100644 --- a/pkm_rs/src/pkm/result.rs +++ b/pkm_rs/src/pkm/result.rs @@ -70,6 +70,10 @@ pub enum Error { field: &'static str, source: Box, }, + TagError { + tag_type: &'static str, + value: u16, + }, MoveError { value: u16, @@ -169,6 +173,9 @@ impl Display for Error { format!("Error reading field {field}: {source}") .to_owned() } + Error::TagError { tag_type, value } => { + format!("Invalid tag value {value} for tag type {tag_type}") + } Error::MoveError { value, source } => { format!("Invalid move reference {value} (source: {source})").to_owned() @@ -200,7 +207,8 @@ impl From for Error { pkm_rs_resources::Error::BufferSize { field, expected, received } => Self::BufferSize { field, expected, received }, pkm_rs_resources::Error::CryptRange { range, buffer_size } => Self::CryptRange { range, buffer_size }, pkm_rs_resources::Error::NationalDex { national_dex } => Self::NationalDex { value: national_dex, source: NdexConvertSource::Other }, - pkm_rs_resources::Error::FormeIndex { national_dex, forme_index } => Self::FormeIndex { national_dex, forme_index }, + pkm_rs_resources::Error::FormeIndex { national_dex, forme_index, + } => Self::FormeIndex { national_dex, forme_index }, pkm_rs_resources::Error::LanguageIndex { language_index } => Self::LanguageIndex { language_index }, pkm_rs_resources::Error::NatureIndex { nature_index } => Self::NatureIndex { nature_index }, pkm_rs_resources::Error::AbilityIndex { ability_index } => Self::AbilityIndex { ability_index }, diff --git a/src/core/pkm/OHPKM.ts b/src/core/pkm/OHPKM.ts index ff2b7fa6a..3cc0e94d9 100644 --- a/src/core/pkm/OHPKM.ts +++ b/src/core/pkm/OHPKM.ts @@ -16,6 +16,7 @@ import { ShinyLeaves, SpeciesAndForme, SpeciesLookup, + Tag, TrainerData, TrainerMemory, updatePidIfWouldBecomeShinyGen345, @@ -328,6 +329,17 @@ export class OHPKM extends OhpkmV2Wasm implements PKMInterface { this.tmFlagsSV = other.tmFlagsSV this.tmFlagsSVDLC = other.tmFlagsSVDLC + + if (other.originalBytes) { + const tag = monFormatToOriginalDataTag(other.format) + if (tag) { + try { + this.trySetOriginalData(tag, new Uint8Array(other.originalBytes)) + } catch (e) { + console.error('Failed to set original data from bytes', e) + } + } + } } if (this.openhomeId === '0004-d889ca57-401aab08-30') { this.extraFormIndex = ExtraFormIndex.CharizardClone @@ -935,6 +947,38 @@ export class OHPKM extends OhpkmV2Wasm implements PKMInterface { ) } } +function monFormatToOriginalDataTag(format: string): Option { + switch (format) { + case 'PK1': + return Tag.Pk1 + case 'PK2': + return Tag.Pk2 + case 'PK3': + return Tag.Pk3 + case 'PK4': + return Tag.Pk4 + case 'PK5': + return Tag.Pk5 + case 'PK6': + return Tag.Pk6 + case 'PK7': + return Tag.Pk7 + case 'PB7': + return Tag.Pb7 + case 'PK8': + return Tag.Pk8 + case 'PA8': + return Tag.Pa8 + case 'PB8': + return Tag.Pb8 + case 'PK9': + return Tag.Pk9 + case 'PA9': + return Tag.Pa9 + default: + return undefined + } +} type AbilityNum = 1 | 2 | 4 const FIRST_ABILITY: AbilityNum = 1 From c839e2bcfedd4269d49e791407e8c2928c631c0e Mon Sep 17 00:00:00 2001 From: Andrew Benington Date: Tue, 24 Mar 2026 22:39:50 -0500 Subject: [PATCH 2/5] fix type of originalBytes --- src/core/pkm/interfaces.ts | 1 - src/core/save/cfru/PK3CFRU.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/core/pkm/interfaces.ts b/src/core/pkm/interfaces.ts index 440a51303..0347bdc65 100644 --- a/src/core/pkm/interfaces.ts +++ b/src/core/pkm/interfaces.ts @@ -25,7 +25,6 @@ export type PKMInterface = AllPKMFields & { // If met in a plugin save, this will be the save's plugin_identifier. otherwise this is empty pluginOrigin?: PluginIdentifier // why are there two of these?? isFakemon?: boolean - originalBytes?: Uint8Array selectColor?: string // User-defined display color for this Pokemon in boxes (CSS color string) displayColor?: string diff --git a/src/core/save/cfru/PK3CFRU.ts b/src/core/save/cfru/PK3CFRU.ts index 3d35a1ca2..91626bf49 100644 --- a/src/core/save/cfru/PK3CFRU.ts +++ b/src/core/save/cfru/PK3CFRU.ts @@ -112,7 +112,7 @@ export abstract class PK3CFRU implements PluginPKMInterface { trainerName: string trainerGender: boolean isFakemon: boolean = false - originalBytes?: Uint8Array + originalBytes?: ArrayBuffer pluginForm?: number @@ -123,7 +123,7 @@ export abstract class PK3CFRU implements PluginPKMInterface { let buffer = arg const dataView = new DataView(buffer) - this.originalBytes = new Uint8Array(arg) + this.originalBytes = arg // https://github.com/Skeli789/Complete-Fire-Red-Upgrade/blob/master/include/new/pokemon_storage_system.h // https://github.com/Skeli789/Complete-Fire-Red-Upgrade/blob/master/include/pokemon.h From 30dfe0a7906214570a15e5b59aeb978917ac4005 Mon Sep 17 00:00:00 2001 From: Andrew Benington Date: Wed, 25 Mar 2026 17:14:11 -0500 Subject: [PATCH 3/5] original bytes in some formats --- packages/pokemon-files/src/pkm/PA8.ts | 2 ++ packages/pokemon-files/src/pkm/PA9.ts | 2 ++ src/core/save/cfru/PK3CFRU.ts | 3 +-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/pokemon-files/src/pkm/PA8.ts b/packages/pokemon-files/src/pkm/PA8.ts index 18878fdfd..87a16c6ed 100644 --- a/packages/pokemon-files/src/pkm/PA8.ts +++ b/packages/pokemon-files/src/pkm/PA8.ts @@ -103,9 +103,11 @@ export default class PA8 { isNoble: boolean ribbons: string[] trainerGender: boolean + originalBytes?: ArrayBuffer constructor(arg: ArrayBuffer | AllPKMFields, encrypted?: boolean) { if (arg instanceof ArrayBuffer) { let buffer = arg + this.originalBytes = buffer if (encrypted) { const unencryptedBytes = encryption.decryptByteArrayGen8A(buffer) const unshuffledBytes = encryption.unshuffleBlocksGen8A(unencryptedBytes) diff --git a/packages/pokemon-files/src/pkm/PA9.ts b/packages/pokemon-files/src/pkm/PA9.ts index 61016c9ca..2345f1ba4 100644 --- a/packages/pokemon-files/src/pkm/PA9.ts +++ b/packages/pokemon-files/src/pkm/PA9.ts @@ -93,9 +93,11 @@ export default class PA9 { tmFlagsLza: Uint8Array ribbons: string[] trainerGender: boolean + originalBytes?: ArrayBuffer constructor(arg: ArrayBuffer | AllPKMFields, encrypted?: boolean) { if (arg instanceof ArrayBuffer) { let buffer = arg + this.originalBytes = buffer if (encrypted) { const unencryptedBytes = encryption.decryptByteArrayGen89(buffer) const unshuffledBytes = encryption.unshuffleBlocksGen89(unencryptedBytes) diff --git a/src/core/save/cfru/PK3CFRU.ts b/src/core/save/cfru/PK3CFRU.ts index 91626bf49..279d71607 100644 --- a/src/core/save/cfru/PK3CFRU.ts +++ b/src/core/save/cfru/PK3CFRU.ts @@ -121,10 +121,9 @@ export abstract class PK3CFRU implements PluginPKMInterface { constructor(arg: ArrayBuffer | OHPKM) { if (arg instanceof ArrayBuffer) { let buffer = arg + this.originalBytes = buffer const dataView = new DataView(buffer) - this.originalBytes = arg - // https://github.com/Skeli789/Complete-Fire-Red-Upgrade/blob/master/include/new/pokemon_storage_system.h // https://github.com/Skeli789/Complete-Fire-Red-Upgrade/blob/master/include/pokemon.h From 29ca28c12462f21913b9e9db58069a63e6d7ed60 Mon Sep 17 00:00:00 2001 From: Andrew Benington Date: Sat, 28 Mar 2026 16:51:42 -0500 Subject: [PATCH 4/5] unconverted pkm bytes section --- pkm_rs/src/pkm/ohpkm/v2.rs | 65 +++++++++++----- pkm_rs/src/pkm/ohpkm/v2_sections.rs | 2 +- .../{original_backup.rs => pkm_bytes.rs} | 77 ++++++++++++++----- 3 files changed, 103 insertions(+), 41 deletions(-) rename pkm_rs/src/pkm/ohpkm/v2_sections/{original_backup.rs => pkm_bytes.rs} (88%) diff --git a/pkm_rs/src/pkm/ohpkm/v2.rs b/pkm_rs/src/pkm/ohpkm/v2.rs index b086045e7..54276d7d2 100644 --- a/pkm_rs/src/pkm/ohpkm/v2.rs +++ b/pkm_rs/src/pkm/ohpkm/v2.rs @@ -1,20 +1,21 @@ use crate::pkm::ohpkm::OhpkmV1; use crate::pkm::ohpkm::sectioned_data::{DataSection, SectionTag, SectionedData}; -#[cfg(feature = "wasm")] -use crate::pkm::ohpkm::v2_sections::MonTag; -use crate::pkm::ohpkm::v2_sections::original_backup; +use crate::pkm::ohpkm::v2_sections::pkm_bytes::{self, OriginalBackup, PkmBytes, UnconvertedPkm}; use crate::pkm::ohpkm::v2_sections::{ BdspData, GameboyData, Gen45Data, Gen67Data, LegendsArceusData, MainDataV2, MonTags, MostRecentSave, Notes, PastHandlerData, PluginData, ScarletVioletData, SwordShieldData, }; use crate::pkm::{Error, Result}; +use strum_macros::Display; + +#[cfg(feature = "wasm")] +use wasm_bindgen::prelude::*; #[cfg(feature = "wasm")] use pkm_rs_types::TrainerData; -use strum_macros::Display; #[cfg(feature = "wasm")] -use wasm_bindgen::prelude::*; +use crate::pkm::ohpkm::v2_sections::MonTag; #[cfg(feature = "wasm")] use crate::pkm::ohpkm::extra_form::ExtraFormIndex; @@ -116,6 +117,7 @@ pub enum SectionTagV2 { MostRecentSave = 0x0B, Tag = 0x0C, OriginalBackup = 0x0D, + UnconvertedPkm = 0x0E, } impl SectionTagV2 { @@ -155,6 +157,7 @@ impl SectionTagV2 { Self::MostRecentSave => 31, Self::Tag => 0, Self::OriginalBackup => 2, // Size of the tag + Self::UnconvertedPkm => 2, // Size of the tag } } } @@ -192,7 +195,8 @@ pub struct OhpkmV2 { notes: Option, most_recent_save: Option, tags: Option, - original_data: Option, + original_data: Option, + unconverted_pkm: Option, } impl OhpkmV2 { @@ -212,6 +216,7 @@ impl OhpkmV2 { most_recent_save: None, tags: None, original_data: None, + unconverted_pkm: None, }) } @@ -239,7 +244,8 @@ impl OhpkmV2 { notes: Notes::extract_from(§ioned_data)?, most_recent_save: MostRecentSave::extract_from(§ioned_data)?, tags: MonTags::extract_from(§ioned_data)?, - original_data: original_backup::OriginalBackup::extract_from(§ioned_data)?, + original_data: OriginalBackup::extract_from(§ioned_data)?, + unconverted_pkm: UnconvertedPkm::extract_from(§ioned_data)?, }; Ok(result) @@ -275,9 +281,8 @@ impl OhpkmV2 { notes: Notes::extract_from(§ioned_data).ok().flatten(), most_recent_save: MostRecentSave::extract_from(§ioned_data).ok().flatten(), tags: MonTags::extract_from(§ioned_data).ok().flatten(), - original_data: original_backup::OriginalBackup::extract_from(§ioned_data) - .ok() - .flatten(), + original_data: OriginalBackup::extract_from(§ioned_data).ok().flatten(), + unconverted_pkm: UnconvertedPkm::extract_from(§ioned_data).ok().flatten(), }; Ok(result) @@ -299,6 +304,7 @@ impl OhpkmV2 { most_recent_save: None, tags: None, original_data: None, + unconverted_pkm: None, } } @@ -312,12 +318,14 @@ impl OhpkmV2 { .add_if_some(self.swsh_data)? .add_if_some(self.bdsp_data)? .add_if_some(self.la_data)? - .add_if_some(self.clone().sv_data)? + .add_if_some(self.sv_data)? .add_all(self.handler_data.clone())? .add_if_some(self.plugin_data.clone())? .add_if_some(self.notes.clone())? .add_if_some(self.most_recent_save.clone())? - .add_if_some(self.tags.clone())?; + .add_if_some(self.tags.clone())? + .add_if_some(self.original_data)? + .add_if_some(self.unconverted_pkm)?; Ok(sectioned_data) } @@ -1888,22 +1896,37 @@ impl OhpkmV2 { // Original Data #[wasm_bindgen(getter = originalData)] pub fn original_data(&self) -> Option> { - Some(self.original_data?.to_byte_vector()) + self.original_data?.to_bytes().ok() } - // Original Data #[wasm_bindgen(js_name = trySetOriginalData)] - pub fn try_set_original_data( - &mut self, - tag: original_backup::Tag, - data: Vec, - ) -> Result<()> { - let backup = original_backup::OriginalBackup::new(tag, &data)?; + pub fn try_set_original_data(&mut self, tag: pkm_bytes::Tag, data: Vec) -> Result<()> { + let pkm_bytes = PkmBytes::new(tag, &data)?; - self.original_data = Some(backup); + self.original_data = Some(OriginalBackup::new(pkm_bytes)); Ok(()) } + // Unconverted PKM (Pokémon that have been opted out of intergenerational conversion) + #[wasm_bindgen(getter = unconvertedPkm)] + pub fn unconverted_pkm(&self) -> Option> { + self.unconverted_pkm?.to_bytes().ok() + } + + #[wasm_bindgen(js_name = trySetUnconvertedPkm)] + pub fn try_set_unconverted_pkm(&mut self, tag: pkm_bytes::Tag, data: Vec) -> Result<()> { + let pkm_bytes = PkmBytes::new(tag, &data)?; + + self.unconverted_pkm = Some(UnconvertedPkm::new(pkm_bytes)); + Ok(()) + } + + #[wasm_bindgen(getter = unconvertedPkm)] + pub fn unconverted_pkm(&self) -> Option> { + self.unconverted_pkm?.to_bytes().ok() + } + + // Calculated #[wasm_bindgen(js_name = isShinyWasm)] pub fn is_shiny(&self) -> bool { diff --git a/pkm_rs/src/pkm/ohpkm/v2_sections.rs b/pkm_rs/src/pkm/ohpkm/v2_sections.rs index 6bad96c16..e1331d433 100644 --- a/pkm_rs/src/pkm/ohpkm/v2_sections.rs +++ b/pkm_rs/src/pkm/ohpkm/v2_sections.rs @@ -21,7 +21,7 @@ use pkm_rs_types::{Gender, OriginGame, PokeDate, ShinyLeaves, TrainerMemory}; use serde::{Deserialize, Serialize}; use std::num::NonZeroU16; -pub mod original_backup; +pub mod pkm_bytes; #[cfg(feature = "wasm")] use pkm_rs_resources::species::NatDexIndex; diff --git a/pkm_rs/src/pkm/ohpkm/v2_sections/original_backup.rs b/pkm_rs/src/pkm/ohpkm/v2_sections/pkm_bytes.rs similarity index 88% rename from pkm_rs/src/pkm/ohpkm/v2_sections/original_backup.rs rename to pkm_rs/src/pkm/ohpkm/v2_sections/pkm_bytes.rs index c062034fd..96d527d68 100644 --- a/pkm_rs/src/pkm/ohpkm/v2_sections/original_backup.rs +++ b/pkm_rs/src/pkm/ohpkm/v2_sections/pkm_bytes.rs @@ -109,7 +109,7 @@ impl Tag { } #[derive(Clone, Copy, PartialEq, Eq, Hash, Display, Debug)] -pub enum OriginalBackup { +pub enum PkmBytes { Pk1([u8; PK1_PARTY_SIZE]), Pk2([u8; PK2_PARTY_SIZE]), Pk3([u8; PK3_PARTY_SIZE]), @@ -131,7 +131,7 @@ pub enum OriginalBackup { const LENGTH_CHECKED_MESSAGE: &str = "data length checked above"; -impl OriginalBackup { +impl PkmBytes { const fn data_as_bytes(&self) -> &[u8] { let bytes: &[u8] = match self { Self::Pk1(bytes) => bytes, @@ -209,21 +209,6 @@ impl OriginalBackup { } } - #[cfg(feature = "wasm")] - pub fn to_byte_vector(self) -> Vec { - let mut bytes = Vec::with_capacity(2 + self.tag().data_size()); - bytes.extend_from_slice(&self.tag().to_le_bytes()); - bytes.extend_from_slice(self.data_as_bytes()); - bytes - } -} - -impl DataSection for OriginalBackup { - type TagType = SectionTagV2; - const TAG: Self::TagType = SectionTagV2::OriginalBackup; - - type ErrorType = Error; - fn from_bytes(bytes: &[u8]) -> Result { if bytes.len() < 2 { return Err(Error::BufferSize { @@ -244,11 +229,65 @@ impl DataSection for OriginalBackup { Self::new(tag, &bytes[2..]) } - fn to_bytes(&self) -> Result> { + pub fn to_bytes(self) -> Vec { let mut bytes = Vec::with_capacity(2 + self.tag().data_size()); bytes.extend_from_slice(&self.tag().to_le_bytes()); bytes.extend_from_slice(self.data_as_bytes()); - Ok(bytes) + bytes + } +} + +#[derive(Debug, Clone, Copy)] +pub struct OriginalBackup(PkmBytes); + +impl OriginalBackup { + pub const fn new(pkm_bytes: PkmBytes) -> Self { + Self(pkm_bytes) + } +} + +impl DataSection for OriginalBackup { + type TagType = SectionTagV2; + const TAG: Self::TagType = SectionTagV2::OriginalBackup; + + type ErrorType = Error; + + fn from_bytes(bytes: &[u8]) -> Result { + Ok(Self(PkmBytes::from_bytes(bytes)?)) + } + + fn to_bytes(&self) -> Result> { + Ok(self.0.to_bytes()) + } + + fn is_empty(&self) -> bool { + // if this section exists, it should always have data, so it is never empty + false + } +} + +#[derive(Debug, Clone, Copy)] +pub struct UnconvertedPkm(PkmBytes); + +#[cfg(feature = "wasm")] +impl UnconvertedPkm { + pub const fn new(pkm_bytes: PkmBytes) -> Self { + Self(pkm_bytes) + } +} + +impl DataSection for UnconvertedPkm { + type TagType = SectionTagV2; + const TAG: Self::TagType = SectionTagV2::UnconvertedPkm; + + type ErrorType = Error; + + fn from_bytes(bytes: &[u8]) -> Result { + Ok(Self(PkmBytes::from_bytes(bytes)?)) + } + + fn to_bytes(&self) -> Result> { + Ok(self.0.to_bytes()) } fn is_empty(&self) -> bool { From 2fdda4a3b6733cdf935606eabdb9aa4310331a0f Mon Sep 17 00:00:00 2001 From: Andrew Benington Date: Sat, 28 Mar 2026 16:52:51 -0500 Subject: [PATCH 5/5] remove dupe method --- Makefile | 2 +- pkm_rs/src/pkm/ohpkm/v2.rs | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 0557c3f4c..fc9918d13 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ clean: @rm -rf src-tauri/target .PHONY: check -check: +check: wasm-compile @pnpm run typecheck @pnpm run lint @pnpm run format diff --git a/pkm_rs/src/pkm/ohpkm/v2.rs b/pkm_rs/src/pkm/ohpkm/v2.rs index 54276d7d2..b3637f617 100644 --- a/pkm_rs/src/pkm/ohpkm/v2.rs +++ b/pkm_rs/src/pkm/ohpkm/v2.rs @@ -1921,12 +1921,6 @@ impl OhpkmV2 { Ok(()) } - #[wasm_bindgen(getter = unconvertedPkm)] - pub fn unconverted_pkm(&self) -> Option> { - self.unconverted_pkm?.to_bytes().ok() - } - - // Calculated #[wasm_bindgen(js_name = isShinyWasm)] pub fn is_shiny(&self) -> bool {