mod tests;
pub mod helpers;
pub mod kfn_data;
pub mod kfn_header;
pub mod kfn_ini;
use std::io::Cursor;
use std::fs;
use std::fs::File;
use std::io::BufReader;
use std::io::Write;
use std::path::Path;
use std::sync::mpsc::Receiver;
use std::sync::mpsc::Sender;
use std::sync::mpsc::channel;
use std::thread;
use std::time::Instant;
use crate::helpers::Entry;
use crate::helpers::file_type::FileType;
use crate::helpers::file_type::ToBinary;
use rodio::{OutputStream, Sink};
use crate::kfn_header::KfnHeader;
use kfn_data::KfnData;
use derivative::Derivative;
#[derive(Derivative)]
#[derivative(Debug)]
pub struct Kfn {
file_data: Vec<u8>,
read_head: usize,
pub header: KfnHeader,
pub data: KfnData,
}
#[derive(Debug)]
pub enum KfnParseError {
InvalidHeaderSignature(String),
Utf8ConversionError,
}
impl Kfn {
pub fn open(filename: &str) -> Self {
Self {
file_data: match fs::read(filename) {
Ok(file) => file,
Err(e) => panic!("File not found! {}", e),
},
read_head: usize::default(),
header: KfnHeader::default(),
data: KfnData::new(),
}
}
pub fn new() -> Self {
Self {
file_data: Vec::new(),
read_head: 0,
header: KfnHeader::default(),
data: KfnData::new(),
}
}
pub fn parse(&mut self) -> Result<bool, KfnParseError> {
let signature = match String::from_utf8(self.read_bytes(4)) {
Ok(s) => s,
Err(_) => return Err(KfnParseError::Utf8ConversionError),
};
if signature != "KFNB" {
return Err(KfnParseError::InvalidHeaderSignature(signature));
}
loop {
let signature = match String::from_utf8(self.read_bytes(4)) {
Ok(s) => s,
Err(_) => return Err(KfnParseError::Utf8ConversionError),
};
let l_type = self.read_byte();
let len_or_value = self.read_dword();
match l_type {
1 => {
match signature.as_str() {
"DIFM" => {
self.header.diff_men = len_or_value;
},
"DIFW" => {
self.header.diff_women = len_or_value;
},
"GNRE" => {
self.header.genre = len_or_value;
},
"SFTV" => {
self.header.sftv = len_or_value;
},
"MUSL" => {
self.header.musl = len_or_value;
},
"ANME" => {
self.header.anme = len_or_value;
},
"TYPE" => {
self.header.kfn_type = len_or_value;
},
"RGHT" => {
self.header.rght = len_or_value;
},
"PROV" => {
self.header.prov = len_or_value;
},
_ => println!("{}, type 1, value {:x}", signature, len_or_value),
}
},
2 => {
let buffer = self.read_bytes(len_or_value);
let buffer_str = String::from_utf8(buffer.clone()).unwrap_or("Unknown".to_string());
match signature.as_str() {
"FLID" => {
self.header.flid = buffer_str;
},
"LANG" => {
self.header.language = buffer_str
},
"TITL" => {
self.header.title = buffer_str
},
"ALBM" => {
self.header.album = buffer_str
},
"ARTS" => {
self.header.artist = buffer_str
},
"COMP" => {
self.header.composer = buffer_str
},
"COPY" => {
self.header.copyright = buffer_str
},
"SORC" => {
self.header.source_file = buffer_str
},
"YEAR" => {
self.header.year = buffer_str
},
"TRAK" => {
self.header.trak = buffer_str
},
"KFNZ" => {
self.header.karafunizer = buffer_str
},
"IDUS" => {
self.header.idus = buffer_str
},
_ => ()
}
},
_ => {
},
}
if signature == "ENDH" {
break;
}
}
let num_files = self.read_dword();
println!("# of files: {}", num_files);
for _ in 0..num_files {
let filename_len = self.read_dword();
let filename = match String::from_utf8(self.read_bytes(filename_len)) {
Ok(s) => s,
Err(_) => return Err(KfnParseError::Utf8ConversionError),
};
let file_type = FileType::from(self.read_dword());
let len1 = self.read_dword() as usize;
let offset = self.read_dword() as usize;
let len2 = self.read_dword() as usize;
let flags = self.read_dword() as usize;
let buf: Vec<u8> = Vec::default();
self.data.entries.push(Entry {
filename, file_type, len1, offset, len2, flags, file_bin: buf,
});
}
self.data.offset_dir_end = self.read_head;
for i in 0..self.data.entries.len() {
self.data.entries[i].offset += self.data.offset_dir_end;
self.data.entries[i].file_bin =
Vec::from(
&self.file_data[
self.data.entries[i].offset
..
self.data.entries[i].offset + self.data.entries[i].len1
]
);
}
self.data.read_ini();
self.data.song.read_eff();
Ok(true)
}
pub fn add_file(&mut self, source: &str) {
self.data.add_entry_from_file(source);
self.update();
}
pub fn remove_file(&mut self, target: &str) {
self.data.remove_entry_by_name(target);
self.update();
}
pub fn set_source(&mut self, target: &str) {
self.header.source_file = target.to_string();
self.data.song.set_source(target);
self.update();
}
pub fn update(&mut self) {
self.data.song.populate_from_header(self.header.clone());
self.data.update_ini();
}
pub fn get_texts_and_syncs(&self) -> Vec<(usize, String)> {
let mut texts_and_syncs: Vec<(usize, String)> = Vec::new();
for eff in &self.data.song.effs {
if eff.id >= 51 {
continue;
}
let mut texts: Vec<String> = Vec::new();
for text in &eff.texts {
if text == "" {
}
if text.contains("/") || text.contains(" ") {
let mut words: Vec<String> = text.split(&['/', ' '][..]).collect::<Vec<&str>>().iter().map(|s| s.to_string()).collect();
texts.append(&mut words);
}
}
if eff.texts.len() > 0 && eff.syncs.len() > 0 {
for i in 0..eff.syncs.len()-1 {
texts_and_syncs.push((eff.syncs[i], texts[i].clone()))
}
}
}
texts_and_syncs
}
pub fn play(&mut self) -> (Sender<String>, Receiver<String>) {
let (sender_player, receiver_caller) = channel();
let (sender_caller, _receiver_player) = channel();
let cursor: Cursor<Vec<u8>> = Cursor::new(self.data.get_entry_by_name(&self.data.song.get_source_name()).unwrap().file_bin);
let syncs_times = self.get_texts_and_syncs();
thread::spawn(move || {
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
let sink = Sink::try_new(&stream_handle).unwrap();
sink.append(rodio::Decoder::new(BufReader::new(cursor)).unwrap());
let start = Instant::now();
let mut i = 0;
loop {
if (syncs_times[i].0 * 10) as u128 == start.elapsed().as_millis() {
sender_player.send(syncs_times[i].1.clone()).unwrap();
sender_player.send(format!("sync {} elapsed {}", syncs_times[i].0 * 10, start.elapsed().as_millis())).unwrap();
i += 1;
}
}
});
(sender_caller, receiver_caller)
}
pub fn export(&mut self, filename: &str) {
let mut data: Vec<u8> = Vec::new();
data.append(&mut self.header.to_binary());
data.append(&mut self.data.to_binary());
fs::write(filename, data).unwrap();
}
pub fn extract_all(&mut self, target_dir: &str) {
for i in 0..self.data.entries.len() {
let mut filename = target_dir.to_string();
filename.push_str(&self.data.entries[i].clone().filename.to_string());
self.extract(self.data.entries[i].clone(), &filename);
}
}
pub fn extract(&mut self, entry: Entry, output_filename: &str) {
let path = Path::new(&output_filename);
let prefix = path.parent().unwrap();
fs::create_dir_all(prefix).unwrap();
let mut output = File::create(path).unwrap();
let buf: Vec<u8> = Vec::from(entry.file_bin);
output.write_all(&buf).unwrap();
}
fn read_byte(&mut self) -> u8 {
let result = self.file_data[self.read_head as usize];
self.read_head += 1;
(result & 0xFF).into()
}
fn read_dword(&mut self) -> u32 {
let b1 = self.read_byte() as u32;
let b2 = self.read_byte() as u32;
let b3 = self.read_byte() as u32;
let b4 = self.read_byte() as u32;
b4 << 24 | b3 << 16 | b2 << 8 | b1
}
fn read_bytes(&mut self, length: u32) -> Vec<u8> {
let mut array: Vec<u8> = Vec::with_capacity(length as usize);
for _ in 0..length {
array.push(self.read_byte());
}
array
}
}