From cce48864a2338a11cf7b9585350a327f70b8376b Mon Sep 17 00:00:00 2001 From: bigfoot547 Date: Sun, 19 Jan 2025 21:07:40 -0600 Subject: update strsub --- src/launcher/assets.rs | 10 ++-- src/launcher/runner.rs | 47 ++++++++++++++++ src/launcher/strsub.rs | 142 +++++++++++++++++++++++++++++++------------------ 3 files changed, 145 insertions(+), 54 deletions(-) create mode 100644 src/launcher/runner.rs (limited to 'src/launcher') diff --git a/src/launcher/assets.rs b/src/launcher/assets.rs index 992af2a..271f79e 100644 --- a/src/launcher/assets.rs +++ b/src/launcher/assets.rs @@ -93,6 +93,10 @@ impl AssetRepository { home }) } + + pub fn get_home(&self) -> &Path { + self.home.as_path() + } fn get_index_path(&self, id: &str) -> Result { let mut indexes_path: PathBuf = [self.home.as_ref(), OsStr::new(INDEX_PATH)].iter().collect(); @@ -192,7 +196,7 @@ impl AssetRepository { format!("{}{:02x}/{}", super::constants::URL_RESOURCE_BASE, obj.hash.bytes()[0], obj.hash) } - fn get_object_path(&self, obj: &Asset) -> PathBuf { + pub fn get_object_path(&self, obj: &Asset) -> PathBuf { let hex_digest = obj.hash.to_string(); [self.home.as_ref(), OsStr::new(OBJECT_PATH), OsStr::new(&hex_digest[..2]), OsStr::new(&hex_digest)].iter().collect() } @@ -214,7 +218,7 @@ impl AssetRepository { error: e })?; - for object in index.objects.iter() { + for object in index.objects.values() { let path = self.get_object_path(object); Self::ensure_dir(path.parent().unwrap()).await.map_err(|error| AssetError::IO { error, what: "creating directory for object" })?; @@ -257,7 +261,7 @@ impl AssetRepository { fs::create_dir_all(&target_path).await.map_err(|e| AssetError::from(("creating virtual assets directory", e)))?; - stream::iter(index.objects.iter() + stream::iter(index.objects.values() .map(|object| { let obj_path = util::check_path(object.name.as_str()).map_err(AssetError::AssetNameError)?; let obj_path = target_path.join(obj_path); diff --git a/src/launcher/runner.rs b/src/launcher/runner.rs new file mode 100644 index 0000000..8d12197 --- /dev/null +++ b/src/launcher/runner.rs @@ -0,0 +1,47 @@ +use std::borrow::Cow; +use crate::launcher::Launch; +use crate::launcher::strsub::SubFunc; + +impl<'k, 'rep, 'l: 'rep> SubFunc<'k, 'rep> for &'rep Launch<'l> { + fn substitute(self, key: &'k str) -> Option> { + match key { + "assets_index_name" => self.asset_index_name.as_ref().map(|s| Cow::Borrowed(s.as_str())), + "assets_root" => Some(self.launcher.assets.get_home().to_string_lossy()), + "auth_access_token" => Some(Cow::Borrowed("-")), // TODO + "auth_player_name" => Some(Cow::Borrowed("Player")), // TODO + "auth_session" => Some(Cow::Borrowed("-")), // TODO + "auth_uuid" => Some(Cow::Borrowed("00000000-0000-0000-0000-000000000000")), // TODO + "auth_xuid" => Some(Cow::Borrowed("00000000-0000-0000-0000-000000000000")), // TODO + "classpath" => Some(Cow::Borrowed(self.classpath.as_str())), // TODO + "classpath_separator" => None, // FIXME + "game_assets" => self.virtual_assets_path.as_ref().map(|s| s.to_string_lossy()), + "game_directory" => Some(self.instance_home.to_string_lossy()), + "language" => Some(Cow::Borrowed("en-us")), // ??? + "launcher_name" => Some(Cow::Borrowed("ozone (olauncher 3)")), // TODO + "launcher_version" => Some(Cow::Borrowed("yeah")), // TODO + "library_directory" => Some(self.launcher.libraries.home.to_string_lossy()), + "natives_directory" => Some(self.natives_path.to_string_lossy()), + "primary_jar" => self.client_jar.as_ref().map(|p| p.to_string_lossy()), + "quickPlayMultiplayer" => None, // TODO + "quickPlayPath" => None, // TODO + "quickPlayRealms" => None, // TODO + "quickPlaySingleplayer" => None, // TODO + "resolution_height" => None, // TODO + "resolution_width" => None, // TODO + "user_properties" => Some(Cow::Borrowed("{}")), // TODO + "user_property_map" => Some(Cow::Borrowed("[]")), // TODO + "user_type" => Some(Cow::Borrowed("legacy")), // TODO + "version_name" => Some(Cow::Borrowed(&self.version_id.as_ref())), + "version_type" => self.version_type.as_ref().map(|s| Cow::Borrowed(s.to_str())), + _ => { + if let Some(asset_key) = key.strip_prefix("asset=") { + return self.asset_index.as_ref() + .map_or(None, |idx| idx.objects.get(asset_key)) + .map(|obj| Cow::Owned(self.launcher.assets.get_object_path(obj).to_string_lossy().into_owned())) + } + + None + } + } + } +} \ No newline at end of file diff --git a/src/launcher/strsub.rs b/src/launcher/strsub.rs index e01ef80..c89681e 100644 --- a/src/launcher/strsub.rs +++ b/src/launcher/strsub.rs @@ -8,39 +8,30 @@ const VAR_BEGIN: &str = "${"; const VAR_END: &str = "}"; const VAR_DEFAULT: &str = ":-"; -fn prev_char(slice: &str, idx: usize) -> Option<(usize, char)> { - slice[..idx].char_indices().rev().next() +pub trait SubFunc<'k, 'rep>: Copy { + fn substitute(self, key: &'k str) -> Option>; } -pub trait SubFunc<'rep>: Fn(&str) -> Option> { - fn substitute(&self, key: &str) -> Option>; -} - -impl<'rep, F> SubFunc<'rep> for F +impl<'k, 'rep, F> SubFunc<'k, 'rep> for F where - F: Fn(&str) -> Option> + F: Fn(&'k str) -> Option> + Copy { - fn substitute(&self, key: &str) -> Option> { + fn substitute(self, key: &'k str) -> Option> { self(key) } } -// basically the same thing as replace_string, but it creates the String itself and returns it. -pub fn replace_str<'rep>(input: &str, sub: impl SubFunc<'rep>) -> String { - let mut input = String::from(input); - replace_string(&mut input, sub); - input -} - -pub fn replace_thru<'rep>(mut input: String, sub: impl SubFunc<'rep>) -> String { - replace_string(&mut input, sub); - input -} +/* NOTE: the in-place implementation has been replaced for the following reasons: + * - it was annoying to get lifetimes to work, so you could only either pass a trait implementation + * or a closure + * - it was probably slower than doing it out-of-place anyway, since you keep having to copy the + * tail of the string for each replacement + */ // handles ${replacements} on this string IN-PLACE. Calls the "sub" function for each key it receives. // if "sub" returns None, it will use a default value or ignore the ${substitution}. // There are no "invalid inputs" and this function should never panic unless "sub" panics. -pub fn replace_string<'rep>(input: &mut String, sub: impl SubFunc<'rep>) { +/*pub fn replace_string(input: &mut String, sub: impl SubFunc) { let mut cursor = input.len(); while let Some(idx) = input[..cursor].rfind(VAR_BEGIN) { // note: for some reason, apache processes escapes BEFORE checking if it's even a valid @@ -76,6 +67,58 @@ pub fn replace_string<'rep>(input: &mut String, sub: impl SubFunc<'rep>) { cursor = idx; } +}*/ + +pub fn replace_string<'k, 'rep>(input: &'k str, sub: impl SubFunc<'k, 'rep>) -> Cow<'k, str> { + let mut ret: Option = None; + let mut cursor = 0usize; + + while let Some(idx) = input[cursor..].find(VAR_BEGIN) { + let idx = idx + cursor; // make idx an absolute index into 'input' + let spec_start = idx + VAR_BEGIN.len(); // the start of the "spec" (area inside {}) + + // first, check if this is escaped + if let Some((prev_idx, ESCAPE)) = input[..idx].char_indices().rev().next() { + let s = ret.get_or_insert_default(); + s.push_str(&input[cursor..prev_idx]); + + // advance past this so we don't match it again + s.push_str(&input[idx..spec_start]); + cursor = spec_start; + continue; + } + + // now, find the closing tag + let Some(spec_end) = input[spec_start..].find(VAR_END).map(|v| v + spec_start) else { + break; // reached the end of the string + }; + + let full_spec = &input[spec_start..spec_end]; + + // check for a default argument + let (name, def) = if let Some(defidx) = full_spec.find(VAR_DEFAULT) { + (&full_spec[..defidx], Some(&full_spec[(defidx + VAR_DEFAULT.len())..])) + } else { + (full_spec, None) + }; + + let after = spec_end + VAR_END.len(); + if let Some(subst) = sub.substitute(name).map_or_else(|| def.map(|d| Cow::Borrowed(d)), |v| Some(v)) { + let s = ret.get_or_insert_default(); + s.push_str(&input[cursor..idx]); + s.push_str(subst.as_ref()); + } else { + ret.get_or_insert_default().push_str(&input[cursor..after]); + } + + cursor = after; + } + + if let Some(ret) = ret.as_mut() { + ret.push_str(&input[cursor..]); + } + + ret.map_or(Cow::Borrowed(input), Cow::Owned) } #[cfg(test)] @@ -93,65 +136,62 @@ mod tests { #[test] fn test_standard_replace() { - assert_eq!(replace_str("this has ${exists} and more", replace_fun), "this has value123 and more"); - assert_eq!(replace_str("multiple ${exists} repl${exists}ace", replace_fun), "multiple value123 replvalue123ace"); - assert_eq!(replace_str("${exists}${exists}", replace_fun), "value123value123"); + assert_eq!(replace_string("this has ${exists} and more", replace_fun), "this has value123 and more"); + assert_eq!(replace_string("multiple ${exists} repl${exists}ace", replace_fun), "multiple value123 replvalue123ace"); + assert_eq!(replace_string("${exists}${exists}", replace_fun), "value123value123"); } #[test] fn test_empty_replace() { - assert_eq!(replace_str("this has ${empty} and more", replace_fun), "this has ${empty} and more"); - assert_eq!(replace_str("multiple ${empty} repl${empty}ace", replace_fun), "multiple ${empty} repl${empty}ace"); - assert_eq!(replace_str("${empty}${empty}", replace_fun), "${empty}${empty}"); + assert_eq!(replace_string("this has ${empty} and more", replace_fun), "this has ${empty} and more"); + assert_eq!(replace_string("multiple ${empty} repl${empty}ace", replace_fun), "multiple ${empty} repl${empty}ace"); + assert_eq!(replace_string("${empty}${empty}", replace_fun), "${empty}${empty}"); } #[test] fn test_homogenous_replace() { - assert_eq!(replace_str("some ${exists} and ${empty} ...", replace_fun), "some value123 and ${empty} ..."); - assert_eq!(replace_str("some ${empty} and ${exists} ...", replace_fun), "some ${empty} and value123 ..."); - assert_eq!(replace_str("${exists}${empty}", replace_fun), "value123${empty}"); - assert_eq!(replace_str("${empty}${exists}", replace_fun), "${empty}value123"); + assert_eq!(replace_string("some ${exists} and ${empty} ...", replace_fun), "some value123 and ${empty} ..."); + assert_eq!(replace_string("some ${empty} and ${exists} ...", replace_fun), "some ${empty} and value123 ..."); + assert_eq!(replace_string("${exists}${empty}", replace_fun), "value123${empty}"); + assert_eq!(replace_string("${empty}${exists}", replace_fun), "${empty}value123"); } #[test] fn test_default_replace() { - assert_eq!(replace_str("some ${exists:-def1} and ${empty:-def2} ...", replace_fun), "some value123 and def2 ..."); - assert_eq!(replace_str("some ${empty:-def1} and ${exists:-def2} ...", replace_fun), "some def1 and value123 ..."); - assert_eq!(replace_str("abc${empty:-}def", replace_fun), "abcdef"); - assert_eq!(replace_str("${empty:-}${empty:-}", replace_fun), ""); + assert_eq!(replace_string("some ${exists:-def1} and ${empty:-def2} ...", replace_fun), "some value123 and def2 ..."); + assert_eq!(replace_string("some ${empty:-def1} and ${exists:-def2} ...", replace_fun), "some def1 and value123 ..."); + assert_eq!(replace_string("abc${empty:-}def", replace_fun), "abcdef"); + assert_eq!(replace_string("${empty:-}${empty:-}", replace_fun), ""); } #[test] fn test_escape() { - assert_eq!(replace_str("an $${escaped} replacement (${exists})", replace_fun), "an ${escaped} replacement (value123)"); - assert_eq!(replace_str("${exists}$${escaped}${exists}", replace_fun), "value123${escaped}value123"); + assert_eq!(replace_string("an $${escaped} replacement (${exists})", replace_fun), "an ${escaped} replacement (value123)"); + assert_eq!(replace_string("${exists}$${escaped}${exists}", replace_fun), "value123${escaped}value123"); // make sure this weird behavior is preserved... (the original code seemed to show it) - assert_eq!(replace_str("some $${ else", replace_fun), "some ${ else"); + assert_eq!(replace_string("some $${ else", replace_fun), "some ${ else"); } #[test] fn test_weird() { - assert_eq!(replace_str("${exists}", replace_fun), "value123"); - assert_eq!(replace_str("$${empty}", replace_fun), "${empty}"); - assert_eq!(replace_str("${empty:-a}", replace_fun), "a"); - assert_eq!(replace_str("${empty:-}", replace_fun), ""); - - // there is no nested evaluation, but the algorithm does proceed through the string backwards - assert_eq!(replace_str("${exists:-${exists}}", replace_fun), "${exists:-value123}"); + assert_eq!(replace_string("${exists}", replace_fun), "value123"); + assert_eq!(replace_string("$${empty}", replace_fun), "${empty}"); + assert_eq!(replace_string("${empty:-a}", replace_fun), "a"); + assert_eq!(replace_string("${empty:-}", replace_fun), ""); } // these make sure it doesn't chop up multibyte characters illegally #[test] fn test_multibyte_surround() { - assert_eq!(replace_str("\u{1f354}$${}\u{1f354}", replace_fun), "\u{1f354}${}\u{1f354}"); - assert_eq!(replace_str("\u{1f354}${exists}\u{1f354}${empty:-}\u{1f354}", replace_fun), "\u{1f354}value123\u{1f354}\u{1f354}"); + assert_eq!(replace_string("\u{1f354}$${}\u{1f354}", replace_fun), "\u{1f354}${}\u{1f354}"); + assert_eq!(replace_string("\u{1f354}${exists}\u{1f354}${empty:-}\u{1f354}", replace_fun), "\u{1f354}value123\u{1f354}\u{1f354}"); } #[test] fn test_multibyte_replace() { - assert_eq!(replace_str("borger ${borger}", replace_fun), "borger \u{1f354}"); - assert_eq!(replace_str("${exists:-\u{1f354}}${empty:-\u{1f354}}", replace_fun), "value123\u{1f354}"); - assert_eq!(replace_str("${borger}$${}${borger}", replace_fun), "\u{1f354}${}\u{1f354}"); + assert_eq!(replace_string("borger ${borger}", replace_fun), "borger \u{1f354}"); + assert_eq!(replace_string("${exists:-\u{1f354}}${empty:-\u{1f354}}", replace_fun), "value123\u{1f354}"); + assert_eq!(replace_string("${borger}$${}${borger}", replace_fun), "\u{1f354}${}\u{1f354}"); } -} \ No newline at end of file +} -- cgit v1.2.3-70-g09d2