Implement gift instance

This commit is contained in:
Richard Stöckl
2024-08-05 11:08:41 +02:00
parent 173a3b4764
commit b7870e0bdd
3 changed files with 39 additions and 14 deletions

View File

@@ -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<dyn Error>> {
@@ -17,7 +17,7 @@ pub fn info(pcd: PathBuf) -> Result<(), Box<dyn Error>> {
Ok(())
}
pub fn set(title: Option<String>, card_type: Option<CardType>, card_id: Option<u16>, games: Option<Vec<Game>>, comment: Option<String>, redistribution: Option<u8>, icons: Option<Vec<u16>>, pgt: Option<PathBuf>, received: Option<u16>, pcd: Option<PathBuf>, output: PathBuf) -> Result<(), Box<dyn Error>> {
pub fn set(title: Option<String>, card_type: Option<CardType>, card_id: Option<u16>, gift_instance: Option<u16>, games: Option<Vec<Game>>, comment: Option<String>, redistribution: Option<u8>, icons: Option<Vec<u16>>, pgt: Option<PathBuf>, received: Option<u16>, pcd: Option<PathBuf>, output: PathBuf) -> Result<(), Box<dyn Error>> {
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<Raw> = PCD::try_from(data.as_slice())?;
@@ -31,10 +31,6 @@ pub fn set(title: Option<String>, card_type: Option<CardType>, card_id: Option<u
pcd.state.title = t;
}
if let Some(c) = card_type {
pcd.state.card_type = c;
}
if let Some(c) = card_id {
pcd.state.card_id = c;
}
@@ -66,6 +62,18 @@ pub fn set(title: Option<String>, card_type: Option<CardType>, card_id: Option<u
Ok(len) => 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<Raw> = (&pcd.serialize()).try_into()?;

View File

@@ -47,7 +47,7 @@ fn main() -> Result<(), Box<dyn Error>> {
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<PathBuf>,
/// Wonder Card title
@@ -117,6 +117,9 @@ enum Command {
/// Wonder Card ID
#[arg(short, long, value_name = "ID")]
card_id: Option<u16>,
/// 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<u16>,
/// Games to distribute to
#[arg(short, long, value_name = "GAMES")]
games: Option<Vec<Game>>,

View File

@@ -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<Game>,
pub comment: String,
@@ -212,18 +214,28 @@ impl From<PCD<Raw>> for PCD<Partitioned> {
}
}
/// 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<Partitioned> {
pub fn deserialize(self) -> PCD<Deserialized> {
// self.state.header
let header: Vec<u16> = self.state.header.chunks_exact(2).map(|c| u16::from_le_bytes([c[0], c[1]])).collect();
let card_data: Vec<u16> = 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<Deserialized> {
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<Deserialized> {
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<Deserialized> {
}
/// 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<Deserialized> {
);
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)
}
}