Added fps unlocker and game running support
This commit is contained in:
parent
19bf0fe01c
commit
de5339fa38
8 changed files with 309 additions and 7 deletions
|
@ -16,13 +16,14 @@ serde = { version = "1.0", features = ["derive"], optional = true }
|
|||
serde_json = { version = "1.0", optional = true }
|
||||
wincompatlib = { version = "0.1.2", features = ["dxvk"], optional = true }
|
||||
lazy_static = { version = "1.4.0", optional = true }
|
||||
md5 = { version = "0.7.0", optional = true }
|
||||
|
||||
[features]
|
||||
states = []
|
||||
config = ["dep:serde", "dep:serde_json"]
|
||||
components = ["dep:wincompatlib", "dep:lazy_static"]
|
||||
runner = []
|
||||
fps-unlocker = []
|
||||
game = ["components"]
|
||||
fps-unlocker = ["dep:md5"]
|
||||
|
||||
default = ["all"]
|
||||
all = ["states", "config", "components", "runner", "fps-unlocker"]
|
||||
all = ["states", "config", "components", "game", "fps-unlocker"]
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Remove excess code from gtk launcher and prepare it for relm4 rewrite;
|
||||
* Prepare codebase for tauri rewrite;
|
||||
|
||||
## Current progress (50%)
|
||||
## Current progress (75%)
|
||||
|
||||
| Status | Feature | Description |
|
||||
| :-: | - | - |
|
||||
|
@ -16,5 +16,5 @@
|
|||
| ✅ | | List Wine and DXVK versions |
|
||||
| ❌ | | Download, delete and select wine |
|
||||
| ❌ | | Download, delete, select and apply DXVK |
|
||||
| ❌ | runner | Run the game |
|
||||
| ❌ | fps-unlocker | Support of FPS unlocker. Manage its config, download, use in game runner |
|
||||
| ✅ | game | Run the game |
|
||||
| ✅ | fps-unlocker | Support of FPS unlocker. Manage its config, download, use in game runner |
|
||||
|
|
|
@ -34,14 +34,17 @@ pub struct Version {
|
|||
}
|
||||
|
||||
impl Version {
|
||||
/// Get latest recommended dxvk version
|
||||
pub fn latest() -> Self {
|
||||
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 {
|
||||
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> {
|
||||
let apply_path = dxvks_folder.into().join(&self.name).join("setup_dxvk.sh");
|
||||
let config = config::get()?;
|
||||
|
@ -75,11 +78,12 @@ impl Version {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get dxvk groups
|
||||
pub fn get_groups() -> Vec<Group> {
|
||||
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>> {
|
||||
let mut downloaded = Vec::new();
|
||||
|
||||
|
|
|
@ -40,14 +40,17 @@ pub struct Version {
|
|||
}
|
||||
|
||||
impl Version {
|
||||
/// Get latest recommended wine version
|
||||
pub fn latest() -> Self {
|
||||
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 {
|
||||
folder.into().join(&self.name).exists()
|
||||
}
|
||||
|
||||
/// Convert current wine struct to one from `wincompatlib`
|
||||
pub fn to_wine(&self) -> Wine {
|
||||
Wine::new(
|
||||
&self.files.wine64,
|
||||
|
@ -69,10 +72,12 @@ pub struct Files {
|
|||
pub winecfg: String
|
||||
}
|
||||
|
||||
/// Get wine groups
|
||||
pub fn get_groups() -> Vec<Group> {
|
||||
GROUPS.clone()
|
||||
}
|
||||
|
||||
/// List downloaded wine versions in some specific folder
|
||||
pub fn get_downloaded<T: Into<PathBuf>>(folder: T) -> std::io::Result<Vec<Version>> {
|
||||
let mut downloaded = Vec::new();
|
||||
|
||||
|
|
66
src/fps_unlocker/config_schema.rs
Normal file
66
src/fps_unlocker/config_schema.rs
Normal 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
82
src/fps_unlocker/mod.rs
Normal 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
138
src/game/mod.rs
Normal 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(())
|
||||
}
|
|
@ -11,6 +11,12 @@ pub mod states;
|
|||
#[cfg(feature = "components")]
|
||||
pub mod components;
|
||||
|
||||
#[cfg(feature = "game")]
|
||||
pub mod game;
|
||||
|
||||
#[cfg(feature = "fps-unlocker")]
|
||||
pub mod fps_unlocker;
|
||||
|
||||
/// Check if specified binary is available
|
||||
///
|
||||
/// ```
|
||||
|
|
Loading…
Reference in a new issue