Back up to VCS

This commit is contained in:
baltdev 2024-04-05 15:57:58 -05:00
commit fe62f96006
11 changed files with 714 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

11
.idea/apple-one.iml Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/apple-one.iml" filepath="$PROJECT_DIR$/.idea/apple-one.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

271
Cargo.lock generated Normal file
View File

@ -0,0 +1,271 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "apple-one"
version = "0.1.0"
dependencies = [
"crossterm",
"r6502",
]
[[package]]
name = "autocfg"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "crossterm"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
dependencies = [
"bitflags 2.5.0",
"crossterm_winapi",
"libc",
"mio",
"parking_lot",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
dependencies = [
"winapi",
]
[[package]]
name = "libc"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "lock_api"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "mio"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys",
]
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets",
]
[[package]]
name = "r6502"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "757565cafd699d777dee11fba136fb84cbe22817302426bef5901709dbf5e1f4"
dependencies = [
"bitflags 2.5.0",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "signal-hook"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
dependencies = [
"libc",
"mio",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc",
]
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"

10
Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "apple-one"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
r6502 = "1.1.1"
crossterm = "0.27"

BIN
src/basic.rom Normal file

Binary file not shown.

8
src/constants.rs Normal file
View File

@ -0,0 +1,8 @@
pub mod variables {
pub const KBD: u16 = 0xD010;
pub const KBD_CR: u16 = 0xD011;
pub const DSP: u16 = 0xD012;
pub const DSP_CR: u16 = 0xD013;
}

391
src/main.rs Normal file
View File

@ -0,0 +1,391 @@
// TODO: ACIE
#![warn(clippy::pedantic, clippy::perf)]
#![allow(clippy::too_many_lines)]
mod constants;
use crossterm::{
cursor::{EnableBlinking, MoveTo, MoveToNextLine},
event::{poll, read, Event, KeyCode, KeyEvent, KeyEventKind},
execute,
style::Print,
terminal::{
disable_raw_mode, enable_raw_mode, Clear, ClearType, DisableLineWrap, EnableLineWrap,
EnterAlternateScreen, LeaveAlternateScreen,
},
};
use r6502::{Emulator, FunctionReadCallback, FunctionWriteCallback, Opcode, State};
use std::{
cell::RefCell,
fs, io,
path::PathBuf,
process::ExitCode,
rc::Rc,
time::{Duration, Instant},
};
fn main() -> ExitCode {
let Err(err) = inner_main() else {
disable_raw_mode().expect("failed to disable raw mode");
execute!(io::stdout(), LeaveAlternateScreen, EnableLineWrap)
.expect("failed to clear alternate screen");
return ExitCode::SUCCESS;
};
disable_raw_mode().expect("failed to disable raw mode");
execute!(io::stdout(), LeaveAlternateScreen, EnableLineWrap)
.expect("failed to clear alternate screen");
eprintln!("io error: {err}");
ExitCode::FAILURE
}
struct EmulatorState {
keyboard_ready: bool,
keyboard_register: u8,
display_x: u8,
display_y: u8,
display_reg: u8,
display_buffer: [u8; 40 * 24],
old_display_buffer: Option<[u8; 40 * 24]>,
paused: bool,
step: bool,
step_counter: u16,
last_display: Instant,
}
static CHARACTER_SET: &[u8] = br##"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ !"#$%&'()*+,-./0123456789:;<=>?"##;
fn inner_main() -> io::Result<()> {
execute!(
io::stdout(),
EnterAlternateScreen,
DisableLineWrap,
EnableBlinking,
Clear(ClearType::All)
)?;
enable_raw_mode()?;
let emu_state = Rc::new(RefCell::new(EmulatorState {
keyboard_ready: false,
keyboard_register: 0b1000_0000,
display_x: 0,
display_y: 0,
display_reg: 0b0000_0000,
display_buffer: [b' '; 40 * 24],
old_display_buffer: None,
paused: false,
step: false,
step_counter: 0,
last_display: Instant::now()
}));
let mut emulator = Emulator::default()
.with_program_counter(0xFF00)
.with_rom_from(include_bytes!("basic.rom"), 0xE000)
.with_rom_from(include_bytes!("wozmon.rom"), 0xFF00)
.with_write_callback(FunctionWriteCallback(
|state: &mut State, addr, mut byte| {
if emu_state.borrow_mut().step {
let _ = execute!(
io::stdout(),
Print(format!("WRITE {addr:04X} {byte:02X}")),
MoveToNextLine(1)
);
}
match addr {
0..=0x7FFF => state.memory[addr as usize] = byte,
constants::variables::DSP => {
let mut emu_state = emu_state.borrow_mut();
byte &= 0b0111_1111;
emu_state.display_reg = byte | 0b1000_0000;
emu_state.last_display = Instant::now();
match byte {
0x00 | 0x7F => {}
b'\n' | b'\r' => {
emu_state.old_display_buffer = Some(emu_state.display_buffer);
emu_state.display_x = 0;
emu_state.display_y += 1;
}
other => {
emu_state.old_display_buffer = Some(emu_state.display_buffer);
let index = (emu_state.display_y as usize) * 40
+ (emu_state.display_x as usize);
emu_state.display_buffer[index] = other;
emu_state.display_x += 1;
}
}
if emu_state.display_x == 40 {
if emu_state.old_display_buffer.is_none() {
emu_state.old_display_buffer = Some(emu_state.display_buffer);
}
emu_state.display_x = 0;
emu_state.display_y += 1;
}
if emu_state.display_y > 23 {
if emu_state.old_display_buffer.is_none() {
emu_state.old_display_buffer = Some(emu_state.display_buffer);
}
emu_state.display_y = 23;
// Copy the screen buffer up for a new line
emu_state.display_buffer.rotate_left(40);
emu_state.display_buffer[40 * 23..].fill(b' ');
}
}
_ => {} // ROM
}
},
))
.with_read_callback(FunctionReadCallback(|state: &mut State, addr| {
let res = match addr {
constants::variables::KBD => {
let mut emu_state = emu_state.borrow_mut();
emu_state.keyboard_ready = false;
emu_state.keyboard_register
}
constants::variables::KBD_CR => {
if emu_state.borrow_mut().keyboard_ready {
0xa7
} else {
0x00
}
}
constants::variables::DSP => emu_state.borrow_mut().display_reg,
constants::variables::DSP_CR => 0b1000_0000,
_ => state.memory[addr as usize],
};
if emu_state.borrow_mut().step {
let _ = execute!(
io::stdout(),
Print(format!("READ {addr:04X} {res:02X}")),
MoveToNextLine(1)
);
}
res
}));
let mut last = Instant::now();
loop {
let mut emu_state = emu_state.borrow_mut();
emu_state.step = false;
if poll(Duration::ZERO)? {
let event = read()?;
if let Event::Key(KeyEvent {
code,
kind: KeyEventKind::Press,
..
}) = event
{
match keycode_ascii(code) {
Ok(ascii) => {
emu_state.keyboard_ready = true;
emu_state.keyboard_register = ascii | 0b1000_0000;
}
Err(KeyCode::End | KeyCode::F(1)) => {
// Stop
break Ok(());
}
Err(KeyCode::Pause | KeyCode::F(2)) => {
// Pause
emu_state.paused ^= true;
emu_state.step_counter = 0;
emu_state.old_display_buffer = Some(emu_state.display_buffer);
}
Err(KeyCode::Home | KeyCode::F(3)) => {
// Reset
let reset = u16::from_le_bytes([
emulator.state.memory[0xFFFC],
emulator.state.memory[0xFFFD],
]);
emulator.state.program_counter = reset;
}
Err(KeyCode::Insert | KeyCode::F(4)) => {
// Clear screen
emu_state.display_buffer = [b' '; 40 * 24];
emu_state.display_x = 0;
emu_state.display_y = 0;
emu_state.step_counter = 0;
emu_state.old_display_buffer = Some([b'@'; 40 * 24]);
execute!(io::stdout(), Clear(ClearType::All))?;
}
Err(KeyCode::F(5)) => {
disable_raw_mode()?;
// Load a file
execute!(
io::stdout(),
MoveTo(0, 32),
EnableLineWrap,
Print("File path?")
)?;
let mut contents = None;
while contents.is_none() {
execute!(io::stdout(), MoveTo(0, 33), Clear(ClearType::CurrentLine))?;
let mut path_str = String::new();
io::stdin().read_line(&mut path_str)?;
if path_str.trim().is_empty() {
break;
}
let path_buf = PathBuf::from(path_str.trim());
if path_buf.exists() {
contents = fs::read(path_buf).ok();
}
}
let Some(contents) = contents else {
enable_raw_mode()?;
continue;
};
execute!(
io::stdout(),
MoveTo(0, 34),
EnableLineWrap,
Print(format!(
"Read {} bytes.\nPlace in ROM (in hex):",
contents.len()
))
)?;
let mut placed = false;
while !placed {
execute!(io::stdout(), MoveTo(0, 36), Clear(ClearType::CurrentLine))?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
if input.trim().is_empty() {
break;
}
let Ok(place) = usize::from_str_radix(input.trim(), 16) else {
continue;
};
if (place + contents.len()) <= 0xFFFF {
placed = true;
emulator.state.memory[place..place + contents.len()]
.copy_from_slice(&contents);
}
}
execute!(
io::stdout(),
MoveTo(0, 32),
DisableLineWrap,
Clear(ClearType::FromCursorDown)
)?;
enable_raw_mode()?;
}
Err(KeyCode::F(6)) if emu_state.paused => {
emu_state.step = true;
}
Err(_) => {}
}
}
}
if emu_state.last_display.elapsed() > Duration::from_secs_f64(1.0 / 60.0) {
emu_state.last_display = Instant::now();
emu_state.display_reg &= 0b0111_1111;
if let Some(old) = emu_state.old_display_buffer.take() {
// Display screen
for y in 0..24 {
for x in 0..40 {
if old[(y * 40 + x) as usize] == emu_state.display_buffer[(y * 40 + x) as usize]
{
continue;
}
let byte_char =
CHARACTER_SET[emu_state.display_buffer[(y * 40 + x) as usize] as usize];
execute!(io::stdout(), MoveTo(x, y), Print(byte_char as char))?;
}
}
execute!(
io::stdout(),
MoveTo(0, 30),
Clear(ClearType::CurrentLine),
Print(if emu_state.paused {
"F1/End: Quit F2/Pause: Resume F3/Home: Reset F4/Insert: Clear F5: Load File F6: Step"
} else {
"F1/End: Quit F2/Pause: Pause F3/Home: Reset F4/Insert: Clear F5: Load File"
}),
MoveTo(
u16::from(emu_state.display_x),
u16::from(emu_state.display_y)
)
)?;
}
}
if !emu_state.paused || emu_state.step {
if emu_state.step {
let opcode_str =
Opcode::load(&emulator.state.memory[emulator.state.program_counter as usize..])
.unwrap_or_default()
.map_or("<INVALID>".into(), |op| format!("{op}"));
execute!(
io::stdout(),
MoveTo(0, 31),
Clear(ClearType::CurrentLine),
Print(&opcode_str),
MoveTo(80, emu_state.step_counter),
Print(format!(
"{:04X} {opcode_str}",
emulator.state.program_counter
)),
MoveTo(0, 32),
Clear(ClearType::CurrentLine),
Print(format!("{:04X?}", emulator.state)),
MoveTo(0, 33),
Print(format!(
"XAM: {:04x} ST: {:04x} HEX: {:04x} YSAV: {:02x} MODE: {:02x}",
u16::from_le_bytes([
emulator.state.memory[0x24],
emulator.state.memory[0x25]
]),
u16::from_le_bytes([
emulator.state.memory[0x26],
emulator.state.memory[0x27]
]),
u16::from_le_bytes([
emulator.state.memory[0x28],
emulator.state.memory[0x29]
]),
emulator.state.memory[0x2A],
emulator.state.memory[0x2B]
)),
MoveTo(0, 34),
Clear(ClearType::FromCursorDown)
)?;
emu_state.step_counter = emu_state.step_counter.wrapping_add(1);
}
drop(emu_state);
let Ok(interrupt_requested) = emulator.step() else {
// !!! INVALID OPCODE!
continue;
};
if interrupt_requested {
// Jump to IRQ vector
let vector = u16::from_le_bytes([emulator.read(0xFFFE), emulator.read(0xFFFF)]);
emulator.state.program_counter = vector;
};
}
std::thread::sleep(Duration::from_micros(1).saturating_sub(last.elapsed()));
last = Instant::now();
}
}
fn keycode_ascii(code: KeyCode) -> Result<u8, KeyCode> {
use KeyCode::{BackTab, Backspace, Char, Delete, Enter, Esc, Null, Tab};
match code {
Enter => Ok(b'\r'),
Tab | BackTab => Ok(b'\t'),
Delete => Ok(0x7f),
Backspace => Ok(b'_'),
Char(c) => Ok(c.to_ascii_uppercase() as u8),
Null => Ok(0),
Esc => Ok(0x1B),
c => Err(c),
}
}
// 0:a9 0 aa 20 ef ff e8 8a 4c 2 0

BIN
src/wozmon.rom Normal file

Binary file not shown.