aah too lazy to create proper git commit
This commit is contained in:
parent
f5e06d3eb6
commit
1a3979a0da
29 changed files with 334 additions and 689 deletions
14
src-tauri/src/commands/install/clean.rs
Normal file
14
src-tauri/src/commands/install/clean.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
use super::util;
|
||||
use super::InstallErr;
|
||||
|
||||
pub async fn clean(game_path: &str) -> Result<(), InstallErr> {
|
||||
match util::uninstall(game_path) {
|
||||
Ok(_) => {}
|
||||
|
||||
Err(_) => {
|
||||
return Err(InstallErr::RemoveOldFilesFailed);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
|
@ -1,12 +1,9 @@
|
|||
use std::env;
|
||||
|
||||
use tauri::Window;
|
||||
|
||||
use super::InstallErr;
|
||||
use crate::commands::install::{emit, InstallSteps};
|
||||
use crate::util;
|
||||
|
||||
pub async fn install_bepinex(window: &Window, game_path: &str) -> Result<(), InstallErr> {
|
||||
pub async fn install_bepinex(game_path: &str) -> Result<(), InstallErr> {
|
||||
println!();
|
||||
println!("Installing BepInEx");
|
||||
|
||||
|
@ -47,7 +44,6 @@ pub async fn install_bepinex(window: &Window, game_path: &str) -> Result<(), Ins
|
|||
match util::unzip(bepinex_path.as_str(), &game_path) {
|
||||
Ok(_) => {
|
||||
println!("Successfully unzipped BepInEx.zip to {}", game_path);
|
||||
emit(&window, InstallSteps::InstallBepInEx);
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
|
@ -65,6 +61,5 @@ pub async fn install_bepinex(window: &Window, game_path: &str) -> Result<(), Ins
|
|||
|
||||
// done
|
||||
|
||||
emit(&window, InstallSteps::DownloadBepInEx);
|
||||
return Ok(());
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
use tauri::Window;
|
||||
|
||||
use super::InstallErr;
|
||||
use crate::commands::install::{emit, util, InstallSteps};
|
||||
use crate::commands::install::util;
|
||||
|
||||
pub async fn install_wbm_mod(window: &Window, game_path: &str) -> Result<(), InstallErr> {
|
||||
pub async fn install_wbm_mod(game_path: &str) -> Result<(), InstallErr> {
|
||||
println!();
|
||||
println!("Installing WBM mod");
|
||||
|
||||
|
@ -50,9 +48,7 @@ pub async fn install_wbm_mod(window: &Window, game_path: &str) -> Result<(), Ins
|
|||
|
||||
// unzip file
|
||||
match util::unzip(zip_path.as_str(), wbm_path.to_str().unwrap()) {
|
||||
Ok(()) => {
|
||||
emit(&window, InstallSteps::InstallWbm);
|
||||
}
|
||||
Ok(()) => {}
|
||||
|
||||
Err(err) => {
|
||||
println!("Failed to unzip WBM.zip: ({:#?})", err);
|
||||
|
@ -69,6 +65,5 @@ pub async fn install_wbm_mod(window: &Window, game_path: &str) -> Result<(), Ins
|
|||
|
||||
// done
|
||||
|
||||
emit(&window, InstallSteps::DownloadWbmZip);
|
||||
return Ok(());
|
||||
}
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
use tauri::Window;
|
||||
|
||||
use super::{InstallErr, InstallResult};
|
||||
use crate::commands::install::{emit, InstallSteps};
|
||||
|
||||
pub async fn launch_game_once(
|
||||
window: &Window,
|
||||
was_game_launched: bool,
|
||||
) -> Result<InstallResult, InstallErr> {
|
||||
println!();
|
||||
println!("Launch Game once");
|
||||
|
||||
if was_game_launched {
|
||||
return Ok(InstallResult::Skip);
|
||||
}
|
||||
|
||||
emit(&window, InstallSteps::LaunchGame);
|
||||
return Ok(InstallResult::LaunchGame); // stop install
|
||||
}
|
|
@ -1,12 +1,9 @@
|
|||
use tauri::Window;
|
||||
|
||||
use super::{InstallErr, InstallResult};
|
||||
use crate::commands::install::{emit, InstallSteps};
|
||||
use super::InstallErr;
|
||||
use crate::util;
|
||||
|
||||
use std::fs;
|
||||
|
||||
pub async fn unix_launch_option_setup(window: &Window) -> Result<InstallResult, InstallErr> {
|
||||
pub async fn unix_launch_option_setup() -> Result<(), InstallErr> {
|
||||
// skip if the OS is not linux or macOS
|
||||
match std::env::consts::OS {
|
||||
"linux" | "macos" => {
|
||||
|
@ -18,18 +15,18 @@ pub async fn unix_launch_option_setup(window: &Window) -> Result<InstallResult,
|
|||
println!();
|
||||
println!("Skipping unix launch option setup");
|
||||
|
||||
return Ok(InstallResult::Skip);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
if is_already_set() {
|
||||
println!("Steam launch option is already set. Skipping.");
|
||||
return Ok(InstallResult::Skip);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
println!("Prompt user to set launch option");
|
||||
emit(&window, InstallSteps::LaunchOption);
|
||||
return Ok(InstallResult::SetLaunchOption);
|
||||
println!("Steam launch option is either not set or invalid.");
|
||||
println!("Prompting to set launch option.");
|
||||
return Err(InstallErr::LaunchOptionNotSet);
|
||||
}
|
||||
|
||||
fn is_already_set() -> bool {
|
||||
|
@ -51,13 +48,17 @@ fn is_already_set() -> bool {
|
|||
//
|
||||
|
||||
match fs::read_to_string(localconfig_path) {
|
||||
Ok(content) => {
|
||||
Ok(_content) => {
|
||||
// todo: improve logic
|
||||
// 1. find line only containing "750470"
|
||||
// 2. find next closest line only containing "}"
|
||||
// 3. check if section contains "./run_bepinex.sh %command%"
|
||||
|
||||
return content.contains("./run_bepinex.sh %command%") && content.contains("750470");
|
||||
// run_bepinex.sh
|
||||
// %command%
|
||||
// 750470
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
|
|
|
@ -1,48 +1,28 @@
|
|||
use tauri::Window;
|
||||
|
||||
use crate::constants;
|
||||
use crate::util;
|
||||
|
||||
mod types;
|
||||
|
||||
mod clean;
|
||||
mod install_bepinex;
|
||||
mod install_mod;
|
||||
mod launch_game;
|
||||
mod launch_options;
|
||||
|
||||
use types::{InstallErr, InstallResult, InstallSteps};
|
||||
use types::InstallErr;
|
||||
|
||||
// todo: show current step in the frontend
|
||||
|
||||
/// automated version of the [manual installation](https://github.com/War-Brokers-Mods/WBM#installation).
|
||||
///
|
||||
/// This function exits if it requires a user input and is called again with the user feedback as its arguments.
|
||||
/// This function exits if it requires a user input and is called again with the user input as its arguments.
|
||||
///
|
||||
/// ## Installation procedure
|
||||
///
|
||||
/// This function exits at the end of each step.
|
||||
///
|
||||
/// 1. BepInEx installation
|
||||
/// 2. Steam launch option setup (only on Linux and MacOS)
|
||||
/// 3. Launch game for plugins folder generation
|
||||
/// 4. Mod installation
|
||||
///
|
||||
/// Some part of the function are unnecessary executed each time the function is called,
|
||||
/// Some parts of the function are unnecessary executed each time the function is called,
|
||||
/// but the time loss is negligible and it's a sacrifice worth for code readability.
|
||||
///
|
||||
/// ## Arguments
|
||||
///
|
||||
/// All arguments except `windows` are empty by default.
|
||||
///
|
||||
/// * `window` - standard tauri argument. See [docs](https://tauri.studio/docs/guides/command#accessing-the-window-in-commands) for more info.
|
||||
/// * `game_path` - absolute path to the game folder/directory.
|
||||
/// * `was_game_launched` - whether if the game was launched once after installing BepInEx to generate the plugins folder.
|
||||
#[tauri::command]
|
||||
pub async fn install(
|
||||
window: Window,
|
||||
game_path: String,
|
||||
was_game_launched: bool,
|
||||
) -> Result<InstallResult, InstallErr> {
|
||||
pub async fn install(game_path: String) -> Result<(), InstallErr> {
|
||||
println!("install command called");
|
||||
|
||||
//
|
||||
|
@ -73,39 +53,29 @@ pub async fn install(
|
|||
|
||||
default_game_path
|
||||
} else {
|
||||
// todo: check if game path is valid and tell the user
|
||||
if !util::is_game_path_valid(&game_path) {
|
||||
return Err(InstallErr::GamePathNotValid);
|
||||
}
|
||||
|
||||
game_path
|
||||
};
|
||||
let game_path = game_path.as_str();
|
||||
|
||||
//
|
||||
// Install BepInEx
|
||||
//
|
||||
//
|
||||
|
||||
match install_bepinex::install_bepinex(&window, game_path).await {
|
||||
Ok(()) => {}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
|
||||
//
|
||||
// Setup steam launch option if OS is linux or macOS
|
||||
//
|
||||
|
||||
match launch_options::unix_launch_option_setup(&window).await {
|
||||
match clean::clean(game_path).await {
|
||||
Ok(_) => {}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
|
||||
//
|
||||
// Run the game once to generate the plugins directory
|
||||
// Install BepInEx
|
||||
//
|
||||
|
||||
match launch_game::launch_game_once(&window, was_game_launched).await {
|
||||
Ok(res) => {
|
||||
if res != InstallResult::Skip {
|
||||
return Ok(res);
|
||||
}
|
||||
}
|
||||
match install_bepinex::install_bepinex(game_path).await {
|
||||
Ok(()) => {}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
|
||||
|
@ -113,21 +83,25 @@ pub async fn install(
|
|||
// Install the mod
|
||||
//
|
||||
|
||||
match install_mod::install_wbm_mod(&window, game_path).await {
|
||||
match install_mod::install_wbm_mod(game_path).await {
|
||||
Ok(()) => {}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
|
||||
//
|
||||
// Set steam launch option if OS is linux or macOS
|
||||
//
|
||||
|
||||
match launch_options::unix_launch_option_setup().await {
|
||||
Ok(_) => {}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
|
||||
//
|
||||
// Tell the frontend that the installation was successful
|
||||
//
|
||||
|
||||
emit(&window, InstallSteps::Done);
|
||||
println!("Install complete!");
|
||||
|
||||
return Ok(InstallResult::NoErr);
|
||||
}
|
||||
|
||||
pub fn emit(window: &Window, payload: InstallSteps) {
|
||||
util::emit(&window, constants::EVENT_INSTALL, payload);
|
||||
return Ok(());
|
||||
}
|
||||
|
|
|
@ -1,64 +1,18 @@
|
|||
/// must be synced with `src/pages/Install/types.ts`
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum InstallSteps {
|
||||
DownloadBepInEx,
|
||||
InstallBepInEx,
|
||||
LaunchOption,
|
||||
LaunchGame,
|
||||
DownloadWbmZip,
|
||||
InstallWbm,
|
||||
Done,
|
||||
}
|
||||
|
||||
impl serde::Serialize for InstallSteps {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_i64(*self as i64)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum InstallResult {
|
||||
NoErr,
|
||||
SetLaunchOption,
|
||||
LaunchGame,
|
||||
Skip, // only used for subcommands
|
||||
}
|
||||
|
||||
impl serde::Serialize for InstallResult {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_i64(*self as i64)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum InstallErr {
|
||||
FailedToGetGamePath,
|
||||
UnsupportedOS,
|
||||
FailedToGetGamePath,
|
||||
GamePathNotValid,
|
||||
RemoveOldFilesFailed,
|
||||
BepInExDownloadFailed,
|
||||
BepInExUnzipFailed,
|
||||
WBMDownloadFailed,
|
||||
WBMRemoveFailed,
|
||||
WBMDirectoryCreationFailed,
|
||||
WBMUnzipFailed,
|
||||
LaunchOptionNotSet,
|
||||
}
|
||||
|
||||
impl serde::Serialize for InstallErr {
|
||||
|
@ -69,6 +23,3 @@ impl serde::Serialize for InstallErr {
|
|||
serializer.serialize_i64(*self as i64)
|
||||
}
|
||||
}
|
||||
|
||||
// #[derive(Clone, serde::Serialize)]
|
||||
// struct InstallPayload(i64);
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
pub mod install;
|
||||
pub mod status;
|
||||
pub mod update;
|
||||
pub mod remove;
|
||||
|
|
8
src-tauri/src/commands/remove/mod.rs
Normal file
8
src-tauri/src/commands/remove/mod.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
mod types;
|
||||
|
||||
use types::RemoveErr;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn remove(_game_path: String) -> Result<(), RemoveErr> {
|
||||
return Ok(());
|
||||
}
|
13
src-tauri/src/commands/remove/types.rs
Normal file
13
src-tauri/src/commands/remove/types.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
/// must be synced with `src/pages/Remove/types.ts`
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum RemoveErr {}
|
||||
|
||||
impl serde::Serialize for RemoveErr {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_i64(*self as i64)
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
// [Sync]: must be synced with `src/pages/Status/index.svelte`
|
||||
|
||||
use crate::util;
|
||||
|
||||
/// [Sync]
|
||||
static LATEST_VERSION: &'static str = "LATEST_VERSION";
|
||||
static GAME_PATH: &'static str = "GAME_PATH";
|
||||
|
||||
/// [Sync]
|
||||
#[derive(serde::Serialize, Default, Clone)]
|
||||
pub struct StatusData {
|
||||
latest_release_version: String,
|
||||
game_path: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn status(req_type: String) -> StatusData {
|
||||
let mut status_data = StatusData::default();
|
||||
|
||||
if req_type == LATEST_VERSION {
|
||||
status_data.latest_release_version = util::get_wbm_release_data().await;
|
||||
} else if req_type == GAME_PATH {
|
||||
// returns an empty string if the game doesn't exist in the default path
|
||||
match util::get_default_game_path() {
|
||||
Some(path) => status_data.game_path = path,
|
||||
// todo: send feedback to frontend
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
return status_data;
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
/// for updating WBM
|
||||
|
||||
#[tauri::command]
|
||||
pub fn update() {
|
||||
println!("Updating WBM!")
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
pub const EVENT_INSTALL: &str = "install";
|
||||
// pub const EVENT_STATUS: &str = "install";
|
||||
// pub const EVENT_UPDATE: &str = "install";
|
|
@ -4,15 +4,13 @@
|
|||
)]
|
||||
|
||||
mod commands;
|
||||
mod constants;
|
||||
mod util;
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
commands::install::install,
|
||||
commands::status::status,
|
||||
commands::update::update
|
||||
commands::remove::remove
|
||||
])
|
||||
// you might see a unresolved macro error in the IDE but it's nothing to worry about.
|
||||
// rust-analyzer doesn't do well with nested macros.
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use tauri::api::path::{cache_dir, home_dir};
|
||||
use tauri::Window;
|
||||
|
||||
use futures_util::StreamExt;
|
||||
use std::cmp::min;
|
||||
|
@ -61,6 +60,10 @@ pub fn get_default_game_path() -> Option<String> {
|
|||
return None;
|
||||
}
|
||||
|
||||
if !is_game_path_valid(&game_path) {
|
||||
return None;
|
||||
}
|
||||
|
||||
return Some(String::from(game_path));
|
||||
}
|
||||
|
||||
|
@ -68,6 +71,13 @@ pub fn get_default_game_path() -> Option<String> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Checks if the path is a valid War Brokers game path
|
||||
pub fn is_game_path_valid(_game_path: &str) -> bool {
|
||||
// todo: implement logic
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// convert `Option<PathBuf>` to `Option<String>`
|
||||
pub fn buf2str(input: Option<PathBuf>) -> Option<String> {
|
||||
if input.is_none() {
|
||||
|
@ -144,11 +154,6 @@ pub async fn download_zip_to_cache_dir(url: &str, file_name: &str) -> Result<Str
|
|||
return Ok(path);
|
||||
}
|
||||
|
||||
/// https://tauri.studio/docs/guides/events
|
||||
pub fn emit<S: serde::Serialize>(window: &Window, event: &str, payload: S) {
|
||||
window.emit(event, payload).unwrap();
|
||||
}
|
||||
|
||||
pub fn unzip(path: &str, destination: &str) -> Result<(), zip::result::ZipError> {
|
||||
let fname = std::path::Path::new(path);
|
||||
let zipfile = std::fs::File::open(&fname).unwrap();
|
||||
|
@ -156,3 +161,10 @@ pub fn unzip(path: &str, destination: &str) -> Result<(), zip::result::ZipError>
|
|||
|
||||
return archive.extract(destination);
|
||||
}
|
||||
|
||||
/// Uninstall WBM and related files
|
||||
pub fn uninstall(_game_path: &str) -> Result<(), ()> {
|
||||
// todo: implement
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
|
|
@ -1,20 +1,11 @@
|
|||
export enum ROUTES {
|
||||
HOME = "#/",
|
||||
STATUS = "#/status",
|
||||
INSTALL = "#/install",
|
||||
UPDATE = "#/update",
|
||||
INSTALL = "#/operation?action=install",
|
||||
REMOVE = "#/operation?action=remove",
|
||||
}
|
||||
|
||||
// https://tauri.studio/docs/guides/command
|
||||
export enum COMMANDS {
|
||||
STATUS = "status",
|
||||
INSTALL = "install",
|
||||
UPDATE = "update",
|
||||
}
|
||||
|
||||
// https://tauri.studio/docs/guides/events
|
||||
export enum EVENTS {
|
||||
STATUS = "status",
|
||||
INSTALL = "install",
|
||||
UPDATE = "update",
|
||||
REMOVE = "remove",
|
||||
}
|
||||
|
|
|
@ -1,30 +1,29 @@
|
|||
<script lang="ts">
|
||||
import FaTasks from "svelte-icons/fa/FaTasks.svelte"
|
||||
import FaArrowAltCircleRight from "svelte-icons/fa/FaArrowAltCircleRight.svelte"
|
||||
import Update from "svelte-icons/fa/FaRedoAlt.svelte"
|
||||
import FaDownload from "svelte-icons/fa/FaDownload.svelte"
|
||||
import MdDeleteSweep from "svelte-icons/md/MdDeleteSweep.svelte"
|
||||
|
||||
import { ROUTES } from "../../constants"
|
||||
</script>
|
||||
|
||||
<div class="main-buttons-container">
|
||||
<a href={ROUTES.STATUS}>
|
||||
<div class="icon"><FaTasks /></div>
|
||||
<div>Status</div>
|
||||
</a>
|
||||
|
||||
<a href={ROUTES.INSTALL}>
|
||||
<div class="icon"><FaArrowAltCircleRight /></div>
|
||||
<div class="icon">
|
||||
<FaDownload />
|
||||
</div>
|
||||
<div>Install</div>
|
||||
</a>
|
||||
|
||||
<a href={ROUTES.UPDATE}>
|
||||
<div class="icon"><Update /></div>
|
||||
<div>Update</div>
|
||||
<a href={ROUTES.REMOVE}>
|
||||
<div class="icon">
|
||||
<MdDeleteSweep />
|
||||
</div>
|
||||
<div>Remove</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.main-buttons-container {
|
||||
@apply grid gap-6 grid-cols-3;
|
||||
@apply w-full flex justify-evenly;
|
||||
|
||||
a {
|
||||
@apply p-4 w-24 h-24 text-center rounded-xl bg-red-500 text-white font-normal;
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
<script lang="ts">
|
||||
import FaBook from "svelte-icons/fa/FaBook.svelte"
|
||||
import FaGithub from "svelte-icons/fa/FaGithub.svelte"
|
||||
import FaDiscord from "svelte-icons/fa/FaDiscord.svelte"
|
||||
import FaBook from "svelte-icons/fa/FaBook.svelte"
|
||||
</script>
|
||||
|
||||
<div class="small-button-container">
|
||||
<a href="https://github.com/War-Brokers-Mods/WBM#usage" target="_blank"
|
||||
><FaBook /></a
|
||||
>
|
||||
<a href="https://github.com/War-Brokers-Mods" target="_blank"><FaGithub /></a>
|
||||
<a href="https://discord.gg/aQqamSCUcS" target="_blank"><FaDiscord /></a>
|
||||
<a href="https://github.com/War-Brokers-Mods/WBM#usage" target="_blank">
|
||||
<FaBook />
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/War-Brokers-Mods" target="_blank">
|
||||
<FaGithub />
|
||||
</a>
|
||||
|
||||
<a href="https://discord.gg/aQqamSCUcS" target="_blank">
|
||||
<FaDiscord />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
<script lang="ts">
|
||||
import type { InstallStatus } from "./types"
|
||||
|
||||
export let installStatus: InstallStatus
|
||||
</script>
|
||||
|
||||
<div class="steps">
|
||||
<div class="step {installStatus.DownloadBepInEx && 'done'}">
|
||||
<div class="number">1</div>
|
||||
<div class="label">Install BepInEx</div>
|
||||
</div>
|
||||
|
||||
<div class="step {installStatus.LaunchGame && 'done'}">
|
||||
<div class="number">2</div>
|
||||
<div class="label">Launch game</div>
|
||||
</div>
|
||||
|
||||
<div class="step {installStatus.InstallWbm && 'done'}">
|
||||
<div class="number">3</div>
|
||||
<div class="label">Install Mod</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.steps {
|
||||
@apply grid grid-cols-3 w-full gap-2 p-2;
|
||||
|
||||
.step {
|
||||
/* layout */
|
||||
@apply flex flex-col text-center gap-2 p-2;
|
||||
/* style */
|
||||
@apply rounded-xl bg-neutral-600;
|
||||
|
||||
.number {
|
||||
@apply text-4xl font-bold;
|
||||
}
|
||||
|
||||
.label {
|
||||
@apply text-sm;
|
||||
}
|
||||
}
|
||||
|
||||
.done {
|
||||
@apply bg-red-600;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,212 +0,0 @@
|
|||
<script lang="ts">
|
||||
// components also used outside
|
||||
import HomeButton from "../../components/HomeButton.svelte"
|
||||
import Spinner from "../../components/Spinner.svelte"
|
||||
|
||||
// components only used here
|
||||
import Steps from "./Steps.svelte"
|
||||
import Interrupts from "./Interrupts.svelte"
|
||||
import Complete from "./Complete.svelte"
|
||||
|
||||
// tauri stuff
|
||||
import { invoke } from "@tauri-apps/api/tauri"
|
||||
import { listen } from "@tauri-apps/api/event"
|
||||
import { open as dialogOpen } from "@tauri-apps/api/dialog"
|
||||
|
||||
// types
|
||||
import { COMMANDS, EVENTS } from "../../constants"
|
||||
import { InstallErr, InstallResult, InstallSteps } from "./types"
|
||||
import type { InstallStatus } from "./types"
|
||||
|
||||
interface Args {
|
||||
gamePath: string
|
||||
wasGameLaunched: boolean
|
||||
}
|
||||
|
||||
//
|
||||
// variables
|
||||
//
|
||||
|
||||
let _gamePath = ""
|
||||
let lastReturnStatus: InstallResult = undefined
|
||||
let lastErrStaus: InstallErr = undefined
|
||||
let didLastRunFail = false
|
||||
let wasInstallButtonClicked = false
|
||||
let spinCog = false
|
||||
|
||||
let installStatus: InstallStatus = {
|
||||
DownloadBepInEx: false,
|
||||
InstallBepInEx: false,
|
||||
LaunchOption: false,
|
||||
LaunchGame: false,
|
||||
DownloadWbmZip: false,
|
||||
InstallWbm: false,
|
||||
Done: false,
|
||||
}
|
||||
|
||||
//
|
||||
// functions
|
||||
//
|
||||
|
||||
/**
|
||||
* only used inside other install functions.
|
||||
* Is never called directly.
|
||||
*
|
||||
* @param {Args} args
|
||||
*/
|
||||
function _install(args: Args) {
|
||||
wasInstallButtonClicked = true
|
||||
spinCog = true
|
||||
|
||||
invoke<InstallResult>(COMMANDS.INSTALL, args as any)
|
||||
.then((res) => {
|
||||
lastReturnStatus = res
|
||||
|
||||
switch (res) {
|
||||
case InstallResult.NoErr: {
|
||||
break
|
||||
}
|
||||
|
||||
case InstallResult.SetLaunchOption: {
|
||||
break
|
||||
}
|
||||
|
||||
case InstallResult.LaunchGame: {
|
||||
break
|
||||
}
|
||||
|
||||
case InstallResult.Skip: {
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err: InstallErr) => {
|
||||
console.log(typeof err, err)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* entry point
|
||||
*/
|
||||
function install() {
|
||||
_install({
|
||||
gamePath: _gamePath,
|
||||
wasGameLaunched: false,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* called when default game path was not found.
|
||||
*/
|
||||
function selectGamePathAndInstall() {
|
||||
dialogOpen({ directory: true, multiple: false }).then((value) => {
|
||||
_gamePath = value as string
|
||||
_install({
|
||||
gamePath: _gamePath,
|
||||
wasGameLaunched: false,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* called after setting the steam launch option.
|
||||
*/
|
||||
function setSteamLaunchOptionAndInstall() {
|
||||
_install({
|
||||
gamePath: _gamePath,
|
||||
wasGameLaunched: false,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* called after launching the game once.
|
||||
*/
|
||||
function launchGameAndInstall() {
|
||||
_install({
|
||||
gamePath: _gamePath,
|
||||
wasGameLaunched: true,
|
||||
})
|
||||
}
|
||||
|
||||
//
|
||||
// Event listener
|
||||
//
|
||||
|
||||
listen<InstallSteps>(EVENTS.INSTALL, (event) => {
|
||||
switch (event.payload) {
|
||||
case InstallSteps.DownloadBepInEx: {
|
||||
installStatus.DownloadBepInEx = true
|
||||
break
|
||||
}
|
||||
|
||||
case InstallSteps.InstallBepInEx: {
|
||||
installStatus.InstallBepInEx = true
|
||||
break
|
||||
}
|
||||
|
||||
case InstallSteps.LaunchOption: {
|
||||
installStatus.LaunchOption = true
|
||||
break
|
||||
}
|
||||
|
||||
case InstallSteps.LaunchGame: {
|
||||
installStatus.LaunchGame = true
|
||||
break
|
||||
}
|
||||
|
||||
case InstallSteps.DownloadWbmZip: {
|
||||
installStatus.DownloadWbmZip = true
|
||||
break
|
||||
}
|
||||
|
||||
case InstallSteps.InstallWbm: {
|
||||
installStatus.InstallWbm = true
|
||||
break
|
||||
}
|
||||
|
||||
case InstallSteps.Done: {
|
||||
spinCog = false
|
||||
installStatus.Done = true
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!-- Allow user to go back to home until they click the install button -->
|
||||
{#if !wasInstallButtonClicked}
|
||||
<HomeButton />
|
||||
{/if}
|
||||
|
||||
<div class="install-page">
|
||||
<Spinner activated={spinCog} />
|
||||
|
||||
{#if !wasInstallButtonClicked}
|
||||
<button on:click={install}>Install!</button>
|
||||
{/if}
|
||||
|
||||
<!-- show only when the install button is clicked -->
|
||||
{#if wasInstallButtonClicked}
|
||||
<Steps {installStatus} />
|
||||
<Interrupts
|
||||
{installStatus}
|
||||
{lastReturnStatus}
|
||||
{lastErrStaus}
|
||||
{selectGamePathAndInstall}
|
||||
{setSteamLaunchOptionAndInstall}
|
||||
{launchGameAndInstall}
|
||||
/>
|
||||
|
||||
{#if installStatus.Done}
|
||||
<Complete />
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import "./button.scss";
|
||||
|
||||
.install-page {
|
||||
@apply flex flex-col items-center;
|
||||
}
|
||||
</style>
|
|
@ -1,39 +0,0 @@
|
|||
// types of event
|
||||
export enum InstallSteps {
|
||||
DownloadBepInEx,
|
||||
InstallBepInEx,
|
||||
LaunchOption,
|
||||
LaunchGame,
|
||||
DownloadWbmZip,
|
||||
InstallWbm,
|
||||
Done,
|
||||
}
|
||||
|
||||
// types of install command return codes
|
||||
export enum InstallResult {
|
||||
NoErr,
|
||||
SetLaunchOption,
|
||||
LaunchGame,
|
||||
Skip, // only used for subcommands
|
||||
}
|
||||
|
||||
export enum InstallErr {
|
||||
FailedToGetGamePath,
|
||||
UnsupportedOS,
|
||||
BepInExDownloadFailed,
|
||||
BepInExUnzipFailed,
|
||||
WBMDownloadFailed,
|
||||
WBMRemoveFailed,
|
||||
WBMDirectoryCreationFailed,
|
||||
WBMUnzipFailed,
|
||||
}
|
||||
|
||||
export interface InstallStatus {
|
||||
DownloadBepInEx: boolean
|
||||
InstallBepInEx: boolean
|
||||
LaunchOption: boolean
|
||||
LaunchGame: boolean
|
||||
DownloadWbmZip: boolean
|
||||
InstallWbm: boolean
|
||||
Done: boolean
|
||||
}
|
|
@ -1,22 +1,25 @@
|
|||
<script lang="ts">
|
||||
/**
|
||||
* Show what the user has to do during instalation
|
||||
*/
|
||||
|
||||
import { copy } from "svelte-copy"
|
||||
import { open as shellOpen } from "@tauri-apps/api/shell"
|
||||
|
||||
import type { InstallStatus } from "./types"
|
||||
import { InstallErr } from "./types"
|
||||
import { InstallResult } from "./types"
|
||||
|
||||
export let lastReturnStatus: InstallResult
|
||||
export let lastErrStaus: InstallErr
|
||||
export let installStatus: InstallStatus
|
||||
export let selectGamePathAndInstall: () => void
|
||||
|
||||
export let setSteamLaunchOptionAndInstall: () => void
|
||||
export let launchGameAndInstall: () => void
|
||||
export let selectGamePathAndInstall: () => void
|
||||
</script>
|
||||
|
||||
<div class="interrupts">
|
||||
<!-- set game launch option -->
|
||||
{#if installStatus.LaunchOption}
|
||||
<!--
|
||||
set game launch option
|
||||
-->
|
||||
|
||||
{#if lastErrStaus == InstallErr.LaunchOptionNotSet}
|
||||
<span
|
||||
use:copy={"./run_bepinex.sh %command%"}
|
||||
on:svelte-copy={(event) => alert(event.detail)}
|
||||
|
@ -27,7 +30,10 @@
|
|||
<button on:click={setSteamLaunchOptionAndInstall}>Resume</button>
|
||||
{/if}
|
||||
|
||||
<!-- if the game was not found in the default install location -->
|
||||
<!--
|
||||
if the game was not found in the default install location
|
||||
-->
|
||||
|
||||
{#if lastErrStaus == InstallErr.FailedToGetGamePath}
|
||||
<p>
|
||||
Default game install location was not found :(
|
||||
|
@ -52,19 +58,14 @@
|
|||
Select folder and Install
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if lastReturnStatus == InstallResult.LaunchGame}
|
||||
Launch game
|
||||
<button on:click={launchGameAndInstall}>Resume</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import "./styles/button.scss";
|
||||
|
||||
.interrupts {
|
||||
@apply mt-2;
|
||||
|
||||
@import "./button.scss";
|
||||
|
||||
p {
|
||||
@apply text-center;
|
||||
}
|
165
src/pages/Operation/index.svelte
Normal file
165
src/pages/Operation/index.svelte
Normal file
|
@ -0,0 +1,165 @@
|
|||
<script lang="ts">
|
||||
// components also used outside
|
||||
import HomeButton from "../../components/HomeButton.svelte"
|
||||
import Spinner from "../../components/Spinner.svelte"
|
||||
|
||||
// components only used here
|
||||
import Interrupts from "./Interrupts.svelte"
|
||||
import Complete from "./Complete.svelte"
|
||||
|
||||
// tauri stuff
|
||||
import { invoke } from "@tauri-apps/api/tauri"
|
||||
import { open as dialogOpen } from "@tauri-apps/api/dialog"
|
||||
|
||||
// svelte stuff
|
||||
import { querystring } from "svelte-spa-router"
|
||||
|
||||
// types
|
||||
import { COMMANDS } from "../../constants"
|
||||
import { InstallErr, RemoveErr } from "./types"
|
||||
|
||||
interface InstallArgs {
|
||||
gamePath: string
|
||||
}
|
||||
|
||||
enum OperationType {
|
||||
Install,
|
||||
Remove,
|
||||
}
|
||||
|
||||
//
|
||||
// variables
|
||||
//
|
||||
|
||||
const operationType: OperationType = $querystring.includes("install")
|
||||
? OperationType.Install
|
||||
: OperationType.Remove
|
||||
let _gamePath = "" // not used directly
|
||||
|
||||
let lastInstallErrStaus: InstallErr = undefined
|
||||
let lastRemoveErrStatus: RemoveErr = undefined
|
||||
|
||||
let wasButtonClicked = false // if the install/remove button was clicked or not
|
||||
let spinCog = false
|
||||
|
||||
let installSuccess = false
|
||||
let removeSuccess = false
|
||||
|
||||
//
|
||||
// functions
|
||||
//
|
||||
|
||||
/**
|
||||
* only used inside other install functions.
|
||||
* Is never called directly.
|
||||
*
|
||||
* @param {InstallArgs} args
|
||||
*/
|
||||
function _install(args: InstallArgs) {
|
||||
wasButtonClicked = true
|
||||
spinCog = true
|
||||
|
||||
invoke<InstallErr>(COMMANDS.INSTALL, args as any)
|
||||
.then((res) => {
|
||||
switch (res) {
|
||||
case InstallErr.FailedToGetGamePath: {
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err: InstallErr) => {
|
||||
console.log(typeof err, err)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* entry point
|
||||
*/
|
||||
function install() {
|
||||
_install({
|
||||
gamePath: _gamePath,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* called when default game path was not found.
|
||||
*/
|
||||
function selectGamePathAndInstall() {
|
||||
dialogOpen({ directory: true, multiple: false }).then((value) => {
|
||||
_gamePath = value as string
|
||||
_install({
|
||||
gamePath: _gamePath,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* called after setting the steam launch option.
|
||||
*/
|
||||
function setSteamLaunchOptionAndInstall() {
|
||||
_install({
|
||||
gamePath: _gamePath,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Allow user to go back to home until they click the install button -->
|
||||
{#if !wasButtonClicked}
|
||||
<HomeButton />
|
||||
{/if}
|
||||
|
||||
{#if operationType == OperationType.Install}
|
||||
<div class="install-page">
|
||||
<Spinner activated={spinCog} />
|
||||
|
||||
{#if !wasButtonClicked}
|
||||
<button on:click={install}>Install!</button>
|
||||
{/if}
|
||||
|
||||
<!-- show only when the install button is clicked -->
|
||||
{#if wasButtonClicked}
|
||||
<Interrupts
|
||||
lastErrStaus={lastInstallErrStaus}
|
||||
{selectGamePathAndInstall}
|
||||
{setSteamLaunchOptionAndInstall}
|
||||
/>
|
||||
|
||||
{#if installSuccess}
|
||||
<Complete />
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="remove-page">
|
||||
<Spinner activated={spinCog} />
|
||||
|
||||
{#if !wasButtonClicked}
|
||||
<button on:click={install}>Remove!</button>
|
||||
{/if}
|
||||
|
||||
<!-- show only when the install button is clicked -->
|
||||
{#if wasButtonClicked}
|
||||
<Interrupts
|
||||
lastErrStaus={lastInstallErrStaus}
|
||||
{selectGamePathAndInstall}
|
||||
{setSteamLaunchOptionAndInstall}
|
||||
/>
|
||||
|
||||
{#if installSuccess}
|
||||
<Complete />
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
@import "./styles/button.scss";
|
||||
|
||||
.install-page {
|
||||
@apply flex flex-col items-center;
|
||||
}
|
||||
|
||||
.remove-page {
|
||||
@apply flex flex-col items-center;
|
||||
}
|
||||
</style>
|
22
src/pages/Operation/types.ts
Normal file
22
src/pages/Operation/types.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* Must be synced with `src-tauri/src/commands/install/types.rs`
|
||||
*/
|
||||
|
||||
export enum InstallErr {
|
||||
UnsupportedOS,
|
||||
FailedToGetGamePath,
|
||||
GamePathNotValid,
|
||||
RemoveOldFilesFailed,
|
||||
BepInExDownloadFailed,
|
||||
BepInExUnzipFailed,
|
||||
WBMDownloadFailed,
|
||||
WBMRemoveFailed,
|
||||
WBMDirectoryCreationFailed,
|
||||
WBMUnzipFailed,
|
||||
LaunchOptionNotSet,
|
||||
}
|
||||
|
||||
export enum RemoveErr {
|
||||
FailedToGetGamePath,
|
||||
GamePathNotValid,
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
<script lang="ts">
|
||||
// [Sync]: must be synced with `src-tauri/src/commands/status.rs`
|
||||
import HomeButton from "../../components/HomeButton.svelte"
|
||||
import Spinner from "../../components/Spinner.svelte"
|
||||
|
||||
import { invoke } from "@tauri-apps/api/tauri"
|
||||
import { open as openShell } from "@tauri-apps/api/shell"
|
||||
import { COMMANDS } from "../../constants"
|
||||
|
||||
// [Sync]
|
||||
enum STATUS_REQUEST {
|
||||
LATEST_VERSION = "LATEST_VERSION",
|
||||
GAME_PATH = "GAME_PATH",
|
||||
}
|
||||
|
||||
// [Sync]
|
||||
interface StatusDataRaw {
|
||||
latest_release_version?: string
|
||||
game_path?: string
|
||||
}
|
||||
|
||||
// [Sync]
|
||||
interface StatusData {
|
||||
latestReleaseVersion?: LatestReleaseVersion
|
||||
gamePath?: string
|
||||
}
|
||||
|
||||
interface LatestReleaseVersion {
|
||||
name: string
|
||||
url: string
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
let statusData: StatusData = {}
|
||||
let isRunning = false
|
||||
|
||||
function _requestStatus(
|
||||
reqType: STATUS_REQUEST,
|
||||
f: (res: StatusDataRaw) => void,
|
||||
f2?: (res: any) => void
|
||||
) {
|
||||
// fallback to default error handling if f2 is not defined
|
||||
f2 ||= (err) => {
|
||||
console.error(err)
|
||||
}
|
||||
|
||||
invoke(COMMANDS.STATUS, { reqType }).then(f).catch(f2)
|
||||
}
|
||||
|
||||
async function status() {
|
||||
isRunning = true
|
||||
_requestStatus(STATUS_REQUEST.LATEST_VERSION, (res) => {
|
||||
const data = (JSON.parse(res.latest_release_version) as any[])[0]
|
||||
statusData.latestReleaseVersion = {
|
||||
name: data.name,
|
||||
url: data.html_url,
|
||||
}
|
||||
})
|
||||
|
||||
_requestStatus(STATUS_REQUEST.GAME_PATH, (res) => {
|
||||
statusData.gamePath = res.game_path || "steam apps folder not found"
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if !isRunning}
|
||||
<HomeButton />
|
||||
{/if}
|
||||
|
||||
<div class="status-page">
|
||||
<Spinner activated={isRunning} />
|
||||
|
||||
<div>
|
||||
<button on:click={status}>Check status!</button>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
Latest version:
|
||||
{#if statusData.latestReleaseVersion}
|
||||
<div
|
||||
on:click={() => {
|
||||
console.log(statusData.latestReleaseVersion.url)
|
||||
openShell(statusData.latestReleaseVersion.url)
|
||||
}}
|
||||
style="display: inline-block; cursor: pointer"
|
||||
>
|
||||
{statusData.latestReleaseVersion.name}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<br />
|
||||
|
||||
game Path:
|
||||
{#if statusData.gamePath}
|
||||
{statusData.gamePath}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.status-page {
|
||||
@apply flex flex-col items-center;
|
||||
}
|
||||
</style>
|
|
@ -1,29 +0,0 @@
|
|||
<script lang="ts">
|
||||
import HomeButton from "../../components/HomeButton.svelte"
|
||||
import Spinner from "../../components/Spinner.svelte"
|
||||
|
||||
import { invoke } from "@tauri-apps/api/tauri"
|
||||
import { COMMANDS } from "../../constants"
|
||||
|
||||
let isRunning = false
|
||||
|
||||
function update() {
|
||||
isRunning = true
|
||||
invoke(COMMANDS.UPDATE)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if !isRunning}
|
||||
<HomeButton />
|
||||
{/if}
|
||||
|
||||
<div class="update-page">
|
||||
<Spinner activated={isRunning} />
|
||||
<button on:click={update}>Update!</button>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.update-page {
|
||||
@apply flex flex-col items-center;
|
||||
}
|
||||
</style>
|
|
@ -1,11 +1,7 @@
|
|||
import Home from "./pages/Home/index.svelte"
|
||||
import Status from "./pages/Status/index.svelte"
|
||||
import Install from "./pages/Install/index.svelte"
|
||||
import Update from "./pages/Update/index.svelte"
|
||||
import Operation from "./pages/Operation/index.svelte"
|
||||
|
||||
export default {
|
||||
"/": Home,
|
||||
"/status": Status,
|
||||
"/install": Install,
|
||||
"/update": Update,
|
||||
"/operation": Operation,
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue