Skip to content

Commit

Permalink
Introduce WIP OG Xbox support
Browse files Browse the repository at this point in the history
  • Loading branch information
astarivi authored and iliazeus committed Oct 8, 2024
1 parent 1f59107 commit 7840c71
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 87 deletions.
9 changes: 3 additions & 6 deletions examples/game_title/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use anyhow::{Context, Error};
use clap::{command, Parser};
use hex;

use iso2god::game_list;

Expand All @@ -25,16 +24,14 @@ struct Cli {
fn main() -> Result<(), Error> {
let args = Cli::parse();

let title_id: [u8; 4] = hex::decode(&args.title_id)?
.try_into()
.map_err(|_| Error::msg("invalid title ID"))?;
let title_id = u32::from_str_radix("4D530064", 16)?;

match args.source {
Source::BuiltIn => {
println!("querying the built-in DB for title ID {}", args.title_id);

if let Some(name) = game_list::find_title_by_id(title_id) {
let title_id = hex::encode_upper(title_id);
let title_id = format!("{:08X}", title_id);
println!("Title ID: {title_id}");
println!(" Name: {name}");
} else {
Expand All @@ -48,7 +45,7 @@ fn main() -> Result<(), Error> {
let client = unity::Client::new().context("error creating XboxUnity client")?;

let unity_title_info = client
.find_xbox_360_title_id(&title_id)
.find_xbox_360_title_id(title_id)
.context("error querying XboxUnity")?;

if let Some(unity_title_info) = &unity_title_info {
Expand Down
6 changes: 2 additions & 4 deletions examples/game_title/unity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ use anyhow::Error;

use std::time::Duration;

use hex;

use std::fmt;

#[derive(Deserialize)]
Expand Down Expand Up @@ -131,8 +129,8 @@ impl Client {
Ok(response)
}

pub fn find_xbox_360_title_id(&self, title_id: &[u8; 4]) -> Result<Option<Title>, Error> {
let title_id = hex::encode_upper(title_id);
pub fn find_xbox_360_title_id(&self, title_id: u32) -> Result<Option<Title>, Error> {
let title_id = format!("{:08X}", title_id);

let title_list = self.search(&title_id)?;

Expand Down
30 changes: 13 additions & 17 deletions src/bin/iso2god.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ use anyhow::{Context, Error};

use clap::{arg, command, Parser};

use iso2god::{game_list, god, iso, xex};
use iso2god::executable::TitleInfo;
use iso2god::god::ContentType;
use iso2god::{game_list, god, iso};

#[derive(Parser)]
#[command(author, version, about, long_about = None)]
Expand Down Expand Up @@ -58,25 +60,22 @@ fn main() -> Result<(), Error> {
let mut source_iso = iso::IsoReader::read(BufReader::new(source_iso_file))
.context("error reading source ISO")?;

let mut default_xex = source_iso
.get_entry(&"\\default.xex".into())
.context("error reading source ISO")?
.context("default.xex file not found")?;
let title_info =
TitleInfo::from_image(&mut source_iso).context("error reading image executable")?;

let default_xex_header =
xex::XexHeader::read(&mut default_xex).context("error reading default.xex")?;

let exe_info = default_xex_header
.fields
.execution_info
.context("no execution info in default.xex header")?;
let exe_info = title_info.execution_info;
let content_type = title_info.content_type;

{
let title_id = hex::encode_upper(exe_info.title_id);
let title_id = format!("{:08X}", exe_info.title_id);
let name = game_list::find_title_by_id(exe_info.title_id).unwrap_or("(unknown)".to_owned());

println!("Title ID: {title_id}");
println!(" Name: {name}");
match content_type {
ContentType::GamesOnDemand => println!(" Type: Games on Demand"),
ContentType::XboxOriginal => println!(" Type: Xbox Original"),
}
}

if args.dry_run {
Expand All @@ -93,9 +92,6 @@ fn main() -> Result<(), Error> {
let block_count = div_ceil(data_size, god::BLOCK_SIZE as u64);
let part_count = div_ceil(block_count, god::BLOCKS_PER_PART);

// the original code does not seem to support other types
let content_type = god::ContentType::GamesOnDemand;

let file_layout = god::FileLayout::new(&args.dest_dir, &exe_info, content_type);

println!("clearing data directory");
Expand Down Expand Up @@ -150,7 +146,7 @@ fn main() -> Result<(), Error> {
part_count as u32,
last_part_size + (part_count - 1) * (god::BLOCK_SIZE as u64) * 0xa290,
)
.with_content_type(god::ContentType::GamesOnDemand)
.with_content_type(content_type)
.with_mht_hash(&mht.digest());

let game_title = args
Expand Down
107 changes: 107 additions & 0 deletions src/executable/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use crate::god::ContentType;
use crate::iso::IsoReader;
use anyhow::{anyhow, Context, Error};
use byteorder::{ReadBytesExt, BE, LE};
use std::io::{Read, Seek, SeekFrom};

pub mod xbe;
pub mod xex;

#[derive(Clone, Debug)]
pub struct TitleExecutionInfo {
pub media_id: u32,
pub version: u32,
pub base_version: u32,
pub title_id: u32,
pub platform: u8,
pub executable_type: u8,
pub disc_number: u8,
pub disc_count: u8,
}

pub struct TitleInfo {
pub content_type: ContentType,
pub execution_info: TitleExecutionInfo,
}

impl TitleExecutionInfo {
pub fn from_xex<R: Read>(reader: &mut R) -> Result<TitleExecutionInfo, Error> {
Ok(TitleExecutionInfo {
media_id: reader.read_u32::<BE>()?,
version: reader.read_u32::<BE>()?,
base_version: reader.read_u32::<BE>()?,
title_id: reader.read_u32::<BE>()?,
platform: reader.read_u8()?,
executable_type: reader.read_u8()?,
disc_number: reader.read_u8()?,
disc_count: reader.read_u8()?,
})
}

pub fn from_xbe<R: Read + Seek>(reader: &mut R) -> Result<TitleExecutionInfo, Error> {
reader.seek(SeekFrom::Current(8))?;
let title_id = reader.read_u32::<LE>()?;

reader.seek(SeekFrom::Current(164))?;
let version = reader.read_u32::<LE>()?;

Ok(TitleExecutionInfo {
media_id: 0,
version,
base_version: 0,
title_id,
platform: 0,
executable_type: 0,
disc_number: 1,
disc_count: 1,
})
}
}

impl TitleInfo {
pub fn from_image<R: Read + Seek>(iso_image: &mut IsoReader<R>) -> Result<TitleInfo, Error> {
let content_type;
let mut executable;

match iso_image.get_entry(&"\\default.xex".into())? {
Some(entry) => {
executable = entry;
content_type = ContentType::GamesOnDemand;
}
None => {
executable = iso_image
.get_entry(&"\\default.xbe".into())?
.ok_or_else(|| anyhow!("no executable found in this image"))?;
content_type = ContentType::XboxOriginal;
}
}

let execution_info;

match content_type {
ContentType::GamesOnDemand => {
let default_xex_header =
xex::XexHeader::read(&mut executable).context("error reading default.xex")?;

execution_info = default_xex_header
.fields
.execution_info
.context("no execution info in default.xex header")?;
}
ContentType::XboxOriginal => {
let default_xbe_header =
xbe::XbeHeader::read(&mut executable).context("error reading default.xbe")?;

execution_info = default_xbe_header
.fields
.execution_info
.context("no execution info in default.xbe header")?;
}
}

Ok(TitleInfo {
content_type,
execution_info,
})
}
}
54 changes: 54 additions & 0 deletions src/executable/xbe.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use crate::executable::TitleExecutionInfo;
use anyhow::{bail, Error};
use byteorder::{ReadBytesExt, LE};
use std::io::{Read, Seek, SeekFrom};

pub struct XbeHeader {
// We only need these fields to get the cert address
pub dw_base_addr: u32,
pub dw_certificate_addr: u32,
pub fields: XbeHeaderFields,
}

#[derive(Clone, Default, Debug)]
pub struct XbeHeaderFields {
pub execution_info: Option<TitleExecutionInfo>,
}

impl XbeHeader {
pub fn read<R: Read + Seek>(reader: &mut R) -> Result<XbeHeader, Error> {
Self::check_magic_bytes(reader)?;

// Offset 0x0104
reader.seek(SeekFrom::Current(256))?;
let dw_base_addr = reader.read_u32::<LE>()?;

// Offset 0x0118
reader.seek(SeekFrom::Current(16))?;
let dw_certificate_addr = reader.read_u32::<LE>()?;

let offset = reader.stream_position()? - 284;
let cert_address = dw_certificate_addr - dw_base_addr;
reader.seek(SeekFrom::Start(offset + (cert_address as u64)))?;

let mut fields: XbeHeaderFields = Default::default();
fields.execution_info = Some(TitleExecutionInfo::from_xbe(reader)?);

Ok(XbeHeader {
dw_base_addr,
dw_certificate_addr,
fields,
})
}

fn check_magic_bytes<R: Read + Seek>(reader: &mut R) -> Result<(), Error> {
let mut magic_bytes = [0u8; 4];
reader.read_exact(&mut magic_bytes)?;

if &magic_bytes != b"XBEH" {
bail!("missing 'XBEH' magic bytes in XBE header");
}

Ok(())
}
}
46 changes: 3 additions & 43 deletions src/xex.rs → src/executable/xex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use byteorder::{ReadBytesExt, BE};
use bitflags::bitflags;
use num_enum::TryFromPrimitive;

use crate::executable::TitleExecutionInfo;
use anyhow::{bail, Error};

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -32,7 +33,7 @@ bitflags! {

#[derive(Clone, Default, Debug)]
pub struct XexHeaderFields {
pub execution_info: Option<XexExecutionInfo>,
pub execution_info: Option<TitleExecutionInfo>,
// other fields will be added if and when necessary
}

Expand Down Expand Up @@ -74,18 +75,6 @@ enum XexHeaderFieldId {
ExportsByName = 0x_00_e1_04_02,
}

#[derive(Clone, Debug)]
pub struct XexExecutionInfo {
pub media_id: [u8; 4],
pub version: u32,
pub base_version: u32,
pub title_id: [u8; 4],
pub platform: u8,
pub executable_type: u8,
pub disc_number: u8,
pub disc_count: u8,
}

impl XexHeader {
pub fn read<R: Read + Seek>(reader: &mut R) -> Result<XexHeader, Error> {
Self::check_magic_bytes(reader)?;
Expand Down Expand Up @@ -133,7 +122,7 @@ impl XexHeader {
Some(Key::ExecutionId) => {
let offset = reader.stream_position()?;
reader.seek(SeekFrom::Start(header_offset + (value as u64)))?;
fields.execution_info = Some(XexExecutionInfo::read(reader)?);
fields.execution_info = Some(TitleExecutionInfo::from_xex(reader)?);
reader.seek(SeekFrom::Start(offset))?;
}

Expand All @@ -149,32 +138,3 @@ impl XexHeader {
})
}
}

impl XexExecutionInfo {
fn read<R: Read>(reader: &mut R) -> Result<XexExecutionInfo, Error> {
let mut media_id = [0_u8; 4];
reader.read_exact(&mut media_id)?;

let version = reader.read_u32::<BE>()?;
let base_version = reader.read_u32::<BE>()?;

let mut title_id = [0_u8; 4];
reader.read_exact(&mut title_id)?;

let platform = reader.read_u8()?;
let executable_type = reader.read_u8()?;
let disc_number = reader.read_u8()?;
let disc_count = reader.read_u8()?;

Ok(XexExecutionInfo {
media_id,
version,
base_version,
title_id,
platform,
executable_type,
disc_number,
disc_count,
})
}
}
4 changes: 2 additions & 2 deletions src/game_list/generate.bash
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ cat <<'EOF'
// Run `./generate.bash titles.jsonl mod.rs` to re-generate.
pub fn find_title_by_id(title_id: [u8; 4]) -> Option<String> {
pub fn find_title_by_id(title_id: u32) -> Option<String> {
GAMES_BY_TITLE_ID
.binary_search_by_key(&u32::from_be_bytes(title_id), |x| x.0)
.binary_search_by_key(&title_id, |x| x.0)
.ok()
.map(|i| GAMES_BY_TITLE_ID[i].1.to_owned())
}
Expand Down
5 changes: 2 additions & 3 deletions src/game_list/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// DO NOT EDIT; this file is auto-generated.
// Run `./generate.bash titles.jsonl mod.rs` to re-generate.


pub fn find_title_by_id(title_id: [u8; 4]) -> Option<String> {
pub fn find_title_by_id(title_id: u32) -> Option<String> {
GAMES_BY_TITLE_ID
.binary_search_by_key(&u32::from_be_bytes(title_id), |x| x.0)
.binary_search_by_key(&title_id, |x| x.0)
.ok()
.map(|i| GAMES_BY_TITLE_ID[i].1.to_owned())
}
Expand Down
Loading

0 comments on commit 7840c71

Please sign in to comment.