From d9fc1dd160dbd098a459ad2c2ea3c9fe27d476f1 Mon Sep 17 00:00:00 2001 From: MetricExpansion <> Date: Sat, 15 Apr 2023 19:10:31 -0700 Subject: [PATCH 1/3] Fix Dragon Aspect --- package-lock.json | 18 ----------------- package.json | 5 ----- src/rust/sos/src/lib.rs | 2 ++ src/rust/sos/src/outfit.rs | 19 ++++++++++++------ src/rust/sos/src/persistence.rs | 3 ++- src/rust/sos/src/settings.rs | 35 ++++++++++++++++++++++----------- 6 files changed, 40 insertions(+), 42 deletions(-) delete mode 100644 package-lock.json delete mode 100644 package.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 9620756..0000000 --- a/package-lock.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "SkyrimOutfitSystemSE", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "devDependencies": { - "@types/lodash": "^4.14.191" - } - }, - "node_modules/@types/lodash": { - "version": "4.14.191", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz", - "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==", - "dev": true - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index d7c7fcf..0000000 --- a/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "devDependencies": { - "@types/lodash": "^4.14.191" - } -} diff --git a/src/rust/sos/src/lib.rs b/src/rust/sos/src/lib.rs index 8bf4f7b..7d8df08 100644 --- a/src/rust/sos/src/lib.rs +++ b/src/rust/sos/src/lib.rs @@ -18,6 +18,7 @@ use commonlibsse::*; use log::*; use once_cell::sync::Lazy; use parking_lot::RwLock; +use settings::GOLDEN_ITEMS; #[no_mangle] pub extern "C" fn plugin_main(skse: *const SKSE_LoadInterface) -> bool { @@ -104,6 +105,7 @@ pub extern "C" fn messaging_callback(message: *mut SKSE_MessagingInterface_Messa let mut service = OUTFIT_SERVICE_SINGLETON.write(); service.replace_with_new(); service.check_consistency(); + *(GOLDEN_ITEMS.lock()) = None; } message_type => { warn!("Got unknown message type {}", message_type); diff --git a/src/rust/sos/src/outfit.rs b/src/rust/sos/src/outfit.rs index c495384..7b10e32 100644 --- a/src/rust/sos/src/outfit.rs +++ b/src/rust/sos/src/outfit.rs @@ -2,7 +2,7 @@ use crate::{ armor::{ArmorExt, ArmorLocatorExt}, interface::ffi::{OptionalStateType, StateType, TESObjectARMOPtr, WeatherFlags}, policy::*, - settings::SETTINGS, + settings::{SETTINGS, GOLDEN_ITEMS, GoldenItems}, stable_ptr::*, strings::UncasedString, }; @@ -161,6 +161,10 @@ impl Outfit { ) -> Vec<*const RE_TESObjectARMO> { let start_time = Instant::now(); let policy_store = &*POLICY_STORE; + let mut golden_items = GOLDEN_ITEMS.lock(); + if golden_items.is_none() { + *golden_items = GoldenItems::new(); + } let equipped = { let mut slots = [std::ptr::null(); RE_BIPED_OBJECTS_BIPED_OBJECT_kEditorTotal as usize]; for armor in equipped { @@ -223,11 +227,14 @@ impl Outfit { // First check if the equipped item is a "golden item". If so, it always wins. if selected_armor.is_none() { - if let Some(equipped_item) = TESObjectARMOStablePtr::new(equipped[slot as usize]) { - if SETTINGS.golden_items().contains(&equipped_item) { - selected_armor = Some(equipped_item.as_ptr()); - } - }; + if let Some(golden_items) = &*golden_items { + if let Some(equipped_item) = TESObjectARMOStablePtr::new(equipped[slot as usize]) { + if golden_items.items.contains(&equipped_item) { + selected_armor = Some(equipped_item.as_ptr()); + } + }; + + } } // Then ask the policy if it has a selection diff --git a/src/rust/sos/src/persistence.rs b/src/rust/sos/src/persistence.rs index 3a323f0..d42e1d2 100644 --- a/src/rust/sos/src/persistence.rs +++ b/src/rust/sos/src/persistence.rs @@ -1,4 +1,4 @@ -use crate::OUTFIT_SERVICE_SINGLETON; +use crate::{OUTFIT_SERVICE_SINGLETON, settings::GOLDEN_ITEMS}; use commonlibsse::*; use log::*; use std::ffi::c_void; @@ -75,6 +75,7 @@ pub extern "C" fn serialization_load_callback(intfc: *mut SKSE_SerializationInte error!("Did not read the correct amount of data for deserialization"); break; } + *(GOLDEN_ITEMS.lock()) = None; let mut service = OUTFIT_SERVICE_SINGLETON.write(); if !service.replace_with_proto(&buffer, intfc) { error!("Failed to use proto data to instantiate"); diff --git a/src/rust/sos/src/settings.rs b/src/rust/sos/src/settings.rs index e5fe178..6fa568f 100644 --- a/src/rust/sos/src/settings.rs +++ b/src/rust/sos/src/settings.rs @@ -6,11 +6,11 @@ use configparser::ini::Ini; use hash_hasher::HashedSet; use log::*; use once_cell::sync::Lazy; +use parking_lot::Mutex; use std::{ffi::CString, path::PathBuf}; pub struct Settings { ini: Ini, - golden_items: HashedSet, } impl Settings { @@ -28,8 +28,7 @@ impl Settings { } else { info!("Loaded INI file in {}", file.display()); } - let golden_items = Self::generate_golden_items(&ini); - Settings { ini, golden_items } + Settings { ini } } pub fn extra_logging_enabled(&self) -> bool { @@ -85,15 +84,25 @@ impl Settings { .unwrap_or_else(|_| None) .unwrap_or_else(|| false) } +} - pub fn golden_items(&self) -> &HashedSet { - &self.golden_items +pub static SETTINGS: Lazy = Lazy::new(|| Settings::new()); + +pub struct GoldenItems { + pub items: HashedSet, +} + +impl GoldenItems { + pub fn new() -> Option { + let items = Self::generate_golden_items(&SETTINGS.ini)?; + Some(GoldenItems { items }) } - fn generate_golden_items(ini: &Ini) -> HashedSet { + fn generate_golden_items(ini: &Ini) -> Option> { // Start with the set of built-in locators - let mut locators = vec![]; - + let mut locators = vec![ + ("Dragonborn.esm", 0x00021739), // Dragon Aspect Armor + ]; // Load locators from INI file. let ini_string = ini .get("Compatibility", "GoldenItems") @@ -109,9 +118,9 @@ impl Settings { let data_handler = unsafe { RE_TESDataHandler_GetSingleton() }; if data_handler.is_null() { - return Default::default(); + return None; } - locators + let items = locators .into_iter() .flat_map(|(mod_name, local_form_id)| { let Ok(mod_name) = CString::new(mod_name) else { return None }; @@ -126,8 +135,10 @@ impl Settings { None } }) - .collect() + .collect(); + Some(items) } } -pub static SETTINGS: Lazy = Lazy::new(|| Settings::new()); + +pub static GOLDEN_ITEMS: Mutex> = Mutex::new(None); From 9a8323be62481e70eccb5b00be4cdeb5537f6f46 Mon Sep 17 00:00:00 2001 From: MetricExpansion <> Date: Sat, 15 Apr 2023 19:18:31 -0700 Subject: [PATCH 2/3] Fix Dragon Aspect better --- src/rust/sos/src/lib.rs | 4 ++-- src/rust/sos/src/outfit.rs | 20 +++++++------------- src/rust/sos/src/persistence.rs | 4 ++-- src/rust/sos/src/settings.rs | 23 +++++++++++------------ 4 files changed, 22 insertions(+), 29 deletions(-) diff --git a/src/rust/sos/src/lib.rs b/src/rust/sos/src/lib.rs index 7d8df08..d341ec2 100644 --- a/src/rust/sos/src/lib.rs +++ b/src/rust/sos/src/lib.rs @@ -18,7 +18,7 @@ use commonlibsse::*; use log::*; use once_cell::sync::Lazy; use parking_lot::RwLock; -use settings::GOLDEN_ITEMS; +use settings::{INTRAGAME_SETTINGS, IntragameSettings}; #[no_mangle] pub extern "C" fn plugin_main(skse: *const SKSE_LoadInterface) -> bool { @@ -105,7 +105,7 @@ pub extern "C" fn messaging_callback(message: *mut SKSE_MessagingInterface_Messa let mut service = OUTFIT_SERVICE_SINGLETON.write(); service.replace_with_new(); service.check_consistency(); - *(GOLDEN_ITEMS.lock()) = None; + *(INTRAGAME_SETTINGS.write()) = IntragameSettings::new(); } message_type => { warn!("Got unknown message type {}", message_type); diff --git a/src/rust/sos/src/outfit.rs b/src/rust/sos/src/outfit.rs index 7b10e32..711f3d0 100644 --- a/src/rust/sos/src/outfit.rs +++ b/src/rust/sos/src/outfit.rs @@ -2,7 +2,7 @@ use crate::{ armor::{ArmorExt, ArmorLocatorExt}, interface::ffi::{OptionalStateType, StateType, TESObjectARMOPtr, WeatherFlags}, policy::*, - settings::{SETTINGS, GOLDEN_ITEMS, GoldenItems}, + settings::{SETTINGS, INTRAGAME_SETTINGS}, stable_ptr::*, strings::UncasedString, }; @@ -161,10 +161,7 @@ impl Outfit { ) -> Vec<*const RE_TESObjectARMO> { let start_time = Instant::now(); let policy_store = &*POLICY_STORE; - let mut golden_items = GOLDEN_ITEMS.lock(); - if golden_items.is_none() { - *golden_items = GoldenItems::new(); - } + let intragame_settings = INTRAGAME_SETTINGS.read(); let equipped = { let mut slots = [std::ptr::null(); RE_BIPED_OBJECTS_BIPED_OBJECT_kEditorTotal as usize]; for armor in equipped { @@ -227,14 +224,11 @@ impl Outfit { // First check if the equipped item is a "golden item". If so, it always wins. if selected_armor.is_none() { - if let Some(golden_items) = &*golden_items { - if let Some(equipped_item) = TESObjectARMOStablePtr::new(equipped[slot as usize]) { - if golden_items.items.contains(&equipped_item) { - selected_armor = Some(equipped_item.as_ptr()); - } - }; - - } + if let Some(equipped_item) = TESObjectARMOStablePtr::new(equipped[slot as usize]) { + if intragame_settings.golden_items.contains(&equipped_item) { + selected_armor = Some(equipped_item.as_ptr()); + } + }; } // Then ask the policy if it has a selection diff --git a/src/rust/sos/src/persistence.rs b/src/rust/sos/src/persistence.rs index d42e1d2..ec6a683 100644 --- a/src/rust/sos/src/persistence.rs +++ b/src/rust/sos/src/persistence.rs @@ -1,4 +1,4 @@ -use crate::{OUTFIT_SERVICE_SINGLETON, settings::GOLDEN_ITEMS}; +use crate::{OUTFIT_SERVICE_SINGLETON, settings::{INTRAGAME_SETTINGS, IntragameSettings}}; use commonlibsse::*; use log::*; use std::ffi::c_void; @@ -75,7 +75,7 @@ pub extern "C" fn serialization_load_callback(intfc: *mut SKSE_SerializationInte error!("Did not read the correct amount of data for deserialization"); break; } - *(GOLDEN_ITEMS.lock()) = None; + *(INTRAGAME_SETTINGS.write()) = IntragameSettings::new(); let mut service = OUTFIT_SERVICE_SINGLETON.write(); if !service.replace_with_proto(&buffer, intfc) { error!("Failed to use proto data to instantiate"); diff --git a/src/rust/sos/src/settings.rs b/src/rust/sos/src/settings.rs index 6fa568f..96f28da 100644 --- a/src/rust/sos/src/settings.rs +++ b/src/rust/sos/src/settings.rs @@ -6,7 +6,7 @@ use configparser::ini::Ini; use hash_hasher::HashedSet; use log::*; use once_cell::sync::Lazy; -use parking_lot::Mutex; +use parking_lot::RwLock; use std::{ffi::CString, path::PathBuf}; pub struct Settings { @@ -88,17 +88,17 @@ impl Settings { pub static SETTINGS: Lazy = Lazy::new(|| Settings::new()); -pub struct GoldenItems { - pub items: HashedSet, +pub struct IntragameSettings { + pub golden_items: HashedSet, } -impl GoldenItems { - pub fn new() -> Option { - let items = Self::generate_golden_items(&SETTINGS.ini)?; - Some(GoldenItems { items }) +impl IntragameSettings { + pub fn new() -> Self { + let golden_items = Self::generate_golden_items(&SETTINGS.ini); + IntragameSettings { golden_items } } - fn generate_golden_items(ini: &Ini) -> Option> { + fn generate_golden_items(ini: &Ini) -> HashedSet { // Start with the set of built-in locators let mut locators = vec![ ("Dragonborn.esm", 0x00021739), // Dragon Aspect Armor @@ -118,7 +118,7 @@ impl GoldenItems { let data_handler = unsafe { RE_TESDataHandler_GetSingleton() }; if data_handler.is_null() { - return None; + return Default::default(); } let items = locators .into_iter() @@ -136,9 +136,8 @@ impl GoldenItems { } }) .collect(); - Some(items) + items } } - -pub static GOLDEN_ITEMS: Mutex> = Mutex::new(None); +pub static INTRAGAME_SETTINGS: Lazy> = Lazy::new(|| RwLock::new(IntragameSettings::new())); From 34a702abd2134843a1fde50d456e0bbc971eb010 Mon Sep 17 00:00:00 2001 From: MetricExpansion <> Date: Sat, 15 Apr 2023 19:22:21 -0700 Subject: [PATCH 3/3] Cleanup --- src/rust/sos/src/lib.rs | 4 +-- src/rust/sos/src/outfit.rs | 4 +-- src/rust/sos/src/persistence.rs | 7 ++++-- src/rust/sos/src/settings.rs | 9 ++++--- src/rust/sos/src/web.rs | 29 +++++++++++++--------- src/rust/warp_static_zip/src/lib.rs | 38 +++++++++++++++-------------- 6 files changed, 51 insertions(+), 40 deletions(-) diff --git a/src/rust/sos/src/lib.rs b/src/rust/sos/src/lib.rs index d341ec2..b49e6cb 100644 --- a/src/rust/sos/src/lib.rs +++ b/src/rust/sos/src/lib.rs @@ -18,7 +18,7 @@ use commonlibsse::*; use log::*; use once_cell::sync::Lazy; use parking_lot::RwLock; -use settings::{INTRAGAME_SETTINGS, IntragameSettings}; +use settings::{IntragameCaches, INTRAGAME_CACHES}; #[no_mangle] pub extern "C" fn plugin_main(skse: *const SKSE_LoadInterface) -> bool { @@ -105,7 +105,7 @@ pub extern "C" fn messaging_callback(message: *mut SKSE_MessagingInterface_Messa let mut service = OUTFIT_SERVICE_SINGLETON.write(); service.replace_with_new(); service.check_consistency(); - *(INTRAGAME_SETTINGS.write()) = IntragameSettings::new(); + *(INTRAGAME_CACHES.write()) = IntragameCaches::new(); } message_type => { warn!("Got unknown message type {}", message_type); diff --git a/src/rust/sos/src/outfit.rs b/src/rust/sos/src/outfit.rs index 711f3d0..dfc09e9 100644 --- a/src/rust/sos/src/outfit.rs +++ b/src/rust/sos/src/outfit.rs @@ -2,7 +2,7 @@ use crate::{ armor::{ArmorExt, ArmorLocatorExt}, interface::ffi::{OptionalStateType, StateType, TESObjectARMOPtr, WeatherFlags}, policy::*, - settings::{SETTINGS, INTRAGAME_SETTINGS}, + settings::{INTRAGAME_CACHES, SETTINGS}, stable_ptr::*, strings::UncasedString, }; @@ -161,7 +161,7 @@ impl Outfit { ) -> Vec<*const RE_TESObjectARMO> { let start_time = Instant::now(); let policy_store = &*POLICY_STORE; - let intragame_settings = INTRAGAME_SETTINGS.read(); + let intragame_settings = INTRAGAME_CACHES.read(); let equipped = { let mut slots = [std::ptr::null(); RE_BIPED_OBJECTS_BIPED_OBJECT_kEditorTotal as usize]; for armor in equipped { diff --git a/src/rust/sos/src/persistence.rs b/src/rust/sos/src/persistence.rs index ec6a683..0d16841 100644 --- a/src/rust/sos/src/persistence.rs +++ b/src/rust/sos/src/persistence.rs @@ -1,4 +1,7 @@ -use crate::{OUTFIT_SERVICE_SINGLETON, settings::{INTRAGAME_SETTINGS, IntragameSettings}}; +use crate::{ + settings::{IntragameCaches, INTRAGAME_CACHES}, + OUTFIT_SERVICE_SINGLETON, +}; use commonlibsse::*; use log::*; use std::ffi::c_void; @@ -75,7 +78,7 @@ pub extern "C" fn serialization_load_callback(intfc: *mut SKSE_SerializationInte error!("Did not read the correct amount of data for deserialization"); break; } - *(INTRAGAME_SETTINGS.write()) = IntragameSettings::new(); + *(INTRAGAME_CACHES.write()) = IntragameCaches::new(); let mut service = OUTFIT_SERVICE_SINGLETON.write(); if !service.replace_with_proto(&buffer, intfc) { error!("Failed to use proto data to instantiate"); diff --git a/src/rust/sos/src/settings.rs b/src/rust/sos/src/settings.rs index 96f28da..4a5221f 100644 --- a/src/rust/sos/src/settings.rs +++ b/src/rust/sos/src/settings.rs @@ -88,14 +88,14 @@ impl Settings { pub static SETTINGS: Lazy = Lazy::new(|| Settings::new()); -pub struct IntragameSettings { +pub struct IntragameCaches { pub golden_items: HashedSet, } -impl IntragameSettings { +impl IntragameCaches { pub fn new() -> Self { let golden_items = Self::generate_golden_items(&SETTINGS.ini); - IntragameSettings { golden_items } + IntragameCaches { golden_items } } fn generate_golden_items(ini: &Ini) -> HashedSet { @@ -140,4 +140,5 @@ impl IntragameSettings { } } -pub static INTRAGAME_SETTINGS: Lazy> = Lazy::new(|| RwLock::new(IntragameSettings::new())); +pub static INTRAGAME_CACHES: Lazy> = + Lazy::new(|| RwLock::new(IntragameCaches::new())); diff --git a/src/rust/sos/src/web.rs b/src/rust/sos/src/web.rs index f99e409..bc836d1 100644 --- a/src/rust/sos/src/web.rs +++ b/src/rust/sos/src/web.rs @@ -1,12 +1,15 @@ -use std::{path::PathBuf, io::{Read, Cursor}}; use log::*; +use std::{ + io::{Cursor, Read}, + path::PathBuf, +}; -use warp_static_zip::ZipArchive; +use crate::{interface::ffi::GetRuntimeDirectory, settings::SETTINGS}; use warp::Filter; use warp::Reply; -use crate::{interface::ffi::GetRuntimeDirectory, settings::SETTINGS}; +use warp_static_zip::ZipArchive; -pub async fn web_server() { +pub async fn web_server() { let get_state = warp::path!("state") .and(warp::get()) .and_then(handlers::get_state); @@ -37,7 +40,7 @@ pub async fn web_server() { .or(policy_info); let static_asset_routes = create_zip_route(); - + let addr = if SETTINGS.server_remote_accessible() { [0, 0, 0, 0] } else { @@ -48,7 +51,7 @@ pub async fn web_server() { match binding { Ok((addr, sock)) => { info!("Server bound to {}", addr); - sock.await; + sock.await; } Err(err) => { warn!("Could not bind address: {}", err); @@ -66,7 +69,7 @@ fn create_zip_route() -> warp::filters::BoxedFilter<(impl Reply,)> { if let Err(error) = zip_data.read_to_end(&mut data) { error!("Failed to read web archive file: {}", error); }; - data + data } Err(error) => { error!("Failed to read zip archive file: {}", error); @@ -74,7 +77,7 @@ fn create_zip_route() -> warp::filters::BoxedFilter<(impl Reply,)> { } } }; - + let zip_archive = Box::leak(Box::new(ZipArchive::new(Cursor::new(&*zip_vector.leak())))); let static_asset_routes = { match zip_archive { @@ -96,9 +99,9 @@ fn create_zip_route() -> warp::filters::BoxedFilter<(impl Reply,)> { mod handlers { use crate::{ - outfit::Outfit, armor::{ArmorExt, ArmorLocatorExt}, interface::ffi::{ArmorSearch, CreateRefreshAllArmorsTask}, + outfit::Outfit, policy::POLICY_STORE, settings::SETTINGS, stable_ptr::TESObjectARMOStablePtr, @@ -207,7 +210,7 @@ mod handlers { let info = ArmorInfo { locator: locator_in, name, - mask + mask, }; Ok(warp::reply::with_status( warp::reply::json(&info), @@ -241,9 +244,11 @@ mod handlers { })?, name: unsafe { let cstr = CStr::from_ptr(armor.borrow()._base._base._base.GetName()); - cstr.to_str().map(|s| s.to_owned()).unwrap_or_else(|_| "".to_owned()) + cstr.to_str() + .map(|s| s.to_owned()) + .unwrap_or_else(|_| "".to_owned()) }, - mask: unsafe { armor.borrow()._base_10.GetSlotMask() } as u32 + mask: unsafe { armor.borrow()._base_10.GetSlotMask() } as u32, }) }) .collect(); diff --git a/src/rust/warp_static_zip/src/lib.rs b/src/rust/warp_static_zip/src/lib.rs index 7d228bd..97870e9 100644 --- a/src/rust/warp_static_zip/src/lib.rs +++ b/src/rust/warp_static_zip/src/lib.rs @@ -8,11 +8,8 @@ use headers::{ use http::{HeaderMap, StatusCode}; use hyper::Body; use std::future::ready; -use std::{ - path::{PathBuf}, - time::SystemTime, -}; use std::io::{Read, Seek}; +use std::{path::PathBuf, time::SystemTime}; use warp::{ filters::header::headers_cloned, path::{tail, Tail}, @@ -29,8 +26,7 @@ pub use once_cell::sync::Lazy; #[doc(hidden)] pub fn failed() -> impl Filter + Clone + 'static { - warp::get() - .and_then(move || ready(Err(reject::not_found()))) + warp::get().and_then(move || ready(Err(reject::not_found()))) } /// Creates a [Filter][warp::Filter] that serves an included directory. @@ -72,13 +68,20 @@ pub fn file<'a, R: Read + Seek + Clone + Send + Sync>( .and_then(move |path, conds| ready(reply(dir, path, conds))) } - -fn parse_path(tail: &str, dir: &ZipArchive) -> Result { - let mut path_str = urlencoding::decode(tail) - .map_err(|_| reject::not_found())?; +fn parse_path( + tail: &str, + dir: &ZipArchive, +) -> Result { + let mut path_str = urlencoding::decode(tail).map_err(|_| reject::not_found())?; let path: PathBuf = path_str.split('/').collect(); - if path.parent().is_none() || dir.clone().by_name(&path_str).map(|v| v.is_dir()).unwrap_or(false) { + if path.parent().is_none() + || dir + .clone() + .by_name(&path_str) + .map(|v| v.is_dir()) + .unwrap_or(false) + { log::debug!("appending index.html to {:?}", path); path_str += "/index.html"; } @@ -151,7 +154,11 @@ fn bytes_range(range: Range, max_len: u64) -> Option<(u64, u64)> { } } -fn reply(dir: &ZipArchive, path: String, conds: Conditionals) -> Result { +fn reply( + dir: &ZipArchive, + path: String, + conds: Conditionals, +) -> Result { let mut contents = Vec::new(); if let Ok(mut file) = dir.clone().by_name(&path) { file.read_to_end(&mut contents).unwrap(); @@ -194,12 +201,7 @@ fn reply_full(buf: Vec, path: &str, last_mod: LastModified) -> Response { resp } -fn reply_range( - buf: Vec, - (s, e): (u64, u64), - path: &str, - last_mod: LastModified, -) -> Response { +fn reply_range(buf: Vec, (s, e): (u64, u64), path: &str, last_mod: LastModified) -> Response { let len = e - s; let buf = (&buf[s as usize..e as usize]).to_owned();