+233
-6
@@ -1,17 +1,244 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
use tauri::State;
|
use tauri::State;
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
use crate::{save_state, LauncherSettings, Mod, SharedState};
|
use crate::{launcher::LAUNCHER_DIR, save_state, LauncherSettings, Mod, SharedState};
|
||||||
|
|
||||||
#[tauri::command]
|
#[derive(Deserialize)]
|
||||||
pub fn get_installed_mods(state: State<SharedState>, version_id: String) -> Vec<Mod> {
|
struct ModrinthVersion {
|
||||||
Vec::new()
|
version_number: String,
|
||||||
|
game_versions: Vec<String>,
|
||||||
|
loaders: Vec<String>,
|
||||||
|
files: Vec<ModrinthFile>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ModrinthFile {
|
||||||
|
url: String,
|
||||||
|
filename: String,
|
||||||
|
primary: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn toggle_mod(state: State<SharedState>, mod_id: String) {}
|
pub async fn get_installed_mods(version_id: String) -> Result<Vec<Mod>, String> {
|
||||||
|
let mut mods = Vec::new();
|
||||||
|
|
||||||
|
let base_dir = LAUNCHER_DIR.data_dir().join(&version_id).join("mods");
|
||||||
|
|
||||||
|
let enabled_dir = base_dir.clone();
|
||||||
|
let disabled_dir = base_dir.join("disabled_mods");
|
||||||
|
|
||||||
|
let cache_dir = LAUNCHER_DIR.data_dir().join("cache");
|
||||||
|
|
||||||
|
// process a directory async
|
||||||
|
async fn process_dir(dir: PathBuf, enabled: bool, cache_dir: PathBuf) -> Vec<Mod> {
|
||||||
|
let mut mods = Vec::new();
|
||||||
|
|
||||||
|
if fs::metadata(&dir).await.is_err() {
|
||||||
|
return mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut entries = match fs::read_dir(&dir).await {
|
||||||
|
Ok(e) => e,
|
||||||
|
Err(_) => return mods,
|
||||||
|
};
|
||||||
|
|
||||||
|
while let Ok(Some(entry)) = entries.next_entry().await {
|
||||||
|
let path = entry.path();
|
||||||
|
|
||||||
|
// only .jar files
|
||||||
|
if path.extension().and_then(|s| s.to_str()) != Some("jar") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let file_name = match path.file_name().and_then(|s| s.to_str()) {
|
||||||
|
Some(name) => name.to_string(),
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let cache_path = cache_dir.join(format!("{}.json", file_name));
|
||||||
|
|
||||||
|
let mod_item = if fs::metadata(&cache_path).await.is_ok() {
|
||||||
|
match fs::read_to_string(&cache_path).await {
|
||||||
|
Ok(content) => match serde_json::from_str::<Mod>(&content) {
|
||||||
|
Ok(mut m) => {
|
||||||
|
m.enabled = enabled;
|
||||||
|
m
|
||||||
|
}
|
||||||
|
Err(_) => fallback_mod(&file_name, enabled),
|
||||||
|
},
|
||||||
|
Err(_) => fallback_mod(&file_name, enabled),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fallback_mod(&file_name, enabled)
|
||||||
|
};
|
||||||
|
|
||||||
|
mods.push(mod_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
mods
|
||||||
|
}
|
||||||
|
|
||||||
|
// run both dirs (sequential, but async-safe)
|
||||||
|
let mut enabled_mods = process_dir(enabled_dir, true, cache_dir.clone()).await;
|
||||||
|
let mut disabled_mods = process_dir(disabled_dir, false, cache_dir.clone()).await;
|
||||||
|
|
||||||
|
mods.append(&mut enabled_mods);
|
||||||
|
mods.append(&mut disabled_mods);
|
||||||
|
|
||||||
|
Ok(mods)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback
|
||||||
|
fn fallback_mod(file_name: &str, enabled: bool) -> Mod {
|
||||||
|
Mod {
|
||||||
|
id: file_name.to_string(),
|
||||||
|
name: file_name.to_string(),
|
||||||
|
version: String::new(),
|
||||||
|
author: String::new(),
|
||||||
|
description: String::new(),
|
||||||
|
enabled,
|
||||||
|
icon: None,
|
||||||
|
supported_versions: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn install_mod(state: State<SharedState>, mut mod_item: Mod) {}
|
pub async fn disable_mod(version: String, file_name: String) -> Result<(), String> {
|
||||||
|
// mods folder
|
||||||
|
let mods_dir: PathBuf = LAUNCHER_DIR.data_dir().join(&version).join("mods");
|
||||||
|
|
||||||
|
// disabled_mods folder
|
||||||
|
let disabled_dir = mods_dir.join("disabled_mods");
|
||||||
|
|
||||||
|
// create disabled_mods if it doesn't exist
|
||||||
|
fs::create_dir_all(&disabled_dir)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let source = mods_dir.join(&file_name);
|
||||||
|
let destination = disabled_dir.join(&file_name);
|
||||||
|
|
||||||
|
if !source.exists() {
|
||||||
|
return Err(format!("Mod file not found: {}", file_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
// move file
|
||||||
|
fs::rename(&source, &destination)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn enable_mod(version: String, file_name: String) -> Result<(), String> {
|
||||||
|
let mods_dir = LAUNCHER_DIR.data_dir().join(&version).join("mods");
|
||||||
|
|
||||||
|
let disabled_dir = mods_dir.join("disabled_mods");
|
||||||
|
|
||||||
|
let source = disabled_dir.join(&file_name);
|
||||||
|
let destination = mods_dir.join(&file_name);
|
||||||
|
|
||||||
|
if !source.exists() {
|
||||||
|
return Err(format!("Disabled mod not found: {}", file_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::rename(&source, &destination)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn install_mod(mut mod_meta: Mod, versions: Vec<String>) -> Result<(), String> {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
println!("Installing {} for {:?}", mod_meta.name, versions);
|
||||||
|
|
||||||
|
let cache_dir = LAUNCHER_DIR.data_dir().join("cache");
|
||||||
|
fs::create_dir_all(&cache_dir)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
for mc_version in versions {
|
||||||
|
let url = format!(
|
||||||
|
"https://api.modrinth.com/v2/project/{}/version",
|
||||||
|
mod_meta.id
|
||||||
|
);
|
||||||
|
|
||||||
|
let res = client.get(&url).send().await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let versions_list: Vec<ModrinthVersion> = res.json().await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let mut compatible: Vec<&ModrinthVersion> = versions_list
|
||||||
|
.iter()
|
||||||
|
.filter(|v| {
|
||||||
|
v.game_versions.contains(&mc_version) && v.loaders.iter().any(|l| l == "fabric")
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if compatible.is_empty() {
|
||||||
|
println!("No compatible version found for {}", mc_version);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
compatible.sort_by(|a, b| b.version_number.cmp(&a.version_number));
|
||||||
|
|
||||||
|
let selected = compatible[0];
|
||||||
|
|
||||||
|
let file = selected
|
||||||
|
.files
|
||||||
|
.iter()
|
||||||
|
.find(|f| f.primary)
|
||||||
|
.or_else(|| selected.files.first())
|
||||||
|
.ok_or("No files found for version")?;
|
||||||
|
|
||||||
|
let bytes = client
|
||||||
|
.get(&file.url)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
|
.bytes()
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let dir: PathBuf = LAUNCHER_DIR.data_dir().join(&mc_version).join("mods");
|
||||||
|
|
||||||
|
fs::create_dir_all(&dir).await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let file_path = dir.join(&file.filename);
|
||||||
|
|
||||||
|
// 1. Save JAR
|
||||||
|
fs::write(&file_path, &bytes)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
// 2. Prepare cache metadata
|
||||||
|
mod_meta.id = file.filename.clone(); // ✅ ID is filename
|
||||||
|
mod_meta.version = selected.version_number.clone();
|
||||||
|
mod_meta.enabled = true;
|
||||||
|
|
||||||
|
let cache_path = cache_dir.join(format!("{}.json", file.filename));
|
||||||
|
|
||||||
|
// 3. Save metadata JSON
|
||||||
|
let json = serde_json::to_string_pretty(&mod_meta).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
fs::write(&cache_path, json)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Installed {} for {} (cached as {})",
|
||||||
|
mod_meta.name, mc_version, file.filename
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn get_settings(state: State<SharedState>) -> LauncherSettings {
|
pub fn get_settings(state: State<SharedState>) -> LauncherSettings {
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ use lyceris::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use tauri::Emitter as _;
|
use tauri::{Emitter as _, State};
|
||||||
|
|
||||||
use crate::GameVersion;
|
use crate::{GameVersion, SharedState};
|
||||||
|
|
||||||
pub static LAUNCHER_DIR: Lazy<ProjectDirs> = Lazy::new(|| {
|
pub static LAUNCHER_DIR: Lazy<ProjectDirs> = Lazy::new(|| {
|
||||||
ProjectDirs::from("org", "SaturnLauncher", "SaturnLauncher")
|
ProjectDirs::from("org", "SaturnLauncher", "SaturnLauncher")
|
||||||
@@ -25,6 +25,7 @@ pub static LAUNCHER_DIR: Lazy<ProjectDirs> = Lazy::new(|| {
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn launch_game(
|
pub async fn launch_game(
|
||||||
app: tauri::AppHandle,
|
app: tauri::AppHandle,
|
||||||
|
state: State<'_, SharedState>,
|
||||||
version: GameVersion,
|
version: GameVersion,
|
||||||
account: MinecraftAccount,
|
account: MinecraftAccount,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
@@ -83,6 +84,9 @@ pub async fn launch_game(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.loader(Fabric(version.loader_version).into())
|
.loader(Fabric(version.loader_version).into())
|
||||||
|
.memory(lyceris::minecraft::config::Memory::Megabyte(
|
||||||
|
state.lock().unwrap().settings.max_memory as u64,
|
||||||
|
))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
println!("Starting installation");
|
println!("Starting installation");
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ pub struct GameVersion {
|
|||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct LauncherSettings {
|
pub struct LauncherSettings {
|
||||||
pub jvm_arguments: String,
|
|
||||||
pub max_memory: u32,
|
pub max_memory: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,11 +43,7 @@ pub struct AppState {
|
|||||||
impl Default for AppState {
|
impl Default for AppState {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
settings: LauncherSettings {
|
settings: LauncherSettings { max_memory: 4096 },
|
||||||
jvm_arguments: "-XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200"
|
|
||||||
.into(),
|
|
||||||
max_memory: 4096,
|
|
||||||
},
|
|
||||||
version: String::from("1.21.6"),
|
version: String::from("1.21.6"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ fn main() {
|
|||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
// Saturn Launcher Commands
|
// Saturn Launcher Commands
|
||||||
saturn_launcher_lib::commands::get_installed_mods,
|
saturn_launcher_lib::commands::get_installed_mods,
|
||||||
saturn_launcher_lib::commands::toggle_mod,
|
saturn_launcher_lib::commands::enable_mod,
|
||||||
|
saturn_launcher_lib::commands::disable_mod,
|
||||||
saturn_launcher_lib::commands::install_mod,
|
saturn_launcher_lib::commands::install_mod,
|
||||||
saturn_launcher_lib::commands::get_settings,
|
saturn_launcher_lib::commands::get_settings,
|
||||||
saturn_launcher_lib::commands::update_settings,
|
saturn_launcher_lib::commands::update_settings,
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ import {
|
|||||||
import {
|
import {
|
||||||
getInstalledMods,
|
getInstalledMods,
|
||||||
discoverMods,
|
discoverMods,
|
||||||
toggleMod,
|
enableMod,
|
||||||
installMod,
|
installMod,
|
||||||
|
disableMod,
|
||||||
} from "@/lib/saturn";
|
} from "@/lib/saturn";
|
||||||
import { clsx, type ClassValue } from "clsx";
|
import { clsx, type ClassValue } from "clsx";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
@@ -41,21 +42,32 @@ const ModsSection: React.FC<ModsSectionProps> = ({ version }) => {
|
|||||||
string[]
|
string[]
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
const loadMods = async () => {
|
||||||
const loadMods = async () => {
|
if (activeTab === "installed") {
|
||||||
if (activeTab === "installed") {
|
const m = await getInstalledMods(version.id);
|
||||||
const m = await getInstalledMods(version.id);
|
setMods(m);
|
||||||
setMods(m);
|
} else {
|
||||||
} else {
|
const m = await discoverMods(searchQuery);
|
||||||
const m = await discoverMods(searchQuery);
|
if (activeTab === "discover") {
|
||||||
|
// Fix: If discover mods takes too long and the user switches tabs it glitches
|
||||||
setMods(m);
|
setMods(m);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
loadMods();
|
};
|
||||||
}, [activeTab, version.id, searchQuery]);
|
|
||||||
|
|
||||||
const handleToggleMod = async (modId: string) => {
|
useEffect(() => {
|
||||||
await toggleMod(modId);
|
const id = setTimeout(loadMods, 800);
|
||||||
|
return () => clearTimeout(id);
|
||||||
|
}, [version.id, searchQuery]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMods([]);
|
||||||
|
loadMods();
|
||||||
|
}, [activeTab]);
|
||||||
|
|
||||||
|
const handleToggleMod = async (modId: string, enable: boolean) => {
|
||||||
|
if (enable) await enableMod(version.id, modId);
|
||||||
|
else await disableMod(version.id, modId);
|
||||||
const m = await getInstalledMods(version.id);
|
const m = await getInstalledMods(version.id);
|
||||||
setMods(m);
|
setMods(m);
|
||||||
};
|
};
|
||||||
@@ -143,7 +155,15 @@ const ModsSection: React.FC<ModsSectionProps> = ({ version }) => {
|
|||||||
className="panel p-4 flex items-center gap-4 hover:border-white/10 transition-colors"
|
className="panel p-4 flex items-center gap-4 hover:border-white/10 transition-colors"
|
||||||
>
|
>
|
||||||
<div className="w-12 h-12 rounded-lg bg-zinc-800 border border-saturn-border flex items-center justify-center shrink-0">
|
<div className="w-12 h-12 rounded-lg bg-zinc-800 border border-saturn-border flex items-center justify-center shrink-0">
|
||||||
<Package size={24} className="text-saturn-text-secondary" />
|
{mod.icon ? (
|
||||||
|
<img
|
||||||
|
src={mod.icon}
|
||||||
|
alt="Mod Icon"
|
||||||
|
className="w-full h-full rounded-lg"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Package size={24} className="text-saturn-text-secondary" />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
@@ -165,7 +185,7 @@ const ModsSection: React.FC<ModsSectionProps> = ({ version }) => {
|
|||||||
{activeTab === "installed" ? (
|
{activeTab === "installed" ? (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleToggleMod(mod.id)}
|
onClick={() => handleToggleMod(mod.id, !mod.enabled)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center gap-2 px-3 py-1.5 rounded-md text-xs font-medium transition-all",
|
"flex items-center gap-2 px-3 py-1.5 rounded-md text-xs font-medium transition-all",
|
||||||
mod.enabled
|
mod.enabled
|
||||||
|
|||||||
@@ -39,8 +39,6 @@ const PlaySection: React.FC<PlaySectionProps> = ({ version, account }) => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
console.log(isLaunching);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full relative">
|
<div className="h-full relative">
|
||||||
{/* Background Image */}
|
{/* Background Image */}
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import {
|
import { MemoryStick, Save, RefreshCcw, Info } from "lucide-react";
|
||||||
Cpu,
|
|
||||||
MemoryStick,
|
|
||||||
Save,
|
|
||||||
RefreshCcw,
|
|
||||||
Info,
|
|
||||||
AlertTriangle,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { getSettings, updateSettings } from "../lib/saturn";
|
import { getSettings, updateSettings } from "../lib/saturn";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { LauncherSettings } from "@/lib/types";
|
import { LauncherSettings } from "@/lib/types";
|
||||||
@@ -41,8 +34,6 @@ const SettingsSection: React.FC = () => {
|
|||||||
const handleReset = async () => {
|
const handleReset = async () => {
|
||||||
// In a real app, this would reset to defaults in the service
|
// In a real app, this would reset to defaults in the service
|
||||||
const defaultSettings: LauncherSettings = {
|
const defaultSettings: LauncherSettings = {
|
||||||
jvm_arguments:
|
|
||||||
"-XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200",
|
|
||||||
max_memory: 4096,
|
max_memory: 4096,
|
||||||
};
|
};
|
||||||
setSettings(defaultSettings);
|
setSettings(defaultSettings);
|
||||||
@@ -142,48 +133,6 @@ const SettingsSection: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* JVM Arguments */}
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 10 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ delay: 0.1 }}
|
|
||||||
className="panel p-6 space-y-6"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<div className="w-8 h-8 rounded-lg bg-saturn-accent/10 flex items-center justify-center text-saturn-accent">
|
|
||||||
<Cpu size={18} />
|
|
||||||
</div>
|
|
||||||
<h2 className="text-lg font-bold">Custom JVM Arguments</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
<p className="text-xs text-saturn-text-secondary leading-relaxed">
|
|
||||||
Advanced flags for the Java Virtual Machine. Saturn Client
|
|
||||||
includes optimized defaults, but you can override them here.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<textarea
|
|
||||||
value={settings.jvm_arguments}
|
|
||||||
onChange={(e) =>
|
|
||||||
setSettings({ ...settings, jvm_arguments: e.target.value })
|
|
||||||
}
|
|
||||||
className="w-full h-32 bg-saturn-bg border border-saturn-border rounded-lg p-4 text-sm font-mono text-saturn-text-secondary focus:outline-none focus:border-saturn-accent/50 focus:ring-1 focus:ring-saturn-accent/50 transition-all resize-none"
|
|
||||||
placeholder="-XX:+UseG1GC..."
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="p-3 bg-yellow-500/5 border border-yellow-500/10 rounded-lg flex gap-3">
|
|
||||||
<AlertTriangle
|
|
||||||
size={16}
|
|
||||||
className="text-yellow-500 shrink-0 mt-0.5"
|
|
||||||
/>
|
|
||||||
<p className="text-xs text-yellow-500/80 leading-relaxed">
|
|
||||||
Incorrect JVM arguments can prevent the game from starting. Only
|
|
||||||
modify if you know what you're doing.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
+38
-39
@@ -6,53 +6,52 @@ export async function getInstalledMods(versionId: string): Promise<Mod[]> {
|
|||||||
return await invoke("get_installed_mods", { versionId });
|
return await invoke("get_installed_mods", { versionId });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function toggleMod(modId: string): Promise<void> {
|
export async function enableMod(version: string, modId: string): Promise<void> {
|
||||||
await invoke("toggle_mod", { modId });
|
await invoke("enable_mod", { version, fileName: modId });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function disableMod(
|
||||||
|
version: string,
|
||||||
|
modId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
await invoke("disable_mod", { version, fileName: modId });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function discoverMods(query: string): Promise<Mod[]> {
|
export async function discoverMods(query: string): Promise<Mod[]> {
|
||||||
const allDiscoverable: Mod[] = [
|
const url = new URL("https://api.modrinth.com/v2/search");
|
||||||
{
|
|
||||||
id: "modmenu",
|
|
||||||
name: "Mod Menu",
|
|
||||||
version: "7.2.2",
|
|
||||||
author: "TerraformersMC",
|
|
||||||
description: "Adds a mod menu to view the list of installed mods.",
|
|
||||||
enabled: false,
|
|
||||||
supported_versions: ["1.20.1", "1.20.4", "1.21"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "starlight",
|
|
||||||
name: "Starlight",
|
|
||||||
version: "1.1.2",
|
|
||||||
author: "Spottedleaf",
|
|
||||||
description: "Rewrites the light engine to fix performance issues.",
|
|
||||||
enabled: false,
|
|
||||||
supported_versions: ["1.20.1"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "ferritecore",
|
|
||||||
name: "FerriteCore",
|
|
||||||
version: "6.0.1",
|
|
||||||
author: "malte0811",
|
|
||||||
description: "Memory usage optimizations.",
|
|
||||||
enabled: false,
|
|
||||||
supported_versions: ["1.20.1", "1.20.4", "1.21"],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!query) return allDiscoverable;
|
if (query) {
|
||||||
|
url.searchParams.set("query", query);
|
||||||
|
}
|
||||||
|
|
||||||
return allDiscoverable.filter((m) =>
|
// Optional but recommended filters
|
||||||
m.name.toLowerCase().includes(query.toLowerCase()),
|
url.searchParams.set("facets", JSON.stringify([["project_type:mod"]]));
|
||||||
|
url.searchParams.set("limit", "20");
|
||||||
|
|
||||||
|
const res = await fetch(url.toString());
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error("Failed to fetch mods from Modrinth");
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
return data.hits.map(
|
||||||
|
(mod: any): Mod => ({
|
||||||
|
id: mod.project_id,
|
||||||
|
name: mod.title,
|
||||||
|
version: "latest",
|
||||||
|
author: mod.author,
|
||||||
|
description: mod.description,
|
||||||
|
enabled: false,
|
||||||
|
icon: mod.icon_url,
|
||||||
|
supported_versions: mod.versions || [],
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function installMod(
|
export async function installMod(mod: Mod, versions: string[]): Promise<void> {
|
||||||
mod: Mod,
|
await invoke("install_mod", { modMeta: mod, versions });
|
||||||
versionsList: string[],
|
|
||||||
): Promise<void> {
|
|
||||||
await invoke("install_mod", { modItem: mod });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Settings ---
|
// --- Settings ---
|
||||||
|
|||||||
@@ -16,6 +16,5 @@ export interface GameVersion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface LauncherSettings {
|
export interface LauncherSettings {
|
||||||
jvm_arguments: string;
|
|
||||||
max_memory: number;
|
max_memory: number;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user