use std::borrow::Cow;
use std::fs::read_dir;
use std::io::Read;
use std::path::Path;
use std::path::PathBuf;
use rootcause::prelude::*;
use vfs::VfsPath;
use vfs::impls::overlay::OverlayFS;
use crate::data::DataFileWithCallback;
use crate::data::Version;
use crate::data::assets_bin_vfs::AssetsBinVfs;
use crate::data::idx;
use crate::data::idx_vfs::IdxVfs;
use crate::data::wrappers::mmap::MmapPkgSource;
use crate::error::GameDataError;
use crate::rpc::entitydefs::EntitySpec;
use crate::rpc::entitydefs::parse_scripts;
pub fn list_available_builds(game_dir: &Path) -> Result<Vec<u32>, GameDataError> {
let bin_dir = game_dir.join("bin");
let mut builds: Vec<u32> = Vec::new();
for entry in read_dir(&bin_dir)? {
let entry = entry?;
if entry.file_type().map(|t| t.is_dir()).unwrap_or(false)
&& let Some(build_num) = entry.file_name().to_str().and_then(|name| name.parse::<u32>().ok())
{
builds.push(build_num);
}
}
builds.sort();
Ok(builds)
}
pub fn find_matching_build(game_dir: &Path, replay_version: &Version) -> Result<u32, GameDataError> {
let available_builds = list_available_builds(game_dir)?;
if available_builds.contains(&replay_version.build) {
Ok(replay_version.build)
} else {
Err(GameDataError::BuildNotFound { build: replay_version.build })
}
}
pub struct GameResources {
pub specs: Vec<EntitySpec>,
pub vfs: VfsPath,
}
pub fn load_game_resources(game_dir: &Path, replay_version: &Version) -> Result<GameResources, GameDataError> {
let build = find_matching_build(game_dir, replay_version)?;
let idx_dir = game_dir.join("bin").join(build.to_string()).join("idx");
let mut idx_files = Vec::new();
for entry in read_dir(&idx_dir)? {
let entry = entry?;
if entry.file_type().map(|t| t.is_file()).unwrap_or(false) {
let file_data = std::fs::read(entry.path())?;
idx_files.push(idx::parse(&file_data)?);
}
}
let pkgs_path = game_dir.join("res_packages");
if !pkgs_path.exists() {
return Err(GameDataError::ResPackagesNotFound);
}
let pkg_source = MmapPkgSource::new(&pkgs_path);
let idx_vfs = IdxVfs::new(pkg_source, &idx_files);
let vfs = VfsPath::new(idx_vfs);
let specs = {
let vfs_ref = &vfs;
let loader = DataFileWithCallback::new(move |path: &str| {
let file_path = vfs_ref.join(path)?;
let mut data = Vec::new();
file_path.open_file()?.read_to_end(&mut data)?;
Ok(Cow::Owned(data))
});
parse_scripts(&loader)?
};
Ok(GameResources { specs, vfs })
}
pub fn translations_path(game_dir: &Path, build: u32) -> PathBuf {
game_dir.join("bin").join(build.to_string()).join("res/texts/en/LC_MESSAGES/global.mo")
}
pub fn build_game_vfs(game_dir: &Path) -> Result<VfsPath, Report> {
let builds = list_available_builds(game_dir).attach_with(|| format!("game_dir: {}", game_dir.display()))?;
let latest_build =
builds.last().ok_or_else(|| rootcause::report!("No builds found in {}/bin", game_dir.display()))?;
let idx_dir = game_dir.join("bin").join(latest_build.to_string()).join("idx");
if !idx_dir.exists() {
bail!("idx directory not found: {}", idx_dir.display());
}
let mut idx_files = Vec::new();
for entry in read_dir(&idx_dir).context_with(|| format!("Failed to read idx dir: {}", idx_dir.display()))? {
let entry = entry?;
if entry.file_type().map(|t| t.is_file()).unwrap_or(false) {
let path = entry.path();
let data = std::fs::read(&path).attach_with(|| format!("path: {}", path.display()))?;
let parsed = idx::parse(&data).attach_with(|| format!("path: {}", path.display()))?;
idx_files.push(parsed);
}
}
let pkgs_dir = game_dir.join("res_packages");
if !pkgs_dir.exists() {
bail!("res_packages not found: {}", pkgs_dir.display());
}
let pkg_source = MmapPkgSource::new(&pkgs_dir);
let idx_vfs = IdxVfs::new(pkg_source, &idx_files);
let pkg_vfs = VfsPath::new(idx_vfs);
let mut assets_bin_data = Vec::new();
let assets_loaded = pkg_vfs
.join("content/assets.bin")
.and_then(|p| p.open_file())
.and_then(|mut f| {
f.read_to_end(&mut assets_bin_data)?;
Ok(())
})
.is_ok();
if assets_loaded && let Ok(assets_vfs) = AssetsBinVfs::new(assets_bin_data) {
let assets_layer = VfsPath::new(assets_vfs);
let overlay = OverlayFS::new(&[assets_layer, pkg_vfs]);
return Ok(VfsPath::new(overlay));
}
Ok(pkg_vfs)
}