mirror of
https://github.com/ALEZ-DEV/Babylonia-terminal.git
synced 2025-12-15 00:48:52 +00:00
WIP download game resources
This commit is contained in:
parent
081123ec60
commit
c60ad6a587
@ -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");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
144
babylonia-terminal-sdk/src/components/game_component.rs
Normal file
144
babylonia-terminal-sdk/src/components/game_component.rs
Normal 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) {}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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!()
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user