From 735cd11a16bfe09282deee18c069b6b82087f003 Mon Sep 17 00:00:00 2001 From: Pedro de Oliveira Date: Wed, 9 Nov 2022 00:47:32 +0000 Subject: [PATCH] First commit --- Cargo.lock | 7 +++ Cargo.toml | 8 +++ README.md | 36 +++++++++++ src/main.rs | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 224 insertions(+) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/main.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..96e8212 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "voctool" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e10cd56 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "voctool" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/README.md b/README.md new file mode 100644 index 0000000..67f51e0 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# Original Lost Eden VOC file + +``` +Voc { type: ASCII, size: 536 } +Voc { type: SoundData, size: 234941, sample_rate: 11111, audio_format: Pcm8BitUnsigned } +Voc { type: Terminator, size: 0 } +``` + +# VOC file created with sox + +`sox pestaninhas.mp3 -r 11111 -b 8 pest.voc` + +``` +Voc { type: SoundData, size: 153252, sample_rate: 11111, audio_format: Pcm8BitUnsigned } +Voc { type: Terminator, size: 0 } +``` + +# VOC file created with ffmpeg + +`ffmpeg -i untitled.wav -ar 11111 -f s8 aids.voc` + +``` +Voc { type: SoundData, size: 487, sample_rate: 11111, audio_format: Pcm8BitUnsigned } +Voc { type: SoundContinue, size: 500 } +Voc { type: SoundContinue, size: 501 } +Voc { type: SoundContinue, size: 500 } +Voc { type: SoundContinue, size: 501 } +Voc { type: SoundContinue, size: 501 } +... +... +Voc { type: SoundContinue, size: 500 } +Voc { type: SoundContinue, size: 160 } +Voc { type: SoundContinue, size: 17 } +Voc { type: Terminator, size: 0 } +Found block type 0. Exiting +``` \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..ea3ec0c --- /dev/null +++ b/src/main.rs @@ -0,0 +1,173 @@ +use std::fmt; +use std::fs::File; +use std::io::{Read, Seek, SeekFrom}; + +#[derive(Debug)] +enum Compression { + Pcm8BitUnsigned, + Adpcm4to8, + Adpcm3to8, + Adpcm2to8, + Pcm16BitSigned, + Alaw1, + Alaw2, + Adpcm4to16 +} + +#[derive(Debug, PartialEq)] +enum BlockType { + Terminator, + SoundData, + SoundContinue, + Silence, + Marker, + ASCII, + Repeat, + EndRepeat +} + +struct DataBlock { + block_type: BlockType, + block_size: i32, + sample_rate: Option, + audio_format: Option, + data: Vec, +} + +impl fmt::Debug for DataBlock { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.block_type == BlockType::SoundData { + f.debug_struct("Voc") + .field("type", &self.block_type) + .field("size", &self.block_size) + .field("sample_rate", &self.sample_rate.unwrap()) + .field("audio_format", &self.audio_format.as_ref().unwrap()) + .finish() + } else { + f.debug_struct("Voc") + .field("type", &self.block_type) + .field("size", &self.block_size) + .finish() + } + } +} + +fn parse_voc(file_name: &str) { + println!("Parsing {} ...", file_name); + let mut fp = File::open(file_name).unwrap(); + + // Signature - Creative Voice File + let mut signature1_buffer: [u8; 19] = [0; 19]; + std::mem::drop(fp.read(&mut signature1_buffer)); + let signature1_expected = "Creative Voice File"; + let signature1 = std::str::from_utf8(&signature1_buffer).unwrap(); + if signature1 != signature1_expected { + panic!("Bad file. Expected \"{}\" got \"{}\"", signature1_expected, signature1); + } + + // Signature - 1A 1A 00 + let mut signature2_buffer: [u8; 3] = [0; 3]; + std::mem::drop(fp.read(&mut signature2_buffer)); + let signature2_expected: [u8; 3] = [0x1A, 0x1A, 0x0]; + if signature2_buffer != signature2_expected { + panic!("Bad file. Expected {:02X?} got {:02X?}", signature2_expected, signature2_buffer); + } + + // Version + let mut version_buffer: [u8; 2] = [0; 2]; + std::mem::drop(fp.read(&mut version_buffer)); + let version_int = i16::from_le_bytes(version_buffer); + let version = (version_buffer[1], version_buffer[0]); + + // Version checksum + let mut checksum_buffer: [u8; 2] = [0; 2]; + std::mem::drop(fp.read(&mut checksum_buffer)); + let checksum = (!version_int + 0x1234).to_le_bytes(); + if checksum_buffer != checksum { + panic!("Bad file. Expected {:02X?} got {:02X?}", checksum, checksum_buffer); + } + + loop { + let mut block_type_buffer: [u8; 1] = [0]; + std::mem::drop(fp.read(&mut block_type_buffer)); + let block_type = match block_type_buffer[0] { + 0 => BlockType::Terminator, + 1 => BlockType::SoundData, + 2 => BlockType::SoundContinue, + 3 => BlockType::Silence, + 4 => BlockType::Marker, + 5 => BlockType::ASCII, + 6 => BlockType::Repeat, + 7 => BlockType::EndRepeat, + _ => panic!("Bad block type. Got {}", block_type_buffer[0]) + }; + + if block_type == BlockType::Terminator { + let data_block = DataBlock { block_type, block_size: 0, sample_rate: None, audio_format: None, data: Vec::new() }; + println!("{:?}", data_block); + println!("Found block type 0. Exiting"); + break; + } + + let mut block_size_buffer: [u8; 3] = [0; 3]; + std::mem::drop(fp.read(&mut block_size_buffer)); + let block_size = i32::from_le_bytes( + [block_size_buffer[0], block_size_buffer[1], block_size_buffer[2], 0] + ); + + let mut address = fp.seek(SeekFrom::Current(0)).unwrap() as i32; + let next = address + block_size; + + let mut data_block: DataBlock; + + match block_type { + BlockType::SoundData => { + let mut sample_rate_buffer: [u8; 1] = [0]; + std::mem::drop(fp.read(&mut sample_rate_buffer)); + let sample_rate: i32 = 1000000i32 / (256i32 - sample_rate_buffer[0] as i32); + + let mut audio_format_buffer: [u8; 1] = [0]; + std::mem::drop(fp.read(&mut audio_format_buffer)); + let audio_format = match audio_format_buffer[0] { + 0 => Compression::Pcm8BitUnsigned, + 1 => Compression::Adpcm4to8, + 2 => Compression::Adpcm3to8, + 3 => Compression::Adpcm2to8, + 4 => Compression::Pcm16BitSigned, + 5 => Compression::Alaw1, + 6 => Compression::Alaw2, + 7 => Compression::Adpcm4to16, + _ => panic!("Bad Sound format. Got {}", audio_format_buffer[0]) + }; + + address = fp.seek(SeekFrom::Current(0)).unwrap() as i32; + let mut data: Vec = vec![0; (next - address) as usize]; + std::mem::drop(fp.read(&mut data)); + + data_block = DataBlock { block_type, block_size, sample_rate: Some(sample_rate), audio_format: Some(audio_format), data }; + }, + BlockType::SoundContinue => { + let mut data: Vec = vec![0; (next - address) as usize]; + std::mem::drop(fp.read(&mut data)); + + data_block = DataBlock { block_type, block_size, sample_rate: None, audio_format: None, data }; + }, + BlockType::ASCII => { + let mut data: Vec = vec![0; (next - address) as usize]; + std::mem::drop(fp.read(&mut data)); + + data_block = DataBlock { block_type, block_size, sample_rate: None, audio_format: None, data }; + }, + _ => panic!("block Type {:?} not implemented", block_type), + } + println!("{:?}", data_block); + } +} + +fn main() { + parse_voc("_C24FF78A.voc"); + parse_voc("pest.voc"); + parse_voc("C24FF78A.voc"); + + parse_voc("pest2.voc"); +}