diff --git a/src/info.rs b/src/info.rs index 3e57efc..687fdf8 100644 --- a/src/info.rs +++ b/src/info.rs @@ -6,7 +6,7 @@ use std::fs::File; use std::io::{Read, Write}; use std::path::PathBuf; use crate::error::err_reason; -use crate::pcd::{CardType, Deserialized, Game, Partitioned, PCD, PCD_LENGTH, Raw}; +use crate::pcd::{CardType, Deserialized, Game, Partitioned, PCD, PCD_LENGTH, pgt_info, Raw}; pub fn info(pcd: PathBuf) -> Result<(), Box> { @@ -17,7 +17,7 @@ pub fn info(pcd: PathBuf) -> Result<(), Box> { Ok(()) } -pub fn set(title: Option, card_type: Option, card_id: Option, games: Option>, comment: Option, redistribution: Option, icons: Option>, pgt: Option, received: Option, pcd: Option, output: PathBuf) -> Result<(), Box> { +pub fn set(title: Option, card_type: Option, card_id: Option, gift_instance: Option, games: Option>, comment: Option, redistribution: Option, icons: Option>, pgt: Option, received: Option, pcd: Option, output: PathBuf) -> Result<(), Box> { let mut pcd = if let Some(f) = pcd { let data = fs::read(f).map_err(|e| err_reason("Unable to read pcd file", e))?; let raw: PCD = PCD::try_from(data.as_slice())?; @@ -31,10 +31,6 @@ pub fn set(title: Option, card_type: Option, card_id: Option, card_type: Option, card_id: Option if len > 0 { eprintln!("warning: provided pgt file is bigger than expected and will be truncated") } Err(e) => eprintln!("warning: unable to check if pgt file is too long: {}", e) } + + let (card_type, gift_instance) = pgt_info(&pcd.state.pgt); + pcd.state.card_type = card_type; + pcd.state.gift_instance = gift_instance; + } + + if let Some(c) = card_type { + pcd.state.card_type = c; + } + + if let Some(gift_instance) = gift_instance { + pcd.state.gift_instance = gift_instance; } let pcd: PCD = (&pcd.serialize()).try_into()?; diff --git a/src/main.rs b/src/main.rs index bd41252..5df2e02 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,7 +47,7 @@ fn main() -> Result<(), Box> { distribute(pcd, region, device, address.unwrap_or([0xa4, 0xc0, 0xe1, 0x6e, 0x76, 0x80]), interval), Command::Decrypt { epcd, checksum, address, pcd } => decrypt(epcd, checksum, address, pcd), Command::Info { pcd } => info(pcd), - Command::Set { title, kind: card_type, card_id, games, description: comment, redistribution, icons, pgt, date: received, pcd, output } => set(title, card_type, card_id, games, comment, redistribution, icons, pgt, received, pcd, output) + Command::Set { title, kind: card_type, gift_instance, card_id, games, description: comment, redistribution, icons, pgt, date: received, pcd, output } => set(title, card_type, card_id, gift_instance, games, comment, redistribution, icons, pgt, received, pcd, output) } } @@ -105,7 +105,7 @@ enum Command { /// Create a new PCD file or edit an existing one #[command(name = "set")] Set { - /// PCD file to edit, non-destructive only for input, leave empty to create from scratch + /// PCD file to edit, non-destructive only for input, leave empty to create from scratch, kind and gift instance id have precedence over pcd data #[arg(short, long, value_name = "PCD_FILE")] pcd: Option, /// Wonder Card title @@ -117,6 +117,9 @@ enum Command { /// Wonder Card ID #[arg(short, long, value_name = "ID")] card_id: Option, + /// Gift instance, use id from item, pokewalker area, poketch app, rule, seal, accessory or unknown + #[arg(long, value_name = "GIFT INSTANCE ID")] + gift_instance: Option, /// Games to distribute to #[arg(short, long, value_name = "GAMES")] games: Option>, diff --git a/src/pcd.rs b/src/pcd.rs index e20574d..1e85279 100644 --- a/src/pcd.rs +++ b/src/pcd.rs @@ -22,6 +22,7 @@ pub const PCD_FRAGMENT_LENGTH: usize = PCD_EXTENDED_LENGTH / (PCD_FRAGMENTS - 1) /// Attribute const offsets are absolute to pcd raw data pub const PCD_CARD_TYPE_OFFSET: usize = 0x0; +pub const PCD_CARD_GIFT_INSTANCE_OFFSET: usize = 0x4; pub const PCD_TITLE_OFFSET: usize = 0x104; pub const PCD_CARD_ID_OFFSET: usize = 0x150; @@ -137,6 +138,7 @@ fn serialize_games(games: &[Game]) -> u16 { pub struct Deserialized { pub title: String, pub card_type: CardType, + pub gift_instance: u16, pub card_id: u16, pub games: Vec, pub comment: String, @@ -212,18 +214,28 @@ impl From> for PCD { } } +/// Get basic information from raw pcd data. +/// +/// # Returns +/// +/// `(CardType, gift_instance)` +pub fn pgt_info(pgt: &[u8; PCD_PGT_LENGTH]) -> (CardType, u16) { + (CardType::try_from(pgt[0]).unwrap_or(Unknown), u16::from_le_bytes([pgt[PCD_CARD_GIFT_INSTANCE_OFFSET], pgt[PCD_CARD_GIFT_INSTANCE_OFFSET + 1]])) +} + impl PCD { pub fn deserialize(self) -> PCD { - - // self.state.header let header: Vec = self.state.header.chunks_exact(2).map(|c| u16::from_le_bytes([c[0], c[1]])).collect(); let card_data: Vec = self.state.card_data.chunks_exact(2).map(|c| u16::from_le_bytes([c[0], c[1]])).collect(); + let pgt = pgt_info(&self.state.pgt); + let icons_offset_rela = (PCD_ICONS_OFFSET - PCD_COMMENT_OFFSET) / 2; let des = Deserialized { title: (&first_str(&self.state.header, PCD_TITLE_MAX_LENGTH)).try_into().unwrap_or_else(|e: DecodeError| e.escaped), - card_type: CardType::try_from(self.state.pgt[0]).unwrap_or(Unknown), + card_type: pgt.0, + gift_instance: pgt.1, card_id: header[(PCD_CARD_ID_OFFSET - PCD_TITLE_OFFSET) / 2], games: Game::parse(header[(PCD_GAMES_OFFSET - PCD_TITLE_OFFSET) / 2].rotate_left(8)), comment: (&first_str(&self.state.card_data, PCD_COMMENT_MAX_LENGTH)).try_into().unwrap_or_else(|e: DecodeError| e.escaped), @@ -259,6 +271,7 @@ impl PCD { state: Deserialized { title: "".to_string(), card_type: CardType::None, + gift_instance: 0, card_id: 0, games: vec![], comment: "".to_string(), @@ -277,6 +290,7 @@ impl PCD { let mut pgt = des.pgt.clone(); pgt[PCD_CARD_TYPE_OFFSET] = des.card_type as u8; + pgt[PCD_CARD_GIFT_INSTANCE_OFFSET..PCD_CARD_GIFT_INSTANCE_OFFSET + 2].copy_from_slice(&des.gift_instance.to_le_bytes()); put_str(&mut header, &des.title, PCD_TITLE_MAX_LENGTH); header[PCD_CARD_ID_OFFSET..PCD_CARD_ID_OFFSET + 2].copy_from_slice(&des.card_id.to_le_bytes()); @@ -296,7 +310,7 @@ impl PCD { } /// Calculates the received date and returns it as a tuple. - /// Representation: (year, month of year, day of month) + /// Representation: (year, month of year, day of month) pub fn received(&self) -> (u16, u8, u8) { let approx_years = self.state.received / 365; // works until 2365, max year un u16 days is 2179 let corrected_days = self.state.received - approx_years / 4 + approx_years / 100 - approx_years / 400 - 1; @@ -339,11 +353,11 @@ impl Display for PCD { ); let (year, month, day) = self.received(); write!(f, "title: {}\ticons: {}({}),{}({}),{}({})\n\ - type: {:?}\tcard ID: {}\n\n\ + type: {:?}\tinstance: {}\tcard ID: {}\n\n\ {}\n\n\ games: {:?}\n\ redistribution limit: {}{}\n\ - received: {}-{:02}-{:02}\n", self.state.title, icon_names.0, self.state.icons.0, icon_names.1, self.state.icons.1, icon_names.2, self.state.icons.2, self.state.card_type, self.state.card_id, self.state.comment, self.state.games, self.state.redistribution, if self.state.redistribution == 0xff { "(unlimited)" } else { "" }, year, month, day) + received: {}-{:02}-{:02}\n", self.state.title, icon_names.0, self.state.icons.0, icon_names.1, self.state.icons.1, icon_names.2, self.state.icons.2, self.state.card_type, self.state.gift_instance, self.state.card_id, self.state.comment, self.state.games, self.state.redistribution, if self.state.redistribution == 0xff { "(unlimited)" } else { "" }, year, month, day) } }