2022-08-09 23:39:31 -04:00
|
|
|
use std::ffi::{CStr, CString, NulError};
|
2022-07-20 19:07:36 -04:00
|
|
|
use std::fs;
|
|
|
|
use std::fs::File;
|
|
|
|
use std::io::Write;
|
|
|
|
use std::os::raw::c_char;
|
|
|
|
|
|
|
|
const FILES_TO_EXTRACT: [&str; 3] = ["data1.cab", "data1.hdr", "data2.cab"];
|
|
|
|
|
|
|
|
const BOOT_COMPONENT_FILES: [&str; 18] = [
|
|
|
|
"cef_license.txt",
|
|
|
|
"FFXIV.ico",
|
|
|
|
"ffxivboot.exe",
|
|
|
|
"ffxivboot.ver",
|
|
|
|
"ffxivboot64.exe",
|
|
|
|
"ffxivconfig.exe",
|
|
|
|
"ffxivconfig64.exe",
|
|
|
|
"ffxivlauncher.exe",
|
|
|
|
"ffxivlauncher64.exe",
|
|
|
|
"ffxivsysinfo.exe",
|
|
|
|
"ffxivsysinfo64.exe",
|
|
|
|
"ffxivupdater.exe",
|
|
|
|
"ffxivupdater64.exe",
|
|
|
|
"FFXIV_sysinfo.ico",
|
|
|
|
"icudt.dll",
|
|
|
|
"libcef.dll",
|
|
|
|
"license.txt",
|
2022-08-16 11:52:07 -04:00
|
|
|
"locales/reserved.txt",
|
2022-07-20 19:07:36 -04:00
|
|
|
];
|
|
|
|
|
|
|
|
const GAME_COMPONENT_FILES: [&str; 1] = ["ffxivgame.ver"];
|
|
|
|
|
|
|
|
#[repr(C)]
|
2022-07-27 21:21:50 -04:00
|
|
|
struct Unshield {
|
2022-08-16 11:52:07 -04:00
|
|
|
_private: [u8; 0],
|
2022-07-27 21:21:50 -04:00
|
|
|
}
|
2022-07-20 19:07:36 -04:00
|
|
|
|
2022-07-21 18:57:57 -04:00
|
|
|
#[link(name = "unshield")]
|
2022-07-20 19:07:36 -04:00
|
|
|
extern "C" {
|
|
|
|
fn unshield_open(filename: *const c_char) -> *mut Unshield;
|
|
|
|
fn unshield_close(unshield: *mut Unshield);
|
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
fn unshield_set_log_level(level: i32);
|
2022-07-20 19:07:36 -04:00
|
|
|
|
|
|
|
fn unshield_file_count(unshield: *mut Unshield) -> i32;
|
2022-08-16 11:52:07 -04:00
|
|
|
fn unshield_file_name(unshield: *mut Unshield, index: i32) -> *const c_char;
|
|
|
|
fn unshield_file_save(unshield: *mut Unshield, index: i32, filename: *const c_char) -> bool;
|
2022-07-20 19:07:36 -04:00
|
|
|
}
|
|
|
|
|
2022-08-09 23:39:31 -04:00
|
|
|
pub enum InstallError {
|
|
|
|
IOFailure,
|
2022-08-16 11:52:07 -04:00
|
|
|
FFIFailure,
|
2022-08-09 23:39:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
impl From<std::io::Error> for InstallError {
|
|
|
|
fn from(_: std::io::Error) -> Self {
|
|
|
|
InstallError::IOFailure
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<NulError> for InstallError {
|
|
|
|
fn from(_: NulError) -> Self {
|
|
|
|
InstallError::FFIFailure
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-20 19:07:36 -04:00
|
|
|
/// Installs the game from the provided retail installer.
|
2022-08-16 11:52:07 -04:00
|
|
|
pub fn install_game(installer_path: &str, game_directory: &str) -> Result<(), InstallError> {
|
2022-07-27 21:21:50 -04:00
|
|
|
let installer_file = fs::read(installer_path).unwrap();
|
2022-07-20 19:07:36 -04:00
|
|
|
|
|
|
|
let mut last_position = 0;
|
|
|
|
let mut last_filename = "";
|
|
|
|
for filename in FILES_TO_EXTRACT {
|
|
|
|
let needle = format!("Disk1\\{}", filename);
|
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
let position = installer_file
|
|
|
|
.windows(needle.len())
|
|
|
|
.position(|window| window == needle.as_str().as_bytes());
|
2022-07-20 19:07:36 -04:00
|
|
|
if position == None {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
let position = position.unwrap();
|
|
|
|
|
|
|
|
if last_position != 0 {
|
|
|
|
let mut new_file = File::create(last_filename).unwrap();
|
|
|
|
|
|
|
|
if last_filename == "data1.hdr" {
|
2022-08-16 11:50:18 -04:00
|
|
|
new_file.write_all(&installer_file[last_position + 30..position - 42])?;
|
2022-07-20 19:07:36 -04:00
|
|
|
} else {
|
2022-08-16 11:50:18 -04:00
|
|
|
new_file.write_all(&installer_file[last_position + 33..position - 42])?;
|
2022-07-20 19:07:36 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
last_position = position;
|
|
|
|
last_filename = filename;
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut new_file = File::create(last_filename).unwrap();
|
|
|
|
|
2022-08-16 11:50:18 -04:00
|
|
|
new_file.write_all(&installer_file[last_position + 33..installer_file.len() - 42])?;
|
2022-07-20 19:07:36 -04:00
|
|
|
|
2022-08-09 23:39:31 -04:00
|
|
|
fs::create_dir_all(format!("{game_directory}/boot"))?;
|
|
|
|
fs::create_dir_all(format!("{game_directory}/game"))?;
|
2022-07-20 19:07:36 -04:00
|
|
|
|
2022-07-27 21:21:50 -04:00
|
|
|
// set unshield to shut up
|
2022-08-09 23:39:31 -04:00
|
|
|
unsafe { unshield_set_log_level(0) };
|
2022-07-20 19:07:36 -04:00
|
|
|
|
2022-08-09 23:39:31 -04:00
|
|
|
let unshield = unsafe { unshield_open(b"data1.cab".as_ptr() as *const c_char) };
|
|
|
|
let file_count = unsafe { unshield_file_count(unshield) };
|
2022-07-20 19:07:36 -04:00
|
|
|
|
2022-07-27 21:21:50 -04:00
|
|
|
for i in 0..file_count {
|
2022-08-09 23:39:31 -04:00
|
|
|
let filename = unsafe { CStr::from_ptr(unshield_file_name(unshield, i)).to_string_lossy() };
|
2022-07-20 19:07:36 -04:00
|
|
|
|
2022-07-27 21:21:50 -04:00
|
|
|
for boot_name in BOOT_COMPONENT_FILES {
|
|
|
|
if boot_name == filename {
|
|
|
|
let save_filename = format!("{game_directory}/boot/{boot_name}");
|
2022-08-09 23:39:31 -04:00
|
|
|
let save_filename_c = CString::new(save_filename)?;
|
|
|
|
unsafe { unshield_file_save(unshield, i, save_filename_c.as_ptr()) };
|
2022-07-20 19:07:36 -04:00
|
|
|
}
|
2022-07-27 21:21:50 -04:00
|
|
|
}
|
2022-07-20 19:07:36 -04:00
|
|
|
|
2022-07-27 21:21:50 -04:00
|
|
|
for game_name in GAME_COMPONENT_FILES {
|
|
|
|
if game_name == filename {
|
|
|
|
let save_filename = format!("{game_directory}/game/{game_name}");
|
2022-08-09 23:39:31 -04:00
|
|
|
let save_filename_c = CString::new(save_filename)?;
|
|
|
|
unsafe { unshield_file_save(unshield, i, save_filename_c.as_ptr()) };
|
2022-07-20 19:07:36 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-07-27 21:21:50 -04:00
|
|
|
|
2022-08-16 11:52:07 -04:00
|
|
|
unsafe {
|
|
|
|
unshield_close(unshield);
|
|
|
|
}
|
2022-08-09 23:39:31 -04:00
|
|
|
|
|
|
|
Ok(())
|
2022-07-20 19:07:36 -04:00
|
|
|
}
|