diff --git a/generate/pkm.db b/generate/pkm.db index f89bfeef6..c4e49e2e2 100644 Binary files a/generate/pkm.db and b/generate/pkm.db differ 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/pkm_rs_resources/src/species/metadata.rs b/pkm_rs_resources/src/species/metadata.rs index c407383cc..6d13fbbac 100644 --- a/pkm_rs_resources/src/species/metadata.rs +++ b/pkm_rs_resources/src/species/metadata.rs @@ -44462,7 +44462,10 @@ pub static ALL_SPECIES: [SpeciesMetadata; NATIONAL_DEX_MAX] = [ forme_index: 0, is_base_forme: true, is_mega: false, - mega_evolution_data: &[], + mega_evolution_data: &[MegaEvolutionMetadata { + mega_forme: unsafe { SpeciesAndForme::new_unchecked(807, 1) }, + required_item_id: Some(2586), + }], is_gmax: false, is_battle_only: false, is_cosmetic: false, diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index a43465e15..d1ee31dd1 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3464,9 +3464,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ "aws-lc-rs", "ring", diff --git a/src-tauri/src/versioning.rs b/src-tauri/src/versioning.rs index d0495ed3c..01d721d1b 100644 --- a/src-tauri/src/versioning.rs +++ b/src-tauri/src/versioning.rs @@ -152,6 +152,7 @@ pub enum SignificantUpdate { V1_9_0, V1_9_1, V1_9_2, + V1_10_0, } impl SignificantUpdate { @@ -167,6 +168,7 @@ impl SignificantUpdate { Self::V1_9_0 => Version::parse("1.9.0").unwrap(), Self::V1_9_1 => Version::parse("1.9.1").unwrap(), Self::V1_9_2 => Version::parse("1.9.2").unwrap(), + Self::V1_10_0 => Version::parse("1.10.0").unwrap(), } } @@ -216,6 +218,21 @@ impl SignificantUpdate { Self::V1_9_2 => Some(vec![String::from( "Fixed a regression causing some Pokémon to 'devolve'", )]), + Self::V1_10_0 => Some(vec![ + String::from( + "Multi-select/drag is now supported in OpenHome boxes. Use the button in the Home boxes to the top right (next to the name edit button) to toggle multi-select", + ), + String::from("Support for Pokémon Luminescent Platinum has been added"), + String::from( + "Cosplay Pikachu and Pokémon with forms exclusive to ROM hacks can now be moved into OpenHome and to supported save files", + ), + String::from( + "Using the Display tab in the Pokémon details modal, you can assign tags and background colors to your Pokémon", + ), + String::from( + "Image resources have been replaced to reduce bundle size and loading times", + ), + ]), _ => None, } } 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 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 diff --git a/src/ui/saves/boxes/BoxCell.tsx b/src/ui/saves/boxes/BoxCell.tsx index c65f0867c..27c8a8307 100644 --- a/src/ui/saves/boxes/BoxCell.tsx +++ b/src/ui/saves/boxes/BoxCell.tsx @@ -219,11 +219,18 @@ function BoxCell({ const handleMouseEnter = useCallback( (e: React.MouseEvent) => { - if (multiSelectEnabled && e.buttons === 1 && mon && onToggleSelect && !isSelected) { + if ( + !dragData && + multiSelectEnabled && + e.buttons === 1 && + mon && + onToggleSelect && + !isSelected + ) { onToggleSelect() } }, - [multiSelectEnabled, mon, onToggleSelect, isSelected] + [dragData, multiSelectEnabled, mon, onToggleSelect, isSelected] ) return ( @@ -260,6 +267,7 @@ function BoxCell({ }} dragData={dragData} dragID={dragID} + isSelected={isSelected} disabled={disabled || isFilteredOut} topRightIndicator={topRightIndicator} showItem={showItem} diff --git a/src/ui/saves/boxes/DraggableMon.tsx b/src/ui/saves/boxes/DraggableMon.tsx index b4963c8e1..123de0bb9 100644 --- a/src/ui/saves/boxes/DraggableMon.tsx +++ b/src/ui/saves/boxes/DraggableMon.tsx @@ -29,6 +29,7 @@ export interface DraggableMonProps { style: CSSProperties dragID?: string dragData?: MonWithLocation + isSelected?: boolean topRightIndicator?: TopRightIndicatorType | null showShiny?: boolean showItem?: boolean @@ -40,8 +41,18 @@ type MonWithManagementData = PKMInterface & { } const DraggableMon = (props: DraggableMonProps) => { - const { onClick, disabled, mon, dragID, dragData, topRightIndicator, showItem, showShiny } = props - const { attributes, listeners, setNodeRef, isDragging } = useDraggable({ + const { + onClick, + disabled, + mon, + dragID, + dragData, + isSelected, + topRightIndicator, + showItem, + showShiny, + } = props + const { attributes, listeners, setNodeRef, isDragging, active } = useDraggable({ id: (dragID ?? '') + mon.personalityValue?.toString(), data: dragData ? { kind: 'mon', monData: dragData } : undefined, disabled: disabled || !dragID, @@ -71,14 +82,13 @@ const DraggableMon = (props: DraggableMonProps) => { [mon, topRightIndicator] ) - const style: CSSProperties = useMemo( - () => ({ - width: '100%', - height: '100%', - visibility: isDragging && dragState.mode === 'mon' ? 'hidden' : undefined, - }), - [dragState.mode, isDragging] - ) + const shouldHide = isDragging || (active && isSelected) + + const style: CSSProperties = { + width: '100%', + height: '100%', + visibility: shouldHide && dragState.mode === 'mon' ? 'hidden' : undefined, + } return (
) : ( dragState.payload?.kind === 'mon' && ( - +
+ + {dragState.selectedLocations.length > 1 && ( + + {dragState.selectedLocations.length} + + )} +
) )}