diff --git a/Cargo.lock b/Cargo.lock
index 7616a07..b2761b0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2015,6 +2015,13 @@ dependencies = [
  "xml-rs",
 ]
 
+[[package]]
+name = "rbxassetid"
+version = "0.1.0"
+dependencies = [
+ "url",
+]
+
 [[package]]
 name = "redox_syscall"
 version = "0.4.1"
diff --git a/Cargo.toml b/Cargo.toml
index f2603a8..8bdb6a6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,6 +12,7 @@ members = [
 	"lib/linear_ops",
 	"lib/ratio_ops",
 	"lib/rbx_loader",
+	"lib/rbxassetid",
 	"lib/roblox_emulator",
 	"lib/snf",
 	"strafe-client",
diff --git a/lib/deferred_loader/src/rbxassetid.rs b/lib/deferred_loader/src/rbxassetid.rs
deleted file mode 100644
index 2cf43d7..0000000
--- a/lib/deferred_loader/src/rbxassetid.rs
+++ /dev/null
@@ -1,48 +0,0 @@
-#[derive(Hash,Eq,PartialEq)]
-pub struct RobloxAssetId(pub u64);
-#[derive(Debug)]
-#[allow(dead_code)]
-pub struct StringWithError{
-	string:String,
-	error:RobloxAssetIdParseErr,
-}
-impl std::fmt::Display for StringWithError{
-	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
-		write!(f,"{self:?}")
-	}
-}
-impl std::error::Error for StringWithError{}
-impl StringWithError{
-	const fn new(
-		string:String,
-		error:RobloxAssetIdParseErr,
-	)->Self{
-		Self{string,error}
-	}
-}
-#[derive(Debug)]
-pub enum RobloxAssetIdParseErr{
-	Url(url::ParseError),
-	UnknownScheme,
-	ParseInt(std::num::ParseIntError),
-	MissingAssetId,
-}
-impl std::str::FromStr for RobloxAssetId{
-	type Err=StringWithError;
-	fn from_str(s:&str)->Result<Self,Self::Err>{
-		let url=url::Url::parse(s).map_err(|e|StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::Url(e)))?;
-		let parsed_asset_id=match url.scheme(){
-			"rbxassetid"=>url.domain().ok_or_else(||StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::MissingAssetId))?.parse(),
-			"http"|"https"=>{
-				let (_,asset_id)=url.query_pairs()
-				.find(|(id,_)|match id.as_ref(){
-					"ID"|"id"|"Id"|"iD"=>true,
-					_=>false,
-				}).ok_or_else(||StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::MissingAssetId))?;
-				asset_id.parse()
-			},
-			_=>Err(StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::UnknownScheme))?,
-		};
-		Ok(Self(parsed_asset_id.map_err(|e|StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::ParseInt(e)))?))
-	}
-}
diff --git a/lib/rbxassetid/Cargo.toml b/lib/rbxassetid/Cargo.toml
new file mode 100644
index 0000000..e228e32
--- /dev/null
+++ b/lib/rbxassetid/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "rbxassetid"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+url = "2.5.4"
diff --git a/lib/rbxassetid/src/lib.rs b/lib/rbxassetid/src/lib.rs
new file mode 100644
index 0000000..d5ea9c3
--- /dev/null
+++ b/lib/rbxassetid/src/lib.rs
@@ -0,0 +1,35 @@
+#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
+pub struct RobloxAssetId(pub u64);
+#[derive(Debug)]
+pub enum RobloxAssetIdParseErr{
+	Url(url::ParseError),
+	UnknownScheme,
+	ParseInt(std::num::ParseIntError),
+	MissingAssetId,
+	MissingIDQueryParam,
+}
+impl std::fmt::Display for RobloxAssetIdParseErr{
+	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
+		write!(f,"{self:?}")
+	}
+}
+impl std::error::Error for RobloxAssetIdParseErr{}
+impl std::str::FromStr for RobloxAssetId{
+	type Err=RobloxAssetIdParseErr;
+	fn from_str(s:&str)->Result<Self,Self::Err>{
+		let url=url::Url::parse(s).map_err(RobloxAssetIdParseErr::Url)?;
+		let parsed_asset_id=match url.scheme(){
+			"rbxassetid"=>url.domain().ok_or(RobloxAssetIdParseErr::MissingAssetId)?.parse(),
+			"http"|"https"=>{
+				let (_,asset_id)=url.query_pairs()
+				.find(|(id,_)|match id.as_ref(){
+					"ID"|"id"|"Id"|"iD"=>true,
+					_=>false,
+				}).ok_or(RobloxAssetIdParseErr::MissingIDQueryParam)?;
+				asset_id.parse()
+			},
+			_=>Err(RobloxAssetIdParseErr::UnknownScheme)?,
+		};
+		Ok(Self(parsed_asset_id.map_err(RobloxAssetIdParseErr::ParseInt)?))
+	}
+}