diff --git a/validation/src/create_submission.rs b/validation/src/create_submission.rs
index 563798b..4e5ea95 100644
--- a/validation/src/create_submission.rs
+++ b/validation/src/create_submission.rs
@@ -3,7 +3,13 @@ use crate::nats_types::CreateSubmissionRequest;
 #[allow(dead_code)]
 #[derive(Debug)]
 pub enum Error{
-	Get(rbx_asset::cookie::GetError),
+	ModelVersionsPage(rbx_asset::cookie::PageError),
+	EmptyVersionsPage,
+	WrongCreatorType,
+	ModelFileDownload(rbx_asset::cookie::GetError),
+	ModelFileDecode(crate::validator::ReadDomError),
+	GetMapInfo(GetMapInfoError),
+	ParseGameID(ParseGameIDError),
 	ApiActionSubmissionCreate(submissions_api::Error),
 }
 impl std::fmt::Display for Error{
@@ -15,16 +21,125 @@ impl std::error::Error for Error{}
 
 impl crate::message_handler::MessageHandler{
 	pub async fn create_submission(&self,create_info:CreateSubmissionRequest)->Result<(),Error>{
+		// discover the latest asset version
+		let asset_versions_page=self.cookie_context.get_asset_versions_page(rbx_asset::cookie::AssetVersionsPageRequest{
+			asset_id:create_info.ModelID,
+			cursor:None
+		}).await.map_err(Error::ModelVersionsPage)?;
+
+		// grab version info
+		let first_version=asset_versions_page.data.first().ok_or(Error::EmptyVersionsPage)?;
+
+		if first_version.creatorType!="User"{
+			return Err(Error::WrongCreatorType);
+		}
+
+		let asset_creator_id=first_version.creatorTargetId;
+		let asset_version=first_version.assetVersionNumber;
+
 		// download the map model version
 		let model_data=self.cookie_context.get_asset(rbx_asset::cookie::GetAssetRequest{
 			asset_id:create_info.ModelID,
 			version:None,
-		}).await.map_err(Error::Get)?;
+		}).await.map_err(Error::ModelFileDownload)?;
+
+		// decode dom (slow!)
+		let dom=crate::validator::read_dom(&mut std::io::Cursor::new(model_data)).map_err(Error::ModelFileDecode)?;
 
 		// parse create fields out of asset
+		let MapInfo{
+			display_name,
+			creator,
+			game_id,
+		}=get_mapinfo(&dom).map_err(Error::GetMapInfo)?;
+
+		let game_id=game_id.map_err(Error::ParseGameID)?;
 
 		// call create on api
+		self.api.create_submission(submissions_api::types::CreateSubmissionRequest{
+			OperationID:create_info.OperationID,
+			AssetOwner:asset_creator_id as i64,
+			DisplayName:display_name.unwrap_or_default(),
+			Creator:creator.unwrap_or_default(),
+			GameID:game_id as i32,
+			AssetID:create_info.ModelID,
+			AssetVersion:asset_version,
+		}).await.map_err(Error::ApiActionSubmissionCreate)?;
 
 		Ok(())
 	}
 }
+
+enum GameID{
+	Bhop=1,
+	Surf=2,
+	FlyTrials=5,
+}
+#[derive(Debug)]
+pub struct ParseGameIDError;
+impl std::str::FromStr for GameID{
+	type Err=ParseGameIDError;
+	fn from_str(s:&str)->Result<Self,Self::Err>{
+		if s.starts_with("bhop_"){
+			return Ok(GameID::Bhop);
+		}
+		if s.starts_with("surf_"){
+			return Ok(GameID::Surf);
+		}
+		if s.starts_with("flytrials_"){
+			return Ok(GameID::FlyTrials);
+		}
+		return Err(ParseGameIDError);
+	}
+}
+
+struct MapInfo<'a>{
+	display_name:Result<&'a str,StringValueError>,
+	creator:Result<&'a str,StringValueError>,
+	game_id:Result<GameID,ParseGameIDError>,
+}
+
+enum StringValueError{
+	ObjectNotFound,
+	ValueNotSet,
+	NonStringValue,
+}
+
+fn string_value(instance:Option<&rbx_dom_weak::Instance>)->Result<&str,StringValueError>{
+	let instance=instance.ok_or(StringValueError::ObjectNotFound)?;
+	let value=instance.properties.get("Value").ok_or(StringValueError::ValueNotSet)?;
+	match value{
+		rbx_dom_weak::types::Variant::String(value)=>Ok(value),
+		_=>Err(StringValueError::NonStringValue),
+	}
+}
+
+#[derive(Debug)]
+pub enum GetMapInfoError{
+	ModelFileRootMustHaveOneChild,
+	ModelFileChildRefIsNil,
+}
+
+fn get_mapinfo(dom:&rbx_dom_weak::WeakDom)->Result<MapInfo,GetMapInfoError>{
+	let &[map_ref]=dom.root().children()else{
+		return Err(GetMapInfoError::ModelFileRootMustHaveOneChild);
+	};
+	let model_instance=dom.get_by_ref(map_ref).ok_or(GetMapInfoError::ModelFileChildRefIsNil)?;
+
+	Ok(MapInfo{
+		display_name:string_value(find_first_child_class(dom,model_instance,"DisplayName","StringValue")),
+		creator:string_value(find_first_child_class(dom,model_instance,"Creator","StringValue")),
+		game_id:model_instance.name.parse(),
+	})
+}
+
+fn find_first_child_class<'a>(dom:&'a rbx_dom_weak::WeakDom,instance:&'a rbx_dom_weak::Instance,name:&'a str,class:&'a str)->Option<&'a rbx_dom_weak::Instance>{
+	for &referent in instance.children(){
+		if let Some(c)=dom.get_by_ref(referent){
+			if c.name==name&&crate::validator::class_is_a(c.class.as_str(),class) {
+				return Some(c);
+			}
+		}
+	}
+	None
+}
diff --git a/validation/src/validator.rs b/validation/src/validator.rs
index 0302c4e..5a0dc83 100644
--- a/validation/src/validator.rs
+++ b/validation/src/validator.rs
@@ -291,7 +291,7 @@ impl std::fmt::Display for ReadDomError{
 }
 impl std::error::Error for ReadDomError{}
 
-fn read_dom<R:std::io::Read+std::io::Seek>(input:&mut R)->Result<rbx_dom_weak::WeakDom,ReadDomError>{
+pub fn read_dom<R:std::io::Read+std::io::Seek>(input:&mut R)->Result<rbx_dom_weak::WeakDom,ReadDomError>{
 	let mut first_8=[0u8;8];
 	std::io::Read::read_exact(input,&mut first_8).map_err(ReadDomError::Read)?;
 	std::io::Seek::rewind(input).map_err(ReadDomError::Seek)?;
@@ -307,7 +307,7 @@ fn read_dom<R:std::io::Read+std::io::Seek>(input:&mut R)->Result<rbx_dom_weak::W
 	}
 }
 
-fn class_is_a(class:&str,superclass:&str)->bool{
+pub fn class_is_a(class:&str,superclass:&str)->bool{
 	if class==superclass{
 		return true
 	}