commit 81a3dd7ab7740519a1546f99521613d5e60163e5
parent eb1222854cc0be2f761dfa2d7552c24eab42368a
Author: Ryan Jeffrey <ryan@ryanmj.xyz>
Date: Fri, 10 Jun 2022 15:07:47 -0700
Output file format, read/write emulator structs, util file.
Diffstat:
M | src/config.rs | | | 11 | ++--------- |
M | src/main.rs | | | 1 | + |
A | src/memory.rs | | | 44 | ++++++++++++++++++++++++++++++++++++++++++++ |
A | src/r2d2.rs | | | 61 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | src/register.rs | | | 106 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- |
A | src/util.rs | | | 121 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
6 files changed, 323 insertions(+), 21 deletions(-)
diff --git a/src/config.rs b/src/config.rs
@@ -20,11 +20,12 @@ extern crate serde_derive;
extern crate toml;
use std::env;
-use std::env::JoinPathsError;
use std::ffi::OsString;
use std::fmt;
use std::fs;
use std::path::Path;
+use util;
+use util::concat_paths;
use self::serde_derive::{Deserialize, Serialize};
@@ -243,11 +244,3 @@ fn os_string_result_to_strings(r: Result<String, OsString>) -> Result<String, St
Ok(rr) => Ok(rr.to_string()),
}
}
-
-fn concat_paths(base: &String, rest: &String) -> Result<String, String> {
- let p = Path::new(&base).join(&rest);
- match p.to_str() {
- None => Err(format!("{} and {} joined is not valid utf8", base, rest)),
- Some(s) => Ok(s.to_string()),
- }
-}
diff --git a/src/main.rs b/src/main.rs
@@ -20,6 +20,7 @@ mod main_test;
mod config;
mod sdl;
+mod util;
use core::convert::TryInto;
use std::fs;
diff --git a/src/memory.rs b/src/memory.rs
@@ -0,0 +1,44 @@
+// RISC II memory scheme.
+// (C) Ryan Jeffrey <ryan@ryanmj.xyz>, 2022
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// Struct definitions.
+
+use util::File;
+
+/// The real memory of the RISC II emulator.
+struct Memory(Vec<u8>);
+
+// Struct impls.
+
+impl Memory {
+ /// Create a memory object.
+ /// # Arguments
+ /// * `config` - A configuration object that determines the size of
+ /// the memory object.
+ pub fn new(config: &Config) -> Self {
+ Self {
+ 0: vec![0u8; config.mem],
+ }
+ }
+
+ pub fn from_vec(memory: &Vec<u8>) {
+ Self { 0: memory }
+ }
+
+ fn write_to_file(&mut self, file: &mut File) -> Result<(), String> {
+ file.write_vec(&self.0)?;
+ Ok(())
+ }
+}
diff --git a/src/r2d2.rs b/src/r2d2.rs
@@ -0,0 +1,61 @@
+// Binary memdmp format.
+// (C) Ryan Jeffrey <ryan@ryanmj.xyz>, 2022
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// Public function definitions.
+
+use memory::Memory;
+use register::State;
+use util::{concat_paths, get_unix_timestamp, File};
+
+use std::time::{SystemTime, UNIX_EPOCH};
+
+use std::fs::OpenOptions;
+
+/// Write a save file to the cache directory. On success, return the name of
+/// the save file. Return an error string on failure.
+/// # Arguments
+/// * `state` - Register state to save.
+/// * `mem` - Memory state to save.
+pub fn write(config: &Config, state: &State, mem: &Memory) -> Result<String, String> {
+ // TODO human readable dates.
+ let output_file = concat_paths(
+ &config.cache_path,
+ &format!("{:?}.r2d2", get_unix_timestamp()?),
+ )?;
+ let file = File::open_ops(output_file, &OpenOptions::new().write(true))?;
+
+ file.write_buf(&state.to_buf())?;
+ mem.write_to_file(&file)?;
+ Ok(output_file)
+}
+
+pub fn read(config: &Config, which: &String) -> Result<(State, Memory), String> {
+ let mut file = File::open(&which)?;
+ let mut register_state = [0u8; register::TOTAL_NUM_REGISTERS];
+ if file.read(register_state)? < register_state.len() {
+ return Err(format!(
+ "Archive {} is not large enough to have a register window.",
+ which
+ ));
+ }
+
+ let metadata = file.get_metadata()?;
+ let mut memory = vec![0u8; metadata.size() as usize - register::TOTAL_NUM_REGISTERS];
+
+ file.read_into_vec(&mut memory)?;
+
+ // TODO make sure there is no deep copying of memory happening
+ Ok(State::from_buf(register_state), Memory::from_vec(&memory))
+}
diff --git a/src/register.rs b/src/register.rs
@@ -13,19 +13,26 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
-/// The number of register windows the RISCII supports.
-const NUM_WINDOWS: usize = 6;
+/// The number of register window_regs the RISCII supports.
+pub const NUM_WINDOW_REGS: usize = 6;
/// The number of local registers per window.
-const NUM_LOCALS: usize = 10;
+pub const NUM_LOCALS: usize = 10;
/// The number of registers shared with the previous register window (input arguments).
-const NUM_SHARED_PREV: usize = 6;
+pub const NUM_SHARED_PREV: usize = 6;
/// The number of registers shared with the next register window (output arguments).
-const NUM_SHARED_NEXT: usize = 6;
+pub const NUM_SHARED_NEXT: usize = 6;
/// The number of registers per window.
-const WINDOW_SIZE: usize = NUM_LOCALS + NUM_SHARED_PREV + NUM_SHARED_NEXT;
+pub const WINDOW_SIZE: usize = NUM_LOCALS + NUM_SHARED_PREV + NUM_SHARED_NEXT;
/// Number of global registers.
-const NUM_GLOBALS: usize = 10;
-
+pub const NUM_GLOBALS: usize = 10;
+/// Number of general purpose registers that exist in window_regs.
+pub const NUM_WINDOW_REGISTERS: usize = NUM_WINDOW_REGS * (NUM_LOCALS + NUM_SHARED_NEXT);
+/// Number of "special" registers (cwp, swp, sp, etc.).
+pub const NUM_SPECIAL_REGISTERS: usize = 2;
+/// The total number of registers on the system.
+pub const TOTAL_NUM_REGISTERS: usize = NUM_SPECIAL_REGISTERS + NUM_GLOBALS + NUM_WINDOW_REGISTERS;
+/// The size of a register::State object (in bytes).
+pub const SIZEOF_STATE: usize = TOTAL_NUM_REGISTERS * 4;
// Struct definitions.
/// A RISC II 32bit register.
@@ -40,26 +47,101 @@ struct State {
/// Global registers.
globals: [Register; NUM_GLOBALS],
/// Register window stack.
- locals: [Register; NUM_WINDOWS * (NUM_LOCALS + NUM_SHARED_NEXT)],
+ window_regs: [Register; NUM_WINDOW_REGISTERS],
}
// Struct implementations.
impl State {
+ /// Create a 0'd out register window.
pub fn new() -> State {
State {
cwp: 0,
swp: 0,
globals: [0; NUM_GLOBALS],
- locals: [0; NUM_WINDOWS * (NUM_LOCALS + NUM_SHARED_NEXT)],
+ window_regs: [0; NUM_WINDOW_REGISTERS],
}
}
- fn inc_cwp(&self) {
+ /// Create a register state from a buffer.
+ /// # Arguments
+ /// * `buffer` - A byte buffer that is the size of the sum of of register::State's
+ /// members (in bytes) (see `SIZEOF_STATE`).
+ /// The registers should appear in the following order:
+ /// - CWP
+ /// - SWP
+ /// - Global registers
+ /// - Window registers
+ pub fn from_buf(buf: [u8; SIZEOF_STATE]) -> Self {
+ // Offset used for gloabls and window_regs.
+ let mut cur_offset = NUM_SPECIAL_REGISTERS * 4;
+ Self {
+ cwp: u32::from_be_bytes(buf[..4].try_into().unwrap()),
+ swp: u32::from_be_bytes(buf[4..8].try_into().unwrap()),
+ globals: {
+ let mut result = [0u32; NUM_GLOBALS];
+ for _ in NUM_GLOBALS {
+ result[i] =
+ u32::from_be_bytes(buf[cur_offset..cur_offset + 4].try_into().unwrap());
+ cur_offset += 4;
+ }
+ result
+ },
+ window_regs: {
+ let mut result = [0u32; NUM_WINDOW_REGISTERS];
+ for _ in NUM_WINDOW_REGISTERS {
+ result[i] =
+ u32::from_be_bytes(buf[cur_offset..cur_offset + 4].try_into().unwrap());
+ cur_offset += 4;
+ }
+ result
+ },
+ }
+ }
+
+ fn to_buf(&self) -> [u8; SIZEOF_STATE] {
+ let mut result: [u8; SIZEOF_STATE] = [
+ self.cwp.to_be_bytes(),
+ self.swp.to_be_bytes(),
+ {
+ let mut tmp = [u8; NUM_GLOBALS * 4];
+ for i in NUM_GLOBALS {
+ let bytes = self.globals[i].to_be_bytes();
+ tmp[i] = bytes[0];
+ tmp[i + 1] = bytes[1];
+ tmp[i + 1] = bytes[2];
+ tmp[i + 1] = bytes[3];
+ }
+ tmp
+ },
+ {
+ let mut tmp = [u8; NUM_WINDOW_REGISTERS * 4];
+ for i in NUM_WINDOW_REGISTERS {
+ let bytes = self.window_regs[i].to_be_bytes();
+ tmp[i] = bytes[0];
+ tmp[i + 1] = bytes[1];
+ tmp[i + 1] = bytes[2];
+ tmp[i + 1] = bytes[3];
+ }
+ tmp
+ },
+ ];
+ result
+ }
+
+ fn push_reg_window(&mut self) {
self.cwp += 1;
while self.cwp >= self.swp {
- // TODO save the top window into memory.
+ // TODO save the top window_regs into memory.
self.swp += 1;
}
}
+
+ fn pop_reg_window(&mut self) {
+ self.cwp -= 1;
+ while self.swp >= self.cwp {
+ // TODO load window_regs from memory.
+ self.swp -= 1;
+ }
+ }
}
diff --git a/src/util.rs b/src/util.rs
@@ -0,0 +1,121 @@
+// Utility functions.
+// (C) Ryan Jeffrey <ryan@ryanmj.xyz>, 2022
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or (at
+// your option) any later version.
+
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use std::fs;
+use std::fs::{Metadata, OpenOptions};
+use std::io::{Read, Write};
+use std::path::Path;
+use std::time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH};
+
+// Public struct definitions.
+
+pub struct File {
+ file: fs::File,
+ path: String,
+}
+
+// Public function definitions.
+
+pub fn read_file_path(path: &String) -> Result<Vec<u8>, String> {
+ File::open(&path)?.read_file()
+}
+
+pub fn concat_paths(base: &String, rest: &String) -> Result<String, String> {
+ let p = Path::new(&base).join(&rest);
+ match p.to_str() {
+ None => Err(format!("{} and {} joined is not valid utf8", base, rest)),
+ Some(s) => Ok(s.to_string()),
+ }
+}
+
+pub fn get_unix_timestamp() -> Result<Duration, String> {
+ match SystemTime::now().duration_since(UNIX_EPOCH) {
+ Ok(r) => Ok(r),
+ Err(e) => Err(format!("Could not format unix timestamp: {}", e)),
+ }
+}
+
+// Struct impls.
+
+impl File {
+ pub fn open(path: &String) -> Result<Self, String> {
+ match fs::File::open(&path) {
+ Ok(r) => Ok(Self {
+ file: r,
+ path: format!("{}", path),
+ }),
+ Err(e) => Err(format!("Could not open file {}: {}", path, e)),
+ }
+ }
+
+ pub fn open_ops(path: &String, ops: &OpenOptions) -> Result<Self, String> {
+ match ops.open(&path) {
+ Ok(r) => Ok(Self {
+ file: r,
+ path: format!("{}", path),
+ }),
+ Err(e) => Err(format!("Could not open file {}: {}", path, e)),
+ }
+ }
+
+ fn read_into_vec(&mut self, buf: &mut Vec<u8>) -> Result<(), String> {
+ match self.file.read_exact(&mut buf[..]) {
+ Ok(r) => Ok(()),
+ Err(e) => Err(format!("Failed to read file {}, {}", self.path, e)),
+ }
+ }
+
+ fn read_file(&mut self) -> Result<Vec<u8>, String> {
+ let metadata = self.get_metadata()?;
+ let mut result = vec![0u8; metadata.len() as usize];
+ self.read_into_vec(&mut result)?;
+
+ Ok(result)
+ }
+
+ fn get_metadata(&mut self) -> Result<Metadata, String> {
+ match self.file.metadata() {
+ Ok(r) => Ok(r),
+ Err(e) => Err(format!("Could not read metadata for {}: {}", self.path, e)),
+ }
+ }
+
+ fn read(&mut self, buf: &mut [u8]) -> Result<(), String> {
+ match self.file.read_exact(buf) {
+ Ok(r) => Ok(()),
+ Err(e) => Err(format!("Could not read buffer from {}: {}", self.path, e)),
+ }
+ }
+
+ fn write_buf(&mut self, buf: &[u8]) -> Result<(), String> {
+ match self.file.write_all(buf) {
+ Ok(r) => Ok(()),
+ Err(e) => Err(format!(
+ "Could not write byte buffer to {}: {}",
+ self.path, e
+ )),
+ }
+ }
+
+ fn write_vec(&mut self, buf: &Vec<u8>) -> Result<(), String> {
+ match self.file.write_all(&buf[..]) {
+ Ok(r) => Ok(()),
+ Err(e) => Err(format!(
+ "Could not write byte buffer to {}: {}",
+ self.path, e
+ )),
+ }
+ }
+}