Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added commands to list vpx contents and script. #112

Merged
merged 13 commits into from
Sep 20, 2023
Merged
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ target/

# local vpxtool config file
vpxtool.cfg

./vscode/**
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ Commands:
diff Prints out a diff between the vbs in the vpx and the sidecar vbs
frontend Acts as a frontend for launching vpx files
index Indexes a directory of vpx files
script Cat the vpx script
ls List directory contents without extracting the vpx file
extract Extracts a vpx file
extractvbs Extracts the vbs from a vpx file next to it
importvbs Imports the vbs next to it into a vpx file
Expand Down
48 changes: 46 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use base64::{engine::general_purpose, Engine as _};

use directb2s::load;
use vpx::tableinfo::{self};
use vpx::{expanded, importvbs, verify, VerifyResult};
use vpx::{cat_script, expanded, importvbs, verify, VerifyResult};
use vpx::{extractvbs, ExtractResult};

use crate::vpx::version;
Expand Down Expand Up @@ -259,11 +259,27 @@ fn main() {
.default_value("true"),
)
.arg(
arg!(<VPXROOTPATH> "The path to the root directory of vpx files")
arg!(<VPXPATH> "The path(s) to the vpx file(s)")
.required(false)
.default_value(&default_tables_root)
),
)
.subcommand(
Command::new("script")
.about("Show a vpx script")
.arg(
arg!(<VPXPATH> "The path to the vpx file")
.required(true),
),
)
.subcommand(
Command::new("ls")
.about("Show a vpx file content")
.arg(
arg!(<VPXPATH> "The path to the vpx file")
.required(true),
),
)
.subcommand(
Command::new("extract")
.about("Extracts a vpx file")
Expand Down Expand Up @@ -391,6 +407,26 @@ fn main() {
&json_path.display()
);
}
Some(("script", sub_matches)) => {
let path = sub_matches
.get_one::<String>("VPXPATH")
.map(|s| s.as_str())
.unwrap_or_default();

let expanded_path = PathBuf::from(expand_path(path));
let code = cat_script(&expanded_path);

println!("{}", code)
}
Some(("ls", sub_matches)) => {
let path = sub_matches
.get_one::<String>("VPXPATH")
.map(|s| s.as_str())
.unwrap_or_default();

let expanded_path = expand_path(path);
ls(expanded_path.as_ref());
}
Some(("extract", sub_matches)) => {
let yes = sub_matches.get_flag("FORCE");
let paths: Vec<&str> = sub_matches
Expand Down Expand Up @@ -731,6 +767,14 @@ fn info(vpx_file_path: &str, json: bool) -> io::Result<()> {
Ok(())
}

pub fn ls(vpx_file_path: &Path) {
let files = expanded::extract_directory_list(vpx_file_path);

for file_path in &files {
println!("{}", file_path);
}
}

pub fn extract(vpx_file_path: &Path, yes: bool) {
let root_dir_path_str = vpx_file_path.with_extension("");
let root_dir_path = Path::new(&root_dir_path_str);
Expand Down
150 changes: 148 additions & 2 deletions src/vpx/expanded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,146 @@ pub fn extract(vpx_file_path: &Path, expanded_path: &Path) -> std::io::Result<()
Ok(())
}

pub fn extract_directory_list(vpx_file_path: &Path) -> Vec<String> {
let root_dir_path_str = vpx_file_path.with_extension("");
let root_dir_path = Path::new(&root_dir_path_str);
let root_dir_parent = root_dir_path
.parent()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_default();

let mut comp = cfb::open(vpx_file_path).unwrap();
let version = version::read_version(&mut comp).unwrap();
let gamedata = read_gamedata(&mut comp, &version).unwrap();

let mut files: Vec<String> = Vec::new();

let images_path = root_dir_path.join("images");
let images_size = gamedata.images_size;
for index in 0..images_size {
let path = format!("GameStg/Image{}", index);
let mut input = Vec::new();
comp.open_stream(&path)
.unwrap()
.read_to_end(&mut input)
.unwrap();
let mut reader = BiffReader::new(&input);
let img = ImageData::biff_read(&mut reader);

let mut jpeg_path = images_path.clone();
let ext = img.ext();

jpeg_path.push(format!("{}.{}", img.name, ext));

files.push(jpeg_path.to_string_lossy().to_string());
}
if images_size == 0 {
files.push(
images_path
.join(std::path::MAIN_SEPARATOR_STR)
.to_string_lossy()
.to_string(),
);
}

let sounds_size = gamedata.sounds_size;
let sounds_path = root_dir_path.join("sounds");
for index in 0..sounds_size {
let path = format!("GameStg/Sound{}", index);
let mut input = Vec::new();
comp.open_stream(&path)
.unwrap()
.read_to_end(&mut input)
.unwrap();
let mut reader = BiffReader::new(&input);
let sound = sound::read(&version, &mut reader);

let ext = sound.ext();
let mut sound_path = sounds_path.clone();
sound_path.push(format!("{}.{}", sound.name, ext));

files.push(sound_path.to_string_lossy().to_string());
}
if sounds_size == 0 {
files.push(
sounds_path
.join(std::path::MAIN_SEPARATOR_STR)
.to_string_lossy()
.to_string(),
);
}

let fonts_size = gamedata.fonts_size;
let fonts_path = root_dir_path.join("fonts");
for index in 0..fonts_size {
let path = format!("GameStg/Font{}", index);
let mut input = Vec::new();
comp.open_stream(&path)
.unwrap()
.read_to_end(&mut input)
.unwrap();
let font = font::read(&input);

let ext = font.ext();
let mut font_path = fonts_path.clone();
font_path.push(format!("Font{}.{}.{}", index, font.name, ext));

files.push(font_path.join("/").to_string_lossy().to_string());
}
if fonts_size == 0 {
files.push(fonts_path.to_string_lossy().to_string());
}

let entries = retrieve_entries_from_compound_file(&mut comp);
entries.iter().for_each(|path| {
let mut stream = comp.open_stream(path).unwrap();
// write the steam directly to a file
let file_path = root_dir_path.join(&path[1..]);
// println!("Writing to {}", file_path.display());
files.push(file_path.to_string_lossy().to_string());
});

files.sort();

// These files are made by:

// -extract_script
files.push(
root_dir_path
.join("script.vbs")
.to_string_lossy()
.to_string(),
);
// -extract_collections
files.push(
root_dir_path
.join("collections.json")
.to_string_lossy()
.to_string(),
);
// -extract_info
files.push(
root_dir_path
.join("TableInfo.json")
.to_string_lossy()
.to_string(),
);
// TODO -extract_gameitems

files = files
.into_iter()
.map(|file_path| {
if let Some(relative_path) = file_path.strip_prefix(&root_dir_parent) {
relative_path.to_string()
} else {
file_path.clone()
}
})
.collect::<Vec<String>>();

files
}

fn extract_info(comp: &mut CompoundFile<File>, root_dir_path: &Path) -> std::io::Result<()> {
let json_path = root_dir_path.join("TableInfo.json");
let mut json_file = std::fs::File::create(&json_path).unwrap();
Expand Down Expand Up @@ -236,8 +376,7 @@ fn extract_gameitems(comp: &mut CompoundFile<File>, gamedata: &GameData, root_di
}
}

fn extract_binaries(comp: &mut CompoundFile<std::fs::File>, root_dir_path: &Path) {
// write all remaining entries
fn retrieve_entries_from_compound_file(comp: &CompoundFile<std::fs::File>) -> Vec<String> {
let entries: Vec<String> = comp
.walk()
.filter(|entry| {
Expand Down Expand Up @@ -267,6 +406,13 @@ fn extract_binaries(comp: &mut CompoundFile<std::fs::File>, root_dir_path: &Path
})
.collect();

entries
}

fn extract_binaries(comp: &mut CompoundFile<std::fs::File>, root_dir_path: &Path) {
// write all remaining entries
let entries = retrieve_entries_from_compound_file(comp);

entries.iter().for_each(|path| {
let mut stream = comp.open_stream(path).unwrap();
// write the steam directly to a file
Expand Down
8 changes: 8 additions & 0 deletions src/vpx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,14 @@ pub fn verify(vpx_file_path: &PathBuf) -> VerifyResult {
}
}

pub fn cat_script(vpx_file_path: &PathBuf) -> String {
let mut comp = cfb::open(vpx_file_path).unwrap();
let version = version::read_version(&mut comp).unwrap();
let gamedata = read_gamedata(&mut comp, &version).unwrap();

gamedata.code.string
}

pub fn vbs_path_for(vpx_file_path: &PathBuf) -> PathBuf {
path_for(vpx_file_path, "vbs")
}
Expand Down