reshader 1.0.1

A tool and library for installing ReShade on Linux!
Documentation
use std::{
    fmt::{Display, Formatter},
    path::PathBuf,
    process::exit,
};

use clap::Parser;
use cli::SubCommand;
use inquire::error::InquireResult;
use strum::{EnumIter, IntoEnumIterator};

use crate::config::Config;
use reshaderlib::{
    clone_reshade_shaders, download_reshade, install_preset_for_game, install_presets,
    install_reshade, install_reshade_shaders, uninstall,
};

mod cli;
mod config;
mod tui;

static QUALIFIER: &str = "eu";
static ORGANIZATION: &str = "cozysoft";
static APPLICATION: &str = "reshader";

#[derive(Debug, EnumIter)]
enum InstallOption {
    ReShade,
    ReShadeVanilla,
    ReShadeShaders,
    GShadePresets,
    Uninstall,
    Quit,
}

impl Display for InstallOption {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            InstallOption::ReShade => write!(
                f,
                "Install/Update ReShade (with addon support, recommended)"
            ),
            InstallOption::ReShadeVanilla => write!(f, "Install/Update ReShade (vanilla)"),
            InstallOption::ReShadeShaders => write!(f, "Install/Update ReShade shaders"),
            InstallOption::GShadePresets => write!(
                f,
                "Install/Update GShade shaders and presets (install ReShade first)"
            ),
            InstallOption::Uninstall => write!(f, "Uninstall ReShade/GShade"),
            InstallOption::Quit => write!(f, "Quit"),
        }
    }
}

async fn tui(
    config: &mut Config,
    client: &reqwest::Client,
    data_dir: &PathBuf,
    config_path: &PathBuf,
    specific_installer: Option<String>,
) -> InquireResult<()> {
    loop {
        let install_option =
            inquire::Select::new("Select an option", InstallOption::iter().collect()).prompt()?;

        let result = match install_option {
            InstallOption::ReShade => {
                download_reshade(client, data_dir, false, None, &specific_installer).await?;
                let install_now = tui::prompt_install()?;
                if install_now {
                    let game_path = tui::prompt_game_path()?;
                    install_reshade(data_dir, &game_path, false).await?;
                    tui::print_reshade_success();

                    if config
                        .game_paths
                        .contains(&game_path.to_str().unwrap().to_string())
                    {
                        return Ok(());
                    }

                    config
                        .game_paths
                        .push(game_path.to_str().unwrap().to_string());

                    Ok(())
                } else {
                    Ok(())
                }
            }
            InstallOption::ReShadeVanilla => {
                download_reshade(client, data_dir, true, None, &specific_installer).await?;
                let install_now = tui::prompt_install()?;
                if install_now {
                    let game_path = tui::prompt_game_path()?;
                    install_reshade(data_dir, &game_path, true).await?;
                    tui::print_reshade_success();

                    if config
                        .game_paths
                        .contains(&game_path.to_str().unwrap().to_string())
                    {
                        return Ok(());
                    }

                    config
                        .game_paths
                        .push(game_path.to_str().unwrap().to_string());

                    Ok(())
                } else {
                    Ok(())
                }
            }
            InstallOption::ReShadeShaders => {
                tui::print_cloning();
                clone_reshade_shaders(data_dir)?;
                let install_now = tui::prompt_install_shaders()?;

                if install_now {
                    if config.game_paths.is_empty() {
                        tui::print_no_game_paths();
                        return Ok(());
                    }
                    let game_paths =
                        tui::prompt_select_game_paths_shaders(config.game_paths.clone())?;
                    for game_path in &game_paths {
                        let game_path = PathBuf::from(game_path);
                        install_reshade_shaders(&data_dir.join("Merged"), &game_path)?;
                    }
                    tui::print_shader_install_successful();
                    Ok(())
                } else {
                    tui::print_shader_download_successful();
                    Ok(())
                }
            }
            InstallOption::GShadePresets => {
                tui::print_gshade_warning();

                let open = tui::prompt_open_links()?;

                if open {
                    tui::print_gshade_file_move(data_dir);

                    let _ = open::that("https://github.com/HereInPlainSight/gshade_installer/blob/master/gshade_installer.sh#L352");
                    tui::print_gshade_hint();
                }

                let done = tui::prompt_confirm_move()?;

                if !done {
                    continue;
                }
                install_presets(
                    data_dir,
                    &data_dir.join("presets.zip"),
                    &data_dir.join("shaders.zip"),
                )
                .await?;

                if config.game_paths.is_empty() {
                    tui::print_presets_success_no_games(data_dir);
                    continue;
                }

                let install_for_games = tui::prompt_install_presets_for_games()?;

                if !install_for_games {
                    tui::print_presets_success_no_games(data_dir);
                    continue;
                }

                let game_paths = tui::prompt_select_game_paths(config.game_paths.clone())?;
                for game_path in &game_paths {
                    let game_path = PathBuf::from(game_path);
                    install_preset_for_game(data_dir, &game_path)?;
                }

                tui::print_presets_success();

                Ok(())
            }
            InstallOption::Uninstall => {
                if config.game_paths.is_empty() {
                    tui::print_no_game_paths();
                    return Ok(());
                }

                let game_path = tui::prompt_select_game_path_uninstall(config.game_paths.clone())?;
                uninstall(&game_path)?;

                config
                    .game_paths
                    .retain(|path| path != &game_path.to_str().unwrap().to_string());

                Ok(())
            }
            InstallOption::Quit => break,
        };
        if let Err(e) = result {
            tui::print_error(e);
            continue;
        }

        let config_str =
            toml::to_string(&config).expect("if you see this error, the toml library is broken");
        std::fs::write(config_path, config_str)?;
    }

    Ok(())
}

async fn cli(
    subcommand: SubCommand,
    config: &mut Config,
    client: &reqwest::Client,
    data_dir: &PathBuf,
    config_path: &PathBuf,
    specific_installer: Option<String>,
) -> InquireResult<()> {
    match subcommand {
        cli::SubCommand::InstallReshade {
            vanilla,
            version,
            game,
        } => {
            download_reshade(client, data_dir, vanilla, version, &specific_installer).await?;
            if let Some(game) = game {
                let game_path = PathBuf::from(game);
                install_reshade(data_dir, &game_path, vanilla).await?;
                tui::print_reshade_success();

                if config
                    .game_paths
                    .contains(&game_path.to_str().unwrap().to_string())
                {
                    return Ok(());
                }

                config
                    .game_paths
                    .push(game_path.to_str().unwrap().to_string());
            } else {
                tui::print_reshade_success_no_games(data_dir);
            }
        }
        cli::SubCommand::InstallReshadeShaders { game } => {
            tui::print_cloning();
            clone_reshade_shaders(data_dir)?;

            if let Some(game_path) = game {
                let game_path = PathBuf::from(game_path);
                install_reshade_shaders(data_dir, &game_path)?;
                tui::print_shader_install_successful();
            } else {
                tui::print_shader_download_successful();
            }
        }
        cli::SubCommand::InstallPresets {
            all,
            game,
            presets,
            shaders,
        } => {
            let presets_path = PathBuf::from(presets);
            let shaders_path = PathBuf::from(shaders);

            install_presets(data_dir, &presets_path, &shaders_path).await?;
            if all {
                for game_path in &config.game_paths {
                    let game_path = PathBuf::from(game_path);
                    install_preset_for_game(data_dir, &game_path)?;
                }

                tui::print_presets_success();
            } else if let Some(game) = game {
                let game_path = PathBuf::from(game);
                install_preset_for_game(data_dir, &game_path)?;

                tui::print_presets_success();
            }
        }
        cli::SubCommand::Uninstall { game } => {
            let game_path = PathBuf::from(game);
            uninstall(&game_path)?;

            config
                .game_paths
                .retain(|path| path != &game_path.to_str().unwrap().to_string());
        }
    }

    let config_str =
        toml::to_string(&config).expect("if you see this error, the toml library is broken");
    std::fs::write(config_path, config_str)?;

    Ok(())
}

#[tokio::main]
async fn main() -> InquireResult<()> {
    if !cfg!(target_os = "linux") {
        println!("This installer is only supported on Linux");
        exit(1);
    }

    let dirs = directories::ProjectDirs::from(QUALIFIER, ORGANIZATION, APPLICATION);
    if dirs.is_none() {
        tui::print_no_home_dir();
        exit(1);
    }
    let dirs = dirs.unwrap();

    std::fs::create_dir_all(dirs.config_dir())?;
    std::fs::create_dir_all(dirs.data_dir())?;

    let config_dir = dirs.config_dir().to_path_buf();
    let data_dir = dirs.data_dir().to_path_buf();

    let config_path = config_dir.join("config.toml");
    let mut config = if config_path.exists() {
        let config_str = std::fs::read_to_string(&config_path)?;
        let result = toml::from_str(&config_str);
        if result.is_err() {
            tui::print_config_deserialization_error();
            exit(1);
        }
        result.unwrap()
    } else {
        let config = Config::default();
        let config_str =
            toml::to_string(&config).expect("if you see this error, the toml library is broken");
        std::fs::write(&config_path, config_str)?;
        config
    };
    let client = reqwest::Client::new();

    let args = cli::CliArgs::parse();
    let specific_installer = args.use_installer;

    if let Some(subcommand) = args.subcommand {
        cli(
            subcommand,
            &mut config,
            &client,
            &data_dir,
            &config_path,
            specific_installer,
        )
        .await?;
    } else {
        tui(
            &mut config,
            &client,
            &data_dir,
            &config_path,
            specific_installer,
        )
        .await?;
    }

    Ok(())
}