WIP download game resources

This commit is contained in:
ALEZ-DEV 2024-04-23 23:40:12 +02:00
parent 081123ec60
commit c60ad6a587
9 changed files with 242 additions and 87 deletions

View File

@ -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 log::{info, LevelFilter};
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
use wincompatlib::prelude::*; use wincompatlib::prelude::*;
@ -18,24 +20,30 @@ async fn main() {
.init() .init()
.unwrap(); .unwrap();
let mut wine_component: Option<WineComponent> = None;
let mut wine: Option<Wine> = None; let mut wine: Option<Wine> = None;
loop { loop {
let state = GameState::get_current_state().await; let state = GameState::get_current_state().await;
if state != GameState::WineNotInstalled && wine.is_none() { if let (Some(component), None) = (&wine_component, &wine) {
wine = Some(GameManager::init_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 { match state {
GameState::WineNotInstalled => { GameState::WineNotInstalled => {
info!("Wine not installed, installing it..."); info!("Wine not installed, installing it...");
wine_component = Some(
GameManager::install_wine( GameManager::install_wine(
GameState::get_config_directory(), GameState::get_config_directory(),
Some(DownloadReporter::create()), Some(DownloadReporter::create(false)),
) )
.await .await
.expect("Failed to install Wine"); .expect("Failed to install Wine"),
);
info!("Wine installed"); info!("Wine installed");
} }
GameState::DXVKNotInstalled => { GameState::DXVKNotInstalled => {
@ -43,7 +51,7 @@ async fn main() {
GameManager::install_dxvk( GameManager::install_dxvk(
&wine.clone().unwrap(), &wine.clone().unwrap(),
GameState::get_config_directory(), GameState::get_config_directory(),
Some(DownloadReporter::create()), Some(DownloadReporter::create(false)),
) )
.await .await
.expect("Failed to installed DXVK"); .expect("Failed to installed DXVK");
@ -51,7 +59,7 @@ async fn main() {
} }
GameState::FontNotInstalled => { GameState::FontNotInstalled => {
info!("Fonts not installed, installing it..."); info!("Fonts not installed, installing it...");
GameManager::install_font(&wine.clone().unwrap(), DownloadReporter::create()) GameManager::install_font(&wine.clone().unwrap(), DownloadReporter::create(false))
.await .await
.expect("Failed to install fonts"); .expect("Failed to install fonts");
info!("Fonts installed"); info!("Fonts installed");
@ -63,6 +71,15 @@ async fn main() {
.expect("Failed to install dependecies"); .expect("Failed to install dependecies");
info!("Dependecies installed"); 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");
}
_ => {} _ => {}
} }

View File

@ -4,18 +4,21 @@ use std::fmt::Write;
struct DownloadReporterPrivate { struct DownloadReporterPrivate {
last_update: std::time::Instant, last_update: std::time::Instant,
max_progress: Option<u64>, max_progress: Option<u64>,
last_current: u64,
message: String, message: String,
pb: ProgressBar, pb: ProgressBar,
} }
pub struct DownloadReporter { pub struct DownloadReporter {
private: std::sync::Mutex<Option<DownloadReporterPrivate>>, private: std::sync::Mutex<Option<DownloadReporterPrivate>>,
relative: bool,
} }
impl DownloadReporter { impl DownloadReporter {
pub fn create() -> std::sync::Arc<Self> { pub fn create(relative: bool) -> std::sync::Arc<Self> {
std::sync::Arc::new(Self { std::sync::Arc::new(Self {
private: std::sync::Mutex::new(None), private: std::sync::Mutex::new(None),
relative,
}) })
} }
} }
@ -31,6 +34,7 @@ impl downloader::progress::Reporter for DownloadReporter {
let private = DownloadReporterPrivate { let private = DownloadReporterPrivate {
last_update: std::time::Instant::now(), last_update: std::time::Instant::now(),
max_progress, max_progress,
last_current: 0,
message: message.to_owned(), message: message.to_owned(),
pb: pb, pb: pb,
}; };
@ -43,7 +47,13 @@ impl downloader::progress::Reporter for DownloadReporter {
fn progress(&self, current: u64) { fn progress(&self, current: u64) {
if let Some(p) = self.private.lock().unwrap().as_mut() { if let Some(p) = self.private.lock().unwrap().as_mut() {
if p.last_update.elapsed().as_millis() >= 1000 { if p.last_update.elapsed().as_millis() >= 1000 {
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.pb.set_position(current);
}
p.last_update = std::time::Instant::now(); p.last_update = std::time::Instant::now();
} else if current == p.max_progress.unwrap() { } else if current == p.max_progress.unwrap() {
p.pb.set_position(current); p.pb.set_position(current);

View File

@ -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<P: downloader::progress::Reporter + 'static>(
&self,
progress: Option<std::sync::Arc<P>>,
) -> anyhow::Result<()> {
Self::download(&self.game_dir, progress).await?;
Ok(())
}
async fn download<P: downloader::progress::Reporter + 'static>(
output_dir: &std::path::PathBuf,
progress: Option<std::sync::Arc<P>>,
) -> anyhow::Result<std::path::PathBuf> {
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::<Vec<_>>()
{
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::<Vec<downloader::Download>>();
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<u64>,
last_current_progress: u64,
}
struct FileDownloadReporter<P: Reporter> {
private: Mutex<Option<FileDownloadReporterPrivate>>,
progress: Mutex<Arc<P>>,
}
impl<P: Reporter> FileDownloadReporter<P> {
pub fn create(progress: Arc<P>) -> Arc<Self> {
Arc::new(Self {
private: Mutex::new(None),
progress: Mutex::new(progress),
})
}
}
impl<P: Reporter> Reporter for FileDownloadReporter<P> {
fn setup(&self, max_progress: Option<u64>, 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) {}
}

View File

@ -1,4 +1,4 @@
pub(crate) mod component_downloader; pub mod component_downloader;
pub(crate) mod dxvk_component; pub mod dxvk_component;
pub(crate) mod pgr_component; pub mod game_component;
pub(crate) mod wine_component; pub mod wine_component;

View File

@ -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<P: downloader::progress::Reporter + 'static>(
&self,
progress: Option<std::sync::Arc<P>>,
) -> anyhow::Result<()> {
Self::download(self.path, progress);
}
async fn download<P: downloader::progress::Reporter + 'static>(
output_dir: &std::path::PathBuf,
progress: Option<std::sync::Arc<P>>,
) -> anyhow::Result<std::path::PathBuf> {
}
async fn uncompress(
file: std::path::PathBuf,
new_filename: std::path::PathBuf,
) -> anyhow::Result<()> {
todo!()
}
}

View File

@ -93,7 +93,7 @@ impl WineComponent {
} }
pub fn init_wine(&self) -> wincompatlib::prelude::Wine { 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"); let wine_prefix = self.path.parent().unwrap().join("data");
if !wine_prefix.exists() { if !wine_prefix.exists() {
create_dir(wine_prefix.clone()).unwrap() create_dir(wine_prefix.clone()).unwrap()

View File

@ -9,7 +9,7 @@ use downloader::progress::Reporter;
use flate2::read::GzDecoder; use flate2::read::GzDecoder;
use log::info; use log::info;
use tar::Archive; use tar::Archive;
use tokio::{join, time::sleep}; use tokio::time::sleep;
use wincompatlib::{prelude::*, wine::bundle::proton}; use wincompatlib::{prelude::*, wine::bundle::proton};
use xz::read::XzDecoder; use xz::read::XzDecoder;
@ -17,6 +17,7 @@ use crate::{
components::{ components::{
component_downloader::ComponentDownloader, component_downloader::ComponentDownloader,
dxvk_component::{self, DXVKComponent}, dxvk_component::{self, DXVKComponent},
game_component::GameComponent,
wine_component::WineComponent, wine_component::WineComponent,
}, },
components_downloader::ComponentsDownloader, components_downloader::ComponentsDownloader,
@ -125,21 +126,20 @@ impl GameManager {
Ok(()) Ok(())
} }
pub fn init_wine() -> Wine { pub async fn install_game<P>(config_dir: PathBuf, progress: Arc<P>) -> anyhow::Result<()>
let wine_path = GameState::get_config_directory().join("wine/bin/wine"); where
let wine_prefix = GameState::get_config_directory().join("data"); P: Reporter + 'static,
if !wine_prefix.exists() { {
create_dir(wine_prefix.clone()).unwrap() let game_component = GameComponent::new(config_dir);
game_component.install(Some(progress)).await?;
let mut config = GameState::get_config().await?;
config.is_game_installed = true;
GameState::save_config(config).await?;
Ok(())
} }
let wine = Wine::from_binary(wine_path);
wine.update_prefix(Some(wine_prefix)).unwrap();
wine
}
pub fn download_game() {}
pub async fn start_game(wine: &Wine) { 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(); wine.run("/home/alez/.steam/steam/steamapps/compatdata/3841903579/pfx/drive_c/Punishing Gray Raven/launcher.exe").unwrap();

View File

@ -29,6 +29,7 @@ pub struct GameConfig {
pub is_dxvk_installed: bool, pub is_dxvk_installed: bool,
pub is_font_installed: bool, pub is_font_installed: bool,
pub is_dependecies_installed: bool, pub is_dependecies_installed: bool,
pub is_game_installed: bool, // will be remove, just for test purpose
} }
impl Default for GameConfig { impl Default for GameConfig {
@ -40,6 +41,7 @@ impl Default for GameConfig {
is_dxvk_installed: false, is_dxvk_installed: false,
is_font_installed: false, is_font_installed: false,
is_dependecies_installed: false, is_dependecies_installed: false,
is_game_installed: false,
} }
} }
} }
@ -98,6 +100,10 @@ impl GameState {
return GameState::DependecieNotInstalled; return GameState::DependecieNotInstalled;
} }
if !config.is_game_installed {
return GameState::GameNotInstalled;
}
GameState::GameInstalled GameState::GameInstalled
} }
} }

View File

@ -1,3 +1,4 @@
use log::debug;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
@ -5,7 +6,7 @@ use serde::Serialize;
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Index { pub struct GameInfo {
pub default: Default, pub default: Default,
pub hash_cache_check_acc_switch: i64, pub hash_cache_check_acc_switch: i64,
} }
@ -94,29 +95,40 @@ pub struct SampleHashInfo {
// end data --------------------------------------------------------------------- // end data ---------------------------------------------------------------------
static URL: &str = concat!( static URL: &str =
"https://", concat!("https://prod-alicdn-gamestarter.k", "uro", "gam", "e.com/pcstarter/prod/game/G143/4/index.json");
"prod",
"-alicdn",
"-gamestarter",
".kur",
"ogame",
".com/pcst",
"arter/pro",
"d/game/G1",
"43/4/index.json"
);
pub async fn fetch_resources() -> anyhow::Result<Resources> { pub async fn fetch_game_info() -> anyhow::Result<GameInfo> {
let response = reqwest::get(URL).await?; let response = reqwest::get(URL).await?;
debug!("{:?}", response.headers());
response.headers_mut().
let body = response.text().await?; let body = response.text().await?;
let index: Index = serde_json::from_str(&body)?; debug!("{}", &body);
Ok(serde_json::from_str(&body)?)
}
let resources_base_url = index.default.cdn_list.first().unwrap().url.clone(); impl GameInfo {
let resources_path_url = index.default.resources; 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<Resources> {
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 resources_url = format!("{}{}", resources_base_url, resources_path_url);
let response = reqwest::get(&resources_url).await?; let response = reqwest::get(&resources_url).await?;
let body = response.text().await?; let body = response.text().await?;
Ok(serde_json::from_str::<Resources>(&body)?) Ok(serde_json::from_str::<Resources>(&body)?)
}
}
impl Resource {
pub fn build_download_url(&self, base_url: &str, zip_uri: &str) -> String {
format!("{}{}{}", base_url, zip_uri, self.dest)
}
} }