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 simple_logger::SimpleLogger;
use wincompatlib::prelude::*;
@ -18,24 +20,30 @@ async fn main() {
.init()
.unwrap();
let mut wine_component: Option<WineComponent> = None;
let mut wine: Option<Wine> = 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");
}
_ => {}
}

View File

@ -4,18 +4,21 @@ use std::fmt::Write;
struct DownloadReporterPrivate {
last_update: std::time::Instant,
max_progress: Option<u64>,
last_current: u64,
message: String,
pb: ProgressBar,
}
pub struct DownloadReporter {
private: std::sync::Mutex<Option<DownloadReporterPrivate>>,
relative: bool,
}
impl DownloadReporter {
pub fn create() -> std::sync::Arc<Self> {
pub fn create(relative: bool) -> std::sync::Arc<Self> {
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);

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(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;

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 {
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()

View File

@ -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<P>(config_dir: PathBuf, progress: Arc<P>) -> 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();

View File

@ -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
}
}

View File

@ -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<Resources> {
pub async fn fetch_game_info() -> anyhow::Result<GameInfo> {
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::<Resources>(&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<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 response = reqwest::get(&resources_url).await?;
let body = response.text().await?;
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)
}
}