Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["clicky-core", "clicky-desktop", "clicky-web", "relativity"]
members = ["clicky-core", "clicky-desktop", "clicky-web", "pprom", "relativity"]

[profile.release]
panic = "abort"
Expand Down
12 changes: 12 additions & 0 deletions pprom/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "pprom"
version = "0.1.0"
authors = ["Jean THOMAS <virgule@jeanthomas.me>"]
edition = "2018"

[dependencies]
log = "0.4"
pretty_env_logger = "0.3"
structopt = "0.3"
thiserror = "1.0"
hxdmp = "0.2"
48 changes: 48 additions & 0 deletions pprom/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# pprom

PortalPlayer-based iPod ROM parsing.

## Usage

```
$ cargo run -p pprom internal_rom_000000-0FFFFF-in1g.bin info
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s
Running `target/debug/pprom internal_rom_000000-0FFFFF-in1g.bin info`
model: IpodNano1g
length: 1048576 bytes
SCfg: 12 keys
```

```
$ cargo run -p pprom internal_rom_000000-0FFFFF-in1g.bin keys
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s
Running `target/debug/pprom internal_rom_000000-0FFFFF-in1g.bin keys`
SCfg v1.1: 12 keys

key: SrNm
0000: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ................
key: FwId
0000: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ................
key: HwId
0000: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ................
key: Btry
0000: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ................
key: RtcA
0000: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ................
key: HwVr
0000: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ................
key: Regn
0000: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ................
key: Mod#
0000: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ................
key: HwO1
0000: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ................
key: Cont
0000: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ................
key: MLBN
0000: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ................
key: DrmV
0000: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ................
```

Data is blank in the above examples for obvious reasons.
73 changes: 73 additions & 0 deletions pprom/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
mod model;
mod syscfg;

pub use crate::model::PMPModel;
pub use crate::syscfg::{Record, SysCfg};

#[derive(Debug, Clone)]
pub struct Rom {
syscfg: Option<SysCfg>,
contents: Vec<u8>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RomError {}

impl std::fmt::Display for RomError {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {}
}
}

impl std::error::Error for RomError {}

impl Default for Rom {
fn default() -> Rom {
Rom::new()
}
}

impl Rom {
pub fn new() -> Rom {
Rom {
syscfg: None,
contents: Vec::new(),
}
}

pub fn from_dump(dump: &[u8]) -> Result<Rom, RomError> {
let syscfg = SysCfg::from_rom(dump);

Ok(Rom {
syscfg,
contents: dump.to_vec(),
})
}

pub fn model(&self) -> PMPModel {
// A dump we couldn't find a table in tells us nothing about the model.
let syscfg = match &self.syscfg {
Some(syscfg) => syscfg,
None => return PMPModel::Unknown,
};

// Nor does one whose table has no `HwVr` record.
let hw_vr = match syscfg.get(b"HwVr") {
Some(record) => record,
None => return PMPModel::Unknown,
};

// The revision is the *second* word of the record, not the first.
let gestalt = hw_vr.word(1);

PMPModel::from_gestalt(gestalt)
}

pub fn syscfg(&self) -> Option<&SysCfg> {
self.syscfg.as_ref()
}

pub fn contents(&self) -> &[u8] {
&self.contents
}
}
105 changes: 105 additions & 0 deletions pprom/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#[macro_use]
extern crate log;

use std::fs;
use std::io;
use std::path::PathBuf;

use structopt::StructOpt;
use thiserror::Error;

use pprom::Rom;

#[derive(Error, Debug)]
enum PpromError {
#[error("couldn't read ROM dump {}: {source}", .path.display())]
Read { path: PathBuf, source: io::Error },

#[error("couldn't parse ROM dump: {0}")]
Rom(#[from] pprom::RomError),

#[error("no SCfg table in this dump")]
NoSysCfg,

#[error("couldn't format a hexdump: {0}")]
Hexdump(#[from] io::Error),
}

#[derive(StructOpt)]
#[structopt(name = "pprom")]
#[structopt(about = "Explore PortalPlayer-based iPod InternalROM dumps.")]
struct Args {
/// Path to a dumped InternalROM binary.
#[structopt(parse(from_os_str))]
rom: PathBuf,

#[structopt(subcommand)]
cmd: Option<Command>,
}

#[derive(StructOpt)]
enum Command {
/// Summarize the ROM. This is the default when no subcommand is given.
Info,
/// Dump every key in the ROM's `SCfg` table.
Keys,
}

fn main() {
pretty_env_logger::init();

if let Err(e) = run() {
eprintln!("error: {}", e);
std::process::exit(1);
}
}

fn run() -> Result<(), PpromError> {
let args = Args::from_args();

let dump = fs::read(&args.rom).map_err(|source| PpromError::Read {
path: args.rom.clone(),
source,
})?;
debug!("read {} bytes from {}", dump.len(), args.rom.display());

let rom = Rom::from_dump(&dump)?;

match args.cmd.unwrap_or(Command::Info) {
Command::Info => cmd_info(&rom),
Command::Keys => cmd_keys(&rom),
}
}

fn cmd_info(rom: &Rom) -> Result<(), PpromError> {
println!("model: {:?}", rom.model());
println!("length: {} bytes", rom.contents().len());

match rom.syscfg() {
Some(cfg) => println!("SCfg: {} keys", cfg.records.len()),
None => println!("SCfg: not found"),
}

Ok(())
}

fn cmd_keys(rom: &Rom) -> Result<(), PpromError> {
let cfg = rom.syscfg().ok_or(PpromError::NoSysCfg)?;

println!(
"SCfg v{}.{}: {} keys",
cfg.version.0,
cfg.version.1,
cfg.records.len(),
);
println!();

for r in &cfg.records {
println!("key: {}", r.tag_str());
let mut buf = Vec::new();
hxdmp::hexdump(&r.value, &mut buf)?;
println!("{}", String::from_utf8_lossy(&buf));
}

Ok(())
}
56 changes: 56 additions & 0 deletions pprom/src/model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PMPModel {
/// iPod (1st Generation)
Ipod1g,

/// iPod (2nd Generation)
Ipod2g,

/// iPod (3rd Generation)
Ipod3g,

/// iPod (4th Generation)
Ipod4g,

/// iPod (5th Generation)
Ipod5g,

/// iPod mini (1st Generation)
IpodMini1g,

/// iPod mini (2nd Generation)
IpodMini2g,

/// iPod color
IpodColor,

/// iPod photo
IpodPhoto,

/// iPod Nano (1st Generation)
IpodNano1g,

/// Unknown Portable Media Player
Unknown,
}

impl PMPModel {
pub fn from_gestalt(gestalt: u32) -> PMPModel {
use PMPModel::*;

// Sourced from: http://www.ipodlinux.org/Generations/
match gestalt {
0x00010000 | 0x00010001 | 0x00010002 => Ipod1g,
0x00020000 | 0x00020001 => Ipod2g,
0x00030001 => Ipod3g,
0x00050013 | 0x00050014 => Ipod4g,
0x000B0005 | 0x000B0010 => Ipod5g,
0x00040013 => IpodMini1g,
0x00070002 => IpodMini2g,
0x000C0005 | 0x000C0006 => IpodNano1g,
0x00060000 => IpodPhoto,
0x00060004 => IpodColor,
_ => Unknown,
}
}
}
Loading
Loading