Downloads a file and writes it to disk

This commit is contained in:
Alexandre 2026-10-21 16:20:36 +02:00
parent ba61b4717e
commit 2d99342b68
15 changed files with 10353 additions and 3 deletions

File diff suppressed because it is too large Load Diff

1827
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,16 @@
[package] [workspace]
name = "mc-launcher-rs" resolver = "3"
members = ["crates/*"]
[workspace.package]
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [workspace.dependencies]
anyhow = "1.0.104"
const_format = "0.2.36"
reqwest = "0.13.5"
serde = { version = "1.0.229", features = ["derive"] }
serde_json = "1.0.154"
tokio = { version = "1.54.0", features = ["full"] }
thiserror = "2.0.21"

43
README.md Normal file
View File

@ -0,0 +1,43 @@
# mc-launcher-rs
A simple minecraft launcher, in Rust
## Goal
The goal of this project is for me to learn a little bit of Rust, and experiment with the ecosystem.
The current scope of the project is just downloading minecraft and launching it.
Maybe modloader after all is stable-ish
## Stack
// Todo, link
| Usage | Choice |
| -------- | ----------- |
| UI | egui |
| Async | tokio |
| HTTP | reqwest |
| JSON | serde |
| Hashing | sha1 |
| Archives | zip |
| Folders | directories |
| Errors | anyhow |
| Logs | tracing |
## Roadmap
Do the download logic, then the UI.
Download:
[x] Read the manifest
[x] Manifest -> JSON
[] Interpret the data
[] Download from the JSON -> HTTP/Tokio
[] Go from sequential to parallel
## Structure
`core` -> all the launcher stuff, version manifest, JSON, download, hashes, launch args...
`cli` -> a small wrapper that calls the core and display text
`gui` -> another small wrapper that calls the core and displays it with `egui`
## Links
https://piston-meta.mojang.com/mc/game/version_manifest_v2.json
This one is the main manifest

14
crates/cli/Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "launcher-cli"
version.workspace = true
edition.workspace = true
[dependencies]
launcher-core = { version = "0.1.0", path = "../core" }
tokio = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
anyhow = { workspace = true }
reqwest = { workspace = true }
const_format = { workspace = true }
thiserror = { workspace = true }

12
crates/cli/src/main.rs Normal file
View File

@ -0,0 +1,12 @@
use anyhow::Result;
use launcher_core::manifest::{get_manifest, init_network, parse_manifest, write_manifest};
#[tokio::main]
async fn main() -> Result<()> {
println!("Runtime test, writing the manifest");
let client = init_network();
let data = get_manifest(client.clone()).await?;
write_manifest(&data).await?;
parse_manifest(&data).await?;
Ok(())
}

16
crates/core/Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
name = "launcher-core"
version.workspace = true
edition.workspace = true
[dependencies]
tokio = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
anyhow = { workspace = true }
reqwest = { workspace = true }
const_format = { workspace = true }
thiserror = { workspace = true }
[dev-dependencies]
tokio = { workspace = true }

13
crates/core/src/errors.rs Normal file
View File

@ -0,0 +1,13 @@
use thiserror::Error;
#[derive(Debug, Error)]
pub enum CoreError {
#[error("Network error: {0}")]
Network(#[from] reqwest::Error),
#[error("Disk error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON error: {0}")]
JSON(#[from] serde_json::Error),
}

12
crates/core/src/lib.rs Normal file
View File

@ -0,0 +1,12 @@
pub mod errors;
pub mod manifest;
use const_format::concatcp;
// Constants
// Paths
pub const FILE_PATH: &str = ".minecraft"; //adjusted later
pub const DOWNLOAD_PATH: &str = concatcp!(FILE_PATH, "/downloads");
pub const MANIFEST_PATH: &str = concatcp!(DOWNLOAD_PATH, "/manifest.json");
// Urls
pub const MANIFEST_URL: &str = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";

View File

@ -0,0 +1,39 @@
use reqwest::Client;
use serde_json::Value;
use std::sync::Arc;
use std::time::Duration;
use crate::{DOWNLOAD_PATH, MANIFEST_PATH, MANIFEST_URL, errors::CoreError};
pub fn init_network() -> Arc<Client> {
let client = Arc::new(
Client::builder()
.timeout(Duration::from_secs(60))
.build()
.expect("Failed to create HTTP client"),
);
return client;
}
pub async fn get_manifest(client: Arc<Client>) -> Result<String, CoreError> {
let body = client.get(MANIFEST_URL).send().await?.text().await?;
Ok(body)
}
pub async fn write_manifest(data: &str) -> Result<(), CoreError> {
// Checks if the folder is there, else, create it
tokio::fs::create_dir_all(DOWNLOAD_PATH).await?;
tokio::fs::write(MANIFEST_PATH, data).await?;
Ok(())
}
pub async fn parse_manifest(data: &str) -> Result<Value, CoreError> {
// Reads from a string
let v: Value = serde_json::from_str(data)?;
println!("Testing stuff.\nSo latest {}", v["latest"]["release"]);
Ok(v)
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,11 @@
/*
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_write() {
assert!()
}
}
*/

7
crates/gui/Cargo.toml Normal file
View File

@ -0,0 +1,7 @@
[package]
name = "launcher-gui"
version.workspace = true
edition.workspace = true
[dependencies]
launcher-core = { version = "0.1.0", path = "../core" }

57
flake.lock generated Normal file
View File

@ -0,0 +1,57 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1792506237,
"narHash": "sha256-hVMDyAXU0kxRuG3GygbZkzf5x+aBNEC0NYEJ6KwHtD4=",
"rev": "5adc50831c259f87faeb09ddf930d43597197473",
"type": "tarball",
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.11pre1097770.5adc50831c25/nixexprs.tar.zst"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"utils": "utils"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1791914995,
"narHash": "sha256-Zf/xfQEAasnwnmEfHqW/4hjDE/s44yn2hD4RuVIZRjA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b7a79d3fea1e214f6d668d521cecec0c01e26544",
"type": "github"
},
"original": {
"id": "flake-utils",
"type": "indirect"
}
}
},
"root": "root",
"version": 7
}