Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
mxve committed Jan 29, 2025
1 parent a27fce5 commit 9973275
Show file tree
Hide file tree
Showing 9 changed files with 189 additions and 51 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@ jobs:
with:
name: windows-i686-build
path: dist/
retention-days: 3
retention-days: 1
13 changes: 0 additions & 13 deletions .github/workflows/clippy.yml

This file was deleted.

10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
## todo
- [x] detect game
- [ ] downloading game
- [ ] better cdn setup
- [ ] hash based cdn urls to allow caching and still make sure the user alwys gets the correct file
- [x] better cdn setup
- [x] hash based cdn urls to allow caching and still make sure the user alwys gets the correct file
- `cdn_url/file_hash` `{ "name": "file/path/name.ext", "size": 123456, "hash": "file_hash" }`
- [ ] cdn fallback
- [ ] cdn url override
- [ ] info.json -> stable.json, unstable.json, all files (named as hash) in "files" dir
- [x] cdn fallback
- [x] cdn url override
- [x] ~~info.json -> stable.json, unstable.json~~, all files (named as hash) in ~~"files" dir~~ next to info.json and files in sub dir for stable/unstable
- [ ] file download
- [ ] http chunked transfer
- [ ] resumable downloads
Expand Down
2 changes: 1 addition & 1 deletion build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ fn main() {
if std::env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
static_vcruntime::metabuild();
}
}
}
54 changes: 47 additions & 7 deletions src/cdn.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::extend::*;
use std::path::PathBuf;

use crate::{extend::*, global, http, utils};

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct File {
Expand All @@ -8,18 +10,22 @@ pub struct File {
}

impl File {
/// CDN URL for the file
pub fn url(&self) -> String {

Check warning on line 14 in src/cdn.rs

View workflow job for this annotation

GitHub Actions / Build

methods `url`, `cache_name`, `cache_path`, and `size_human` are never used
format!("{}/{}", crate::global::CDN_URL, self.hash)
format!("{}/{}", get_cdn_url(), self.hash)
}

/// Temporary file name on disk, truncated to 24 characters to prevent MAX_PATH issues
pub fn cache_name(&self) -> String {
format!("{}", self.hash[..24].to_string())
self.hash[..24].to_string()
}

pub fn cache_path(&self) -> String {
format!("{}/{}", crate::global::CACHE_DIR, self.cache_name())
/// Temporary (full) file path for downloading
pub fn cache_path(&self) -> PathBuf {
format!("{}/{}", &global::CACHE_DIR, self.cache_name()).into()
}

/// Human-readable file size
pub fn size_human(&self) -> String {
self.size.human_readable_size()
}
Expand All @@ -31,11 +37,45 @@ pub struct Info {
pub files: Vec<File>,
}

pub fn get_cdn_url() -> String {
format!(
"{}://{}/{}",
utils::get_mutex(&global::CDN_PROTOCOL),
utils::get_mutex(&global::CDN_HOST),
utils::get_mutex(&global::CDN_BRANCH)
)
}

/// Get info from CDN
pub async fn get_info() -> Result<Info, Box<dyn std::error::Error>> {
let info = crate::http::quick_request(&format!("{0}/info.json", crate::global::CDN_URL)).await?;
Ok(serde_json::from_str(&info)?)
for host in global::CDN_HOSTS {
println!("Checking {}", host);
utils::set_mutex(&global::CDN_HOST, host.to_owned());

let url = format!("{}/info.json", get_cdn_url());

match http::quick_request(&url).await {
Ok(response) => match serde_json::from_str::<Info>(&response) {
Ok(info) => {
println!("Successfully connected to {}", host);
return Ok(info);
}
Err(e) => {
println!("Invalid JSON from {}: {}", host, e);
continue;
}
},
Err(e) => {
println!("Failed to get info from {}: {}", host, e);
continue;
}
}
}

Err("No CDN host is reachable or returned valid info".into())
}

/// Filter files by game
pub fn filter_files(files: Vec<File>, game: crate::game::Game) -> Vec<File> {
files
.into_iter()
Expand Down
12 changes: 11 additions & 1 deletion src/game.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![allow(unused)]

use std::path::Path;
use std::path::{Path, PathBuf};
use strum::IntoEnumIterator;
use strum_macros::EnumIter;

Expand Down Expand Up @@ -132,6 +132,16 @@ impl Client {
Client::S1Mod => "S1 Mod",
}
}

pub fn internal_name(&self) -> &str {
match self {
Client::IW4x => "iw4x",
Client::IW4xSP => "iw4x-sp",
Client::IW5Mod => "iw5-mod",
Client::IW6Mod => "iw6-mod",
Client::S1Mod => "s1-mod",
}
}
}

/// Detect game in the given path
Expand Down
11 changes: 8 additions & 3 deletions src/global.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![allow(unused)]

use once_cell::sync::Lazy;
use once_cell::sync::OnceCell;
use std::path::PathBuf;
use std::sync::Mutex;

Expand All @@ -9,10 +10,14 @@ pub const GITHUB_OWNER: &str = "mxve";
/// The repository of the launcher
pub const GITHUB_REPO: &str = "alterware-launcher";

pub const CDN_HOSTS: [&str; 2] = ["test.test", "cdn.getserve.rs"];
pub static CDN_PROTOCOL: OnceCell<Mutex<String>> = OnceCell::new();
pub static CDN_BRANCH: OnceCell<Mutex<String>> = OnceCell::new();
pub static CDN_HOST: OnceCell<Mutex<String>> = OnceCell::new();

// TODO: Make this configurable
/// Base URL for file downloads
pub const CDN_URL: &str = "https://cdn.getserve.rs/stable";
pub static GAME: OnceCell<Mutex<crate::game::Game>> = OnceCell::new();
pub static GAME_DIR: OnceCell<Mutex<PathBuf>> = OnceCell::new();
pub static GAME_CLIENT: OnceCell<Mutex<Option<crate::game::Client>>> = OnceCell::new();

// TODO: Make this configurable
/// The path to the download cache
Expand Down
116 changes: 96 additions & 20 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,108 @@ mod game;
mod global;
mod hash;
mod http;
mod utils;

use std::path::Path;
use std::path::PathBuf;
use strum::IntoEnumIterator;

fn arg_value(args: &[String], possible_args: &[&str]) -> Option<String> {
for arg in possible_args {
if let Some(e) = args.iter().position(|r| r == *arg) {
if e + 1 < args.len() {
let value = args[e + 1].clone();
if value.starts_with('-') {
continue;
}

arg_value_remove(&mut args.to_vec(), arg);
return Some(value);
}
}
}
None
}

fn arg_value_remove(args: &mut Vec<String>, arg: &str) {
if let Some(e) = args.iter().position(|r| r == arg) {
args.remove(e);
args.remove(e);
};
}

fn arg_bool(args: &[String], possible_args: &[&str]) -> bool {

Check warning on line 36 in src/main.rs

View workflow job for this annotation

GitHub Actions / Build

function `arg_bool` is never used
possible_args
.iter()
.any(|arg| args.iter().any(|r| r == *arg))
}

fn arg_path(args: &[String], possible_args: &[&str]) -> Option<PathBuf> {
arg_value(args, possible_args).map(PathBuf::from)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = Path::new("I:\\SteamLibrary\\steamapps\\common\\Call of Duty Modern Warfare 2");
let game = game::detect_game(path);

if let Some(game) = game {
println!("Game: {:?}", game);
let clients = game.clients();
println!("Clients: {:?}", clients);

let info = cdn::get_info().await?;
let files = cdn::filter_files(info.files.clone(), game);
println!("Files: {:?}", files);
for file in files {
println!("File: {:?}", file);
println!("Size: {:?}", file.size_human());
println!("URL: {:?}", file.url());
println!("Cache name: {:?}", file.cache_name());
println!("Cache path: {:?}", file.cache_path());
}
let args = std::env::args().collect::<Vec<String>>();

utils::set_mutex(
&global::CDN_PROTOCOL,
arg_value(&args, &["--protocol"]).unwrap_or("https".to_owned()),
);
utils::set_mutex(
&global::CDN_BRANCH,
arg_value(&args, &["--branch"]).unwrap_or("stable".to_owned()),
);
utils::set_mutex(
&global::GAME_DIR,
arg_path(&args, &["-p", "--path", "--game-path"])
.unwrap_or(std::env::current_dir().unwrap()),
);

let client = args
.iter()
.find_map(|arg| game::Client::iter().find(|&client| client.internal_name() == arg));

if let Some(client) = client {
utils::set_mutex(&global::GAME_CLIENT, Some(client));
utils::set_mutex(&global::GAME, client.game());
} else {
println!("No game detected");
if let Some(game) = game::detect_game(&utils::get_mutex(&global::GAME_DIR)) {
utils::set_mutex(&global::GAME, game);
println!("Game: {:?}", game);

match game.clients().len() {
0 => {
println!("No clients found for game");
return Ok(());
}
1 => {
utils::set_mutex(&global::GAME_CLIENT, Some(game.clients()[0]));
}
_ => {
println!("Multiple clients found for game, please specify one");
return Ok(());
}
}
} else {
println!("No game detected");
return Ok(());
}
}

let game = utils::get_mutex(&global::GAME);
let clients = game.clients();
println!("Clients: {:?}", clients);

let info = cdn::get_info().await?;
let files = cdn::filter_files(info.files.clone(), game);

Check warning on line 100 in src/main.rs

View workflow job for this annotation

GitHub Actions / Build

unused variable: `files`
//println!("Files: {:?}", files);
//for file in files {
//println!("File: {:?}", file);
// println!("Size: {:?}", file.size_human());
// println!("URL: {:?}", file.url());
// println!("Cache name: {:?}", file.cache_name());
// println!("Cache path: {:?}", file.cache_path());
//}

Ok(())
}
20 changes: 20 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use once_cell::sync::OnceCell;
use std::sync::Mutex;

pub fn set_mutex<T: Clone>(cell: &OnceCell<Mutex<T>>, value: T) {
if let Some(mutex) = cell.get() {
if let Ok(mut guard) = mutex.lock() {
*guard = value;
}
} else {
cell.set(Mutex::new(value)).ok();
}
}

pub fn get_mutex<T: Clone>(cell: &OnceCell<Mutex<T>>) -> T {
cell.get()
.expect("Failed to get once cell value")
.lock()
.expect("Failed to lock mutex")
.clone()
}

0 comments on commit 9973275

Please sign in to comment.