From c60ad6a58711de483c823494c9bc73212d886610 Mon Sep 17 00:00:00 2001 From: ALEZ-DEV Date: Tue, 23 Apr 2024 23:40:12 +0200 Subject: [PATCH] WIP download game resources --- babylonia-terminal-cli/src/main.rs | 39 +++-- babylonia-terminal-cli/src/reporter.rs | 14 +- .../src/components/game_component.rs | 144 ++++++++++++++++++ babylonia-terminal-sdk/src/components/mod.rs | 8 +- .../src/components/pgr_component.rs | 34 ----- .../src/components/wine_component.rs | 2 +- babylonia-terminal-sdk/src/game_manager.rs | 24 +-- babylonia-terminal-sdk/src/game_state.rs | 6 + .../src/utils/kuro_prod_api.rs | 58 ++++--- 9 files changed, 242 insertions(+), 87 deletions(-) create mode 100644 babylonia-terminal-sdk/src/components/game_component.rs delete mode 100644 babylonia-terminal-sdk/src/components/pgr_component.rs diff --git a/babylonia-terminal-cli/src/main.rs b/babylonia-terminal-cli/src/main.rs index e0abe2d..ce970e1 100644 --- a/babylonia-terminal-cli/src/main.rs +++ b/babylonia-terminal-cli/src/main.rs @@ -1,4 +1,6 @@ -use babylonia_terminal_sdk::{game_manager::GameManager, game_state::GameState}; +use babylonia_terminal_sdk::{ + components::wine_component::WineComponent, game_manager::GameManager, game_state::GameState, +}; use log::{info, LevelFilter}; use simple_logger::SimpleLogger; use wincompatlib::prelude::*; @@ -18,24 +20,30 @@ async fn main() { .init() .unwrap(); + let mut wine_component: Option = None; let mut wine: Option = None; loop { let state = GameState::get_current_state().await; - if state != GameState::WineNotInstalled && wine.is_none() { - wine = Some(GameManager::init_wine()); + if let (Some(component), None) = (&wine_component, &wine) { + wine = Some(component.init_wine()); + } else if GameState::get_current_state().await != GameState::WineNotInstalled { + let wine_component = WineComponent::new(GameState::get_config_directory().join("wine")); + wine = Some(wine_component.init_wine()); } match state { GameState::WineNotInstalled => { info!("Wine not installed, installing it..."); - GameManager::install_wine( - GameState::get_config_directory(), - Some(DownloadReporter::create()), - ) - .await - .expect("Failed to install Wine"); + wine_component = Some( + GameManager::install_wine( + GameState::get_config_directory(), + Some(DownloadReporter::create(false)), + ) + .await + .expect("Failed to install Wine"), + ); info!("Wine installed"); } GameState::DXVKNotInstalled => { @@ -43,7 +51,7 @@ async fn main() { GameManager::install_dxvk( &wine.clone().unwrap(), GameState::get_config_directory(), - Some(DownloadReporter::create()), + Some(DownloadReporter::create(false)), ) .await .expect("Failed to installed DXVK"); @@ -51,7 +59,7 @@ async fn main() { } GameState::FontNotInstalled => { info!("Fonts not installed, installing it..."); - GameManager::install_font(&wine.clone().unwrap(), DownloadReporter::create()) + GameManager::install_font(&wine.clone().unwrap(), DownloadReporter::create(false)) .await .expect("Failed to install fonts"); info!("Fonts installed"); @@ -63,6 +71,15 @@ async fn main() { .expect("Failed to install dependecies"); info!("Dependecies installed"); } + GameState::GameNotInstalled => { + info!("Game not installed, installing it..."); + GameManager::install_game( + GameState::get_config_directory(), + DownloadReporter::create(true), + ) + .await + .expect("Failed to install the game"); + } _ => {} } diff --git a/babylonia-terminal-cli/src/reporter.rs b/babylonia-terminal-cli/src/reporter.rs index 58738b0..0c11047 100644 --- a/babylonia-terminal-cli/src/reporter.rs +++ b/babylonia-terminal-cli/src/reporter.rs @@ -4,18 +4,21 @@ use std::fmt::Write; struct DownloadReporterPrivate { last_update: std::time::Instant, max_progress: Option, + last_current: u64, message: String, pb: ProgressBar, } pub struct DownloadReporter { private: std::sync::Mutex>, + relative: bool, } impl DownloadReporter { - pub fn create() -> std::sync::Arc { + pub fn create(relative: bool) -> std::sync::Arc { std::sync::Arc::new(Self { private: std::sync::Mutex::new(None), + relative, }) } } @@ -31,6 +34,7 @@ impl downloader::progress::Reporter for DownloadReporter { let private = DownloadReporterPrivate { last_update: std::time::Instant::now(), max_progress, + last_current: 0, message: message.to_owned(), pb: pb, }; @@ -43,7 +47,13 @@ impl downloader::progress::Reporter for DownloadReporter { fn progress(&self, current: u64) { if let Some(p) = self.private.lock().unwrap().as_mut() { if p.last_update.elapsed().as_millis() >= 1000 { - p.pb.set_position(current); + if self.relative { + let new_current = current - p.last_current; + p.pb.set_position(new_current); + p.last_current = new_current; + } else { + p.pb.set_position(current); + } p.last_update = std::time::Instant::now(); } else if current == p.max_progress.unwrap() { p.pb.set_position(current); diff --git a/babylonia-terminal-sdk/src/components/game_component.rs b/babylonia-terminal-sdk/src/components/game_component.rs new file mode 100644 index 0000000..5cf3d50 --- /dev/null +++ b/babylonia-terminal-sdk/src/components/game_component.rs @@ -0,0 +1,144 @@ +use downloader::progress; +use downloader::progress::Reporter; +use downloader::Downloader; +use log::debug; +use log::info; +use serde::Deserialize; +use serde::Serialize; +use std::path::PathBuf; +use std::sync::Arc; +use std::sync::Mutex; +use std::time::Instant; +use std::vec; +use tokio::fs::create_dir_all; + +use super::component_downloader::ComponentDownloader; +use crate::utils::github_requester::GithubRequester; +use crate::utils::kuro_prod_api; + +pub struct GameComponent { + game_dir: PathBuf, +} + +impl GameComponent { + pub fn new(game_dir: PathBuf) -> Self { + Self { game_dir } + } +} + +impl GithubRequester for GameComponent {} + +impl ComponentDownloader for GameComponent { + async fn install( + &self, + progress: Option>, + ) -> anyhow::Result<()> { + Self::download(&self.game_dir, progress).await?; + + Ok(()) + } + + async fn download( + output_dir: &std::path::PathBuf, + progress: Option>, + ) -> anyhow::Result { + let game_info = kuro_prod_api::fetch_game_info().await?; + let resources = game_info.fetch_resources().await?; + let mut downloader = Downloader::builder() + .download_folder(output_dir) + .parallel_requests(5) + .build()?; + + for chunk_resource in resources.resource.chunks(5) { + for path in chunk_resource + .iter() + .map(|r| { + output_dir + .join(r.dest.strip_prefix("/").unwrap()) + .parent() + .unwrap() + .to_path_buf() + }) + .collect::>() + { + create_dir_all(path).await?; + } + + let to_download = chunk_resource + .iter() + .map(|r| { + let url = r.build_download_url( + &game_info.get_first_cdn(), + &game_info.get_resource_base_path(), + ); + debug!("starting download for {}", url); + let mut dl = downloader::Download::new(&url); + + if let Some(p) = progress.clone() { + dl = dl.progress(FileDownloadReporter::create(p)); + } + dl + }) + .collect::>(); + + downloader.async_download(&to_download).await?; + } + + Ok(output_dir.clone()) + } + + async fn uncompress( + file: std::path::PathBuf, + new_filename: std::path::PathBuf, + ) -> anyhow::Result<()> { + panic!("How did you run this function??!!") + } +} + +struct FileDownloadReporterPrivate { + last_update: Instant, + max_progress: Option, + last_current_progress: u64, +} + +struct FileDownloadReporter { + private: Mutex>, + progress: Mutex>, +} + +impl FileDownloadReporter

{ + pub fn create(progress: Arc

) -> Arc { + Arc::new(Self { + private: Mutex::new(None), + progress: Mutex::new(progress), + }) + } +} + +impl Reporter for FileDownloadReporter

{ + fn setup(&self, max_progress: Option, message: &str) { + let private = FileDownloadReporterPrivate { + last_update: Instant::now(), + max_progress, + last_current_progress: 0, + }; + + let mut guard = self.private.lock().unwrap(); + *guard = Some(private); + } + + fn progress(&self, current: u64) { + if let Some(private) = self.private.lock().unwrap().as_mut() { + let progress = self.progress.lock().unwrap(); + if private.last_update.elapsed().as_millis() >= 250 { + progress.progress(current - private.last_current_progress); // to add to the progress + private.last_current_progress = current; + } + private.last_update = Instant::now(); + } + } + + fn set_message(&self, message: &str) {} + + fn done(&self) {} +} diff --git a/babylonia-terminal-sdk/src/components/mod.rs b/babylonia-terminal-sdk/src/components/mod.rs index c60d39f..ee1c0cb 100644 --- a/babylonia-terminal-sdk/src/components/mod.rs +++ b/babylonia-terminal-sdk/src/components/mod.rs @@ -1,4 +1,4 @@ -pub(crate) mod component_downloader; -pub(crate) mod dxvk_component; -pub(crate) mod pgr_component; -pub(crate) mod wine_component; +pub mod component_downloader; +pub mod dxvk_component; +pub mod game_component; +pub mod wine_component; diff --git a/babylonia-terminal-sdk/src/components/pgr_component.rs b/babylonia-terminal-sdk/src/components/pgr_component.rs deleted file mode 100644 index ca20028..0000000 --- a/babylonia-terminal-sdk/src/components/pgr_component.rs +++ /dev/null @@ -1,34 +0,0 @@ -use serde::Deserialize; -use serde::Serialize; -use std::path::PathBuf; - -use super::component_downloader::ComponentDownloader; -use crate::utils::github_requester::GithubRequester; - -pub struct PGRComponent { - path: PathBuf, -} - -impl GithubRequester for PGRComponent {} - -impl ComponentDownloader for PGRComponent { - async fn install( - &self, - progress: Option>, - ) -> anyhow::Result<()> { - Self::download(self.path, progress); - } - - async fn download( - output_dir: &std::path::PathBuf, - progress: Option>, - ) -> anyhow::Result { - } - - async fn uncompress( - file: std::path::PathBuf, - new_filename: std::path::PathBuf, - ) -> anyhow::Result<()> { - todo!() - } -} diff --git a/babylonia-terminal-sdk/src/components/wine_component.rs b/babylonia-terminal-sdk/src/components/wine_component.rs index a57c7c8..3209caa 100644 --- a/babylonia-terminal-sdk/src/components/wine_component.rs +++ b/babylonia-terminal-sdk/src/components/wine_component.rs @@ -93,7 +93,7 @@ impl WineComponent { } pub fn init_wine(&self) -> wincompatlib::prelude::Wine { - let wine_path = self.path.parent().unwrap().join("bin/wine"); + let wine_path = self.path.join("bin/wine"); let wine_prefix = self.path.parent().unwrap().join("data"); if !wine_prefix.exists() { create_dir(wine_prefix.clone()).unwrap() diff --git a/babylonia-terminal-sdk/src/game_manager.rs b/babylonia-terminal-sdk/src/game_manager.rs index 057e699..eb74320 100644 --- a/babylonia-terminal-sdk/src/game_manager.rs +++ b/babylonia-terminal-sdk/src/game_manager.rs @@ -9,7 +9,7 @@ use downloader::progress::Reporter; use flate2::read::GzDecoder; use log::info; use tar::Archive; -use tokio::{join, time::sleep}; +use tokio::time::sleep; use wincompatlib::{prelude::*, wine::bundle::proton}; use xz::read::XzDecoder; @@ -17,6 +17,7 @@ use crate::{ components::{ component_downloader::ComponentDownloader, dxvk_component::{self, DXVKComponent}, + game_component::GameComponent, wine_component::WineComponent, }, components_downloader::ComponentsDownloader, @@ -125,21 +126,20 @@ impl GameManager { Ok(()) } - pub fn init_wine() -> Wine { - let wine_path = GameState::get_config_directory().join("wine/bin/wine"); - let wine_prefix = GameState::get_config_directory().join("data"); - if !wine_prefix.exists() { - create_dir(wine_prefix.clone()).unwrap() - } + pub async fn install_game

(config_dir: PathBuf, progress: Arc

) -> anyhow::Result<()> + where + P: Reporter + 'static, + { + let game_component = GameComponent::new(config_dir); + game_component.install(Some(progress)).await?; - let wine = Wine::from_binary(wine_path); - wine.update_prefix(Some(wine_prefix)).unwrap(); + let mut config = GameState::get_config().await?; + config.is_game_installed = true; + GameState::save_config(config).await?; - wine + Ok(()) } - pub fn download_game() {} - pub async fn start_game(wine: &Wine) { wine.run("/home/alez/.steam/steam/steamapps/compatdata/3841903579/pfx/drive_c/Punishing Gray Raven/launcher.exe").unwrap(); diff --git a/babylonia-terminal-sdk/src/game_state.rs b/babylonia-terminal-sdk/src/game_state.rs index 4941d21..6b34fe7 100644 --- a/babylonia-terminal-sdk/src/game_state.rs +++ b/babylonia-terminal-sdk/src/game_state.rs @@ -29,6 +29,7 @@ pub struct GameConfig { pub is_dxvk_installed: bool, pub is_font_installed: bool, pub is_dependecies_installed: bool, + pub is_game_installed: bool, // will be remove, just for test purpose } impl Default for GameConfig { @@ -40,6 +41,7 @@ impl Default for GameConfig { is_dxvk_installed: false, is_font_installed: false, is_dependecies_installed: false, + is_game_installed: false, } } } @@ -98,6 +100,10 @@ impl GameState { return GameState::DependecieNotInstalled; } + if !config.is_game_installed { + return GameState::GameNotInstalled; + } + GameState::GameInstalled } } diff --git a/babylonia-terminal-sdk/src/utils/kuro_prod_api.rs b/babylonia-terminal-sdk/src/utils/kuro_prod_api.rs index 3436933..340c11e 100644 --- a/babylonia-terminal-sdk/src/utils/kuro_prod_api.rs +++ b/babylonia-terminal-sdk/src/utils/kuro_prod_api.rs @@ -1,3 +1,4 @@ +use log::debug; use serde::Deserialize; use serde::Serialize; @@ -5,7 +6,7 @@ use serde::Serialize; #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct Index { +pub struct GameInfo { pub default: Default, pub hash_cache_check_acc_switch: i64, } @@ -94,29 +95,40 @@ pub struct SampleHashInfo { // end data --------------------------------------------------------------------- -static URL: &str = concat!( - "https://", - "prod", - "-alicdn", - "-gamestarter", - ".kur", - "ogame", - ".com/pcst", - "arter/pro", - "d/game/G1", - "43/4/index.json" -); +static URL: &str = + concat!("https://prod-alicdn-gamestarter.k", "uro", "gam", "e.com/pcstarter/prod/game/G143/4/index.json"); -pub async fn fetch_resources() -> anyhow::Result { +pub async fn fetch_game_info() -> anyhow::Result { let response = reqwest::get(URL).await?; + debug!("{:?}", response.headers()); + response.headers_mut(). let body = response.text().await?; - let index: Index = serde_json::from_str(&body)?; - - let resources_base_url = index.default.cdn_list.first().unwrap().url.clone(); - let resources_path_url = index.default.resources; - let resources_url = format!("{}{}", resources_base_url, resources_path_url); - - let response = reqwest::get(&resources_url).await?; - let body = response.text().await?; - Ok(serde_json::from_str::(&body)?) + debug!("{}", &body); + Ok(serde_json::from_str(&body)?) +} + +impl GameInfo { + pub fn get_first_cdn(&self) -> String { + self.default.cdn_list.first().unwrap().url.clone() + } + + pub fn get_resource_base_path(&self) -> String { + self.default.resources_base_path.clone() + } + + pub async fn fetch_resources(&self) -> anyhow::Result { + let resources_base_url = self.get_first_cdn(); + let resources_path_url = &self.default.resources; + let resources_url = format!("{}{}", resources_base_url, resources_path_url); + + let response = reqwest::get(&resources_url).await?; + let body = response.text().await?; + Ok(serde_json::from_str::(&body)?) + } +} + +impl Resource { + pub fn build_download_url(&self, base_url: &str, zip_uri: &str) -> String { + format!("{}{}{}", base_url, zip_uri, self.dest) + } }