diff --git a/src/v1.rs b/src/v1.rs
index 07bc82d..42a0567 100644
--- a/src/v1.rs
+++ b/src/v1.rs
@@ -249,8 +249,8 @@ struct EventChunkHeader{
 // first time I've managed to write BinRead generics without this stupid T::Args<'a>:Required thing blocking me
 fn read_data_into_events<'a,R:BinReaderExt,T>(data:&mut R,events:&mut Vec<T>,num_events:usize)->binrw::BinResult<()>
 where
-    T:binrw::BinRead,
-    T::Args<'a>:binrw::__private::Required,
+	T:binrw::BinRead,
+	T::Args<'a>:binrw::__private::Required,
 {
 	// there is only supposed to be at most one of each type of event chunk per block, so no need to amortize.
 	events.reserve_exact(num_events);
@@ -259,10 +259,26 @@ where
 	}
 	Ok(())
 }
+fn read_data_into_events_amortized<'a,R:BinReaderExt,T>(data:&mut R,events:&mut Vec<T>,num_events:usize)->binrw::BinResult<()>
+where
+	T:binrw::BinRead,
+	T::Args<'a>:binrw::__private::Required,
+{
+	// this is used when reading multiple blocks into a single object, so amortize the allocation cost.
+	events.reserve(num_events);
+	for _ in 0..num_events{
+		events.push(data.read_le()?);
+	}
+	Ok(())
+}
 
 impl Block{
-	fn read<R:BinReaderExt>(mut data:R)->binrw::BinResult<Block>{
+	fn read<R:BinReaderExt>(data:R)->binrw::BinResult<Block>{
 		let mut block=Block::default();
+		Block::read_into(data,&mut block)?;
+		Ok(block)
+	}
+	fn read_into<R:BinReaderExt>(mut data:R,block:&mut Block)->binrw::BinResult<()>{
 		// well... this looks error prone
 		while let Ok(event_chunk_header)=data.read_le::<EventChunkHeader>(){
 			match event_chunk_header.event_type{
@@ -276,7 +292,23 @@ impl Block{
 				EventType::Setting=>read_data_into_events(&mut data,&mut block.setting_events,event_chunk_header.num_events as usize)?,
 			}
 		}
-		Ok(block)
+		Ok(())
+	}
+	fn read_into_amortized<R:BinReaderExt>(mut data:R,block:&mut Block)->binrw::BinResult<()>{
+		// sad code duplication
+		while let Ok(event_chunk_header)=data.read_le::<EventChunkHeader>(){
+			match event_chunk_header.event_type{
+				EventType::Input=>read_data_into_events_amortized(&mut data,&mut block.input_events,event_chunk_header.num_events as usize)?,
+				EventType::Output=>read_data_into_events_amortized(&mut data,&mut block.output_events,event_chunk_header.num_events as usize)?,
+				EventType::Sound=>read_data_into_events_amortized(&mut data,&mut block.sound_events,event_chunk_header.num_events as usize)?,
+				EventType::World=>read_data_into_events_amortized(&mut data,&mut block.world_events,event_chunk_header.num_events as usize)?,
+				EventType::Gravity=>read_data_into_events_amortized(&mut data,&mut block.gravity_events,event_chunk_header.num_events as usize)?,
+				EventType::Run=>read_data_into_events_amortized(&mut data,&mut block.run_events,event_chunk_header.num_events as usize)?,
+				EventType::Camera=>read_data_into_events_amortized(&mut data,&mut block.camera_events,event_chunk_header.num_events as usize)?,
+				EventType::Setting=>read_data_into_events_amortized(&mut data,&mut block.setting_events,event_chunk_header.num_events as usize)?,
+			}
+		}
+		Ok(())
 	}
 }
 
@@ -306,6 +338,16 @@ pub struct TimedBlockId{
 	pub time:f64,
 	pub block_id:BlockId,
 }
+impl PartialEq for TimedBlockId{
+	fn eq(&self,other:&Self)->bool{
+		self.time.eq(&other.time)
+	}
+}
+impl PartialOrd for TimedBlockId{
+	fn partial_cmp(&self,other:&Self)->Option<core::cmp::Ordering>{
+		self.time.partial_cmp(&other.time)
+	}
+}
 
 #[binrw]
 #[brw(little)]
@@ -337,6 +379,41 @@ impl FileHeader{
 	}
 }
 
+struct MergeIter<T,It0:Iterator<Item=T>,It1:Iterator<Item=T>>{
+	it0:It0,
+	it1:It1,
+	item0:Option<T>,
+	item1:Option<T>,
+}
+impl<T,It0:Iterator<Item=T>,It1:Iterator<Item=T>> MergeIter<T,It0,It1>{
+	fn new(mut it0:It0,mut it1:It1)->Self{
+		Self{
+			item0:it0.next(),
+			item1:it1.next(),
+			it0,
+			it1,
+		}
+	}
+}
+impl<T:PartialOrd,It0:Iterator<Item=T>,It1:Iterator<Item=T>> Iterator for MergeIter<T,It0,It1>{
+	type Item=T;
+	fn next(&mut self)->Option<Self::Item>{
+		match (&self.item0,&self.item1){
+			(None,None)=>None,
+			(Some(_),None)=>core::mem::replace(&mut self.item0,self.it0.next()),
+			(None,Some(_))=>core::mem::replace(&mut self.item1,self.it1.next()),
+			(Some(item0),Some(item1))=>match item0.partial_cmp(item1){
+				Some(core::cmp::Ordering::Less)
+				|Some(core::cmp::Ordering::Equal)
+				|None
+				=>core::mem::replace(&mut self.item0,self.it0.next()),
+				Some(core::cmp::Ordering::Greater)
+				=>core::mem::replace(&mut self.item1,self.it1.next()),
+			},
+		}
+	}
+}
+
 pub struct File<R:BinReaderExt>{
 	pub header:FileHeader,
 	pub data:FileData<R>,
@@ -348,6 +425,18 @@ impl<R:BinReaderExt> File<R>{
 			data:FileData{data},
 		})
 	}
+	pub fn read_all(&mut self)->Result<Block,Error>{
+		let block_iter=MergeIter::new(
+			self.header.offline_blocks_timeline.iter(),
+			self.header.realtime_blocks_timeline.iter(),
+		);
+		let mut big_block=Block::default();
+		for &TimedBlockId{time:_,block_id} in block_iter{
+			let block_info=self.header.block_info(block_id)?;
+			self.data.read_block_info_into(block_info,&mut big_block)?;
+		}
+		Ok(big_block)
+	}
 }
 
 pub struct FileData<R:BinReaderExt>{
@@ -366,4 +455,9 @@ impl<R:BinReaderExt> FileData<R>{
 		let block=Block::read(data).map_err(Error::InvalidData)?;
 		Ok(block)
 	}
+	pub fn read_block_info_into(&mut self,block_info:BlockInfo,block:&mut Block)->Result<(),Error>{
+		let data=self.block_reader(block_info)?;
+		Block::read_into_amortized(data,block).map_err(Error::InvalidData)?;
+		Ok(())
+	}
 }