Skip to content

Commit 2ae14dc

Browse files
committed
Added Downloading different mod loaders from modrinth. Pending curseforge implementation
1 parent 305e0e3 commit 2ae14dc

File tree

15 files changed

+1284
-266
lines changed

15 files changed

+1284
-266
lines changed

Cargo.lock

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@ hex = "0.4.3"
1414
hmac-sha512 = "1.1.7"
1515
inquire = "0.7.5"
1616
itertools = "0.14.0"
17+
percent-encoding = "2.3.1"
18+
pretty_assertions = { version = "1.4.1" }
1719
reqwest = "0.12.18"
1820
serde = { version = "1.0.219", features = ["derive"] }
1921
serde_json = "1.0.140"
22+
strum = { version = "0.27.1", features = ["derive"] }
2023
tabwriter = "1.4.1"
24+
tempfile = "3.20.0"
2125
thiserror = "2.0.12"
2226
tokio = { version = "1.45.1", features = ["full", "rt-multi-thread"] }
2327
tracing = "0.1.41"

core/src/actions.rs

Lines changed: 214 additions & 153 deletions
Large diffs are not rendered by default.

core/src/cli.rs

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use clap::{Parser, Subcommand};
22
use std::{fmt::Display, path::PathBuf};
3+
use strum::EnumIter;
4+
5+
use crate::ModLoader;
36

47
/// Modder is a tool for managing mods for Minecraft.
58
/// It can add mods from Modrinth and Github.
@@ -39,6 +42,12 @@ pub enum Commands {
3942
/// Github token for any mods nested in a github repo.
4043
#[arg(short, long)]
4144
token: Option<String>,
45+
/// Mod Loader
46+
#[arg(short, long, default_value_t= ModLoader::Fabric)]
47+
loader: ModLoader,
48+
/// The directory to update mods in
49+
#[arg( default_value_os_t = PathBuf::from("./"))]
50+
dir: PathBuf,
4251
},
4352
/// Bulk-update a directory of mods to the specified version
4453
#[command(arg_required_else_help = true)]
@@ -54,6 +63,14 @@ pub enum Commands {
5463
/// Github token for any mods nested in a github repo.
5564
#[arg(short, long)]
5665
token: Option<String>,
66+
/// Where to download the mod from
67+
#[arg(short, long)]
68+
source: Option<Source>,
69+
/// Don't check other sources if the mod is not found on <source>
70+
#[arg(long, default_value_t = true)]
71+
other_sources: bool,
72+
#[arg(short, long)]
73+
loader: Option<ModLoader>,
5774
},
5875
/// Quickly add mods from a curated list to the supplied directory (defaults to current directory)
5976
QuickAdd {
@@ -63,15 +80,9 @@ pub enum Commands {
6380
/// Find top `limit` mods from Modrinth
6481
#[arg(short, long, default_value_t = 100)]
6582
limit: u16,
66-
},
67-
/// All the other options, just run in the minecraft directory
68-
InPlace {
69-
/// The game version to add this mod for
70-
#[arg(short, long)]
71-
version: Option<String>,
72-
/// Passed down to the quick add command
73-
#[arg(short, long, default_value_t = 100)]
74-
limit: u16,
83+
/// The mod loader to use
84+
#[arg(short, long, default_value_t = ModLoader::Fabric)]
85+
loader: ModLoader,
7586
},
7687
/// Toggle a mod in the supplied directory (defaults to current directory)
7788
Toggle {
@@ -99,26 +110,27 @@ impl Display for Commands {
99110
Commands::QuickAdd { .. } => "Quick Add".to_string(),
100111
Commands::Update { .. } => "Update".to_string(),
101112
Commands::Add { .. } => "Add".to_string(),
102-
Commands::InPlace { .. } => "Edit Minecraft Directory".to_string(),
103113
Commands::Toggle { .. } => "Toggle".to_string(),
104114
Commands::List { .. } => "List".to_string(),
105115
};
106116
write!(f, "{}", text)
107117
}
108118
}
109119

110-
#[derive(Debug, Clone, clap::ValueEnum, PartialEq, Default, Hash, Eq)]
120+
#[derive(Debug, Clone, clap::ValueEnum, PartialEq, Default, Hash, Eq, EnumIter)]
111121
pub enum Source {
112122
#[default]
113123
Modrinth,
114124
Github,
125+
CurseForge,
115126
}
116127

117128
impl Display for Source {
118129
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119130
let text = match self {
120131
Source::Modrinth => "modrinth".to_string(),
121132
Source::Github => "github".to_string(),
133+
Source::CurseForge => "curseforge".to_string(),
122134
};
123135
write!(f, "{}", text)
124136
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use std::fs::File;
2+
use std::io::{self, Read};
3+
use std::path::Path;
4+
5+
/// Reads the entire content of a file into a byte vector.
6+
///
7+
/// # Arguments
8+
/// * `jar_file_path` - The path to the file to read.
9+
///
10+
/// # Returns
11+
/// A `Result` which is `Ok(Vec<u8>)` on success, or `Err(std::io::Error)` if an error occurs
12+
/// during file opening or reading.
13+
pub fn get_jar_contents(jar_file_path: &Path) -> io::Result<Vec<u8>> {
14+
let mut file = File::open(jar_file_path)?;
15+
let mut buffer = Vec::new();
16+
file.read_to_end(&mut buffer)?;
17+
Ok(buffer)
18+
}
19+
20+
#[cfg(test)]
21+
mod tests {
22+
use super::*;
23+
use pretty_assertions::assert_eq;
24+
use std::io::Write;
25+
use tempfile::NamedTempFile;
26+
27+
#[test]
28+
fn test_get_jar_contents() {
29+
let content = b"This is some test content.";
30+
let mut temp_file = NamedTempFile::new().expect("Failed to create temp file");
31+
temp_file
32+
.write_all(content)
33+
.expect("Failed to write to temp file");
34+
let path = temp_file.path();
35+
36+
let read_content = get_jar_contents(path).expect("Failed to read jar contents");
37+
assert_eq!(read_content, content);
38+
}
39+
40+
#[test]
41+
fn test_get_jar_contents_non_existent_file() {
42+
let path = Path::new("non_existent_file.jar");
43+
let result = get_jar_contents(path);
44+
assert!(result.is_err());
45+
assert_eq!(result.unwrap_err().kind(), io::ErrorKind::NotFound);
46+
}
47+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//! Ported from https://github.com/meza/curseforge-fingerprint/blob/b15012c026c56ca89fad90f8cf9a8e140616e2c0/src/addon/fingerprint.cpp
2+
#![allow(clippy::let_and_return)]
3+
pub struct MurmurHash2;
4+
5+
const MULTIPLEX: u32 = 1540483477;
6+
7+
impl MurmurHash2 {
8+
pub fn hash(data: &[u8]) -> u32 {
9+
let n_length = MurmurHash2::normalise(data);
10+
let mut seed = 1_u32 ^ n_length;
11+
let mut num_1 = 0;
12+
let mut num_2 = 0;
13+
for c in data.iter().filter(|&c| !is_whitespace(*c)) {
14+
num_1 |= (*c as u32) << num_2;
15+
num_2 += 8;
16+
if num_2 == 32 {
17+
seed = seed.wrapping_mul(MULTIPLEX) ^ MurmurHash2::mix(num_1);
18+
num_1 = 0;
19+
num_2 = 0;
20+
}
21+
}
22+
if num_2 > 0 {
23+
seed = (seed ^ num_1).wrapping_mul(MULTIPLEX);
24+
}
25+
let hash = (seed ^ (seed >> 13)).wrapping_mul(MULTIPLEX);
26+
hash ^ (hash >> 15)
27+
}
28+
#[inline]
29+
const fn mix(num_1: u32) -> u32 {
30+
let num_3 = num_1.wrapping_mul(MULTIPLEX);
31+
let num_4 = (num_3 ^ (num_3 >> 24)).wrapping_mul(MULTIPLEX);
32+
num_4
33+
}
34+
fn normalise(data: &[u8]) -> u32 {
35+
let mut n_len = 0;
36+
data.iter()
37+
.filter(|&c| !is_whitespace(*c))
38+
.for_each(|_| n_len += 1);
39+
n_len
40+
}
41+
}
42+
43+
fn is_whitespace(c: u8) -> bool {
44+
c == b' ' || c == b'\t' || c == b'\r' || c == b'\n'
45+
}
46+
47+
#[cfg(test)]
48+
mod tests {
49+
use super::*;
50+
use pretty_assertions::assert_eq;
51+
#[test]
52+
fn test_murmur_hash() {
53+
let file = "Hello world";
54+
let hash = MurmurHash2::hash(file.as_bytes());
55+
assert_eq!(hash, 1423925525);
56+
}
57+
}

0 commit comments

Comments
 (0)