#![cfg(windows)] #![forbid(missing_debug_implementations)] #![allow(non_upper_case_globals)] extern crate winapi; use winapi::shared::minwindef::{FALSE, HMODULE}; use winapi::um::libloaderapi::{FreeLibrary, GetProcAddress, LoadLibraryW}; use winapi::um::winbase::CopyFileW; extern crate linefeed; use linefeed::{ReadResult, Reader}; extern crate terminal_size; use terminal_size::{terminal_size, Height, Width}; extern crate trivial_colours; extern crate randomize; extern crate vibha; use vibha::*; // std library stuff use std::ffi::{OsStr, OsString}; use std::io::Write; use std::os::raw::{c_char, c_void}; use std::os::windows::ffi::OsStrExt; static game_init_name: &'static [u8] = b"game_init\0"; static game_process_line_name: &'static [u8] = b"game_process_line\0"; static game_drop_name: &'static [u8] = b"game_drop\0"; /// A loaded game DLL. #[derive(Debug)] struct GameDLL { dll_handle: HMODULE, game_init: GameInitFn, game_process_line: GameProcessLineFn, game_drop: GameDropFn, } impl Drop for GameDLL { fn drop(&mut self) { unsafe { FreeLibrary(self.dll_handle) }; } } /// The kinds of problems you might expect to have when loading a game. #[derive(Debug)] enum DLLProblem { NoDLL, FunctionsMission, } impl GameDLL { pub fn new(dll_name: &OsStr) -> Result { let name_wide: Vec = dll_name.encode_wide().chain(Some(0)).collect(); let _temp = OsString::from("_temp"); let name_wide_temp: Vec = dll_name.encode_wide().chain(_temp.encode_wide()).chain(Some(0)).collect(); unsafe { CopyFileW(name_wide.as_ptr(), name_wide_temp.as_ptr(), FALSE) }; let dll_handle = unsafe { LoadLibraryW(name_wide_temp.as_ptr()) }; if dll_handle.is_null() { Err(DLLProblem::NoDLL) } else { let game_init: Option = unsafe { std::mem::transmute(GetProcAddress(dll_handle, game_init_name.as_ptr() as *const c_char)) }; let game_process_line: Option = unsafe { std::mem::transmute(GetProcAddress(dll_handle, game_process_line_name.as_ptr() as *const c_char)) }; let game_drop: Option = unsafe { std::mem::transmute(GetProcAddress(dll_handle, game_drop_name.as_ptr() as *const c_char)) }; if game_init.is_some() && game_process_line.is_some() && game_drop.is_some() { Ok(GameDLL { dll_handle, game_init: game_init.unwrap(), game_process_line: game_process_line.unwrap(), game_drop: game_drop.unwrap(), }) } else { unsafe { FreeLibrary(dll_handle) }; Err(DLLProblem::FunctionsMission) } } } pub fn spawn_session(&self, seed: u64) -> Option { let ptr = unsafe { (self.game_init)(seed) }; if ptr.is_null() { None } else { Some(GameSession { game_dll: self, _seed: seed, ptr, }) } } } /// An individual game session of some game. /// /// You get new game sessions from `GameDLL::spawn_session`. struct GameSession<'a> { game_dll: &'a GameDLL, _seed: u64, ptr: *mut c_void, } impl<'a> Drop for GameSession<'a> { fn drop(&mut self) { unsafe { (self.game_dll.game_drop)(self.ptr) } } } impl<'a> GameSession<'a> { pub fn process_line<'s>(&mut self, line: &str, out_buffer: &'s mut [u8]) -> usize { let bytes_written = unsafe { (self.game_dll.game_process_line)(self.ptr, line.as_ptr(), line.len(), out_buffer.as_mut_ptr(), out_buffer.len()) }; let cow_output = String::from_utf8_lossy(&out_buffer[0..bytes_written]); let modal_slices = mode_split_str(cow_output.as_ref()); if modal_slices.len() > 0 { println!(); for modal_slice in modal_slices { match modal_slice { ModalSlice::Text(s) => print!("{}", s), FOREGROUND_BLACK => print!("{}", trivial_colours::Colour::Black), FOREGROUND_RED => print!("{}", trivial_colours::Colour::Red), FOREGROUND_GREEN => print!("{}", trivial_colours::Colour::Green), FOREGROUND_YELLOW => print!("{}", trivial_colours::Colour::Yellow), FOREGROUND_BLUE => print!("{}", trivial_colours::Colour::Blue), FOREGROUND_MAGENTA => print!("{}", trivial_colours::Colour::Magenta), FOREGROUND_CYAN => print!("{}", trivial_colours::Colour::Cyan), FOREGROUND_WHITE => print!("{}", trivial_colours::Colour::White), BACKGROUND_BLACK => print!("{}", trivial_colours::BgColour::Black), BACKGROUND_RED => print!("{}", trivial_colours::BgColour::Red), BACKGROUND_GREEN => print!("{}", trivial_colours::BgColour::Green), BACKGROUND_YELLOW => print!("{}", trivial_colours::BgColour::Yellow), BACKGROUND_BLUE => print!("{}", trivial_colours::BgColour::Blue), BACKGROUND_MAGENTA => print!("{}", trivial_colours::BgColour::Magenta), BACKGROUND_CYAN => print!("{}", trivial_colours::BgColour::Cyan), BACKGROUND_WHITE => print!("{}", trivial_colours::BgColour::White), ModalSlice::Header(h) => { if h.starts_with(VIBHA_VERSION_PREFIX) { println!("[DEBUG]:got vibha version {}", &h[VIBHA_VERSION_PREFIX.len()..h.len()]); } else { println!("[UKH]: line {}, header {}", line, h); } } } // We must immediately flush our changes so that the color color // change values have their `fmt` method called on them, // triggering the actual console color change. std::io::stdout().flush().ok(); } println!("\n{}{}", trivial_colours::Colour::White, trivial_colours::BgColour::Black); if bytes_written == out_buffer.len() { println!("[WARNING]: last output hit the buffer limit."); } } bytes_written } pub fn process_line_quiet<'s>(&mut self, line: &str, out_buffer: &'s mut [u8]) -> usize { let bytes_written = unsafe { (self.game_dll.game_process_line)(self.ptr, line.as_ptr(), line.len(), out_buffer.as_mut_ptr(), out_buffer.len()) }; let cow_output = String::from_utf8_lossy(&out_buffer[0..bytes_written]); let modal_slices = mode_split_str(cow_output.as_ref()); if modal_slices.len() > 0 { for modal_slice in modal_slices { match modal_slice { ModalSlice::Text(_) => {} FOREGROUND_BLACK => {} FOREGROUND_RED => {} FOREGROUND_GREEN => {} FOREGROUND_YELLOW => {} FOREGROUND_BLUE => {} FOREGROUND_MAGENTA => {} FOREGROUND_CYAN => {} FOREGROUND_WHITE => {} BACKGROUND_BLACK => {} BACKGROUND_RED => {} BACKGROUND_GREEN => {} BACKGROUND_YELLOW => {} BACKGROUND_BLUE => {} BACKGROUND_MAGENTA => {} BACKGROUND_CYAN => {} BACKGROUND_WHITE => {} ModalSlice::Header(h) => { if h.starts_with(VIBHA_VERSION_PREFIX) { // do nothing we're in quiet mode } else { println!("[UKH]: line {}, header {}", line, h); } } } } if bytes_written == out_buffer.len() { println!("[WARNING]: last output hit the buffer limit."); } } bytes_written } } fn main() { let dll_name = match std::env::args_os().nth(1) { Some(dll_string) => dll_string, None => { eprintln!( "{}USAGE:{} zavis [dll_name]", trivial_colours::Colour::Red, trivial_colours::Colour::White ); return; } }; let mut reader = Reader::new("Zavis").unwrap(); reader.set_prompt("> "); if let Some((Width(w), Height(h))) = terminal_size() { println!("Terminal size: {} wide, {} tall", w, h); } else { println!("Unable to get terminal size"); } let intro_signal_string = INTRO_HEADER.to_string(); let color_available_signal_string = ANSI_COLOR_AVAILABLE_HEADER.to_string(); let mut skein: Vec = vec![color_available_signal_string.clone(), intro_signal_string.clone()]; let mut skein_is_fluid = true; let mut next_load_quiet = false; let mut seed = randomize::u64_from_time(); // This value is just some "very big" default buffer size so that we almost // never hit the cap. The exact figure comes from 24 lines * 80 cols * 6 bytes // per character cell, rounded up some. const OUT_BUFFER_CAPACITY: usize = 12_000; let mut output_buffer = vec![0u8; OUT_BUFFER_CAPACITY]; 'loadgame: loop { let game = match GameDLL::new(dll_name.as_ref()) { Ok(game) => game, Err(e) => { eprintln!("Could not load {:?}: {:?}", &dll_name, e); eprintln!("Exiting."); return; } }; let mut biggest_output = 0; 'startsession: loop { let mut session = game.spawn_session(seed).expect("Couldn't start a session!"); if next_load_quiet { println!("***Replaying skein quietly... please wait...\n"); next_load_quiet = false; for line in &skein { biggest_output = biggest_output.max(session.process_line_quiet(line, &mut output_buffer)); } } else { println!("***Replaying skein... please wait...\n"); for line in &skein { println!("skein== {}", line); biggest_output = biggest_output.max(session.process_line(line, &mut output_buffer)); } } while let Ok(ReadResult::Input(input)) = reader.read_line() { if reader.history_len() > 0 && reader.history().nth(reader.history_len() - 1) != Some(input.as_ref()) { reader.add_history(input.clone()); } else if reader.history_len() == 0 { reader.add_history(input.clone()); } match input.as_ref() { "`f" => { skein_is_fluid = false; println!("Freezing the skein"); } "`t" => { skein_is_fluid = true; println!("Thawing the skein"); } "`scoop" => { println!("Scooped out a skein entry: {:?}", skein.pop()); } "`r" => { println!("***Reloading the DLL..."); continue 'loadgame; } "`rq" => { println!("***Reloading the DLL quietly..."); next_load_quiet = true; continue 'loadgame; } "`c" => { println!("***Clearing the session..."); skein.clear(); skein.push(color_available_signal_string.clone()); skein.push(intro_signal_string.clone()); skein_is_fluid = true; continue 'startsession; } "`s" => { println!("***Scratching this universe..."); skein.clear(); skein.push(color_available_signal_string.clone()); skein.push(intro_signal_string.clone()); skein_is_fluid = true; seed = randomize::u64_from_time(); continue 'loadgame; } "`seed" => { println!("The current seed is {}", seed); } "`biggest" => { println!("Biggest output during this DLL load cycle: {}", biggest_output); if biggest_output == output_buffer.len() { println!("(Warning: that equals the buffer size)\n"); } } _ => { if skein_is_fluid { skein.push(input.clone()); } biggest_output = biggest_output.max(session.process_line(&input, &mut output_buffer)); } } } // If the user canceled input, we break out of the outer loop and exit. break 'loadgame; } } println!("\nExiting..."); }