summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar bigfoot547 <[email protected]>2025-02-10 22:22:45 -0600
committerLibravatar bigfoot547 <[email protected]>2025-02-10 22:22:45 -0600
commita4853f8fdabad445f49968d063f736f0f461314e (patch)
tree941a49ef58996d4d4e83c65c179f11d81a27a99c
parentlast commit before rewriting settings system?? (diff)
basic cli functionality
-rw-r--r--Cargo.lock3
-rw-r--r--ozone-cli/Cargo.toml3
-rw-r--r--ozone-cli/src/cli.rs139
-rw-r--r--ozone-cli/src/main.rs130
-rw-r--r--ozone/src/auth.rs2
-rw-r--r--ozone/src/launcher.rs42
-rw-r--r--ozone/src/launcher/constants.rs3
-rw-r--r--ozone/src/launcher/settings.rs175
-rw-r--r--ozone/src/launcher/version.rs10
9 files changed, 272 insertions, 235 deletions
diff --git a/Cargo.lock b/Cargo.lock
index b818ed7..1ebf372 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3537,10 +3537,13 @@ version = "0.1.0"
dependencies = [
"clap",
"clap_complete",
+ "lazy_static",
"log",
"ozone",
+ "regex",
"simple_logger",
"tokio",
+ "uuid",
]
[[package]]
diff --git a/ozone-cli/Cargo.toml b/ozone-cli/Cargo.toml
index 7f1aad2..db6503b 100644
--- a/ozone-cli/Cargo.toml
+++ b/ozone-cli/Cargo.toml
@@ -10,3 +10,6 @@ simple_logger = { version = "5.0.0", features = ["colors"] }
log = "0.4.25"
clap = { version = "4.5.27", features = ["derive", "cargo"] }
clap_complete = { version = "4.5.44", features = ["unstable-dynamic"] }
+lazy_static = "1.5.0"
+regex = "1.11.1"
+uuid = { version = "1.12.1", features = ["v4"] }
diff --git a/ozone-cli/src/cli.rs b/ozone-cli/src/cli.rs
index 281a996..f60c4d2 100644
--- a/ozone-cli/src/cli.rs
+++ b/ozone-cli/src/cli.rs
@@ -1,56 +1,147 @@
use std::path::PathBuf;
use clap::{Args, Parser, Subcommand};
+use clap::error::ErrorKind;
+use ozone::launcher::{Instance, InstanceVersion, JavaRuntimeSetting, Resolution};
#[derive(Args, Debug)]
-pub struct ProfileSelectArgs {
- /// The name of the profile to select.
+pub struct InstanceSelectArgs {
+ /// The name of the instance to select.
#[arg(index = 1)]
- pub profile: String
+ pub instance: String
+}
+
+fn parse_resolution_argument(val: &str) -> Result<Option<Resolution>, clap::Error> {
+ match val.to_ascii_lowercase().as_str() {
+ "default" => Ok(None),
+ s => {
+ let Some(idx) = s.find('x') else {
+ return Err(clap::Error::raw(ErrorKind::InvalidValue, "malformed resolution argument (expected 'x')"));
+ };
+
+ let width: u32 = s[..idx].parse().map_err(|e| clap::Error::raw(ErrorKind::InvalidValue, e))?;
+ let height: u32 = s[idx+1..].parse().map_err(|e| clap::Error::raw(ErrorKind::InvalidValue, e))?;
+
+ if width == 0 || height == 0 {
+ return Err(clap::Error::raw(ErrorKind::InvalidValue, "malformed resolution argument (width and height must be positive)"));
+ }
+
+ Ok(Some(Resolution { width, height }))
+ }
+ }
+}
+
+#[derive(Args, Debug)]
+pub struct InstanceSettingsArgs {
+ /// Specify a version of the game which this profile should launch.
+ #[arg(long, short = 'v', conflicts_with_all = ["latest_release", "latest_snapshot"])]
+ pub version: Option<String>,
+
+ /// If set, this profile should always use the latest release (default).
+ ///
+ /// **Note**: Whenever a new version of Minecraft is released, this profile will
+ /// automatically switch to that version. This could cause unintended upgrades of settings and
+ /// worlds.
+ #[arg(long, conflicts_with_all = ["version", "latest_snapshot"])]
+ pub latest_release: bool,
+
+ /// If set, this profile should always use the latest snapshot.
+ ///
+ /// **Note**: Whenever a new version of Minecraft is released, this profile will
+ /// automatically switch to that version. This could cause unintended upgrades of settings and
+ /// worlds.
+ #[arg(long, short = 's', conflicts_with_all = ["version", "latest_release"])]
+ pub latest_snapshot: bool,
+
+ /// Specify the path to the java runtime that this profile should use to run the game.
+ ///
+ /// By default, the launcher will attempt to download a compatible java runtime before running the game.
+ #[arg(long, short = 'j', conflicts_with_all = ["jre_component", "jre_default"])]
+ pub jre_path: Option<PathBuf>,
+
+ /// Specify the java runtime component which this profile should download and use when launching
+ /// the game. If you don't understand what this means, you probably don't need this argument.
+ #[arg(long, conflicts_with_all = ["jre_path", "jre_default"])]
+ pub jre_component: Option<String>,
+
+ /// If set, configures this profile to automatically determine the java runtime to download and
+ /// use when launching the game. This is the default behavior.
+ #[arg(long, conflicts_with_all = ["jre_path", "jre_component"])]
+ pub jre_default: bool,
+
+ /// Specify the resolution of the Minecraft game window. Note that this argument is a suggestion
+ /// to the game process, and could be ignored or unsupported on some versions.
+ ///
+ /// Pass the resolution as _width_x_height_ (like `800x600').
+ ///
+ /// Use the special value _default_ to reset to the default value.
+ #[arg(long, short = 'r', value_parser = parse_resolution_argument)]
+ pub resolution: Option<Option<Resolution>>,
+}
+
+impl InstanceSettingsArgs {
+ pub fn apply_to(&self, inst: &mut Instance) {
+ if let Some(ref ver) = self.version {
+ inst.game_version = InstanceVersion::Specific(ver.clone());
+ } else if self.latest_release {
+ inst.game_version = InstanceVersion::LatestRelease;
+ } else if self.latest_snapshot {
+ inst.game_version = InstanceVersion::LatestSnapshot;
+ }
+
+ if let Some(ref path) = self.jre_path {
+ inst.java_runtime = Some(JavaRuntimeSetting::Path(path.clone()));
+ } else if let Some(ref comp) = self.jre_component {
+ inst.java_runtime = Some(JavaRuntimeSetting::Component(comp.clone()));
+ } else if self.jre_default {
+ inst.java_runtime = None;
+ }
+
+ if let Some(res) = self.resolution {
+ inst.resolution = res;
+ }
+ }
}
#[derive(Args, Debug)]
-pub struct ProfileCreateArgs {
- /// The name of the new profile.
+pub struct InstanceCreateArgs {
+ /// The name of the new instance. Must not be an empty string.
#[arg(index = 1)]
pub name: String,
- /// Clone profile information from an existing profile.
+ /// If set, clones settings from the selected instance.
#[arg(long, short = 'c')]
- pub clone: Option<String>,
+ pub clone: bool,
- /// The Minecraft version to be launched by this profile. Will use the latest release by default.
- #[arg(long, short = 'v')]
- pub version: Option<String>,
+ /// If set, don't select the newly created instance.
+ #[arg(long)]
+ pub no_select: bool,
- /// The instance in which this profile will launch the game. By default, will create a new instance
- /// with the same name as this profile.
- #[arg(long, short = 'i')]
- pub instance: Option<String>
+ #[command(flatten)]
+ pub settings: InstanceSettingsArgs
}
#[derive(Subcommand, Debug)]
-pub enum ProfileCommand {
- Select(ProfileSelectArgs),
- Create(ProfileCreateArgs),
+pub enum InstanceCommand {
+ Select(InstanceSelectArgs),
+ Create(InstanceCreateArgs),
List
}
#[derive(Args, Debug)]
-pub struct ProfileArgs {
+pub struct InstanceArgs {
#[command(subcommand)]
- subcmd: Option<ProfileCommand>
+ subcmd: Option<InstanceCommand>
}
-impl ProfileArgs {
- pub fn command(&self) -> &ProfileCommand {
- self.subcmd.as_ref().unwrap_or(&ProfileCommand::List)
+impl InstanceArgs {
+ pub fn command(&self) -> &InstanceCommand {
+ self.subcmd.as_ref().unwrap_or(&InstanceCommand::List)
}
}
#[derive(Subcommand, Debug)]
pub enum RootCommand {
- Profile(ProfileArgs),
- Instance,
+ Instance(InstanceArgs),
Launch
}
diff --git a/ozone-cli/src/main.rs b/ozone-cli/src/main.rs
index e0cbada..dc4ca4c 100644
--- a/ozone-cli/src/main.rs
+++ b/ozone-cli/src/main.rs
@@ -4,9 +4,10 @@ use std::error::Error;
use std::process::ExitCode;
use log::{error, info, trace, LevelFilter};
use clap::Parser;
-use ozone::launcher::{Instance, Launcher, Profile, Settings};
+use ozone::launcher::{Instance, Launcher, Settings};
use ozone::launcher::version::{VersionList, VersionResult};
-use crate::cli::{Cli, ProfileCommand, RootCommand};
+use uuid::Uuid;
+use crate::cli::{Cli, InstanceCommand, RootCommand};
async fn main_inner(cli: Cli) -> Result<ExitCode, Box<dyn Error>> {
let Some(home) = cli.home.or_else(Launcher::sensible_home) else {
@@ -18,83 +19,116 @@ async fn main_inner(cli: Cli) -> Result<ExitCode, Box<dyn Error>> {
let mut settings = Settings::load(home.join("ozone.json")).await?;
match &cli.subcmd {
- RootCommand::Profile(p) => match p.command() {
- ProfileCommand::List => {
+ RootCommand::Instance(p) => match p.command() {
+ InstanceCommand::List => {
let mut first = true;
- if settings.get_profiles().is_empty() {
- eprintln!("There are no profiles. Create one with `profile create <name>'.");
+ if settings.instances.is_empty() {
+ eprintln!("There are no instances. Create one with `profile create <name>'.");
return Ok(ExitCode::SUCCESS);
}
- for (name, profile) in settings.get_profiles().iter() {
+ for (cur_id, instance) in settings.instances.iter() {
if !first {
println!();
}
first = false;
- let sel = if settings.get_selected_profile_name().is_some_and(|n| n == name) { " (selected)" } else { "" };
- let exists = if settings.get_instances().contains_key(profile.get_instance_name()) { "" } else { " (missing!)" };
+ let cur_id = *cur_id;
+ let sel = if settings.selected_instance.is_some_and(|id| id == cur_id) { " (selected)" } else { "" };
- println!("Profile {name}:{sel}");
- println!(" Version: {}", profile.game_version);
- println!(" Instance: {}{}", profile.get_instance_name(), exists);
+ println!("Instance `{}'{sel}:", instance.name);
+ println!(" Id: {}", cur_id);
+ println!(" Version: {}", instance.game_version);
+ println!(" Location: {}", home.join(Settings::get_instance_path(cur_id)).display());
}
},
- ProfileCommand::Create(args) => {
- if settings.profiles.contains_key(&args.name) {
- eprintln!("A profile with that name already exists.");
+ InstanceCommand::Create(args) => {
+ if args.name.is_empty() {
+ eprintln!("The instance must not have an empty name.");
return Ok(ExitCode::FAILURE);
}
- if let Err(e) = Profile::check_name(&args.name) {
- eprintln!("The profile name is invalid: {e}");
- return Ok(ExitCode::FAILURE);
- }
-
- let mut profile = if let Some(ref src) = args.clone {
- if let Some(profile) = settings.get_profiles().get(src) {
- profile.clone()
+ let mut inst = if args.clone {
+ if let Some(selected_inst) = settings.selected_instance.and_then(|i| settings.instances.get(&i)) {
+ let mut inst = selected_inst.clone();
+ inst.name.replace_range(.., &args.name);
+ inst
} else {
- eprintln!("Unknown profile `{src}'.");
+ eprintln!("You do not have an instance selected.");
return Ok(ExitCode::FAILURE);
}
} else {
- let inst_name = args.instance.as_ref().unwrap_or(&args.name);
- if let Err(e) = Instance::check_name(inst_name) {
- eprintln!("The profile name is invalid for an instance: {e}");
- eprintln!("Please specify an instance name manually with --instance.");
+ Instance::new(&args.name)
+ };
+
+ if let Some(ref ver_name) = args.settings.version {
+ // FIXME: don't hardcode "versions" path
+ let versions = VersionList::new(home.join("versions"), !cli.offline).await?;
+ if matches!(versions.get_version_lazy(ver_name), VersionResult::None) {
+ eprintln!("The version `{}' could not be found.", ver_name);
return Ok(ExitCode::FAILURE);
}
+ }
+
+ args.settings.apply_to(&mut inst);
+
+ let new_id = Uuid::new_v4();
+ settings.instances.insert(new_id, inst);
+
+ if !args.no_select {
+ settings.selected_instance = Some(new_id);
+ }
- Profile::new(inst_name)
- };
-
- // creating a new profile from scratch
- todo!()
+ settings.save().await?;
},
- ProfileCommand::Select(args) => {
- let ver = VersionList::new(home.join("versions"), !cli.offline).await?;
-
- match ver.get_version_lazy(&args.profile) {
- VersionResult::None => {
- println!("Unknown version");
- },
- VersionResult::Remote(v) => {
- println!("Remote version: {v:?}");
- },
- VersionResult::Complete(v) => {
- println!("Complete version: {v:?}");
+ InstanceCommand::Select(args) => {
+ if let Ok(uuid) = args.instance.parse::<Uuid>() {
+ if settings.instances.get(&uuid).is_none() {
+ eprintln!("No instances were found by that UUID.");
+ return Ok(ExitCode::FAILURE);
}
+
+ settings.selected_instance = Some(uuid);
+ settings.save().await?;
+
+ return Ok(ExitCode::SUCCESS);
}
+
+ let search_norm = args.instance.to_lowercase();
+
+ let found: Vec<_> = settings.instances.iter()
+ .filter(|(_, inst)| {
+ // FIXME: find a better way of doing this matching
+ inst.name.to_lowercase().starts_with(&search_norm)
+ }).collect();
+
+ if found.is_empty() {
+ eprintln!("No instances were found.");
+ return Ok(ExitCode::FAILURE);
+ }
+
+ if found.len() > 1 {
+ eprintln!("Ambiguous argument. Found {} instances:", found.len());
+ for (id, inst) in found {
+ eprintln!("- {} ({id})", inst.name);
+ }
+
+ return Ok(ExitCode::FAILURE);
+ }
+
+ let (found_id, found_inst) = found.first().unwrap();
+ println!("Selected instance {} ({found_id}).", found_inst.name);
+
+ settings.selected_instance = Some(**found_id);
+ settings.save().await?;
}
- _ => todo!()
},
RootCommand::Launch => {
settings.save().await?;
- let launcher = Launcher::new(&home, !cli.offline).await?;
+/* let launcher = Launcher::new(&home, !cli.offline).await?;
let profile = settings.get_profiles().get("default").unwrap();
let launch = launcher.prepare_launch(profile, settings.get_instances().get(profile.get_instance_name()).unwrap(), settings.get_client_id()).await.map_err(|e| {
@@ -105,7 +139,7 @@ async fn main_inner(cli: Cli) -> Result<ExitCode, Box<dyn Error>> {
dbg!(&launch);
info!("ok");
- ozone::launcher::run_the_game(&launch)?;
+ ozone::launcher::run_the_game(&launch)?;*/
}
_ => todo!()
}
diff --git a/ozone/src/auth.rs b/ozone/src/auth.rs
index 2387ada..9e8f6d8 100644
--- a/ozone/src/auth.rs
+++ b/ozone/src/auth.rs
@@ -12,7 +12,7 @@ use oauth2::{AccessToken, DeviceAuthorizationUrl, DeviceCodeErrorResponse, Endpo
use oauth2::basic::{BasicErrorResponse, BasicErrorResponseType, BasicRevocationErrorResponse, BasicTokenIntrospectionResponse, BasicTokenResponse};
use reqwest::{IntoUrl, Method, RequestBuilder};
pub use types::*;
-use crate::auth::msa::{XSTS_RP_MINECRAFT_SERVICES, XSTS_RP_XBOX_LIVE};
+use self::msa::{XSTS_RP_MINECRAFT_SERVICES, XSTS_RP_XBOX_LIVE};
use crate::util;
#[derive(Debug)]
diff --git a/ozone/src/launcher.rs b/ozone/src/launcher.rs
index 5cda9d7..a2902da 100644
--- a/ozone/src/launcher.rs
+++ b/ozone/src/launcher.rs
@@ -225,14 +225,14 @@ pub struct Launch {
jni_launch: bool
}
-struct ProfileFeatureMatcher<'prof> {
- profile: &'prof Profile
+struct InstanceFeatureMatcher<'prof> {
+ instance: &'prof Instance
}
-impl FeatureMatcher for ProfileFeatureMatcher<'_> {
+impl FeatureMatcher for InstanceFeatureMatcher<'_> {
fn matches(&self, feature: &str) -> bool {
match feature {
- "has_custom_resolution" => self.profile.get_resolution().is_some(),
+ "has_custom_resolution" => self.instance.resolution.is_some(),
_ => false
}
}
@@ -328,19 +328,16 @@ impl Launcher {
* - launch game using JNI
* - auth
*/
- pub async fn prepare_launch(&self, profile: &Profile, instance: &Instance, client_id: Uuid) -> Result<Launch, LaunchError> {
+ pub async fn prepare_launch(&self, instance: &Instance, inst_home: impl AsRef<Path>, client_id: Uuid) -> Result<Launch, LaunchError> {
let start = Instant::now();
- let feature_matcher = ProfileFeatureMatcher { profile };
- let version_id = &profile.game_version;
+ let feature_matcher = InstanceFeatureMatcher { instance };
+ let version_id = &instance.game_version;
- let version_id = self.versions.get_profile_version_id(version_id);
+ let version_id = self.versions.get_instance_version_id(version_id);
info!("Preparing launch for \"{}\"...", version_id);
- let inst_home = instance.get_path(&self.home).await.map_err(|e| LaunchError::IO {
- what: "resolving instance directory",
- error: e
- })?;
+ let inst_home = self.home.join(&inst_home);
fs::create_dir_all(inst_home.as_path()).await.map_err(|e| LaunchError::IO {
what: "creating instance directory",
@@ -487,11 +484,11 @@ impl Launcher {
info!("Resolving java runtime environment path");
let runtime_path;
- if let Some(JavaRuntimeSetting::Path(ref profile_jre)) = profile.get_java_runtime() {
- runtime_path = fs::canonicalize(profile_jre).await
+ if let Some(JavaRuntimeSetting::Path(ref jre_path)) = instance.java_runtime {
+ runtime_path = fs::canonicalize(jre_path).await
.map_err(|e| LaunchError::ResolveJavaRuntime {what: "resolving jre path", error: e})?;
} else {
- let Some(component) = profile.get_java_runtime().and_then(|r| r.get_component())
+ let Some(component) = instance.java_runtime.as_ref().and_then(|r| r.get_component())
.or_else(|| ver.java_version.as_ref().map(|ver| ver.component.as_str())) else {
warn!("Version {} does not specify java version information. You must select a runtime manually.", ver.id);
@@ -502,7 +499,7 @@ impl Launcher {
runtime_path = self.java_runtimes.ensure_jre(component, runtime).await.map_err(LaunchError::JavaRuntimeRepo)?;
}
- let Some(runtime_exe_path) = runner::find_java(runtime_path.as_path(), profile.is_jni_launch()).await
+ let Some(runtime_exe_path) = runner::find_java(runtime_path.as_path(), instance.jni_launch).await
.map_err(|e| LaunchError::ResolveJavaRuntime {what: "finding java executable", error: e})? else {
return Err(LaunchError::MissingJavaRuntime);
};
@@ -526,7 +523,7 @@ impl Launcher {
version_type: ver.version_type.clone(),
asset_index: asset_idx,
- resolution: profile.get_resolution()
+ resolution: instance.resolution
};
let Some(ref main_class) = ver.main_class else {
@@ -534,9 +531,12 @@ impl Launcher {
};
// yuck
- let jvm_args = profile.iter_arguments().map(OsString::from)
- .chain(runner::build_arguments(&info, ver.as_ref(), ArgumentType::Jvm).drain(..))
- .chain(log_arg.iter().map(OsString::from)).collect();
+ let mut jvm_args: Vec<_> = instance.jvm_arguments.as_ref()
+ .map(|a| a.iter().map(OsString::from).collect())
+ .unwrap_or_else(|| iter_default_arguments().map(OsString::from).collect());
+ jvm_args.extend(runner::build_arguments(&info, ver.as_ref(), ArgumentType::Jvm).into_iter());
+ jvm_args.extend(log_arg.iter().map(OsString::from));
+
let game_args = runner::build_arguments(&info, ver.as_ref(), ArgumentType::Game);
let diff = Instant::now().duration_since(start);
@@ -548,7 +548,7 @@ impl Launcher {
main_class: main_class.clone(),
instance_path: inst_home,
runtime_path: runtime_exe_path,
- jni_launch: profile.is_jni_launch()
+ jni_launch: instance.jni_launch
})
}
}
diff --git a/ozone/src/launcher/constants.rs b/ozone/src/launcher/constants.rs
index 4506ab5..78e53f5 100644
--- a/ozone/src/launcher/constants.rs
+++ b/ozone/src/launcher/constants.rs
@@ -7,8 +7,7 @@ pub const URL_JRE_MANIFEST: &str = "https://piston-meta.mojang.com/v1/products/j
pub const NATIVES_PREFIX: &str = "natives-";
-pub const DEF_INSTANCE_NAME: &str = "default";
-pub const DEF_PROFILE_NAME: &str = "default";
+pub const DEF_INSTANCE_NAME: &str = "Default";
// https://github.com/unmojang/FjordLauncher/pull/14/files
// https://login.live.com/oauth20_authorize.srf?client_id=00000000402b5328&redirect_uri=ms-xal-00000000402b5328://auth&response_type=token&display=touch&scope=service::user.auth.xboxlive.com::MBI_SSL%20offline_access&prompt=select_account
diff --git a/ozone/src/launcher/settings.rs b/ozone/src/launcher/settings.rs
index 62d72c2..d0057bd 100644
--- a/ozone/src/launcher/settings.rs
+++ b/ozone/src/launcher/settings.rs
@@ -4,26 +4,17 @@ use std::fmt::{Display, Formatter};
use std::io::ErrorKind;
use std::ops::{Deref, DerefMut};
use std::path::{Path, PathBuf};
-use lazy_static::lazy_static;
-use regex::Regex;
use serde::{Deserialize, Serialize};
use tokio::{fs, io};
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
use uuid::Uuid;
-use super::constants;
-
-lazy_static! {
- static ref VALID_IDENTIFIER: Regex = Regex::new("^[a-z0-9_.\\-]{1,128}$").expect("hardcoded regex");
-}
+use super::constants::DEF_INSTANCE_NAME;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SettingsData {
- pub profiles: HashMap<String, Profile>,
- pub instances: HashMap<String, Instance>,
-
- selected_profile: Option<String>,
- selected_instance: Option<String>,
+ pub instances: HashMap<Uuid, Instance>,
+ pub selected_instance: Option<Uuid>,
#[serde(default = "uuid::Uuid::new_v4")]
pub client_id: Uuid
@@ -31,24 +22,12 @@ pub struct SettingsData {
impl SettingsData {
fn check_consistent(&self) -> Result<(), String> {
- if let Some((name, profile)) = self.profiles.iter().find(|(_, profile)| !self.instances.contains_key(&profile.instance)) {
- return Err(format!("profile {} refers to instance {}, which does not exist.", name, &profile.instance));
- }
-
- if self.selected_profile.as_ref().is_some_and(|p| !self.profiles.contains_key(p)) {
- return Err(format!("selected profile {} does not exist", self.selected_profile.as_ref().unwrap()));
- }
-
- if self.selected_instance.as_ref().is_some_and(|p| !self.instances.contains_key(p)) {
- return Err(format!("selected instance {} does not exist", self.selected_instance.as_ref().unwrap()));
- }
-
- if let Some(key) = self.profiles.keys().find(|k| Profile::check_name(k).is_err()) {
- return Err(format!("profile {key} has an invalid name"));
+ if self.selected_instance.as_ref().is_some_and(|i| !self.instances.contains_key(i)) {
+ return Err(format!("selected instance {} does not exist", self.selected_instance.unwrap()));
}
- if let Some(key) = self.instances.keys().find(|k| Instance::check_name(k).is_err()) {
- return Err(format!("instance {key} has an invalid name"));
+ if let Some((key, _)) = self.instances.iter().find(|(_, i)| i.name.is_empty()) {
+ return Err(format!("instance {} has empty name", key));
}
Ok(())
@@ -89,11 +68,11 @@ impl Error for SettingsError {
impl Default for SettingsData {
fn default() -> Self {
+ let def_instance = Uuid::new_v4();
+
SettingsData {
- instances: [(String::from(constants::DEF_INSTANCE_NAME), PathBuf::from(constants::DEF_INSTANCE_NAME).into())].into_iter().collect(),
- profiles: [(String::from(constants::DEF_PROFILE_NAME), Profile::new(constants::DEF_INSTANCE_NAME))].into_iter().collect(),
- selected_profile: Some(String::from(constants::DEF_PROFILE_NAME)),
- selected_instance: Some(String::from(constants::DEF_INSTANCE_NAME)),
+ instances: [(def_instance, Instance::new(DEF_INSTANCE_NAME))].into_iter().collect(),
+ selected_instance: Some(def_instance),
client_id: Uuid::new_v4()
}
}
@@ -162,68 +141,37 @@ impl Settings {
self.save_to(self.path.as_ref().expect("save() called on Settings instance not loaded from file")).await
}
-
- pub fn get_instances(&self) -> &HashMap<String, Instance> {
- &self.inner.instances
- }
-
- pub fn get_profiles(&self) -> &HashMap<String, Profile> {
- &self.inner.profiles
- }
-
- pub fn get_client_id(&self) -> Uuid {
- self.inner.client_id
- }
-
- pub fn get_selected_instance_name(&self) -> Option<&str> {
- self.inner.selected_instance.as_deref()
- }
-
- pub fn get_selected_instance(&self) -> Option<&Instance> {
- self.inner.selected_instance.as_ref().and_then(|s| self.inner.instances.get(s))
- }
-
- pub fn get_selected_profile_name(&self) -> Option<&str> {
- self.inner.selected_profile.as_deref()
- }
-
- pub fn get_selected_profile(&self) -> Option<&Profile> {
- self.inner.selected_profile.as_ref().and_then(|s| self.inner.profiles.get(s))
- }
-
- fn check_identifier(ident: &str) -> Result<(), &'static str> {
- if VALID_IDENTIFIER.is_match(ident) {
- Ok(())
- } else {
- Err("must contain 1-128 lowercase characters, digits, and the following symbols: _ . -")
- }
+
+ pub fn get_instance_path(uuid: Uuid) -> PathBuf {
+ ["instances", uuid.to_string().as_str()].into_iter().collect()
}
}
#[derive(Deserialize, Serialize, Debug, Clone)]
-pub struct Instance {
- pub path: PathBuf // relative to launcher home (or absolute)
-}
-
-#[derive(Deserialize, Serialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
-pub enum ProfileVersion {
+pub enum InstanceVersion {
LatestSnapshot,
LatestRelease,
#[serde(untagged)]
Specific(String)
}
-impl Display for ProfileVersion {
+impl Display for InstanceVersion {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
- ProfileVersion::LatestSnapshot => f.write_str("Latest Snapshot"),
- ProfileVersion::LatestRelease => f.write_str("Latest Release"),
- ProfileVersion::Specific(version) => f.write_str(version.as_str())
+ InstanceVersion::LatestSnapshot => f.write_str("Latest Snapshot"),
+ InstanceVersion::LatestRelease => f.write_str("Latest Release"),
+ InstanceVersion::Specific(version) => f.write_str(version.as_str())
}
}
}
+impl Default for InstanceVersion {
+ fn default() -> Self {
+ Self::LatestRelease
+ }
+}
+
#[derive(Deserialize, Serialize, Debug, Clone, Copy)]
pub struct Resolution {
pub width: u32,
@@ -253,13 +201,14 @@ impl JavaRuntimeSetting {
}
#[derive(Deserialize, Serialize, Debug, Clone)]
-pub struct Profile {
- pub game_version: ProfileVersion,
+pub struct Instance {
+ pub name: String,
+
+ pub game_version: InstanceVersion,
pub java_runtime: Option<JavaRuntimeSetting>,
- pub instance: String,
- #[serde(default, skip_serializing_if = "Vec::is_empty")]
- pub jvm_arguments: Vec<String>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub jvm_arguments: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub jni_launch: bool,
@@ -267,26 +216,17 @@ pub struct Profile {
pub resolution: Option<Resolution>
}
-impl<P: AsRef<Path>> From<P> for Instance {
- fn from(path: P) -> Self {
- Self { path: path.as_ref().into() }
- }
-}
-
impl Instance {
- pub async fn get_path(&self, home: impl AsRef<Path>) -> Result<PathBuf, io::Error> {
- let path = self.path.as_path();
-
- if path.is_relative() {
- Ok([home.as_ref(), Path::new("instances"), path].iter().collect::<PathBuf>())
- } else {
- fs::canonicalize(path).await
+ pub fn new(name: &str) -> Self {
+ Self {
+ name: name.to_owned(),
+ game_version: InstanceVersion::LatestRelease,
+ java_runtime: None,
+ jvm_arguments: None,
+ jni_launch: false,
+ resolution: None
}
}
-
- pub fn check_name(name: &str) -> Result<(), &'static str> {
- Settings::check_identifier(name)
- }
}
const DEF_JVM_ARGUMENTS: [&str; 7] = [
@@ -299,39 +239,6 @@ const DEF_JVM_ARGUMENTS: [&str; 7] = [
"-XX:G1HeapRegionSize=32M"
];
-impl Profile {
- pub fn new(instance_name: &str) -> Self {
- Self {
- game_version: ProfileVersion::LatestRelease,
- java_runtime: None,
- instance: instance_name.into(),
- jvm_arguments: DEF_JVM_ARGUMENTS.iter().map(|s| String::from(*s)).collect(),
- jni_launch: false,
- resolution: None
- }
- }
-
- pub fn get_instance_name(&self) -> &str {
- &self.instance
- }
-
- pub fn iter_arguments(&self) -> impl Iterator<Item = &String> {
- self.jvm_arguments.iter()
- }
-
- pub fn get_resolution(&self) -> Option<Resolution> {
- self.resolution
- }
-
- pub fn get_java_runtime(&self) -> Option<&JavaRuntimeSetting> {
- self.java_runtime.as_ref()
- }
-
- pub fn is_jni_launch(&self) -> bool {
- self.jni_launch
- }
-
- pub fn check_name(name: &str) -> Result<(), &'static str> {
- Settings::check_identifier(name)
- }
+pub fn iter_default_arguments() -> impl Iterator<Item = &'static str> {
+ DEF_JVM_ARGUMENTS.into_iter()
}
diff --git a/ozone/src/launcher/version.rs b/ozone/src/launcher/version.rs
index a5369b9..83e6100 100644
--- a/ozone/src/launcher/version.rs
+++ b/ozone/src/launcher/version.rs
@@ -8,7 +8,7 @@ use log::{debug, info, warn};
use serde::{Deserialize, Serialize};
use sha1_smol::{Digest, Sha1};
use tokio::{fs, io};
-use super::settings::ProfileVersion;
+use super::settings::InstanceVersion;
use crate::util;
use crate::version::{*, manifest::*};
@@ -428,11 +428,11 @@ impl VersionList {
.unwrap_or(VersionResult::None)
}
- pub fn get_profile_version_id<'v>(&self, ver: &'v ProfileVersion) -> Cow<'v, str> {
+ pub fn get_instance_version_id<'v>(&self, ver: &'v InstanceVersion) -> Cow<'v, str> {
match ver {
- ProfileVersion::LatestRelease => self.remote.latest.release.clone().into(),
- ProfileVersion::LatestSnapshot => self.remote.latest.snapshot.clone().into(),
- ProfileVersion::Specific(ver) => Cow::Borrowed(ver)
+ InstanceVersion::LatestRelease => self.remote.latest.release.clone().into(),
+ InstanceVersion::LatestSnapshot => self.remote.latest.snapshot.clone().into(),
+ InstanceVersion::Specific(ver) => Cow::Borrowed(ver)
}
}