Added fps unlocker and game running support

This commit is contained in:
Observer KRypt0n_ 2022-11-11 23:46:14 +02:00
parent 19bf0fe01c
commit de5339fa38
No known key found for this signature in database
GPG key ID: 844DA47BA25FE1E2
8 changed files with 309 additions and 7 deletions

View file

@ -16,13 +16,14 @@ serde = { version = "1.0", features = ["derive"], optional = true }
serde_json = { version = "1.0", optional = true } serde_json = { version = "1.0", optional = true }
wincompatlib = { version = "0.1.2", features = ["dxvk"], optional = true } wincompatlib = { version = "0.1.2", features = ["dxvk"], optional = true }
lazy_static = { version = "1.4.0", optional = true } lazy_static = { version = "1.4.0", optional = true }
md5 = { version = "0.7.0", optional = true }
[features] [features]
states = [] states = []
config = ["dep:serde", "dep:serde_json"] config = ["dep:serde", "dep:serde_json"]
components = ["dep:wincompatlib", "dep:lazy_static"] components = ["dep:wincompatlib", "dep:lazy_static"]
runner = [] game = ["components"]
fps-unlocker = [] fps-unlocker = ["dep:md5"]
default = ["all"] default = ["all"]
all = ["states", "config", "components", "runner", "fps-unlocker"] all = ["states", "config", "components", "game", "fps-unlocker"]

View file

@ -6,7 +6,7 @@
* Remove excess code from gtk launcher and prepare it for relm4 rewrite; * Remove excess code from gtk launcher and prepare it for relm4 rewrite;
* Prepare codebase for tauri rewrite; * Prepare codebase for tauri rewrite;
## Current progress (50%) ## Current progress (75%)
| Status | Feature | Description | | Status | Feature | Description |
| :-: | - | - | | :-: | - | - |
@ -16,5 +16,5 @@
| ✅ | | List Wine and DXVK versions | | ✅ | | List Wine and DXVK versions |
| ❌ | | Download, delete and select wine | | ❌ | | Download, delete and select wine |
| ❌ | | Download, delete, select and apply DXVK | | ❌ | | Download, delete, select and apply DXVK |
| ❌ | runner | Run the game | | ✅ | game | Run the game |
| | fps-unlocker | Support of FPS unlocker. Manage its config, download, use in game runner | | | fps-unlocker | Support of FPS unlocker. Manage its config, download, use in game runner |

View file

@ -34,14 +34,17 @@ pub struct Version {
} }
impl Version { impl Version {
/// Get latest recommended dxvk version
pub fn latest() -> Self { pub fn latest() -> Self {
get_groups()[0].versions[0].clone() get_groups()[0].versions[0].clone()
} }
/// Check is current dxvk downloaded in specified folder
pub fn is_downloaded_in<T: Into<PathBuf>>(&self, folder: T) -> bool { pub fn is_downloaded_in<T: Into<PathBuf>>(&self, folder: T) -> bool {
folder.into().join(&self.name).exists() folder.into().join(&self.name).exists()
} }
/// Apply current dxvk to specified wine prefix
pub fn apply<T: Into<PathBuf>>(&self, dxvks_folder: T, prefix_path: T) -> anyhow::Result<Output> { pub fn apply<T: Into<PathBuf>>(&self, dxvks_folder: T, prefix_path: T) -> anyhow::Result<Output> {
let apply_path = dxvks_folder.into().join(&self.name).join("setup_dxvk.sh"); let apply_path = dxvks_folder.into().join(&self.name).join("setup_dxvk.sh");
let config = config::get()?; let config = config::get()?;
@ -75,11 +78,12 @@ impl Version {
} }
} }
/// Get dxvk groups
pub fn get_groups() -> Vec<Group> { pub fn get_groups() -> Vec<Group> {
GROUPS.clone() GROUPS.clone()
} }
/// List downloaded DXVK versions in some specific folder /// List downloaded dxvk versions in some specific folder
pub fn get_downloaded<T: Into<PathBuf>>(folder: T) -> std::io::Result<Vec<Version>> { pub fn get_downloaded<T: Into<PathBuf>>(folder: T) -> std::io::Result<Vec<Version>> {
let mut downloaded = Vec::new(); let mut downloaded = Vec::new();

View file

@ -40,14 +40,17 @@ pub struct Version {
} }
impl Version { impl Version {
/// Get latest recommended wine version
pub fn latest() -> Self { pub fn latest() -> Self {
get_groups()[0].versions[0].clone() get_groups()[0].versions[0].clone()
} }
/// Check is current wine downloaded in specified folder
pub fn is_downloaded_in<T: Into<PathBuf>>(&self, folder: T) -> bool { pub fn is_downloaded_in<T: Into<PathBuf>>(&self, folder: T) -> bool {
folder.into().join(&self.name).exists() folder.into().join(&self.name).exists()
} }
/// Convert current wine struct to one from `wincompatlib`
pub fn to_wine(&self) -> Wine { pub fn to_wine(&self) -> Wine {
Wine::new( Wine::new(
&self.files.wine64, &self.files.wine64,
@ -69,10 +72,12 @@ pub struct Files {
pub winecfg: String pub winecfg: String
} }
/// Get wine groups
pub fn get_groups() -> Vec<Group> { pub fn get_groups() -> Vec<Group> {
GROUPS.clone() GROUPS.clone()
} }
/// List downloaded wine versions in some specific folder
pub fn get_downloaded<T: Into<PathBuf>>(folder: T) -> std::io::Result<Vec<Version>> { pub fn get_downloaded<T: Into<PathBuf>>(folder: T) -> std::io::Result<Vec<Version>> {
let mut downloaded = Vec::new(); let mut downloaded = Vec::new();

View file

@ -0,0 +1,66 @@
use serde::Serialize;
use super::FpsUnlockerConfig;
#[derive(Debug, Clone, Serialize)]
#[allow(non_snake_case)]
pub struct ConfigSchema {
pub DllList: Vec<String>,
pub Priority: u64,
pub MonitorNum: u64,
pub CustomResY: u64,
pub CustomResX: u64,
pub FPSTarget: u64,
pub UsePowerSave: bool,
pub StartMinimized: bool,
pub IsExclusiveFullscreen: bool,
pub UseCustomRes: bool,
pub Fullscreen: bool,
pub PopupWindow: bool,
pub AutoClose: bool,
pub AutoDisableVSync: bool,
pub AutoStart: bool,
pub GamePath: Option<String>
}
impl Default for ConfigSchema {
fn default() -> Self {
Self {
DllList: vec![],
Priority: 3,
MonitorNum: 1,
CustomResY: 1080,
CustomResX: 1920,
FPSTarget: 120,
UsePowerSave: false,
IsExclusiveFullscreen: false,
UseCustomRes: false,
Fullscreen: false,
PopupWindow: false,
AutoDisableVSync: true,
GamePath: None,
// Launcher-specific settings
AutoStart: true,
AutoClose: true,
StartMinimized: true
}
}
}
impl ConfigSchema {
pub fn from_config(config: FpsUnlockerConfig) -> Self {
Self {
FPSTarget: config.fps,
UsePowerSave: config.power_saving,
Fullscreen: config.fullscreen,
Priority: config.priority,
..Self::default()
}
}
pub fn json(&self) -> serde_json::Result<String> {
serde_json::to_string(self)
}
}

82
src/fps_unlocker/mod.rs Normal file
View file

@ -0,0 +1,82 @@
use std::path::PathBuf;
use anime_game_core::installer::downloader::Downloader;
use crate::config::game::enhancements::fps_unlocker::config::Config as FpsUnlockerConfig;
pub mod config_schema;
const LATEST_INFO: (&str, &str) = (
"6040a6f0be5dbf4d55d6b129cad47b5b",
"https://github.com/34736384/genshin-fps-unlock/releases/download/v2.0.0/unlockfps_clr.exe"
);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FpsUnlocker {
dir: PathBuf
}
impl FpsUnlocker {
/// Get FpsUnlocker from its containment directory
///
/// Returns
/// - `Err(..)` if failed to read `unlocker.exe` file
/// - `Ok(None)` if version is not latest
/// - `Ok(..)` if version is latest
pub fn from_dir<T: Into<PathBuf>>(dir: T) -> anyhow::Result<Option<Self>> {
let dir = dir.into();
let hash = format!("{:x}", md5::compute(std::fs::read(dir.join("unlocker.exe"))?));
Ok(if hash == LATEST_INFO.0 {
Some(Self { dir })
} else {
None
})
}
/// Download FPS unlocker to specified directory
pub fn download<T: Into<PathBuf>>(dir: T) -> anyhow::Result<Self> {
let mut downloader = Downloader::new(LATEST_INFO.1)?;
let dir = dir.into();
// Create FPS unlocker folder if needed
if !dir.exists() {
std::fs::create_dir_all(&dir)?;
}
match downloader.download_to(dir.join("unlocker.exe"), |_, _| {}) {
Ok(_) => Ok(Self {
dir
}),
Err(err) => {
let err: std::io::Error = err.into();
Err(err.into())
}
}
}
pub fn get_binary(&self) -> PathBuf {
Self::get_binary_in(&self.dir)
}
pub fn get_binary_in<T: Into<PathBuf>>(dir: T) -> PathBuf {
dir.into().join("unlocker.exe")
}
pub fn dir(&self) -> &PathBuf {
&self.dir
}
/// Generate and save FPS unlocker config file to the game's directory
pub fn update_config(&self, config: FpsUnlockerConfig) -> anyhow::Result<()> {
let config = config_schema::ConfigSchema::from_config(config);
Ok(std::fs::write(
self.dir.join("fps_config.json"),
config.json()?
)?)
}
}

138
src/game/mod.rs Normal file
View file

@ -0,0 +1,138 @@
use std::process::Command;
use anime_game_core::genshin::telemetry;
use super::consts;
use super::config;
use super::fps_unlocker::FpsUnlocker;
/// Try to run the game
///
/// If `debug = true`, then the game will be run in the new terminal window
pub fn run() -> anyhow::Result<()> {
let config = config::get()?;
if !config.game.path.exists() {
return Err(anyhow::anyhow!("Game is not installed"));
}
let wine_executable = match config.try_get_wine_executable() {
Some(path) => path,
None => return Err(anyhow::anyhow!("Couldn't find wine executable"))
};
// Check telemetry servers
if let Some(server) = telemetry::is_disabled(consts::TELEMETRY_CHECK_TIMEOUT) {
return Err(anyhow::anyhow!("Telemetry server is not disabled: {server}"));
}
// Prepare fps unlocker
// 1) Download if needed
// 2) Generate config file
// 3) Generate fpsunlocker.bat from launcher.bat
#[cfg(feature = "fps-unlocker")]
if config.game.enhancements.fps_unlocker.enabled {
let unlocker = match FpsUnlocker::from_dir(&config.game.enhancements.fps_unlocker.path) {
Ok(Some(unlocker)) => unlocker,
other => {
// Ok(None) means unknown version, so we should delete it before downloading newer one
// because otherwise downloader will try to continue downloading "partially downloaded" file
if let Ok(None) = other {
std::fs::remove_file(FpsUnlocker::get_binary_in(&config.game.enhancements.fps_unlocker.path))?;
}
match FpsUnlocker::download(&config.game.enhancements.fps_unlocker.path) {
Ok(unlocker) => unlocker,
Err(err) => return Err(anyhow::anyhow!("Failed to download FPS unlocker: {err}"))
}
}
};
// Generate FPS unlocker config file
if let Err(err) = unlocker.update_config(config.game.enhancements.fps_unlocker.config.clone()) {
return Err(anyhow::anyhow!("Failed to update FPS unlocker config: {err}"));
}
let bat_path = config.game.path.join("fpsunlocker.bat");
let original_bat_path = config.game.path.join("launcher.bat");
// Generate fpsunlocker.bat from launcher.bat
std::fs::write(bat_path, std::fs::read_to_string(original_bat_path)?
.replace("start GenshinImpact.exe %*", &format!("start GenshinImpact.exe %*\n\nZ:\ncd \"{}\"\nstart unlocker.exe", unlocker.dir().to_string_lossy()))
.replace("start YuanShen.exe %*", &format!("start YuanShen.exe %*\n\nZ:\ncd \"{}\"\nstart unlocker.exe", unlocker.dir().to_string_lossy())))?;
}
// Prepare bash -c '<command>'
let mut bash_chain = String::new();
if config.game.enhancements.gamemode {
bash_chain += "gamemoderun ";
}
bash_chain += &format!("'{}' ", wine_executable.to_string_lossy());
if let Some(virtual_desktop) = config.game.wine.virtual_desktop.get_command() {
bash_chain += &format!("{virtual_desktop} ");
}
bash_chain += if config.game.enhancements.fps_unlocker.enabled && cfg!(feature = "fps-unlocker") {
"fpsunlocker.bat "
} else {
"launcher.bat "
};
if config.game.wine.borderless {
bash_chain += "-screen-fullscreen 0 -popupwindow ";
}
// https://notabug.org/Krock/dawn/src/master/TWEAKS.md
if config.game.enhancements.fsr.enabled {
bash_chain += "-window-mode exclusive ";
}
// gamescope <params> -- <command to run>
if let Some(gamescope) = config.game.enhancements.gamescope.get_command() {
bash_chain = format!("{gamescope} -- {bash_chain}");
}
let bash_chain = match &config.game.command {
Some(command) => command.replace("%command%", &bash_chain),
None => bash_chain
};
let mut command = Command::new("bash");
command.arg("-c");
command.arg(&bash_chain);
// Setup environment
command.env("WINEARCH", "win64");
command.env("WINEPREFIX", &config.game.wine.prefix);
// Add DXVK_ASYNC=1 for dxvk-async builds automatically
if let Ok(Some(dxvk)) = &config.try_get_selected_dxvk_info() {
if dxvk.version.contains("async") {
command.env("DXVK_ASYNC", "1");
}
}
command.envs(config.game.wine.sync.get_env_vars());
command.envs(config.game.enhancements.hud.get_env_vars(&config));
command.envs(config.game.enhancements.fsr.get_env_vars());
command.envs(config.game.wine.language.get_env_vars());
command.envs(config.game.environment);
// Run command
println!("Running command: bash -c \"{}\"", bash_chain);
command.current_dir(config.game.path).spawn()?;
Ok(())
}

View file

@ -11,6 +11,12 @@ pub mod states;
#[cfg(feature = "components")] #[cfg(feature = "components")]
pub mod components; pub mod components;
#[cfg(feature = "game")]
pub mod game;
#[cfg(feature = "fps-unlocker")]
pub mod fps_unlocker;
/// Check if specified binary is available /// Check if specified binary is available
/// ///
/// ``` /// ```