mod tests;
pub mod helpers;
pub mod kfn_data;
pub mod kfn_header;
pub mod kfn_ini;
use std::fs;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::string::FromUtf8Error;
use regex::Regex;
use dbg_hex::dbg_hex;
use crate::helpers::Entry;
use crate::helpers::file_type::FileType;
use crate::helpers::file_type::ToBinary;
use crate::kfn_header::KfnHeader;
use kfn_data::KfnData;
#[derive(Debug)]
pub struct Kfn {
file_data: Vec<u8>,
read_head: usize,
pub header: KfnHeader,
pub data: KfnData,
}
impl Kfn {
pub fn read(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 dump(&mut self) -> Result<bool, FromUtf8Error> {
let signature = String::from_utf8(self.read_bytes(4))?;
if signature != "KFNB" {
panic!("Bad signature error");
}
loop {
let signature = String::from_utf8(self.read_bytes(4))?;
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;
}
}
dbg_hex!(&self.header);
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(e) => return Err(e),
};
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();
Ok(true)
}
pub fn get_syncs(&mut self) -> Vec<usize> {
let mut syncs: Vec<usize> = Vec::new();
let contents_raw = fs::read_to_string(&self.data.path_song_ini).unwrap();
let contents: Vec<&str> = contents_raw.split("\n").collect();
for line in contents {
let re = Regex::new(r"^Sync\d+=(.*)$").unwrap();
if re.is_match(line) {
let syncline_str = re.captures(line).unwrap().get(1).map_or("", |m| m.as_str());
let mut syncline_split: Vec<usize> = syncline_str.split(",").map(|n| usize::from_str_radix(n, 10).unwrap()).collect();
syncs.append(&mut syncline_split);
}
}
syncs
}
pub fn get_text(&mut self) -> Vec<String> {
let mut text: Vec<String> = Vec::new();
let contents_raw = fs::read_to_string(&self.data.path_song_ini).unwrap();
let contents: Vec<&str> = contents_raw.split("\n").collect();
for line in contents {
let re = Regex::new(r"^Text\d+=(.*)$").unwrap();
if re.is_match(line) {
let textline_str = re.captures(line).unwrap().get(1).map_or("", |m| m.as_str());
let mut textline_split: Vec<String> = textline_str.split(&['/', ' ', '\n']).map(|s| s.to_string()).collect();
if textline_str != "" {
text.append(&mut textline_split);
}
}
}
text
}
pub fn add_file(&mut self, source: &str) {
self.data.add_entry_from_file(source);
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 remove_file(&mut self, target: &str) {
self.data.remove_entry_by_name(target);
self.update();
}
fn update(&mut self) {
self.data.song.populate_from_header(self.header.clone());
self.data.update_ini();
}
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_word(&mut self) -> u16 {
let b1 = self.read_byte() as u16;
let b2 = self.read_byte() as u16;
b2 << 8 | b1
}
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
}
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);
}
}
fn extract(&mut self, entry: Entry, output_filename: String) {
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(&self.file_data[entry.offset..entry.offset+entry.len1]);
output.write_all(&buf).unwrap();
}
}