use std::{
ffi::OsStr,
fs, io,
path::{Path, PathBuf},
};
use crate::error::{Error, ErrorValue, Result};
const TRY_PATHS: &[&str] = &[
r"C:\SquareEnix\FINAL FANTASY XIV - A Realm Reborn",
r"C:\Program Files (x86)\Steam\steamapps\common\FINAL FANTASY XIV Online",
r"C:\Program Files (x86)\Steam\steamapps\common\FINAL FANTASY XIV - A Realm Reborn",
r"C:\Program Files (x86)\FINAL FANTASY XIV - A Realm Reborn",
r"C:\Program Files (x86)\SquareEnix\FINAL FANTASY XIV - A Realm Reborn",
];
const WSL_PREFIX: &[&str] = &["/mnt", "c"];
const SQPACK_PATH: &[&str] = &["game", "sqpack"];
const CATEGORIES: &[Option<&str>] = &[
Some("common"),
Some("bgcommon"),
Some("bg"),
Some("cut"),
Some("chara"),
Some("shader"),
Some("ui"),
Some("sound"),
Some("vfx"),
Some("ui_script"),
Some("exd"),
Some("game_script"),
Some("music"),
None,
None,
None,
None,
None,
Some("sqpack_test"),
Some("debug"),
];
#[allow(dead_code)]
#[derive(Debug)]
enum Platform {
Win32 = 0,
PS3 = 1,
PS4 = 2,
}
#[derive(Debug)]
pub struct FsResource {
path: PathBuf,
repositories: Vec<String>,
platform: Platform,
}
impl FsResource {
pub fn search() -> Option<Self> {
Some(Self::at(&find_install()?))
}
pub fn at(path: &Path) -> Self {
let sqpack_path = path
.iter()
.chain(SQPACK_PATH.iter().map(|s| OsStr::new(*s)))
.collect::<PathBuf>();
let repositories = find_repositories(&sqpack_path);
Self {
path: sqpack_path,
repositories,
platform: Platform::Win32,
}
}
fn build_file_path(
&self,
repository: u8,
category: u8,
chunk: u8,
extension: &str,
) -> Result<PathBuf> {
let platform = match self.platform {
Platform::Win32 => "win32",
Platform::PS3 => todo!("PS3 platform"),
Platform::PS4 => todo!("PS4 platform"),
};
let file_name = format!("{category:02x}{repository:02x}{chunk:02x}.{platform}.{extension}");
let file_path = self.path.join(
[self.get_repository_name(repository)?, &file_name]
.iter()
.collect::<PathBuf>(),
);
Ok(file_path)
}
fn get_repository_name(&self, repository: u8) -> Result<&String> {
self.repositories
.get(usize::from(repository))
.ok_or_else(|| Error::NotFound(ErrorValue::Other(format!("repository {repository}"))))
}
}
#[cfg(feature = "sqpack")]
use crate::sqpack::Resource;
#[cfg(feature = "sqpack")]
impl Resource for FsResource {
fn path_metadata(&self, path: &str) -> Option<(u8, u8)> {
let split = path.split('/').take(2).collect::<Vec<_>>();
match split[..] {
[path_category, path_repository] => Some((
self.repositories
.iter()
.position(|repository| repository == path_repository)
.unwrap_or(0)
.try_into()
.unwrap(),
CATEGORIES
.iter()
.position(|category| category == &Some(path_category))?
.try_into()
.unwrap(),
)),
_ => None,
}
}
fn version(&self, repository: u8) -> Result<String> {
let path = match repository {
0 => self.path.join("..").join("ffxivgame.ver"),
repo => {
let repository_name = self.get_repository_name(repo)?;
self.path
.join(&repository_name)
.join(format!("{repository_name}.ver"))
}
};
Ok(fs::read_to_string(path)?)
}
type Index = io::Cursor<Vec<u8>>;
fn index(&self, repository: u8, category: u8, chunk: u8) -> Result<Self::Index> {
read_index(self.build_file_path(repository, category, chunk, "index")?)
}
type Index2 = io::Cursor<Vec<u8>>;
fn index2(&self, repository: u8, category: u8, chunk: u8) -> Result<Self::Index2> {
read_index(self.build_file_path(repository, category, chunk, "index2")?)
}
type Dat = fs::File;
fn dat(&self, repository: u8, category: u8, chunk: u8, dat: u8) -> Result<Self::Dat> {
let path = self.build_file_path(repository, category, chunk, &format!("dat{dat}"))?;
Ok(fs::File::open(path)?)
}
}
fn find_install() -> Option<PathBuf> {
TRY_PATHS
.iter()
.flat_map(|path| {
[
PathBuf::from(path),
WSL_PREFIX
.iter()
.copied()
.chain(path.split('\\').skip(1))
.collect::<PathBuf>(),
]
})
.find(|path| path.exists())
}
fn find_repositories(path: &Path) -> Vec<String> {
(0..=9)
.filter_map(|index| {
let name = if index == 0 {
"ffxiv".into()
} else {
format!("ex{}", index)
};
if path.join(&name).exists() {
Some(name)
} else {
None
}
})
.collect()
}
fn read_index(path: PathBuf) -> Result<io::Cursor<Vec<u8>>> {
let buffer = fs::read(&path).map_err(|error| match error.kind() {
io::ErrorKind::NotFound => {
Error::NotFound(ErrorValue::Other(format!("file path {path:?}")))
}
_ => Error::Resource(error.into()),
})?;
Ok(io::Cursor::new(buffer))
}