Skip to content
This repository was archived by the owner on Jun 13, 2025. It is now read-only.

Commit 9973275

Browse files
committed
wip
1 parent a27fce5 commit 9973275

File tree

9 files changed

+189
-51
lines changed

9 files changed

+189
-51
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,4 @@ jobs:
4242
with:
4343
name: windows-i686-build
4444
path: dist/
45-
retention-days: 3
45+
retention-days: 1

.github/workflows/clippy.yml

Lines changed: 0 additions & 13 deletions
This file was deleted.

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
## todo
22
- [x] detect game
33
- [ ] downloading game
4-
- [ ] better cdn setup
5-
- [ ] hash based cdn urls to allow caching and still make sure the user alwys gets the correct file
4+
- [x] better cdn setup
5+
- [x] hash based cdn urls to allow caching and still make sure the user alwys gets the correct file
66
- `cdn_url/file_hash` `{ "name": "file/path/name.ext", "size": 123456, "hash": "file_hash" }`
7-
- [ ] cdn fallback
8-
- [ ] cdn url override
9-
- [ ] info.json -> stable.json, unstable.json, all files (named as hash) in "files" dir
7+
- [x] cdn fallback
8+
- [x] cdn url override
9+
- [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
1010
- [ ] file download
1111
- [ ] http chunked transfer
1212
- [ ] resumable downloads

build.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ fn main() {
22
if std::env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
33
static_vcruntime::metabuild();
44
}
5-
}
5+
}

src/cdn.rs

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use crate::extend::*;
1+
use std::path::PathBuf;
2+
3+
use crate::{extend::*, global, http, utils};
24

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

1012
impl File {
13+
/// CDN URL for the file
1114
pub fn url(&self) -> String {
12-
format!("{}/{}", crate::global::CDN_URL, self.hash)
15+
format!("{}/{}", get_cdn_url(), self.hash)
1316
}
1417

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

19-
pub fn cache_path(&self) -> String {
20-
format!("{}/{}", crate::global::CACHE_DIR, self.cache_name())
23+
/// Temporary (full) file path for downloading
24+
pub fn cache_path(&self) -> PathBuf {
25+
format!("{}/{}", &global::CACHE_DIR, self.cache_name()).into()
2126
}
2227

28+
/// Human-readable file size
2329
pub fn size_human(&self) -> String {
2430
self.size.human_readable_size()
2531
}
@@ -31,11 +37,45 @@ pub struct Info {
3137
pub files: Vec<File>,
3238
}
3339

40+
pub fn get_cdn_url() -> String {
41+
format!(
42+
"{}://{}/{}",
43+
utils::get_mutex(&global::CDN_PROTOCOL),
44+
utils::get_mutex(&global::CDN_HOST),
45+
utils::get_mutex(&global::CDN_BRANCH)
46+
)
47+
}
48+
49+
/// Get info from CDN
3450
pub async fn get_info() -> Result<Info, Box<dyn std::error::Error>> {
35-
let info = crate::http::quick_request(&format!("{0}/info.json", crate::global::CDN_URL)).await?;
36-
Ok(serde_json::from_str(&info)?)
51+
for host in global::CDN_HOSTS {
52+
println!("Checking {}", host);
53+
utils::set_mutex(&global::CDN_HOST, host.to_owned());
54+
55+
let url = format!("{}/info.json", get_cdn_url());
56+
57+
match http::quick_request(&url).await {
58+
Ok(response) => match serde_json::from_str::<Info>(&response) {
59+
Ok(info) => {
60+
println!("Successfully connected to {}", host);
61+
return Ok(info);
62+
}
63+
Err(e) => {
64+
println!("Invalid JSON from {}: {}", host, e);
65+
continue;
66+
}
67+
},
68+
Err(e) => {
69+
println!("Failed to get info from {}: {}", host, e);
70+
continue;
71+
}
72+
}
73+
}
74+
75+
Err("No CDN host is reachable or returned valid info".into())
3776
}
3877

78+
/// Filter files by game
3979
pub fn filter_files(files: Vec<File>, game: crate::game::Game) -> Vec<File> {
4080
files
4181
.into_iter()

src/game.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![allow(unused)]
22

3-
use std::path::Path;
3+
use std::path::{Path, PathBuf};
44
use strum::IntoEnumIterator;
55
use strum_macros::EnumIter;
66

@@ -132,6 +132,16 @@ impl Client {
132132
Client::S1Mod => "S1 Mod",
133133
}
134134
}
135+
136+
pub fn internal_name(&self) -> &str {
137+
match self {
138+
Client::IW4x => "iw4x",
139+
Client::IW4xSP => "iw4x-sp",
140+
Client::IW5Mod => "iw5-mod",
141+
Client::IW6Mod => "iw6-mod",
142+
Client::S1Mod => "s1-mod",
143+
}
144+
}
135145
}
136146

137147
/// Detect game in the given path

src/global.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#![allow(unused)]
22

33
use once_cell::sync::Lazy;
4+
use once_cell::sync::OnceCell;
45
use std::path::PathBuf;
56
use std::sync::Mutex;
67

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

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

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

1722
// TODO: Make this configurable
1823
/// The path to the download cache

src/main.rs

Lines changed: 96 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,108 @@ mod game;
44
mod global;
55
mod hash;
66
mod http;
7+
mod utils;
78

8-
use std::path::Path;
9+
use std::path::PathBuf;
10+
use strum::IntoEnumIterator;
11+
12+
fn arg_value(args: &[String], possible_args: &[&str]) -> Option<String> {
13+
for arg in possible_args {
14+
if let Some(e) = args.iter().position(|r| r == *arg) {
15+
if e + 1 < args.len() {
16+
let value = args[e + 1].clone();
17+
if value.starts_with('-') {
18+
continue;
19+
}
20+
21+
arg_value_remove(&mut args.to_vec(), arg);
22+
return Some(value);
23+
}
24+
}
25+
}
26+
None
27+
}
28+
29+
fn arg_value_remove(args: &mut Vec<String>, arg: &str) {
30+
if let Some(e) = args.iter().position(|r| r == arg) {
31+
args.remove(e);
32+
args.remove(e);
33+
};
34+
}
35+
36+
fn arg_bool(args: &[String], possible_args: &[&str]) -> bool {
37+
possible_args
38+
.iter()
39+
.any(|arg| args.iter().any(|r| r == *arg))
40+
}
41+
42+
fn arg_path(args: &[String], possible_args: &[&str]) -> Option<PathBuf> {
43+
arg_value(args, possible_args).map(PathBuf::from)
44+
}
945

1046
#[tokio::main]
1147
async fn main() -> Result<(), Box<dyn std::error::Error>> {
12-
let path = Path::new("I:\\SteamLibrary\\steamapps\\common\\Call of Duty Modern Warfare 2");
13-
let game = game::detect_game(path);
14-
15-
if let Some(game) = game {
16-
println!("Game: {:?}", game);
17-
let clients = game.clients();
18-
println!("Clients: {:?}", clients);
19-
20-
let info = cdn::get_info().await?;
21-
let files = cdn::filter_files(info.files.clone(), game);
22-
println!("Files: {:?}", files);
23-
for file in files {
24-
println!("File: {:?}", file);
25-
println!("Size: {:?}", file.size_human());
26-
println!("URL: {:?}", file.url());
27-
println!("Cache name: {:?}", file.cache_name());
28-
println!("Cache path: {:?}", file.cache_path());
29-
}
48+
let args = std::env::args().collect::<Vec<String>>();
49+
50+
utils::set_mutex(
51+
&global::CDN_PROTOCOL,
52+
arg_value(&args, &["--protocol"]).unwrap_or("https".to_owned()),
53+
);
54+
utils::set_mutex(
55+
&global::CDN_BRANCH,
56+
arg_value(&args, &["--branch"]).unwrap_or("stable".to_owned()),
57+
);
58+
utils::set_mutex(
59+
&global::GAME_DIR,
60+
arg_path(&args, &["-p", "--path", "--game-path"])
61+
.unwrap_or(std::env::current_dir().unwrap()),
62+
);
63+
64+
let client = args
65+
.iter()
66+
.find_map(|arg| game::Client::iter().find(|&client| client.internal_name() == arg));
67+
68+
if let Some(client) = client {
69+
utils::set_mutex(&global::GAME_CLIENT, Some(client));
70+
utils::set_mutex(&global::GAME, client.game());
3071
} else {
31-
println!("No game detected");
72+
if let Some(game) = game::detect_game(&utils::get_mutex(&global::GAME_DIR)) {
73+
utils::set_mutex(&global::GAME, game);
74+
println!("Game: {:?}", game);
75+
76+
match game.clients().len() {
77+
0 => {
78+
println!("No clients found for game");
79+
return Ok(());
80+
}
81+
1 => {
82+
utils::set_mutex(&global::GAME_CLIENT, Some(game.clients()[0]));
83+
}
84+
_ => {
85+
println!("Multiple clients found for game, please specify one");
86+
return Ok(());
87+
}
88+
}
89+
} else {
90+
println!("No game detected");
91+
return Ok(());
92+
}
3293
}
3394

95+
let game = utils::get_mutex(&global::GAME);
96+
let clients = game.clients();
97+
println!("Clients: {:?}", clients);
98+
99+
let info = cdn::get_info().await?;
100+
let files = cdn::filter_files(info.files.clone(), game);
101+
//println!("Files: {:?}", files);
102+
//for file in files {
103+
//println!("File: {:?}", file);
104+
// println!("Size: {:?}", file.size_human());
105+
// println!("URL: {:?}", file.url());
106+
// println!("Cache name: {:?}", file.cache_name());
107+
// println!("Cache path: {:?}", file.cache_path());
108+
//}
109+
34110
Ok(())
35111
}

src/utils.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use once_cell::sync::OnceCell;
2+
use std::sync::Mutex;
3+
4+
pub fn set_mutex<T: Clone>(cell: &OnceCell<Mutex<T>>, value: T) {
5+
if let Some(mutex) = cell.get() {
6+
if let Ok(mut guard) = mutex.lock() {
7+
*guard = value;
8+
}
9+
} else {
10+
cell.set(Mutex::new(value)).ok();
11+
}
12+
}
13+
14+
pub fn get_mutex<T: Clone>(cell: &OnceCell<Mutex<T>>) -> T {
15+
cell.get()
16+
.expect("Failed to get once cell value")
17+
.lock()
18+
.expect("Failed to lock mutex")
19+
.clone()
20+
}

0 commit comments

Comments
 (0)