geezshade/src/main.rs

286 lines
9.0 KiB
Rust

use copypasta::{ClipboardContext, ClipboardProvider};
use isahc::{prelude::*, ReadResponseExt, Request};
use native_dialog::{FileDialog, MessageDialog, MessageType};
use serde::Deserialize;
use std::{io::Cursor, path::PathBuf};
use zip::ZipArchive;
#[derive(Deserialize, Debug, Clone)]
struct GeezShadeConfig {
reshade_url: String,
gshade_shaders_url: String,
gshade_presets_url: String,
geezshade_version: String,
geezshade_user_agent: String,
}
fn get_geezshade_config() -> anyhow::Result<GeezShadeConfig> {
let req = Request::get("https://notnite.com/geezshade.json").header(
"User-Agent",
format!("GeezShade/{}", env!("CARGO_PKG_VERSION")),
);
let mut resp = req.body(())?.send()?;
let text = resp.text()?;
let config: GeezShadeConfig = serde_json::from_str(&text)?;
Ok(config)
}
fn get_file(path: &str, user_agent: String) -> anyhow::Result<Vec<u8>> {
let req = Request::get(path)
.header("User-Agent", user_agent)
.redirect_policy(isahc::config::RedirectPolicy::Follow);
let mut resp = req.body(())?.send()?;
Ok(resp.bytes()?)
}
fn get_reshade(xiv_install: PathBuf, config: GeezShadeConfig) -> anyhow::Result<()> {
println!("fetching reshade");
let reshade_installer = get_file(&config.reshade_url, config.geezshade_user_agent)?;
println!("extracting dll");
let mut zip = zip::ZipArchive::new(Cursor::new(reshade_installer))?;
let mut dll = zip.by_name("ReShade64.dll")?;
println!("writing dll");
let mut dll_file = std::fs::File::create(xiv_install.join("dxgi.dll"))?;
std::io::copy(&mut dll, &mut dll_file)?;
Ok(())
}
fn get_gshade_presets(xiv_install: PathBuf, config: GeezShadeConfig) -> anyhow::Result<()> {
println!("fetching presets");
let gshade_shaders = get_file(
&config.gshade_shaders_url,
config.geezshade_user_agent.clone(),
)?;
let gshade_presets = get_file(&config.gshade_presets_url, config.geezshade_user_agent)?;
println!("opening presets");
let shaders_zip = zip::ZipArchive::new(Cursor::new(gshade_shaders))?;
let presets_zip = zip::ZipArchive::new(Cursor::new(gshade_presets))?;
let shaders_folder_name = shaders_zip
.file_names()
.next()
.unwrap()
.split('/')
.next()
.unwrap()
.to_string();
let presets_folder_name = presets_zip
.file_names()
.next()
.unwrap()
.split('/')
.next()
.unwrap()
.to_string();
let shaders_folders = vec!["Textures", "Shaders", "ComputeShaders"]
.iter()
.map(|x| format!("{}/{}", shaders_folder_name, x))
.collect::<Vec<String>>();
let presets_folders = vec![format!("{}/FFXIV", presets_folder_name)];
let dir = xiv_install.join("reshade-shaders");
println!("extracting presets");
extract(dir.clone(), shaders_zip, shaders_folders)?;
extract(dir, presets_zip, presets_folders)?;
Ok(())
}
fn extract(
dir: PathBuf,
presets_zip: ZipArchive<Cursor<Vec<u8>>>,
presets_folders: Vec<String>,
) -> anyhow::Result<()> {
for filename in presets_zip.file_names() {
for folder in &presets_folders {
if filename.starts_with(folder) {
let mut presets_zip = presets_zip.clone();
let mut file = presets_zip.by_name(filename)?;
let path = filename.split('/').collect::<Vec<&str>>();
let path = path[1..].join("/");
let path = dir.join(path);
if path.is_dir() || path.display().to_string().ends_with('/') {
continue;
}
let dir = path.parent().unwrap().to_path_buf();
if !dir.exists() {
std::fs::create_dir_all(dir)?;
}
let mut out = std::fs::File::create(path)?;
std::io::copy(&mut file, &mut out)?;
}
}
}
Ok(())
}
fn get_game_exe() -> anyhow::Result<PathBuf> {
let shared = "Check the install path in the Settings app in Windows, Steam, or XIVLauncher if you don't know where you installed FFXIV.";
MessageDialog::new()
.set_title("GeezShade")
.set_text(&format!(
"You will now be asked to select ffxiv_dx11.exe from your game install.\n{}",
shared
))
.set_type(MessageType::Info)
.show_alert()?;
loop {
let choice = FileDialog::new()
.add_filter("Executable", &["exe"])
.show_open_single_file()?;
if let Some(path) = choice {
if path.file_name().map(|x| x.to_str().unwrap()) == Some("ffxiv_dx11.exe") {
return Ok(path);
} else {
MessageDialog::new()
.set_title("GeezShade")
.set_text(&format!(
"This is not the right file. Please select ffxiv_dx11.exe.\n{}",
shared
))
.set_type(MessageType::Error)
.show_alert()?;
}
} else {
MessageDialog::new()
.set_title("GeezShade")
.set_text(&format!(
"This is not the right file. Please select ffxiv_dx11.exe.\n{}",
shared
))
.set_type(MessageType::Error)
.show_alert()?;
}
}
}
fn show_error(message: &str, error: String) {
MessageDialog::new()
.set_title("GeezShade")
.set_text(&format!("{}\n\nAn error message will be copied to your clipboard. Please report this to geezshade@notnite.com.", message))
.set_type(MessageType::Error)
.show_alert().unwrap();
let mut clipboard = ClipboardContext::new().unwrap();
clipboard.set_contents(error).unwrap();
}
fn do_the_thing(xiv_install: PathBuf, config: GeezShadeConfig) -> anyhow::Result<()> {
if let Err(e) = get_reshade(xiv_install.clone(), config.clone()) {
show_error(
"Failed to download ReShade. You will have to manually install ReShade yourself.",
format!("{:?}", e),
);
return Ok(());
}
if let Err(e) = get_gshade_presets(xiv_install.clone(), config) {
show_error(
"Failed to download GShade shaders & presets. Please try again later.",
format!("{:?}", e),
);
return Ok(());
}
println!("making screenshot folders");
std::fs::create_dir_all(xiv_install.join("reshade-screenshots"))?;
// write config file
let config = r#"
[GENERAL]
EffectSearchPaths=.\reshade-shaders\Shaders\**
TextureSearchPaths=.\reshade-shaders\Textures\**
[SCREENSHOT]
SavePath=.\reshade-screenshots
"#
.trim();
if !xiv_install.join("ReShade.ini").exists() {
println!("writing config");
std::fs::write(xiv_install.join("ReShade.ini"), config)?;
} else {
println!("config already exists");
}
Ok(())
}
fn main() -> anyhow::Result<()> {
let config = get_geezshade_config();
let config = match config {
Ok(config) => config,
Err(e) => {
show_error(
"An error occured trying to fetch information required for GeezShade. This may be because you are offline, the server is down, or the installer is critically out of date.\nTry downloading the latest version at https://notnite.com/geezshade or try again later.",
format!("{:?}", e),
);
std::process::exit(1);
}
};
let semver_current = semver::Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
let semver_latest = semver::Version::parse(&config.geezshade_version).unwrap();
if semver_latest > semver_current {
let choice = MessageDialog::new()
.set_title("GeezShade")
.set_text("A new version of GeezShade is available to download.\nPlease go to https://notnite.com/geezshade for the latest version.\nNot updating may break the installation process, but it is not required.\n\nContinue anyways?")
.set_type(MessageType::Warning)
.show_confirm()?;
if !choice {
return Ok(());
}
}
let game_exe = get_game_exe()?;
let xiv_install = game_exe.parent().unwrap();
let choice = MessageDialog::new()
.set_title("GeezShade")
.set_text("GeezShade will now download ReShade and the GShade presets.\nMake a backup before of your presets and settings before continuing.\n\nContinue?")
.set_type(MessageType::Info)
.show_confirm()?;
if !choice {
return Ok(());
}
if let Err(e) = do_the_thing(xiv_install.to_path_buf(), config) {
show_error("An error occured setting up ReShade.", format!("{:?}", e));
return Err(e);
}
MessageDialog::new()
.set_title("GeezShade")
.set_text("GeezShade has finished installing.\nYou can now launch the game and use the ReShade menu to enable your presets.\nEnjoy!")
.set_type(MessageType::Info)
.show_alert()?;
Ok(())
}