Skip to content

Instantly share code, notes, and snippets.

@PARTYMANX
Created December 12, 2024 08:07
Show Gist options
  • Save PARTYMANX/68112d487259c027251f19223ec74897 to your computer and use it in GitHub Desktop.
Save PARTYMANX/68112d487259c027251f19223ec74897 to your computer and use it in GitHub Desktop.
THPS trg gap parser
use std::env;
use std::str;
use std::fs;
use std::fs::File;
use std::io::{ self, BufRead, BufReader };
use std::io::Seek;
use std::io::Read;
use std::io::Write;
use std::path::Path;
use std::io::SeekFrom;
use std::convert::TryInto;
struct Gap {
gap_type: u16,
score: u16,
name: String,
}
fn read_u8(file: &mut File) -> u8 {
let mut buf = [0; 1];
file.read(&mut buf[..]);
return buf[0];
}
fn read_u16(file: &mut File) -> u16 {
let mut buf = [0; 2];
file.read(&mut buf[..]); // TODO: should probably make sure 2 bytes were read...
return (buf[0] as u16) | ((buf[1] as u16) << 8);
}
fn read_u32(file: &mut File) -> u32 {
let mut buf = [0; 4];
let count = file.read(&mut buf[..]); // TODO: should probably make sure 4 bytes were read...
assert_eq!(matches!(count, Ok(4)), true);
//println!("{} {} {} {}", buf[0], buf[1], buf[2], buf[3]);
return (buf[0] as u32) | ((buf[1] as u32) << 8) | ((buf[2] as u32) << 16) | ((buf[3] as u32) << 24);
}
fn read_string(file: &mut File, max_len: u32) -> String {
let mut result = String::from("");
let mut i = 0;
let mut is_end = false;
while !is_end {
i += 1;
let c = read_u8(file);
if c == 0 || i >= max_len {
is_end = true;
} else {
result.push(char::from(c));
}
}
return result;
}
fn read_gaplist(filename: &String) -> Vec<Gap> {
println!("Reading gap list from file {}", filename);
let path = Path::new(&filename);
let mut file = File::open(path).unwrap();
file.seek(SeekFrom::Start(0x99a38 as u64));
let mut result = Vec::new();
let mut is_end = false;
while !is_end {
let gap_type = read_u16(&mut file);
let score = read_u16(&mut file);
let name_addr = read_u32(&mut file);
let cur_offset = file.seek(SeekFrom::Current(0)).unwrap();
let name = if name_addr != 0 {
file.seek(SeekFrom::Start((name_addr - 0x8000f800) as u64));
let result = read_string(&mut file, 128);
file.seek(SeekFrom::Start(cur_offset));
result
} else {
String::from("")
};
let gap = Gap {
gap_type,
score,
name,
};
if gap.gap_type == 0xffff {
is_end = true;
} else {
result.push(gap);
}
}
return result;
}
fn gaplist_dump(filename: &String) {
let gaplist = read_gaplist(filename);
for gap in gaplist {
println!("{}: type: {} score: {}", gap.name, gap.gap_type, gap.score);
}
}
fn get_checklist(gaplist: &Vec<Gap>, gap_checklist: &mut Vec<bool>, trg_name: &String) {
let path = Path::new(trg_name);
let mut file = File::open(path).unwrap();
let magic_num = read_u32(&mut file);
let ver = read_u32(&mut file);
if magic_num != 0x4752545f || ver != 2 {
println!("TRG FILE INVALID");
return;
}
let num_offsets = read_u32(&mut file);
let mut offsets = Vec::with_capacity(num_offsets as usize);
for _ in 0..num_offsets {
offsets.push(read_u32(&mut file));
}
for offset in offsets {
file.seek(SeekFrom::Start(offset as u64));
let node_type = read_u16(&mut file);
if node_type == 6 || node_type == 2 || node_type == 9 {
// command point. dump the node
let mut checksum_offset = offset + 1 + (read_u16(&mut file) as u32 * 2) + 1;
if (checksum_offset & 2) != 0 {
checksum_offset += 2;
}
file.seek(SeekFrom::Start(checksum_offset as u64));
let checksum = read_u32(&mut file);
let mut is_end = false;
while !is_end {
let cmd = read_u16(&mut file);
if cmd == 0x00c9 {
let id_offset = file.seek(SeekFrom::Current(4)).unwrap();
if (id_offset & 2) != 0 {
file.seek(SeekFrom::Current(2));
}
let gap_id = read_u16(&mut file);
let search_result = {
let mut result = None;
for i in 0..gaplist.len() {
if gaplist[i].gap_type == gap_id {
result = Some(i);
}
}
result
};
if let Some(gap_idx) = search_result {
gap_checklist[gap_idx] = true;
} else {
println!("Unknown gap??? {} ({})", gap_id, checksum_offset);
}
} else if cmd == 0xffff {
is_end = true;
}
}
}
}
}
fn gap_dump(exe_name: &String, trg_names: &Vec<String>) {
let gaplist = read_gaplist(&exe_name);
let mut gap_checklist = vec![false; gaplist.len()];
for trg_name in trg_names {
get_checklist(&gaplist, &mut gap_checklist, &trg_name);
}
if trg_names.len() == 1 {
for i in 0..gaplist.len() {
if gap_checklist[i] {
println!("{}: score: {}", gaplist[i].name, gaplist[i].score);
}
}
} else {
for i in 0..gaplist.len() {
if !gap_checklist[i] {
println!("{}: score: {} id: {}", gaplist[i].name, gaplist[i].score, gaplist[i].gap_type);
}
}
}
}
fn main() {
let mut args: Vec<String> = env::args().collect();
args.remove(0); // ignore first arg
if args.len() > 1 {
gap_dump(&args.remove(0), &args);
} else if args.len() == 1 {
gaplist_dump(&args[0]);
} else {
println!("Usage: gapdump.exe [executable] [trg file]")
}
}
WAREHOUSE:
[TRANSFER]: score: 200
[TAXI GAP]: score: 600
[KICKER GAP]: score: 100
[OVER THE PIPE]: score: 300
[SECRET ROOM]: score: 300
[FACEPLANT]: score: 400
[CHANNEL GAP]: score: 250
[KICKER 2 LEDGE]: score: 200
[BIG RAIL]: score: 200
[DECK 2 RAIL]: score: 300
[TAXI 2 LEDGE]: score: 500
[TAXI 2 RAIL]: score: 1000
[MONSTER GRIND]: score: 500
[HIGH RAIL]: score: 200
[HOLY SHI...]: score: 3000
[TRANSITION GRIND]: score: 400
SCHOOL:
[ROOF 2 ROOF GAP]: score: 500
[SWIM TEAM GAP]: score: 1000
[GARBAGE OLLIE]: score: 50
[ROOF TO AWNING GAP]: score: 750
[DITCH SLAP]: score: 250
[OVER THE AIR CONDITIONER]: score: 750
[OVER A FOOTBRIDGE]: score: 1000
[PARK GAP]: score: 500
[MINI GAP]: score: 250
[PLANTER GAP]: score: 100
[KICKER GAP]: score: 100
[HALL PASS GAP]: score: 1000
[DUMPSTER RAIL GAP]: score: 250
[PLAYGROUND RAIL]: score: 500
[RAIL TO RAIL TRANSFER]: score: 750
[HUGE RAIL]: score: 1000
[LONG ASS RAIL]: score: 2500
[FUNBOX TO RAIL TRANSFER]: score: 250
[FUNBOX TO TABLE TRANSFER]: score: 500
[GIMME GAP]: score: 50
[HANDICAP RAMP RAIL]: score: 500
MALL:
[OVER A 16 STAIR SET]: score: 250
[OVER A HUGE 32 STAIR GAP]: score: 2000
[SKATER ESCALATOR GAP]: score: 500
[32 STEPS OFF A MEZZANINE]: score: 2500
[THE FLYING LEAP]: score: 100
[PLANTER GAP]: score: 100
[GOING UP GAP]: score: 250
[GOING DOWN GAP]: score: 250
[FOUNTAIN GAP]: score: 250
[COFFEE GRIND]: score: 1000
[FOR THE WHOLE ATRIUM]: score: 500
[RAIL COMBO]: score: 500
SKATEPARK:
[TRANSFER]: score: 200
[ACID DROP]: score: 1000
[WHOOP GAP]: score: 1000
[WALL GAP]: score: 100
[OVER THE BOX]: score: 100
[OVER THE RAFTERS]: score: 2000
[OVER THE PIPE]: score: 700
[POOL HIP]: score: 500
[POOL 2 WALKWAY]: score: 700
[HP TRANSFER]: score: 250
[RAFTER RAIL]: score: 1000
[PIPE 2 BOX GRIND]: score: 1000
[LIGHT GRIND]: score: 500
[WALKWAY RAIL TRANS]: score: 700
[POOL RAIL TRANS]: score: 1000
DOWNTOWN:
[PHAT GAP]: score: 1000
[TRANSFER]: score: 200
[KICKER GAP]: score: 100
[KICKER 2 STREET]: score: 100
[BS GAP]: score: 500
[T 2 T GAP]: score: 500
[SECRET TUNNEL ENTRANCE]: score: 500
[TUNNEL GAP]: score: 1000
[OVER THE TUNNEL]: score: 2000
[CAR OLLIE]: score: 100
[CHEESY DECK GAP]: score: 50
[DECK GAP]: score: 250
[BURLY DECK GAP]: score: 2500
[TRUCK GAP]: score: 250
[ROOF 2 ROOF]: score: 2000
[SUCKY ROOM GAP]: score: 1500
[BIG ASS]: score: 1500
[GLASS GAP]: score: 750
[KICKER 2 EDGE]: score: 100
[BS GRIND]: score: 200
[RAIL 2 RAIL TRANSFER]: score: 750
[BILLBOARD GRIND]: score: 500
[DIRTY RAIL]: score: 3000
[DEATH GRIND]: score: 2000
DHJ:
[HUGE WATER HAZARD GAP]: score: 1000
[50 FEET]: score: 50
[100 FEET]: score: 100
[150 FEET]: score: 150
[200 FEET]: score: 200
[250 FEET]: score: 250
[SMALL WATER HAZARD GAP]: score: 250
[25 FEET]: score: 25
[75 FEET]: score: 75
[125 FEET]: score: 125
[175 FEET]: score: 175
[225 FEET]: score: 225
[NEVERSOFT ELEC CO GAP]: score: 1500
BURNSIDE:
[TRANSFER]: score: 200
[BRIDGE GAP]: score: 1000
[VERT WALL GAP]: score: 700
[TWINKIE TRANSFER]: score: 700
[OVER DA POOL]: score: 800
[TRIPLE RAIL]: score: 1000
[BRIDGE GRIND]: score: 800
STREETS:
[PORCH GAP]: score: 250
[LOMBARD GAP]: score: 5000
[THE GONZ GAP]: score: 500
[PAGODA GAP]: score: 1000
[OVER THE SEVEN]: score: 100
[HUBBA GAP]: score: 750
[STREET GAP]: score: 500
[HANDI GAP]: score: 1000
[C BLOCK GAP]: score: 500
[PLANTER GAP]: score: 500
[FOUNTAIN GAP]: score: 750
[SPINE GAP]: score: 1000
[RAMP 2 RAMP]: score: 500
[RAMP 2 RAMP]: score: 750
[OVERSIZED 8 SET]: score: 500
[ACID DROP-IN]: score: 1000
[KICKER GAP]: score: 500
[DOWN THE SPIRAL]: score: 2000
[LOMBARD LEDGE]: score: 250
[HUBBA LEDGE]: score: 500
[HOOK RAIL]: score: 750
[RAIL 2 RAIL]: score: 500
[BACKWOODS LEDGE]: score: 250
[BENDY'S LIP]: score: 500
ROSWELL:
[LOW DECK GAP]: score: 500
[HIGH DECK GAP]: score: 1000
[DECK GAP]: score: 1500
[ROLL IN CHANNEL GAP]: score: 1000
[CHANNEL GAP]: score: 500
[ET GRIND]: score: 1000
[BHOUSE RAIL]: score: 1000
[POOL GRIND]: score: 2000
[DECK GRIND]: score: 800
[MB EMERSON GRIND]: score: 2000
UNUSED:
[GAP]: score: 500
[AWNING GAP]: score: 125
[]: score: 500
[RAMP GAP]: score: 250
[RAGING GORGE GAP]: score: 250
[BIG WATER HAZARD GAP]: score: 500
[RAMP 2 RAMP]: score: 250
[TUNNEL GAP]: score: 500
[PORCH GAP]: score: 500
[STREET GAP]: score: 250
[FOUNTAIN GAP: score: 250
[PORCH GAP]: score: 750
[PAGODA HOP]: score: 500
[PAGODA HOP]: score: 750
[GAP]: score: 1000
[FREAKIN POOL GRIND]: score: 100
[GAP]: score: 100
[GAP]: score: 100
[GAP]: score: 1000
[GAP]: score: 1000
[GAP]: score: 1000
[GAP]: score: 1000
[GAP]: score: 1000
[KICKER 2 LEDGE]: score: 200
[HAWK BRIDGE GRIND]: score: 1000
[ARE YOU KIDDING?]: score: 3000
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment