diff --git a/src/lib.rs b/src/lib.rs
index 4fed855..c8b5e99 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -8,7 +8,10 @@ mod roblox;
 mod source;
 
 pub mod texture;
+#[cfg(any(feature="source",feature="legacy"))]
 pub mod valve_mesh;
+#[cfg(any(feature="roblox",feature="legacy"))]
+pub mod roblox_mesh;
 
 #[cfg(feature="legacy")]
 pub fn roblox_legacy()->roblox_legacy::Loader{
diff --git a/src/roblox_legacy.rs b/src/roblox_legacy.rs
index d4f70a2..9da167e 100644
--- a/src/roblox_legacy.rs
+++ b/src/roblox_legacy.rs
@@ -1,7 +1,8 @@
 use std::io::Read;
 use std::collections::HashMap;
-use crate::texture::{Texture,RenderConfigs};
-use strafesnet_common::model::{TextureId,RenderConfigId,RenderConfig};
+use crate::roblox_mesh;
+use crate::texture::{RenderConfigs,Texture};
+use strafesnet_common::model::{MeshId,RenderConfig,RenderConfigId,TextureId};
 
 #[derive(Hash,Eq,PartialEq)]
 struct RobloxAssetId(u64);
@@ -28,22 +29,14 @@ impl std::fmt::Display for RobloxAssetIdParseErr{
 }
 impl std::error::Error for RobloxAssetIdParseErr{}
 
-pub struct Loader{
+#[derive(Default)]
+pub struct RenderConfigLoader{
 	texture_count:u32,
 	render_configs:Vec<RenderConfig>,
 	render_config_id_from_asset_id:HashMap<Option<RobloxAssetId>,RenderConfigId>,
 }
-impl Loader{
-	pub fn new()->Self{
-		Self{
-			texture_count:0,
-			render_configs:Vec::new(),
-			render_config_id_from_asset_id:HashMap::new(),
-		}
-	}
-}
 
-impl Loader{
+impl RenderConfigLoader{
 	pub fn acquire_render_config_id(&mut self,name:Option<&str>)->RenderConfigId{
 		let render_id=RenderConfigId::new(self.render_config_id_from_asset_id.len() as u32);
 		let index=name.and_then(|name|{
@@ -68,11 +61,62 @@ impl Loader{
 			render_id
 		})
 	}
+}
+
+#[derive(Default)]
+pub struct MeshLoader{
+	mesh_id_from_asset_id:HashMap<Option<RobloxAssetId>,MeshId>,
+}
+
+impl MeshLoader{
+	pub fn acquire_mesh_id(&mut self,name:&str)->MeshId{
+		let mesh_id=MeshId::new(self.mesh_id_from_asset_id.len() as u32);
+		let index=match name.parse::<RobloxAssetId>(){
+			Ok(asset_id)=>Some(asset_id),
+			Err(e)=>{
+				println!("Failed to parse AssetId: {e}");
+				None
+			},
+		};
+		*self.mesh_id_from_asset_id.entry(index).or_insert(mesh_id)
+	}
+	pub fn load_meshes(&mut self)->Result<roblox_mesh::Meshes,std::io::Error>{
+		let mut mesh_data=vec![None;self.mesh_id_from_asset_id.len()];
+		for (asset_id_option,mesh_id) in &self.mesh_id_from_asset_id{
+			if let Some(asset_id)=asset_id_option{
+				if let Ok(mut file)=std::fs::File::open(format!("meshes/{}",asset_id.0)){
+					//TODO: parallel
+					let mut data=Vec::<u8>::new();
+					file.read_to_end(&mut data)?;
+					mesh_data[mesh_id.get() as usize]=Some(roblox_mesh::RobloxMeshData::new(data));
+				}else{
+					println!("no model name={}",asset_id.0);
+				}
+			}
+		}
+		Ok(roblox_mesh::Meshes::new(mesh_data))
+	}
+}
+
+pub struct Loader{
+	render_config_loader:RenderConfigLoader,
+	mesh_loader:MeshLoader,
+}
+impl Loader{
+	pub fn new()->Self{
+		Self{
+			render_config_loader:RenderConfigLoader::default(),
+			mesh_loader:MeshLoader::default(),
+		}
+	}
+	pub fn get_inner_mut(&mut self)->(&mut RenderConfigLoader,&mut MeshLoader){
+		(&mut self.render_config_loader,&mut self.mesh_loader)
+	}
 	pub fn into_render_configs(mut self)->Result<RenderConfigs,std::io::Error>{
-		let mut sorted_textures=vec![None;self.texture_count as usize];
-		for (asset_id,render_config_id) in self.render_config_id_from_asset_id{
-			let render_config=self.render_configs.get_mut(render_config_id.get() as usize).unwrap();
-			if let (Some(asset_id),Some(texture_id))=(asset_id,render_config.texture){
+		let mut sorted_textures=vec![None;self.render_config_loader.texture_count as usize];
+		for (asset_id_option,render_config_id) in self.render_config_loader.render_config_id_from_asset_id{
+			let render_config=self.render_config_loader.render_configs.get_mut(render_config_id.get() as usize).unwrap();
+			if let (Some(asset_id),Some(texture_id))=(asset_id_option,render_config.texture){
 				if let Ok(mut file)=std::fs::File::open(format!("textures/{}.dds",asset_id.0)){
 					//TODO: parallel
 					let mut data=Vec::<u8>::new();
@@ -86,7 +130,7 @@ impl Loader{
 		}
 		Ok(RenderConfigs::new(
 			sorted_textures,
-			self.render_configs,
+			self.render_config_loader.render_configs,
 		))
 	}
 }
\ No newline at end of file
diff --git a/src/roblox_mesh.rs b/src/roblox_mesh.rs
new file mode 100644
index 0000000..cdb545f
--- /dev/null
+++ b/src/roblox_mesh.rs
@@ -0,0 +1,30 @@
+use strafesnet_common::model::MeshId;
+
+#[derive(Clone)]
+pub struct RobloxMeshData(Vec<u8>);
+impl RobloxMeshData{
+	pub(crate) fn new(data:Vec<u8>)->Self{
+		Self(data)
+	}
+	pub fn get(self)->Vec<u8>{
+		self.0
+	}
+}
+pub struct Meshes{
+	meshes:Vec<Option<RobloxMeshData>>,
+}
+impl Meshes{
+	pub(crate) const fn new(meshes:Vec<Option<RobloxMeshData>>)->Self{
+		Self{
+			meshes,
+		}
+	}
+	pub fn get_texture(&self,texture_id:MeshId)->Option<&RobloxMeshData>{
+		self.meshes.get(texture_id.get() as usize)?.as_ref()
+	}
+	pub fn into_iter(self)->impl Iterator<Item=(MeshId,RobloxMeshData)>{
+		self.meshes.into_iter().enumerate().filter_map(|(mesh_id,maybe_mesh)|
+			maybe_mesh.map(|mesh|(MeshId::new(mesh_id as u32),mesh))
+		)
+	}
+}
\ No newline at end of file