172 lines
5.4 KiB
Rust
172 lines
5.4 KiB
Rust
use std::{error::Error, fs::File, io::Read, str::from_utf8, slice::Iter, convert::TryInto};
|
|
use flate2::read::ZlibDecoder;
|
|
|
|
const DMF_MAGIC_NUMBER: &'static str = ".DelekDefleMask.";
|
|
const DMF_SUPPORTED_VERSION: u8 = 0x18;
|
|
const DMF_MD: u8 = 0x02;
|
|
const DMF_MD_ENHANCED_CH3: u8 = 0x42;
|
|
|
|
#[derive(Debug)]
|
|
pub enum FrameMode {
|
|
Ntsc,
|
|
Pal,
|
|
Custom( u8, u8, u8 )
|
|
}
|
|
|
|
pub struct DmfModule {
|
|
platform: u8,
|
|
version: u8,
|
|
time_base: u8,
|
|
speed_a: u8,
|
|
speed_b: u8,
|
|
frame_mode: FrameMode,
|
|
rows_per_pattern: u32,
|
|
patterns: Vec<Vec<u8>>
|
|
}
|
|
|
|
fn get_string( bytes: &mut Iter<'_, u8>, take: usize ) -> Result<String, Box<dyn Error>> {
|
|
let took = bytes.take( take );
|
|
let took: Vec<u8> = took.cloned().collect();
|
|
let took = from_utf8( &took )?.to_string();
|
|
|
|
Ok( took )
|
|
}
|
|
|
|
fn get_u8( bytes: &mut Iter<'_, u8> ) -> Result<u8, Box<dyn Error>> {
|
|
let took = bytes.take( 1 ).cloned().next().ok_or( "invalid file: expected 1 byte" )?;
|
|
|
|
Ok( took )
|
|
}
|
|
|
|
fn get_u32( bytes: &mut Iter<'_, u8> ) -> Result<u32, Box<dyn Error>> {
|
|
let took = bytes.take( 4 );
|
|
let took: Vec<u8> = took.cloned().collect();
|
|
let took = u32::from_le_bytes( took[0..4].try_into()? );
|
|
|
|
Ok( took )
|
|
}
|
|
|
|
fn skip( bytes: &mut Iter<'_, u8>, by: usize ) {
|
|
for i in 0..by {
|
|
bytes.next();
|
|
}
|
|
}
|
|
|
|
impl DmfModule {
|
|
|
|
pub fn from_file( path: &str ) -> Result<DmfModule, Box<dyn Error>> {
|
|
let mut file = File::open( path )?;
|
|
let mut compressed: Vec<u8> = Vec::new();
|
|
file.read_to_end( &mut compressed )?;
|
|
|
|
let mut inflator = ZlibDecoder::new( &compressed[..] );
|
|
let mut bytes: Vec<u8> = Vec::new();
|
|
inflator.read_to_end( &mut bytes )?;
|
|
|
|
let mut bytes = bytes.iter();
|
|
|
|
if get_string( bytes.by_ref(), 16 )? != DMF_MAGIC_NUMBER {
|
|
return Err( format!( "invalid file: missing DMF header" ) )?
|
|
}
|
|
|
|
println!( ".DelekDefleMask. DMF file" );
|
|
|
|
let version = get_u8( bytes.by_ref() )?;
|
|
if version != DMF_SUPPORTED_VERSION {
|
|
return Err( format!( "invalid file: unsupported version: {}", version ) )?
|
|
}
|
|
|
|
let platform = get_u8( bytes.by_ref() )?;
|
|
match platform {
|
|
DMF_MD => println!( "Mode:\tSega Megadrive" ),
|
|
DMF_MD_ENHANCED_CH3 => println!( "Mode:\t\tSega Megadrive (Ext. Ch. 3 Mode)" ),
|
|
_ => return Err( "invalid file: invalid console format" )?
|
|
};
|
|
|
|
let song_name_len = get_u8( bytes.by_ref() )?;
|
|
println!( "Song name:\t{}", get_string( bytes.by_ref(), song_name_len.into() )? );
|
|
|
|
let song_author_len = get_u8( bytes.by_ref() )?;
|
|
println!( "Song author:\t{}", get_string( bytes.by_ref(), song_author_len.into() )? );
|
|
|
|
// Burn 2 bytes, don't care about the highlight patterns
|
|
skip( bytes.by_ref(), 2 );
|
|
|
|
// DMF format appears to subtract by 1 from the base time shown in the UI
|
|
// Base time is multiplied by speed_a and speed_b so setting it to 1 and
|
|
// getting 0 doesn't make any sense.
|
|
let time_base = get_u8( bytes.by_ref() )? + 1;
|
|
let speed_a = get_u8( bytes.by_ref() )?;
|
|
let speed_b = get_u8( bytes.by_ref() )?;
|
|
|
|
println!( "Time base:\t{}", time_base );
|
|
println!( "Speed A:\t{}", speed_a );
|
|
println!( "Speed B:\t{}", speed_b );
|
|
|
|
let frame_mode = match get_u8( bytes.by_ref() )? {
|
|
0 => FrameMode::Pal,
|
|
1 => FrameMode::Ntsc,
|
|
_ => return Err( "invalid file: invalid frame mode" )?
|
|
};
|
|
|
|
let custom_frame_mode = get_u8( bytes.by_ref() )? == 1;
|
|
let custom_frame_mode_hz_1 = get_u8( bytes.by_ref() )?;
|
|
let custom_frame_mode_hz_2 = get_u8( bytes.by_ref() )?;
|
|
let custom_frame_mode_hz_3 = get_u8( bytes.by_ref() )?;
|
|
|
|
let frame_mode = if custom_frame_mode {
|
|
FrameMode::Custom( custom_frame_mode_hz_1, custom_frame_mode_hz_2, custom_frame_mode_hz_3 )
|
|
} else {
|
|
frame_mode
|
|
};
|
|
|
|
println!( "Frame mode:\t{:?}", frame_mode );
|
|
|
|
let rows_per_pattern = get_u32( bytes.by_ref() )?;
|
|
|
|
println!( "Rows/pattern:\t{}", rows_per_pattern );
|
|
|
|
let patterns_count = get_u8( bytes.by_ref() )?;
|
|
|
|
println!( "Patterns:\t{}", patterns_count );
|
|
|
|
println!( "\nPattern Matrix:" );
|
|
let system_total_channels = if platform == DMF_MD {
|
|
println!( "FM1 FM2 FM3 FM4 FM5 FM6 SN1 SN2 SN3 SN4" );
|
|
10
|
|
} else {
|
|
println!( "FM1 FM2 OP1 OP2 OP3 OP4 FM4 FM5 FM6 SN1 SN2 SN3 SN4" );
|
|
13
|
|
};
|
|
|
|
let mut patterns: Vec<Vec<u8>> = vec![vec![0; system_total_channels]; patterns_count.into()];
|
|
for channel in 0..system_total_channels {
|
|
for pattern in 0..patterns_count {
|
|
let channel: usize = channel.into();
|
|
let pattern: usize = pattern.into();
|
|
patterns[ pattern ][ channel ] = get_u8( bytes.by_ref() )?;
|
|
}
|
|
}
|
|
|
|
for pattern in &patterns {
|
|
for channel in pattern {
|
|
print!( "{:#04X?} ", channel );
|
|
}
|
|
print!( "\n" );
|
|
}
|
|
|
|
print!( "\n" );
|
|
|
|
// TODO !!
|
|
|
|
Ok(
|
|
DmfModule { platform, version, time_base, speed_a, speed_b, frame_mode, rows_per_pattern, patterns }
|
|
)
|
|
}
|
|
|
|
pub fn to_esf( self ) -> Result<Vec<u8>, Box<dyn Error>> {
|
|
// TODO !!
|
|
todo!()
|
|
}
|
|
|
|
} |