Compare commits
34 Commits
3f40dc1ae3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a37caf8b2 | |||
| 3de2c63fe0 | |||
| 3856d81c47 | |||
| a621ec235a | |||
| f464dbfaf1 | |||
| 7d0b66f418 | |||
| 6e55faa2c0 | |||
| 952b79cf91 | |||
| 8ac0cbc57b | |||
| c2aef4f249 | |||
| 07d40c05d5 | |||
| dcaeece7fa | |||
| 7f8e00af23 | |||
| fb4fae430b | |||
| ac4619406d | |||
| 8890853656 | |||
| 4f0615b563 | |||
| 001d3e434c | |||
| 229b8b450d | |||
| f9198cd0b1 | |||
| 0ec54d6672 | |||
| b1b9c64468 | |||
| 5f65c32e56 | |||
| c154bdc89a | |||
| 705dcd3185 | |||
| 40ede17ae1 | |||
| 9808616203 | |||
| 10559bde8b | |||
| 5b9312f643 | |||
| 95acceeabd | |||
| 78dad90fc9 | |||
| ef438f48d2 | |||
| 7ce48e59fc | |||
| cf0f87649f |
@@ -1,2 +1,7 @@
|
||||
/target
|
||||
log
|
||||
george.o
|
||||
Cargo.lock
|
||||
.DS_Store
|
||||
*.bin
|
||||
/result
|
||||
|
||||
Generated
-7
@@ -1,7 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "georgeemu"
|
||||
version = "0.1.0"
|
||||
+22
@@ -2,7 +2,29 @@
|
||||
name = "georgeemu"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
build = "build.rs"
|
||||
|
||||
[[target.'cfg(not(target_arch = "wasm32"))'.bin]]
|
||||
path = "src/bin/main.rs"
|
||||
name = "george"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
bdf-parser = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.81"
|
||||
minifb = { git = "https://github.com/emoon/rust_minifb" }
|
||||
serde = { version = "1.0.197", features = ["serde_derive", "derive"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
web-sys = "0.3.70"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
termion = "4.0.2"
|
||||
toml = { version = "0.8.12" }
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# george-emu
|
||||
|
||||
a ramshackle emulator for george 😌
|
||||
|
||||
plenty of inspiration & guidance taken from [emulator_6502](https://docs.rs/emulator_6502/latest/emulator_6502/) (don't use `george-emu` in a serious project! `emulator_6502` is the crate you're looking for)
|
||||
|
||||
## structure
|
||||
|
||||
see [the george wiki](https://git.augustkline.com/august/george/wiki) for how george works, why she exists, who she is, etc.
|
||||
|
||||
the george emulator contains 3 main structs: `Cpu`, `Mem`, and `MemMappedDevice`. `Cpu` represents a 65c02 processor, with functions for interacting with memory and executing instructions. `Mem` is a collection of `MemMappedDevice`'s, which each hold the data for some address space. `Mem` can add `MemMappedDevice`'s, and read from and write to them. A `MemMappedDevice` can have multiple banks at the same address space, and can translate 'global' addresses to 'local' ones (e.g. 0xFFFF in the system's [memory map](https://git.augustkline.com/august/george-hardware/src/branch/main/memory.md) corresponds to 0x1FFF in the ROM).
|
||||
|
||||
## font generation
|
||||
|
||||
george uses a modified version of [cozette](https://github.com/slavfox/Cozette) for her main font. the modified version has 8 pixel wide characters for use with the character generator rom, and has a limit of 256 (0xFF) characters. the toolchain to generate a rom binary is still pretty clunky, and someday i might get around to streamlining it, but for now to make any changes:
|
||||
|
||||
- open `./src/Cozette.sfd` in fontforge
|
||||
- make your edits
|
||||
- to reorder glyphs, edit `./src/georgeencoding.txt` and load it with "Encoding -> Load Encoding -> (select georgeencoding.txt, name it whatever) -> Reencode -> (name of encoding)"
|
||||
- ensure that there are exactly 256 characters in the font
|
||||
- generate a bdf font with "File -> Generate Fonts... -> (name of the font, select bdf in the options panel)"
|
||||
- open the generated bdf file in a text editor and change the line `FONTBOUNDINGBOX 11 13 0 -3` to `FONTBOUNDINGBOX 8 13 0 -3`
|
||||
- now open the font in [bdf view](https://emurenmrz.github.io/bdf_view/), and export a single-row png
|
||||
@@ -0,0 +1,83 @@
|
||||
use std::{
|
||||
env,
|
||||
fs::File,
|
||||
io::{stdin, stdout, IsTerminal, Read, Write},
|
||||
ops::Neg,
|
||||
os::unix::process::CommandExt,
|
||||
path::Path,
|
||||
process::{exit, Command},
|
||||
};
|
||||
|
||||
// takes all charaters in bdf and returns a vec of each character row byte in order, normalized to
|
||||
// width & height of the font (only works with 8 or fewer pixel wide fonts, should work for any height)
|
||||
fn bdf_to_bitmap(mut bdf: File) -> [u8; 0x8000] {
|
||||
let mut bdf_font_bytes = Vec::new();
|
||||
bdf.read_to_end(&mut bdf_font_bytes).unwrap();
|
||||
|
||||
let bdf_font = bdf_parser::BdfFont::parse(&bdf_font_bytes).unwrap();
|
||||
let mut bdf_vec = vec![];
|
||||
for glyph in bdf_font.glyphs.iter() {
|
||||
let glyph_offset_x = glyph.bounding_box.offset.x;
|
||||
let glyph_offset_y = glyph.bounding_box.offset.y;
|
||||
let glyph_height = glyph.bounding_box.size.y;
|
||||
let font_height = bdf_font.metadata.bounding_box.size.y;
|
||||
let font_offset_y = bdf_font.metadata.bounding_box.offset.y;
|
||||
|
||||
let top_space = font_height + font_offset_y - glyph_height - glyph_offset_y;
|
||||
for _ in 0..top_space {
|
||||
bdf_vec.push(0x00);
|
||||
}
|
||||
for bitmap in glyph.bitmap.iter() {
|
||||
bdf_vec.push(bitmap >> glyph_offset_x);
|
||||
}
|
||||
|
||||
let bottom_space = font_offset_y.neg() + glyph_offset_y;
|
||||
for _ in 0..bottom_space {
|
||||
bdf_vec.push(0x00);
|
||||
}
|
||||
}
|
||||
let height = bdf_font.metadata.bounding_box.size.y as usize;
|
||||
reorder_bitmap(&bdf_vec, height)
|
||||
}
|
||||
|
||||
// takes an vec of ordered characters and translates them for use with the character rom
|
||||
// TODO: make this work for any arbitrary char rom pin format using some kinda interface
|
||||
fn reorder_bitmap(bitmap: &[u8], font_height: usize) -> [u8; 0x8000] {
|
||||
let mut rom = [0; 0x8000]; // create vec the size of character rom
|
||||
|
||||
for row in 0..font_height {
|
||||
for ascii_address in 0..u8::MAX {
|
||||
// first 8 bits of address pick character
|
||||
// next 5 bits pick row
|
||||
// TODO: final 2 pick character set
|
||||
let byte = bitmap[ascii_address as usize * font_height + row];
|
||||
let rom_index: u16 = ((row as u16) << 8) + ascii_address as u16;
|
||||
rom[rom_index as usize] = byte;
|
||||
}
|
||||
}
|
||||
rom
|
||||
}
|
||||
|
||||
fn rom_from_file<P>(path: P) -> [u8; 0x8000]
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let file = File::open(path).unwrap();
|
||||
bdf_to_bitmap(file)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut regen_font = Command::new("regen-font.sh");
|
||||
let mut cleanup = Command::new("cleanup.sh");
|
||||
|
||||
regen_font.exec();
|
||||
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
let dest_path = Path::new(&out_dir).join("cozette.rom");
|
||||
|
||||
let cozette_rom_bytes = rom_from_file("build/cozette.bdf");
|
||||
let mut cozette_rom = File::create(dest_path).unwrap();
|
||||
cozette_rom.write_all(&cozette_rom_bytes).unwrap();
|
||||
|
||||
cleanup.exec();
|
||||
}
|
||||
+2410
File diff suppressed because it is too large
Load Diff
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#! /bin/sh
|
||||
|
||||
rm *.bdf*
|
||||
File diff suppressed because it is too large
Load Diff
+3903
File diff suppressed because it is too large
Load Diff
+3903
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,523 @@
|
||||
# this is georgescii, august's version of extended ascii for george <3
|
||||
# we're limited to 255 characters
|
||||
# format: ascii byte, unicode hex, # unicode name
|
||||
#
|
||||
# format: three tab-separated columns
|
||||
# column #1 is the ISO/IEC 8859-1 code (in hex as 0xXX)
|
||||
# column #2 is the Unicode (in hex as 0xXXXX)
|
||||
# column #3 the Unicode name (follows a comment sign, '#')
|
||||
|
||||
# 0x00 0x00 #
|
||||
# 0x01 0x2591 # ░
|
||||
# 0x02 0x2592 # ▒
|
||||
# 0x03 0x2593 # ▓
|
||||
# 0x04 0x2661 # ♡
|
||||
# 0x05 0x2665 # ♥
|
||||
# 0x06 0x2B50 # ⭐
|
||||
# 0x07 0x272D # ✭
|
||||
# 0x08 0xF005 #
|
||||
# 0x09 0x2726 # ✦
|
||||
# 0x0a 0x2728 # ✨
|
||||
# 0x0b 0x2640 # ♀
|
||||
# 0x0c 0x2642 # ♂
|
||||
# 0x0d 0x26A2 # ⚢
|
||||
# 0x0E 0x26A3 # ⚣
|
||||
# 0x0F 0x26A5 # ⚥
|
||||
# 0x10 0x2669 # ♩
|
||||
# 0x11 0x266A # ♪
|
||||
# 0x12 0x266B # ♫
|
||||
# 0x13 0x266C # ♬
|
||||
# 0x14 0xFC5D # ﱝ
|
||||
# 0x15 0xF026 #
|
||||
# 0x16 0xF027 #
|
||||
# 0x17 0xF028 #
|
||||
# 0x18 0xFA7E # 奄
|
||||
# 0x19 0xFA7F # 奔
|
||||
# 0x1A 0xFA80 # 婢
|
||||
# 0x1B 0xFC5C # ﱜ
|
||||
# 0x1C 0xFC5B # ﱛ
|
||||
# 0x1D 0xF0AC #
|
||||
# 0x1E 0xF04B #
|
||||
# 0x1F 0xF04D #
|
||||
# 0x20 0x0020 #
|
||||
# 0x21 0x0021 # !
|
||||
# 0x22 0x0022 # "
|
||||
# 0x23 0x0023 # #
|
||||
# 0x24 0x0024 # $
|
||||
# 0x25 0x0025 # %
|
||||
# 0x26 0x0026 # &
|
||||
# 0x27 0x0027 # '
|
||||
# 0x28 0x0028 # (
|
||||
# 0x29 0x0029 # )
|
||||
# 0x2A 0x002A # *
|
||||
# 0x2B 0x002B # +
|
||||
# 0x2C 0x002C # ,
|
||||
# 0x2D 0x002D # -
|
||||
# 0x2E 0x002E # .
|
||||
# 0x2F 0x002F # /
|
||||
# 0x30 0x0030 # 0
|
||||
# 0x31 0x0031 # 1
|
||||
# 0x32 0x0032 # 2
|
||||
# 0x33 0x0033 # 3
|
||||
# 0x34 0x0034 # 4
|
||||
# 0x35 0x0035 # 5
|
||||
# 0x36 0x0036 # 6
|
||||
# 0x37 0x0037 # 7
|
||||
# 0x38 0x0038 # 8
|
||||
# 0x39 0x0039 # 9
|
||||
# 0x3A 0x003A # :
|
||||
# 0x3B 0x003B # ;
|
||||
# 0x3C 0x003C # <
|
||||
# 0x3D 0x003D # =
|
||||
# 0x3E 0x003E # >
|
||||
# 0x3F 0x003F # ?
|
||||
# 0x40 0x0040 # @
|
||||
# 0x41 0x0041 # A
|
||||
# 0x42 0x0042 # B
|
||||
# 0x43 0x0043 # C
|
||||
# 0x44 0x0044 # D
|
||||
# 0x45 0x0045 # E
|
||||
# 0x46 0x0046 # F
|
||||
# 0x47 0x0047 # G
|
||||
# 0x48 0x0048 # H
|
||||
# 0x49 0x0049 # I
|
||||
# 0x4A 0x004A # J
|
||||
# 0x4B 0x004B # K
|
||||
# 0x4C 0x004C # L
|
||||
# 0x4D 0x004D # M
|
||||
# 0x4E 0x004E # N
|
||||
# 0x4F 0x004F # O
|
||||
# 0x50 0x0050 # P
|
||||
# 0x51 0x0051 # Q
|
||||
# 0x52 0x0052 # R
|
||||
# 0x53 0x0053 # S
|
||||
# 0x54 0x0054 # T
|
||||
# 0x55 0x0055 # U
|
||||
# 0x56 0x0056 # V
|
||||
# 0x57 0x0057 # W
|
||||
# 0x58 0x0058 # X
|
||||
# 0x59 0x0059 # Y
|
||||
# 0x5A 0x005A # Z
|
||||
# 0x5B 0x005B # [
|
||||
# 0x5C 0x005C # \
|
||||
# 0x5D 0x005D # ]
|
||||
# 0x5E 0x005E # ^
|
||||
# 0x5F 0x005F # _
|
||||
# 0x60 0x0060 # `
|
||||
# 0x61 0x0061 # a
|
||||
# 0x62 0x0062 # b
|
||||
# 0x63 0x0063 # c
|
||||
# 0x64 0x0064 # d
|
||||
# 0x65 0x0065 # e
|
||||
# 0x66 0x0066 # f
|
||||
# 0x67 0x0067 # g
|
||||
# 0x68 0x0068 # h
|
||||
# 0x69 0x0069 # i
|
||||
# 0x6A 0x006A # j
|
||||
# 0x6B 0x006B # k
|
||||
# 0x6C 0x006C # l
|
||||
# 0x6D 0x006D # m
|
||||
# 0x6E 0x006E # n
|
||||
# 0x6F 0x006F # o
|
||||
# 0x70 0x0070 # p
|
||||
# 0x71 0x0071 # q
|
||||
# 0x72 0x0072 # r
|
||||
# 0x73 0x0073 # s
|
||||
# 0x74 0x0074 # t
|
||||
# 0x75 0x0075 # u
|
||||
# 0x76 0x0076 # v
|
||||
# 0x77 0x0077 # w
|
||||
# 0x78 0x0078 # x
|
||||
# 0x79 0x0079 # y
|
||||
# 0x7A 0x007A # z
|
||||
# 0x7B 0x007B # {
|
||||
# 0x7C 0x007C # |
|
||||
# 0x7D 0x007D # }
|
||||
# 0x7E 0x007E # ~
|
||||
# 0x7F 0x2500 # ─
|
||||
# 0x80 0x2502 # │
|
||||
# 0x81 0x250C # ┌
|
||||
# 0x82 0x2514 # └
|
||||
# 0x83 0x251C # ├
|
||||
# 0x84 0x2524 # ┤
|
||||
# 0x85 0x252C # ┬
|
||||
# 0x86 0x2534 # ┴
|
||||
# 0x87 0x253C # ┼
|
||||
# 0x88 0x256D # ╭
|
||||
# 0x89 0x256E # ╮
|
||||
# 0x8A 0x256F # ╯
|
||||
# 0x8B 0x2570 # ╰
|
||||
# 0x8C 0x2571 # ╱
|
||||
# 0x8D 0x2572 # ╲
|
||||
# 0x8E 0x2573 # ╳
|
||||
# 0x8F 0x2550 # ═
|
||||
# 0x90 0x2551 # ║
|
||||
# 0x91 0x2554 # ╔
|
||||
# 0x92 0x2557 # ╗
|
||||
# 0x93 0x255a # ╚
|
||||
# 0x94 0x255D # ╝
|
||||
# 0x95 0x2560 # ╠
|
||||
# 0x96 0x2563 # ╣
|
||||
# 0x97 0x2566 # ╦
|
||||
# 0x98 0x2569 # ╩
|
||||
# 0x99 0x256C # ╬
|
||||
# 0x9A 0xF04E #
|
||||
# 0x9B 0xF050 #
|
||||
# 0x9C 0xF051 #
|
||||
# 0x9D 0xF052 #
|
||||
# 0x9E 0xF048 #
|
||||
# 0x9F 0xE0B0 #
|
||||
# 0xA0 0xE0B2 #
|
||||
# 0xA1 0xE0B4 #
|
||||
# 0xA2 0xE0B6 #
|
||||
# 0xA3 0xE0B8 #
|
||||
# 0xA4 0xE0BA #
|
||||
# 0xA5 0xE0BC #
|
||||
# 0xA6 0xE0BE #
|
||||
# 0xA7 0x2581 # ▁
|
||||
# 0xA8 0x2582 # ▂
|
||||
# 0xA9 0x2583 # ▃
|
||||
# 0xAA 0x2584 # ▄
|
||||
# 0xAB 0x2585 # ▅
|
||||
# 0xAC 0x2586 # ▆
|
||||
# 0xAD 0x2587 # ▇
|
||||
# 0xAE 0x2588 # █
|
||||
# 0xAF 0x2589 # ▉
|
||||
# 0xB0 0x258A # ▊
|
||||
# 0xB1 0x258B # ▋
|
||||
# 0xB2 0x258C # ▌
|
||||
# 0xB3 0x258D # ▍
|
||||
# 0xB4 0x258E # ▎
|
||||
# 0xB5 0x258F # ▏
|
||||
# 0xB6 0x0295 # ʕ
|
||||
# 0xB7 0x00B7 # ·
|
||||
# 0xB8 0x1D25 # ᴥ
|
||||
# 0xB9 0x0294 # ʔ
|
||||
# 0xBA 0x2596 # ▖
|
||||
# 0xBB 0x2597 # ▗
|
||||
# 0xBC 0x2598 # ▘
|
||||
# 0xBD 0x2599 # ▙
|
||||
# 0xBE 0x259A # ▚
|
||||
# 0xBF 0x259B # ▛
|
||||
# 0xC0 0x259C # ▜
|
||||
# 0xC1 0x259D # ▝
|
||||
# 0xC2 0x259E # ▞
|
||||
# 0xC3 0x259F # ▟
|
||||
# 0xC4 0x2190 # ←
|
||||
# 0xC5 0x2191 # ↑
|
||||
# 0xC6 0x2192 # →
|
||||
# 0xC7 0x2193 # ↓
|
||||
# 0xC8 0x2B60 # ⭠
|
||||
# 0xC9 0x2B61 # ⭡
|
||||
# 0xCA 0x2B62 # ⭢
|
||||
# 0xCB 0x2B63 # ⭣
|
||||
# 0xCC 0x2B80 # ⮀
|
||||
# 0xCD 0x2B81 # ⮁
|
||||
# 0xCE 0x2B82 # ⮂
|
||||
# 0xCF 0x2B83 # ⮃
|
||||
# 0xD0 0xF049 #
|
||||
# 0xD1 0xF04A #
|
||||
# 0xD2 0x23F3 # ⏳
|
||||
# 0xD3 0xF07B #
|
||||
# 0xD4 0xF07C #
|
||||
# 0xD5 0xF114 #
|
||||
# 0xD6 0xF115 #
|
||||
# 0xD7 0xF250 #
|
||||
# 0xD8 0xF251 #
|
||||
# 0xD9 0xF253 #
|
||||
# 0xDA 0xF254 #
|
||||
# 0xDB 0xF461 #
|
||||
# 0xDC 0xF016 #
|
||||
# 0xDD 0xF401 #
|
||||
# 0xDE 0x1F52E # 🔮
|
||||
# 0xDF 0xF2DB #
|
||||
# 0xE0 0xF008 #
|
||||
# 0xE1 0x25C7 # ◇
|
||||
# 0xE2 0x25C8 # ◈
|
||||
# 0xE3 0x1F311 # 🌑
|
||||
# 0xE4 0x1F312 # 🌒
|
||||
# 0xE5 0x1F313 # 🌓
|
||||
# 0xE6 0x1F314 # 🌔
|
||||
# 0xE7 0x1F315 # 🌕
|
||||
# 0xE8 0x1F316 # 🌖
|
||||
# 0xE9 0x1F317 # 🌗
|
||||
# 0xEA 0x1F318 # 🌘
|
||||
# 0xEB 0xF04C #
|
||||
# 0xEC 0x2714 # ✔
|
||||
# 0xED 0x2718 # ✘
|
||||
# 0xEE 0x25C6 # ◆
|
||||
# 0xEF 0xF15D #
|
||||
# 0xF0 0xF15E #
|
||||
# 0xF1 0xF071 #
|
||||
# 0xF2 0xF449 #
|
||||
# 0xF3 0xF529 #
|
||||
# 0xF4 0xF658 #
|
||||
# 0xF5 0xF659 #
|
||||
# 0xF6 0x1f381 # 🎁
|
||||
# 0xF7 0xf05a #
|
||||
# 0xF8 0xf06a #
|
||||
# 0xF9 0xf834 #
|
||||
# 0xFA 0xf835 #
|
||||
# 0xFB 0x2690 # ⚐
|
||||
# 0xFC 0x2691 # ⚑
|
||||
# 0xFD 0xf8d7 #
|
||||
# 0xFE 0xf0e7 #
|
||||
# 0xFF 0xf7d9 #
|
||||
|
||||
|
||||
0x00 0x00 #
|
||||
0x01 0x0295 # ʕ
|
||||
0x02 0x00B7 # ·
|
||||
0x03 0x1D25 # ᴥ
|
||||
0x04 0x0294 # ʔ
|
||||
0x05 0x2661 # ♡
|
||||
0x06 0x2665 # ♥
|
||||
0x07 0x2726 # ✦
|
||||
0x08 0x25C7 # ◇
|
||||
0x09 0x25C6 # ◆
|
||||
0x0a 0x272D # ✭
|
||||
0x0b 0xF005 #
|
||||
0x0c 0x2728 # ✨
|
||||
0x0d 0x000d # CR
|
||||
0x0e 0x2518 # ┘
|
||||
0x0f 0x2514 # └
|
||||
0x10 0x250c # ┌
|
||||
0x11 0x2510 # ┐
|
||||
0x12 0x2500 # ─
|
||||
0x13 0x2502 # │
|
||||
0x14 0x2524 # ┤
|
||||
0x15 0x2534 # ┴
|
||||
0x16 0x251C # ├
|
||||
0x17 0x252C # ┬
|
||||
0x18 0x253C # ┼
|
||||
0x19 0x2571 # ╱
|
||||
0x1a 0x2572 # ╲
|
||||
0x1b 0x2573 # ╳
|
||||
0x1c 0x2591 # ░
|
||||
0x1d 0x2592 # ▒
|
||||
0x1e 0x2593 # ▓
|
||||
0x1f 0x2588 # █
|
||||
0x20 0x0020 #
|
||||
0x21 0x0021 # !
|
||||
0x22 0x0022 # "
|
||||
0x23 0x0023 # #
|
||||
0x24 0x0024 # $
|
||||
0x25 0x0025 # %
|
||||
0x26 0x0026 # &
|
||||
0x27 0x0027 # '
|
||||
0x28 0x0028 # (
|
||||
0x29 0x0029 # )
|
||||
0x2a 0x002A # *
|
||||
0x2b 0x002B # +
|
||||
0x2c 0x002C # ,
|
||||
0x2d 0x002D # -
|
||||
0x2e 0x002E # .
|
||||
0x2f 0x002F # /
|
||||
0x30 0x0030 # 0
|
||||
0x31 0x0031 # 1
|
||||
0x32 0x0032 # 2
|
||||
0x33 0x0033 # 3
|
||||
0x34 0x0034 # 4
|
||||
0x35 0x0035 # 5
|
||||
0x36 0x0036 # 6
|
||||
0x37 0x0037 # 7
|
||||
0x38 0x0038 # 8
|
||||
0x39 0x0039 # 9
|
||||
0x3a 0x003A # :
|
||||
0x3b 0x003B # ;
|
||||
0x3c 0x003C # <
|
||||
0x3d 0x003D # =
|
||||
0x3e 0x003E # >
|
||||
0x3f 0x003F # ?
|
||||
0x40 0x0040 # @
|
||||
0x41 0x0041 # A
|
||||
0x42 0x0042 # B
|
||||
0x43 0x0043 # C
|
||||
0x44 0x0044 # D
|
||||
0x45 0x0045 # E
|
||||
0x46 0x0046 # F
|
||||
0x47 0x0047 # G
|
||||
0x48 0x0048 # H
|
||||
0x49 0x0049 # I
|
||||
0x4a 0x004A # J
|
||||
0x4b 0x004B # K
|
||||
0x4c 0x004C # L
|
||||
0x4d 0x004D # M
|
||||
0x4e 0x004E # N
|
||||
0x4f 0x004F # O
|
||||
0x50 0x0050 # P
|
||||
0x51 0x0051 # Q
|
||||
0x52 0x0052 # R
|
||||
0x53 0x0053 # S
|
||||
0x54 0x0054 # T
|
||||
0x55 0x0055 # U
|
||||
0x56 0x0056 # V
|
||||
0x57 0x0057 # W
|
||||
0x58 0x0058 # X
|
||||
0x59 0x0059 # Y
|
||||
0x5a 0x005A # Z
|
||||
0x5b 0x005B # [
|
||||
0x5c 0x005C # \
|
||||
0x5d 0x005D # ]
|
||||
0x5e 0x005E # ^
|
||||
0x5f 0x005F # _
|
||||
0x60 0x0060 # `
|
||||
0x61 0x0061 # a
|
||||
0x62 0x0062 # b
|
||||
0x63 0x0063 # c
|
||||
0x64 0x0064 # d
|
||||
0x65 0x0065 # e
|
||||
0x66 0x0066 # f
|
||||
0x67 0x0067 # g
|
||||
0x68 0x0068 # h
|
||||
0x69 0x0069 # i
|
||||
0x6a 0x006A # j
|
||||
0x6b 0x006B # k
|
||||
0x6c 0x006C # l
|
||||
0x6d 0x006D # m
|
||||
0x6e 0x006E # n
|
||||
0x6f 0x006F # o
|
||||
0x70 0x0070 # p
|
||||
0x71 0x0071 # q
|
||||
0x72 0x0072 # r
|
||||
0x73 0x0073 # s
|
||||
0x74 0x0074 # t
|
||||
0x75 0x0075 # u
|
||||
0x76 0x0076 # v
|
||||
0x77 0x0077 # w
|
||||
0x78 0x0078 # x
|
||||
0x79 0x0079 # y
|
||||
0x7a 0x007A # z
|
||||
0x7b 0x007B # {
|
||||
0x7c 0x007C # |
|
||||
0x7d 0x007D # }
|
||||
0x7e 0x007E # ~
|
||||
0x7f 0x256F # ╯
|
||||
0x80 0x2570 # ╰
|
||||
0x81 0x256D # ╭
|
||||
0x82 0x256E # ╮
|
||||
0x83 0x255D # ╝
|
||||
0x84 0x255a # ╚
|
||||
0x85 0x2554 # ╔
|
||||
0x86 0x2557 # ╗
|
||||
0x87 0x2550 # ═
|
||||
0x88 0x2551 # ║
|
||||
0x89 0x2563 # ╣
|
||||
0x8a 0x2569 # ╩
|
||||
0x8b 0x2560 # ╠
|
||||
0x8c 0x2566 # ╦
|
||||
0x8d 0x256C # ╬
|
||||
0x8e 0xE0B8 #
|
||||
0x8f 0xE0BA #
|
||||
0x90 0xE0BC #
|
||||
0x91 0xE0BE #
|
||||
0x92 0xE0B2 #
|
||||
0x93 0xE0B0 #
|
||||
0x94 0xE0B6 #
|
||||
0x95 0xE0B4 #
|
||||
0x96 0x2596 # ▖
|
||||
0x97 0x2597 # ▗
|
||||
0x98 0x2598 # ▘
|
||||
0x99 0x2599 # ▙
|
||||
0x9a 0x259A # ▚
|
||||
0x9b 0x259B # ▛
|
||||
0x9c 0x259C # ▜
|
||||
0x9d 0x259D # ▝
|
||||
0x9e 0x259E # ▞
|
||||
0x9f 0x259F # ▟
|
||||
0xa0 0x2581 # ▁
|
||||
0xa1 0x2582 # ▂
|
||||
0xa2 0x2583 # ▃
|
||||
0xa3 0x2584 # ▄
|
||||
0xa4 0x2585 # ▅
|
||||
0xa5 0x2586 # ▆
|
||||
0xa6 0x2587 # ▇
|
||||
0xa7 0x2589 # ▉
|
||||
0xa8 0x258A # ▊
|
||||
0xa9 0x258B # ▋
|
||||
0xaa 0x258C # ▌
|
||||
0xab 0x258D # ▍
|
||||
0xac 0x258E # ▎
|
||||
0xad 0x258F # ▏
|
||||
0xae 0x1F311 # 🌑
|
||||
0xaf 0x1F312 # 🌒
|
||||
0xb0 0x1F313 # 🌓
|
||||
0xb1 0x1F314 # 🌔
|
||||
0xb2 0x1F315 # 🌕
|
||||
0xb3 0x1F316 # 🌖
|
||||
0xb4 0x1F317 # 🌗
|
||||
0xb5 0x1F318 # 🌘
|
||||
0xb6 0xF254 #
|
||||
0xb7 0xF251 #
|
||||
0xb8 0x23F3 # ⏳
|
||||
0xb9 0xF253 #
|
||||
0xba 0xF250 #
|
||||
0xbb 0x2190 # ←
|
||||
0xbc 0x2191 # ↑
|
||||
0xbd 0x2192 # →
|
||||
0xbe 0x2193 # ↓
|
||||
0xbf 0x2B60 # ⭠
|
||||
0xc0 0x2B61 # ⭡
|
||||
0xc1 0x2B62 # ⭢
|
||||
0xc2 0x2B63 # ⭣
|
||||
0xc3 0x2B80 # ⮀
|
||||
0xc4 0x2B81 # ⮁
|
||||
0xc5 0x2B82 # ⮂
|
||||
0xc6 0x2B83 # ⮃
|
||||
0xc7 0xF049 #
|
||||
0xc8 0xF04A #
|
||||
0xc9 0xF048 #
|
||||
0xca 0xF04B #
|
||||
0xcb 0xF04C #
|
||||
0xcc 0xF04D #
|
||||
0xcd 0xF052 #
|
||||
0xce 0xF051 #
|
||||
0xcf 0xF04E #
|
||||
0xd0 0xF050 #
|
||||
0xd1 0xFC5D # ﱝ
|
||||
0xd2 0xF026 #
|
||||
0xd3 0xF027 #
|
||||
0xd4 0xF028 #
|
||||
0xd5 0xFA80 # 婢
|
||||
0xd6 0xFC5C # ﱜ
|
||||
0xd7 0xFC5B # ﱛ
|
||||
0xd8 0x2669 # ♩
|
||||
0xd9 0x266A # ♪
|
||||
0xda 0x266B # ♫
|
||||
0xdb 0x266C # ♬
|
||||
0xdc 0xF016 #
|
||||
0xdd 0xF07B #
|
||||
0xde 0xF07C #
|
||||
0xdf 0xF114 #
|
||||
0xe0 0xF115 #
|
||||
0xe1 0xF15D #
|
||||
0xe2 0xF15E #
|
||||
0xe3 0xF529 #
|
||||
0xe4 0xF071 #
|
||||
0xe5 0xF449 #
|
||||
0xe6 0xf05a #
|
||||
0xe7 0xF659 #
|
||||
0xe8 0xF658 #
|
||||
0xe9 0xf835 #
|
||||
0xea 0xf834 #
|
||||
0xeb 0x2690 # ⚐
|
||||
0xec 0x2691 # ⚑
|
||||
0xed 0xf7d9 #
|
||||
0xee 0x2714 # ✔
|
||||
0xef 0x2718 # ✘
|
||||
0xf0 0xf0e7 #
|
||||
0xf1 0xF2DB #
|
||||
0xf2 0xF008 #
|
||||
0xf3 0xF461 #
|
||||
0xf4 0x2B50 # ⭐
|
||||
0xf5 0xF401 #
|
||||
0xf6 0x1F52E # 🔮
|
||||
0xf7 0x1f381 # 🎁
|
||||
0xf8 0xf8d7 #
|
||||
0xf9 0xF0AC #
|
||||
0xfa 0x25C8 # ◈
|
||||
0xfb 0x2640 # ♀
|
||||
0xfc 0x2642 # ♂
|
||||
0xfd 0x26A2 # ⚢
|
||||
0xfe 0x26A3 # ⚣
|
||||
0xff 0x26A5 # ⚥
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#! /bin/sh
|
||||
|
||||
fontforge -lang=ff -c 'Open($1); LoadEncodingFile($2, "george"); Reencode("george"); Generate($3)' Cozette.sfd georgeencoding.txt cozette.bdf
|
||||
sed -i'' -e 's/FONTBOUNDINGBOX 11 13 0 -3/FONTBOUNDINGBOX 8 13 0 -3/' *.bdf
|
||||
mv cozette-13.bdf cozette.bdf
|
||||
Generated
+112
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"naersk": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1698420672,
|
||||
"narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=",
|
||||
"owner": "nmattia",
|
||||
"repo": "naersk",
|
||||
"rev": "aeb58d5e8faead8980a807c840232697982d47b9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nmattia",
|
||||
"ref": "master",
|
||||
"repo": "naersk",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1712696601,
|
||||
"narHash": "sha256-puFPFSa/RC83JilUgB48/VL387eu2QN066Jv6X971LY=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "062fc6cf99d809921ecef47317752fc92468e6ae",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1712608508,
|
||||
"narHash": "sha256-vMZ5603yU0wxgyQeHJryOI+O61yrX2AHwY6LOFyV1gM=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "4cba8b53da471aea2ab2b0c1f30a81e7c451f4b6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"naersk": "naersk",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"utils": "utils"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
inputs = {
|
||||
naersk.url = "github:nmattia/naersk/master";
|
||||
# This must be the stable nixpkgs if you're running the app on a
|
||||
# stable NixOS install. Mixing EGL library versions doesn't work.
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
utils.url = "github:numtide/flake-utils";
|
||||
flake-compat = {
|
||||
url = github:edolstra/flake-compat;
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, utils, naersk, ... }:
|
||||
utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
naersk-lib = pkgs.callPackage naersk { };
|
||||
libPath = with pkgs; lib.makeLibraryPath [
|
||||
libxkbcommon
|
||||
wayland
|
||||
xorg.libX11
|
||||
xorg.libXcursor
|
||||
xorg.libXi
|
||||
xorg.libXrandr
|
||||
];
|
||||
in
|
||||
{
|
||||
defaultPackage = naersk-lib.buildPackage {
|
||||
src = ./.;
|
||||
doCheck = true;
|
||||
pname = "georgeemu";
|
||||
nativeBuildInputs = [ pkgs.makeWrapper ];
|
||||
buildInputs = with pkgs; [
|
||||
xorg.libxcb
|
||||
];
|
||||
postInstall = ''
|
||||
wrapProgram "$out/bin/georgeemu" --prefix LD_LIBRARY_PATH : "${libPath}"
|
||||
'';
|
||||
};
|
||||
|
||||
defaultApp = utils.lib.mkApp {
|
||||
drv = self.defaultPackage."${system}";
|
||||
};
|
||||
|
||||
devShell = with pkgs; mkShell {
|
||||
buildInputs = [
|
||||
cargo
|
||||
cargo-insta
|
||||
pre-commit
|
||||
rust-analyzer
|
||||
rustPackages.clippy
|
||||
rustc
|
||||
rustfmt
|
||||
|
||||
xorg.libxcb
|
||||
];
|
||||
RUST_SRC_PATH = rustPlatform.rustLibSrc;
|
||||
LD_LIBRARY_PATH = libPath;
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
char_rom = "./src/roms/cozette.rom"
|
||||
rom = "./src/roms/keyboard_sys.rom"
|
||||
screen = "Window"
|
||||
@@ -0,0 +1,11 @@
|
||||
.org $00
|
||||
|
||||
.byte $80
|
||||
|
||||
.org $8000
|
||||
|
||||
reset:
|
||||
bbr7 $00, reset
|
||||
|
||||
.org $fffc
|
||||
.word reset
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+238
@@ -0,0 +1,238 @@
|
||||
; .setcpu "65C02"
|
||||
.include "./macro.inc"
|
||||
|
||||
.org $8000
|
||||
|
||||
n = $01 ; temporary storage for data stack operations
|
||||
|
||||
temp = $20 ; scratchpad page
|
||||
|
||||
cursor = $300
|
||||
cursor_x = cursor
|
||||
cursor_y = cursor + 1
|
||||
|
||||
rand_index = $200
|
||||
|
||||
reset:
|
||||
sei
|
||||
ldx #0; initialize data stack pointer
|
||||
|
||||
init:
|
||||
cli
|
||||
|
||||
main:
|
||||
jsr print
|
||||
jsr rand_draw
|
||||
jmp main
|
||||
|
||||
newline: ; sets cursor to start of next line
|
||||
stz cursor_x
|
||||
lda cursor_y
|
||||
cmp #28
|
||||
bne .end
|
||||
stz cursor_y
|
||||
rts
|
||||
.end:
|
||||
inc cursor_y
|
||||
rts
|
||||
|
||||
text:
|
||||
.byte 1,2,3,2,4
|
||||
.asciiz "- george loves u <3"
|
||||
|
||||
random_y:
|
||||
.byte 25,12,0,20,4,25,5,13
|
||||
.byte 20, 1, 1, 22, 12, 19, 19, 19
|
||||
.byte 9, 0, 4, 18, 13, 14, 4, 16
|
||||
.byte 8, 17, 21, 14, 23, 21, 9, 0
|
||||
.byte 14, 10, 14, 2, 26, 18, 15, 23
|
||||
.byte 12, 2, 5, 4, 25, 20, 27, 4
|
||||
.byte 28, 21, 3, 22, 11, 25, 2, 25
|
||||
.byte 13, 17, 17, 24, 8, 8, 20, 21
|
||||
.byte 11, 24, 27, 25, 8, 12, 7, 0
|
||||
.byte 27, 12, 19, 27, 10, 3, 19, 2
|
||||
.byte 2, 23, 22, 5, 26, 28, 4, 16
|
||||
.byte 18, 7, 10, 9, 6, 19, 9, 2
|
||||
.byte 14, 8, 14, 18, 18, 2, 13, 0
|
||||
.byte 15, 26, 3, 23, 17, 12, 18, 11
|
||||
.byte 4, 16, 17, 22, 9, 25, 3, 15
|
||||
.byte 28, 3, 6, 14, 25, 5, 21, 8
|
||||
.byte 15, 18, 15, 5, 28, 6, 15, 4
|
||||
.byte 10, 1, 16, 24, 6, 9, 22, 3
|
||||
.byte 17, 18, 10, 19, 27, 11, 22, 16
|
||||
.byte 22, 17, 15, 6, 23, 11, 11, 11
|
||||
.byte 4, 15, 5, 25, 19, 1, 8, 26
|
||||
.byte 21, 20, 17, 27, 11, 3, 11, 20
|
||||
.byte 15, 28, 0, 6, 14, 23, 20, 21
|
||||
.byte 17, 20, 16, 15, 19, 6, 21, 19
|
||||
.byte 15, 27, 1, 22, 7, 0, 5, 2
|
||||
.byte 14, 24, 15, 4, 20, 16, 1, 14
|
||||
.byte 4, 16, 4, 8, 13, 26, 3, 9
|
||||
.byte 12, 25, 5, 0, 7, 17, 14, 20
|
||||
.byte 2, 26, 2, 27, 18, 23, 5, 8
|
||||
.byte 4, 21, 10, 11, 28, 22, 6, 6
|
||||
.byte 10, 13, 23, 12, 20, 28, 20, 1
|
||||
.byte 27, 19, 25, 6, 1, 10, 1
|
||||
|
||||
random_x:
|
||||
.byte 42, 59, 11, 5, 18, 0, 26, 1
|
||||
.byte 61, 16, 1, 51, 36, 47, 23, 1
|
||||
.byte 16, 50, 46, 4, 55, 31, 15, 2
|
||||
.byte 45, 21, 59, 53, 15, 43, 0, 2
|
||||
.byte 64, 31, 38, 41, 25, 12, 12, 3
|
||||
.byte 30, 13, 64, 44, 21, 8, 48, 3
|
||||
.byte 46, 1, 2, 33, 4, 32, 59, 28
|
||||
.byte 4, 24, 58, 53, 21, 41, 30, 2
|
||||
.byte 56, 53, 31, 10, 42, 12, 9, 54
|
||||
.byte 14, 14, 24, 29, 43, 60, 54, 26
|
||||
.byte 5, 53, 17, 55, 27, 46, 31, 3
|
||||
.byte 26, 44, 63, 30, 10, 34, 62, 48
|
||||
.byte 42, 47, 51, 7, 55, 32, 14, 21
|
||||
.byte 15, 26, 52, 37, 48, 0, 13, 2
|
||||
.byte 50, 20, 35, 32, 8, 41, 2, 24
|
||||
.byte 18, 9, 52, 22, 52, 12, 19, 32
|
||||
.byte 29, 46, 34, 58, 54, 51, 43, 57
|
||||
.byte 62, 10, 12, 57, 36, 39, 4, 30
|
||||
.byte 38, 9, 30, 32, 33, 57, 3, 25
|
||||
.byte 21, 36, 59, 30, 19, 39, 9, 60
|
||||
.byte 34, 50, 52, 37, 34, 42, 3, 33
|
||||
.byte 40, 19, 2, 26, 10, 38, 46, 30
|
||||
.byte 3, 1, 19, 16, 26, 58, 42, 49
|
||||
.byte 63, 1, 63, 41, 0, 21, 41, 19
|
||||
.byte 21, 45, 44, 52, 20, 5, 11, 64
|
||||
.byte 1, 62, 16, 5, 5, 8, 58, 56
|
||||
.byte 16, 26, 6, 37, 19, 16, 25, 29
|
||||
.byte 64, 59, 16, 6, 41, 28, 8, 51
|
||||
.byte 54, 5, 19, 28, 13, 38, 52, 35
|
||||
.byte 42, 13, 34, 33, 61, 61, 7, 27
|
||||
.byte 38, 33, 9, 57, 10, 30, 8, 4
|
||||
.byte 46, 3, 39, 46, 62, 20, 48, 7
|
||||
|
||||
|
||||
; increments the cursor line by line, looping to (0, 0) after (63, 28)
|
||||
|
||||
inc_cursor:
|
||||
lda cursor_x
|
||||
cmp #63
|
||||
beq .newline
|
||||
inc cursor_x
|
||||
rts
|
||||
.newline:
|
||||
lda cursor_y
|
||||
cmp #28
|
||||
beq .newscreen
|
||||
stz cursor_x
|
||||
inc cursor_y
|
||||
rts
|
||||
.newscreen:
|
||||
stz cursor_y
|
||||
stz cursor_x
|
||||
rts
|
||||
|
||||
; zeroes out the display, resets cursor to 0,0
|
||||
|
||||
clear:
|
||||
lda #0
|
||||
ldy #0
|
||||
|
||||
.loop:
|
||||
sta $6000,y
|
||||
sta $6100,y
|
||||
sta $6200,y
|
||||
sta $6300,y
|
||||
sta $6400,y
|
||||
sta $6500,y
|
||||
sta $6600,y
|
||||
sta $6700,y ; this goes slightly over but it's fine
|
||||
iny
|
||||
bne .loop
|
||||
stz cursor
|
||||
stz cursor + 1
|
||||
rts
|
||||
|
||||
|
||||
; prints string from cursor position, stopping at end of string or at 256 chars, whichever comes first
|
||||
; $6000 + (64*Y) + X
|
||||
; THIS WILL WRITE OUT OF BOUNDS IF THE CURSOR IS OUT OF BOUNDS/STRING IS TOO LONG
|
||||
|
||||
; TODO: figure out a simple way of writing arbitrary length strings
|
||||
|
||||
print:
|
||||
jsr cursor_addr
|
||||
ldy #0
|
||||
; y_overflow = temp + 5
|
||||
.loop:
|
||||
lda text, y
|
||||
beq .end
|
||||
sta (temp), y
|
||||
iny
|
||||
bra .loop
|
||||
.end:
|
||||
rts
|
||||
|
||||
|
||||
; calculates real vram address from cursor (x, y)
|
||||
|
||||
cursor_addr:
|
||||
stz temp
|
||||
stz temp + 1
|
||||
lda cursor_y
|
||||
beq .add_x ; if y's zero just add x
|
||||
.y_mult:
|
||||
; multiply by 64
|
||||
clc
|
||||
asl
|
||||
rol temp + 1
|
||||
asl
|
||||
rol temp + 1
|
||||
asl
|
||||
rol temp + 1
|
||||
asl
|
||||
rol temp + 1
|
||||
asl
|
||||
rol temp + 1
|
||||
asl
|
||||
rol temp + 1
|
||||
sta temp
|
||||
.add_x:
|
||||
clc
|
||||
lda cursor_x
|
||||
adc temp
|
||||
sta temp
|
||||
lda #0
|
||||
adc temp + 1
|
||||
sta temp + 1
|
||||
clc
|
||||
|
||||
lda #$60
|
||||
adc temp + 1
|
||||
sta temp + 1
|
||||
rts
|
||||
|
||||
|
||||
rand_draw:
|
||||
ldx rand_index
|
||||
lda random_x, x
|
||||
sta cursor_x
|
||||
lda random_y, x
|
||||
sta cursor_y
|
||||
jsr cursor_addr
|
||||
inc rand_index
|
||||
rts
|
||||
|
||||
|
||||
isr: ; interrupt service routine
|
||||
pha
|
||||
phx
|
||||
phy
|
||||
; jsr irq
|
||||
ply
|
||||
plx
|
||||
pla
|
||||
rti
|
||||
|
||||
.include "math.inc"
|
||||
|
||||
.org $fffc
|
||||
.word reset
|
||||
.word isr
|
||||
Binary file not shown.
+189
@@ -0,0 +1,189 @@
|
||||
; .setcpu "65C02"
|
||||
.include "./macro.inc"
|
||||
|
||||
; okay so rn i wanna set up a very basic system init, and write a few subroutines to draw characters at x,y coordinates
|
||||
n = $01 ; temporary storage for data stack operations
|
||||
|
||||
key_row = $200 ; used for character lookup when key pressed
|
||||
key_col = $201
|
||||
cursor = $202
|
||||
|
||||
char_buffer = $300 ; 256 byte character buffer
|
||||
|
||||
kb_row = $4400 ; keyboard hardware register
|
||||
kb_row_cache = $203 ; cache
|
||||
|
||||
.org $8000
|
||||
|
||||
reset:
|
||||
sei
|
||||
ldx #0; initialize data stack pointer
|
||||
|
||||
initdisplay:
|
||||
lda #0
|
||||
ldy #0
|
||||
|
||||
cleardisplay:
|
||||
sta $6000,y
|
||||
sta $6100,y
|
||||
sta $6200,y
|
||||
sta $6300,y
|
||||
sta $6400,y
|
||||
sta $6500,y
|
||||
sta $6600,y
|
||||
sta $6700,y ; this goes slightly over but it's fine
|
||||
iny
|
||||
bne cleardisplay
|
||||
cli
|
||||
|
||||
print_test:
|
||||
lda #0
|
||||
sta key_row
|
||||
lda #5
|
||||
sta key_col
|
||||
push_coords #5, #5
|
||||
|
||||
main:
|
||||
; jsr printtext
|
||||
; key_zero:
|
||||
; stz keyboard_cache, x
|
||||
; dex
|
||||
; bpl key_zero
|
||||
; fim:
|
||||
; cli
|
||||
; bra fim
|
||||
jsr print
|
||||
; jsr print
|
||||
stp
|
||||
jmp main
|
||||
|
||||
|
||||
|
||||
; keyboard: ; reads keyboard registers and stores the column and row of the first key found
|
||||
; ; TODO: make this routine store up to 8 indices (for 8 key rollover)
|
||||
; ldy #0
|
||||
; .check_row: ; loop through each row
|
||||
; lda kb_row, y
|
||||
; beq .skip_row ; if row has no key pressed, skip checking which key
|
||||
; ; jmp key_down
|
||||
; sta kb_row_cache, y ; if key pressed, cache it
|
||||
; lda kb_row, y
|
||||
; cmp kb_row_cache, y ; has key changed?
|
||||
; beq key_down
|
||||
; .skip_row:
|
||||
; iny
|
||||
; cpy #5
|
||||
; bne .check_row
|
||||
; rts
|
||||
|
||||
; key_down: ; a is loaded with the row byte
|
||||
; phy
|
||||
; sty key_row ; store character row
|
||||
; ldy #0
|
||||
; .find_col: ; test each row bit, store column if key pressed
|
||||
; lsr ; test bit 7
|
||||
; bcs store_col ; if unset, don't go store character columnb
|
||||
; .skip:
|
||||
; iny
|
||||
; cpy #8
|
||||
; bne .find_col ; loop until we've checked each bit
|
||||
; rts
|
||||
|
||||
; store_col:
|
||||
; sty key_col
|
||||
; jsr print
|
||||
; rts
|
||||
|
||||
|
||||
|
||||
|
||||
print: ; x y -- prints the key indexed with key_col and key_row at position x, y
|
||||
|
||||
keymap_index:
|
||||
push
|
||||
lda key_col
|
||||
stz 1, x
|
||||
sta 0, x
|
||||
push
|
||||
lda #8
|
||||
stz 1, x
|
||||
sta 0, x
|
||||
push
|
||||
lda key_row
|
||||
stz 1, x
|
||||
sta 0, x
|
||||
jsr mult
|
||||
jsr plus
|
||||
lda 0, x
|
||||
tay
|
||||
lda keymap, y
|
||||
push
|
||||
sta 0, x
|
||||
stz 1, x
|
||||
jsr draw_char
|
||||
rts
|
||||
|
||||
keymap:
|
||||
.byte "?outrew?"
|
||||
.byte "?piygsq?"
|
||||
.byte "a??khvd?"
|
||||
.byte "42ljbfz?"
|
||||
.byte "31?mncx?"
|
||||
.byte "????? m"
|
||||
|
||||
draw:
|
||||
; push_coords #0, #0
|
||||
; push_char #$00
|
||||
; jsr draw_char
|
||||
rts
|
||||
|
||||
draw_char: ; draw a character c at (x, y) (n1: x n2: y n3: c -- )
|
||||
lda 0, x ; load a with character to draw
|
||||
pop ; and pop it off the stack
|
||||
jsr get_char_address ; calculate where to put the character in memory
|
||||
sta (0, x) ; store a at the address pointed to on the stack
|
||||
rts
|
||||
|
||||
get_char_address: ; gets vram address for a character at (x, y),
|
||||
; (n1: x n2: y -- n: $6000 + x + (64 * y))
|
||||
;jsr push_lit ; push 64 onto stack, low byte first
|
||||
;.byte 64
|
||||
;.byte 0
|
||||
pha
|
||||
lda #64
|
||||
push ; doing this instead until `push_lit` is fixed
|
||||
sta 0, x
|
||||
stz 1, x
|
||||
jsr mult ; multiply 64 with y (n2)
|
||||
jsr plus ; add result with x (n1)
|
||||
|
||||
;jsr push_lit ; push vram address onto the stack
|
||||
;.byte $00
|
||||
;.byte $60
|
||||
lda #$60
|
||||
push
|
||||
sta 1, x
|
||||
stz 0, x
|
||||
jsr plus ; add vram start address to result
|
||||
|
||||
pla
|
||||
rts
|
||||
|
||||
fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1 n3: y1 n4: x2 n5: y2 -- )
|
||||
jsr get_char_address
|
||||
|
||||
isr: ; interrupt service routine
|
||||
pha
|
||||
phx
|
||||
phy
|
||||
; jsr keyboard
|
||||
ply
|
||||
plx
|
||||
pla
|
||||
rti
|
||||
|
||||
.include "math.inc"
|
||||
|
||||
.org $fffc
|
||||
.word reset
|
||||
.word isr
|
||||
Binary file not shown.
@@ -0,0 +1,124 @@
|
||||
; .setcpu "65C02"
|
||||
.include "./macro.inc"
|
||||
|
||||
.org $8000
|
||||
|
||||
n = $01 ; temporary storage for data stack operations
|
||||
key_buffer = $200
|
||||
|
||||
reset:
|
||||
sei
|
||||
ldx #0; initialize data stack pointer
|
||||
|
||||
initdisplay:
|
||||
lda #0
|
||||
ldy #0
|
||||
|
||||
cleardisplay:
|
||||
sta $6000,y
|
||||
sta $6100,y
|
||||
sta $6200,y
|
||||
sta $6300,y
|
||||
sta $6400,y
|
||||
sta $6500,y
|
||||
sta $6600,y
|
||||
sta $6700,y ; this goes slightly over but it's fine
|
||||
iny
|
||||
bne cleardisplay
|
||||
; cli
|
||||
|
||||
main:
|
||||
jsr read_keys
|
||||
lda key_buffer
|
||||
sta $6000
|
||||
; lda $4400
|
||||
; sta $6000
|
||||
jmp main
|
||||
|
||||
; first, let's do the simplest case of just the letter a being pressed
|
||||
; 1. check row 2 ($4402, where the a key is) for pressed keys
|
||||
; 2. if any keys are pressed, check if the key is a (bit 0)
|
||||
; 3. if the key is not a, store zero in the key buffer
|
||||
; 4. if the key is a, store the ascii for a in the key buffer
|
||||
; 5. return
|
||||
|
||||
; so u don't have to scroll: key_buffer = $200
|
||||
; key buffer just shows the current state of the keyboard
|
||||
; it is not a queue of keys to print
|
||||
|
||||
read_keys:
|
||||
lda $4402 ; check row 2
|
||||
beq check_key ; if there're any keys, check which
|
||||
clear_buffer:
|
||||
stz key_buffer
|
||||
rts
|
||||
check_key: ; if there is a key pressed, check if it's a
|
||||
ror
|
||||
bmi store_key
|
||||
rts
|
||||
store_key:
|
||||
; in a sec we'll set up ascii table lookup,
|
||||
; for now let's just load the ascii byte for a
|
||||
lda #$61
|
||||
sta key_buffer
|
||||
rts
|
||||
|
||||
draw:
|
||||
push_coords #0, #0
|
||||
push_char #$af
|
||||
jsr draw_char
|
||||
rts
|
||||
|
||||
draw_char: ; draw a character c at (x, y) (n1: x n2: y n3: c -- )
|
||||
lda 0, x ; load a with character to draw
|
||||
pop ; and pop it off the stack
|
||||
jsr get_char_address ; calculate where to put the character in memory
|
||||
sta (0, x) ; store a at the address pointed to on the stack
|
||||
rts
|
||||
|
||||
get_char_address: ; gets vram address for a character at (x, y),
|
||||
; (n1: x n2: y -- n: $6000 + x + (64 * y))
|
||||
;jsr push_lit ; push 64 onto stack, low byte first
|
||||
;.byte 64
|
||||
;.byte 0
|
||||
pha
|
||||
lda #64
|
||||
push ; doing this instead until `push_lit` is fixed
|
||||
sta 0, x
|
||||
stz 1, x
|
||||
jsr mult ; multiply 64 with y (n2)
|
||||
jsr plus ; add result with x (n1)
|
||||
|
||||
;jsr push_lit ; push vram address onto the stack
|
||||
;.byte $00
|
||||
;.byte $60
|
||||
lda #$60
|
||||
push
|
||||
sta 1, x
|
||||
stz 0, x
|
||||
jsr plus ; add vram start address to result
|
||||
|
||||
pla
|
||||
rts
|
||||
|
||||
fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1 n3: y1 n4: x2 n5: y2 -- )
|
||||
jsr get_char_address
|
||||
|
||||
irq:
|
||||
rts
|
||||
|
||||
isr: ; interrupt service routine
|
||||
pha
|
||||
phx
|
||||
phy
|
||||
jsr irq
|
||||
ply
|
||||
plx
|
||||
pla
|
||||
rti
|
||||
|
||||
.include "math.inc"
|
||||
|
||||
.org $fffc
|
||||
.word reset
|
||||
.word isr
|
||||
Binary file not shown.
@@ -0,0 +1,215 @@
|
||||
; .setcpu "65C02"
|
||||
.include "./macro.inc"
|
||||
|
||||
.org $8000
|
||||
|
||||
n = $01 ; temporary storage for data stack operations
|
||||
|
||||
temp = $20 ; scratchpad page
|
||||
str_ptr = $30
|
||||
|
||||
cursor = $300
|
||||
cursor_x = cursor
|
||||
cursor_y = cursor + 1
|
||||
|
||||
char_buf = $302
|
||||
char_buf_index = char_buf + 8
|
||||
|
||||
reset:
|
||||
sei
|
||||
ldx #0; initialize data stack pointer
|
||||
|
||||
init:
|
||||
lda #$31
|
||||
sta str_ptr
|
||||
lda #$80
|
||||
sta str_ptr + 1
|
||||
|
||||
jsr clear
|
||||
lda #0
|
||||
sta cursor_x
|
||||
lda #0
|
||||
sta cursor_y
|
||||
cli
|
||||
|
||||
main:
|
||||
jsr print
|
||||
jmp main
|
||||
|
||||
newline: ; sets cursor to start of next line
|
||||
stz cursor_x
|
||||
lda cursor_y
|
||||
cmp #28
|
||||
bne .end
|
||||
stz cursor_y
|
||||
rts
|
||||
.end:
|
||||
inc cursor_y
|
||||
rts
|
||||
|
||||
text:
|
||||
.asciiz "hello <3"
|
||||
|
||||
; increments the cursor line by line, looping to (0, 0) after (63, 28)
|
||||
|
||||
inc_cursor:
|
||||
lda cursor_x
|
||||
cmp #63
|
||||
beq .newline
|
||||
inc cursor_x
|
||||
rts
|
||||
.newline:
|
||||
lda cursor_y
|
||||
cmp #28
|
||||
beq .newscreen
|
||||
stz cursor_x
|
||||
inc cursor_y
|
||||
rts
|
||||
.newscreen:
|
||||
stz cursor_y
|
||||
stz cursor_x
|
||||
rts
|
||||
|
||||
; zeroes out the display, resets cursor to 0,0
|
||||
|
||||
clear:
|
||||
lda #0
|
||||
ldy #0
|
||||
|
||||
.loop:
|
||||
sta $6000,y
|
||||
sta $6100,y
|
||||
sta $6200,y
|
||||
sta $6300,y
|
||||
sta $6400,y
|
||||
sta $6500,y
|
||||
sta $6600,y
|
||||
sta $6700,y ; this goes slightly over but it's fine
|
||||
iny
|
||||
bne .loop
|
||||
stz cursor
|
||||
stz cursor + 1
|
||||
rts
|
||||
|
||||
|
||||
; prints string from cursor position, stopping at end of string or at 256 chars, whichever comes first
|
||||
; $6000 + (64*Y) + X
|
||||
; THIS WILL WRITE OUT OF BOUNDS IF THE CURSOR IS OUT OF BOUNDS/STRING IS TOO LONG
|
||||
|
||||
; TODO: figure out a simple way of writing arbitrary length strings
|
||||
; and
|
||||
|
||||
print:
|
||||
jsr cursor_addr
|
||||
ldy #0
|
||||
; y_overflow = temp + 5
|
||||
.loop:
|
||||
lda (str_ptr), y
|
||||
beq .end
|
||||
sta (temp), y
|
||||
iny
|
||||
bra .loop
|
||||
.end:
|
||||
rts
|
||||
|
||||
|
||||
; calculates real vram address from cursor (x, y)
|
||||
|
||||
cursor_addr:
|
||||
stz temp
|
||||
stz temp + 1
|
||||
lda cursor_y
|
||||
beq .add_x ; if y's zero just add x
|
||||
.y_mult:
|
||||
; multiply by 64
|
||||
clc
|
||||
asl
|
||||
rol temp + 1
|
||||
asl
|
||||
rol temp + 1
|
||||
asl
|
||||
rol temp + 1
|
||||
asl
|
||||
rol temp + 1
|
||||
asl
|
||||
rol temp + 1
|
||||
asl
|
||||
rol temp + 1
|
||||
sta temp
|
||||
.add_x:
|
||||
clc
|
||||
lda cursor_x
|
||||
adc temp
|
||||
sta temp
|
||||
lda #0
|
||||
adc temp + 1
|
||||
sta temp + 1
|
||||
clc
|
||||
|
||||
lda #$60
|
||||
adc temp + 1
|
||||
sta temp + 1
|
||||
rts
|
||||
|
||||
|
||||
|
||||
; print_text:
|
||||
; lda text,y
|
||||
; beq .end
|
||||
; sta $6000, y
|
||||
; iny
|
||||
; bra print_text
|
||||
; .end:
|
||||
; ldy #0
|
||||
; rts
|
||||
|
||||
; draw_char: ; draw a character c at (x, y) (n1: x n2: y n3: c -- )
|
||||
; lda 0, x ; load a with character to draw
|
||||
; pop ; and pop it off the stack
|
||||
; jsr get_char_address ; calculate where to put the character in memory
|
||||
; sta (0, x) ; store a at the address pointed to on the stack
|
||||
; rts
|
||||
|
||||
; get_char_address: ; gets vram address for a character at (x, y),
|
||||
; ; (n1: x n2: y -- n: $6000 + x + (64 * y))
|
||||
; ;jsr push_lit ; push 64 onto stack, low byte first
|
||||
; ;.byte 64
|
||||
; ;.byte 0
|
||||
; pha
|
||||
; lda #64
|
||||
; push ; doing this instead until `push_lit` is fixed
|
||||
; sta 0, x
|
||||
; stz 1, x
|
||||
; jsr mult ; multiply 64 with y (n2)
|
||||
; jsr plus ; add result with x (n1)
|
||||
|
||||
; ;jsr push_lit ; push vram address onto the stack
|
||||
; ;.byte $00
|
||||
; ;.byte $60
|
||||
; lda #$60
|
||||
; push
|
||||
; sta 1, x
|
||||
; stz 0, x
|
||||
; jsr plus ; add vram start address to result
|
||||
|
||||
; pla
|
||||
; rts
|
||||
|
||||
; fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1 n3: y1 n4: x2 n5: y2 -- )
|
||||
; jsr get_char_address
|
||||
|
||||
isr: ; interrupt service routine
|
||||
pha
|
||||
phx
|
||||
phy
|
||||
; jsr irq
|
||||
ply
|
||||
plx
|
||||
pla
|
||||
rti
|
||||
|
||||
.include "math.inc"
|
||||
|
||||
.org $fffc
|
||||
.word reset
|
||||
.word isr
|
||||
Binary file not shown.
@@ -0,0 +1,62 @@
|
||||
.macro breakpoint ; $02 isn't a valid instruction, the emulator will see this and halt, dump memory contents
|
||||
.byte $02
|
||||
.endm
|
||||
|
||||
.macro pop ; drops a data stack cell
|
||||
inx
|
||||
inx
|
||||
.endm
|
||||
|
||||
.macro pop2 ; drops 2 data stack cells
|
||||
inx
|
||||
inx
|
||||
inx
|
||||
inx
|
||||
.endm
|
||||
|
||||
.macro push ; push a data stack cell
|
||||
dex
|
||||
dex
|
||||
.endm
|
||||
|
||||
.macro push2 ; push 2 data stack cells
|
||||
dex
|
||||
dex
|
||||
dex
|
||||
dex
|
||||
.endm
|
||||
|
||||
.macro push_char, char; pushes an ascii character code onto the stack
|
||||
lda \char
|
||||
push
|
||||
sta 0, x ; char low byte
|
||||
stz 1, x ; char high byte
|
||||
.endm
|
||||
|
||||
.macro push_coords, coord_x, coord_y ; push a set of (x,y) coordinates onto the data stack
|
||||
lda \coord_x
|
||||
push
|
||||
sta 0, x ; low byte
|
||||
stz 1,x ; high byte is zero
|
||||
lda \coord_y
|
||||
push
|
||||
sta 0,x ; same here
|
||||
stz 1,x
|
||||
.endm
|
||||
|
||||
.macro to_r ; pop the top of the stack off and save it in the return (hardware) stack: (n -- )
|
||||
lda 1, x
|
||||
pha
|
||||
lda 0, x
|
||||
pha
|
||||
pop
|
||||
.endm
|
||||
|
||||
.macro from_r ; pop the top of the return stack off and put it on the data stack: ( -- n)
|
||||
push
|
||||
pla
|
||||
sta 0, x
|
||||
pla
|
||||
sta 1, x
|
||||
.endm
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
; --- Data Stack --- ;
|
||||
; on this channel we love garth wilson: https://wilsonminesco.com/stacks/StackOps.ASM
|
||||
; data stack is built up of 2-byte cells
|
||||
|
||||
|
||||
; TODO: this is broken, jumping here does nothing to the stack and skips several instructions, could be an emulator problem tho
|
||||
; push_lit: ; this bad boy lets you inline a literal (low byte first) right after `jsr push_lit` and put it on the stack, once again, on this channel we love garth wilson
|
||||
; push2
|
||||
; phx
|
||||
; tsx
|
||||
; txa
|
||||
; tay
|
||||
; plx
|
||||
|
||||
; lda $102, y
|
||||
; sta 0, x
|
||||
; clc
|
||||
; adc #2
|
||||
; sta $102, y
|
||||
|
||||
; lda $103, y
|
||||
; sta 1, x
|
||||
; adc #0
|
||||
; sta $103, y
|
||||
|
||||
; fetch:
|
||||
; lda (0, x)
|
||||
; pha
|
||||
; inc 0, x
|
||||
; bne .1
|
||||
; inc 1, x
|
||||
; .1:
|
||||
; lda (0, x)
|
||||
; bra put
|
||||
; push
|
||||
|
||||
; put:
|
||||
; sta 1, x
|
||||
; pla
|
||||
; sta 0, x
|
||||
; rts
|
||||
|
||||
plus: ; add: (n1 n2 -- n1+n2)
|
||||
clc
|
||||
lda 0, x
|
||||
adc 2, x
|
||||
sta 2, x
|
||||
lda 1, x
|
||||
adc 3, x
|
||||
sta 3, x
|
||||
pop
|
||||
rts
|
||||
|
||||
|
||||
mult: ; multiply: (n1 n2 -- n1*n2), frankly, i don't know how this works, but TODO: will try to figure it out later
|
||||
phy
|
||||
stz n
|
||||
ldy #0
|
||||
.1:
|
||||
lsr 3, x
|
||||
ror 2, x
|
||||
bcc .2
|
||||
clc
|
||||
lda n
|
||||
adc 0, x
|
||||
sta n
|
||||
tya
|
||||
adc 1, x
|
||||
tay
|
||||
.2:
|
||||
asl 0, x
|
||||
rol 1, x
|
||||
lda 2, x
|
||||
ora 3, x
|
||||
bne .1
|
||||
lda n
|
||||
sta 2, x
|
||||
sty 3, x
|
||||
pop
|
||||
ply
|
||||
rts
|
||||
@@ -0,0 +1,88 @@
|
||||
; .setcpu "65C02"
|
||||
.include "./macro.inc"
|
||||
|
||||
.org $8000
|
||||
|
||||
n = $01 ; temporary storage for data stack operations
|
||||
|
||||
reset:
|
||||
sei
|
||||
ldx #0; initialize data stack pointer
|
||||
|
||||
initdisplay:
|
||||
lda #0
|
||||
ldy #0
|
||||
|
||||
cleardisplay:
|
||||
sta $6000,y
|
||||
sta $6100,y
|
||||
sta $6200,y
|
||||
sta $6300,y
|
||||
sta $6400,y
|
||||
sta $6500,y
|
||||
sta $6600,y
|
||||
sta $6700,y ; this goes slightly over but it's fine
|
||||
iny
|
||||
bne cleardisplay
|
||||
cli
|
||||
|
||||
main:
|
||||
jsr draw
|
||||
jmp main
|
||||
|
||||
draw:
|
||||
push_coords #0, #0
|
||||
push_char #$af
|
||||
jsr draw_char
|
||||
rts
|
||||
|
||||
draw_char: ; draw a character c at (x, y) (n1: x n2: y n3: c -- )
|
||||
lda 0, x ; load a with character to draw
|
||||
pop ; and pop it off the stack
|
||||
jsr get_char_address ; calculate where to put the character in memory
|
||||
sta (0, x) ; store a at the address pointed to on the stack
|
||||
rts
|
||||
|
||||
get_char_address: ; gets vram address for a character at (x, y),
|
||||
; (n1: x n2: y -- n: $6000 + x + (64 * y))
|
||||
;jsr push_lit ; push 64 onto stack, low byte first
|
||||
;.byte 64
|
||||
;.byte 0
|
||||
pha
|
||||
lda #64
|
||||
push ; doing this instead until `push_lit` is fixed
|
||||
sta 0, x
|
||||
stz 1, x
|
||||
jsr mult ; multiply 64 with y (n2)
|
||||
jsr plus ; add result with x (n1)
|
||||
|
||||
;jsr push_lit ; push vram address onto the stack
|
||||
;.byte $00
|
||||
;.byte $60
|
||||
lda #$60
|
||||
push
|
||||
sta 1, x
|
||||
stz 0, x
|
||||
jsr plus ; add vram start address to result
|
||||
|
||||
pla
|
||||
rts
|
||||
|
||||
fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1 n3: y1 n4: x2 n5: y2 -- )
|
||||
jsr get_char_address
|
||||
|
||||
isr: ; interrupt service routine
|
||||
pha
|
||||
phx
|
||||
phy
|
||||
; jsr irq
|
||||
ply
|
||||
plx
|
||||
pla
|
||||
rti
|
||||
|
||||
.include "math.inc"
|
||||
|
||||
.org $fffc
|
||||
.word reset
|
||||
.word isr
|
||||
Binary file not shown.
+173
@@ -0,0 +1,173 @@
|
||||
; .setcpu "65C02"
|
||||
.include "./macro.inc"
|
||||
|
||||
; okay so rn i wanna set up a very basic system init, and write a few subroutines to draw characters at x,y coordinates
|
||||
n = $01 ; temporary storage for data stack operations
|
||||
|
||||
key_row = $200 ; used for character lookup when key pressed
|
||||
key_col = $201
|
||||
cursor = $202
|
||||
|
||||
char_buffer = $300 ; 256 byte character buffer
|
||||
|
||||
kb_row = $4400 ; keyboard hardware register
|
||||
kb_row_cache = $203 ; cache
|
||||
|
||||
.org $8000
|
||||
|
||||
reset:
|
||||
sei
|
||||
ldx #0; initialize data stack pointer
|
||||
jmp main
|
||||
|
||||
initdisplay:
|
||||
lda #20
|
||||
ldy #0
|
||||
|
||||
cleardisplay:
|
||||
sta $6000,y
|
||||
sta $6100,y
|
||||
sta $6200,y
|
||||
sta $6300,y
|
||||
sta $6400,y
|
||||
sta $6500,y
|
||||
sta $6600,y
|
||||
sta $6700,y ; this goes slightly over but it's fine
|
||||
iny
|
||||
bne cleardisplay
|
||||
cli
|
||||
|
||||
main:
|
||||
; jsr keyboard
|
||||
; key_zero:
|
||||
; stz keyboard_cache, x
|
||||
; dex
|
||||
; bpl key_zero
|
||||
; fim:
|
||||
; cli
|
||||
; bra fim
|
||||
; jsr kitty_keys
|
||||
lda #9
|
||||
sta $6000
|
||||
jmp main
|
||||
|
||||
not_keyboard:
|
||||
ldy #0
|
||||
.check_row: ; loop through each row
|
||||
lda kb_row, y
|
||||
beq .skip_row ; if row has no key pressed, skip checking which key
|
||||
sta kb_row_cache, y ; if key pressed, cache it
|
||||
lda kb_row, y
|
||||
cmp kb_row_cache, y ; has key changed?
|
||||
beq key_down
|
||||
.skip_row:
|
||||
iny
|
||||
cpy #5
|
||||
bne .check_row
|
||||
rts
|
||||
|
||||
key_down: ; a is loaded with the row byte
|
||||
phy
|
||||
sty key_row ; store character row
|
||||
ldy #0
|
||||
.find_col: ; test each row bit, store column if key pressed
|
||||
lsr ; test bit 7
|
||||
bcs store_col ; if unset, don't go store character columnb
|
||||
.skip:
|
||||
iny
|
||||
cpy #8
|
||||
bne .find_col ; loop until we've checked each bit
|
||||
|
||||
store_col:
|
||||
sty key_col
|
||||
|
||||
keymap_index:
|
||||
push
|
||||
lda key_col
|
||||
stz 1, x
|
||||
sta 0, x
|
||||
push
|
||||
lda #8
|
||||
stz 1, x
|
||||
sta 0, x
|
||||
push
|
||||
lda key_row
|
||||
stz 1, x
|
||||
sta 0, x
|
||||
jsr mult
|
||||
jsr plus
|
||||
lda 0, x
|
||||
tay
|
||||
|
||||
print: ; we've stored the character position, now let's
|
||||
lda keymap, y
|
||||
ldy cursor
|
||||
sta $6000, y
|
||||
inc cursor
|
||||
ply
|
||||
rts
|
||||
|
||||
keymap:
|
||||
.byte "?outrew?"
|
||||
.byte "?piygsq?"
|
||||
.byte "a??khvd?"
|
||||
.byte "42ljbfz?"
|
||||
.byte "31?mncx?"
|
||||
.byte "????? m"
|
||||
|
||||
; draw:
|
||||
; ; push_coords #0, #0
|
||||
; ; push_char #$00
|
||||
; ; jsr draw_char
|
||||
; rts
|
||||
|
||||
; draw_char: ; draw a character c at (x, y) (n1: x n2: y n3: c -- )
|
||||
; lda 0, x ; load a with character to draw
|
||||
; pop ; and pop it off the stack
|
||||
; jsr get_char_address ; calculate where to put the character in memory
|
||||
; sta (0, x) ; store a at the address pointed to on the stack
|
||||
; rts
|
||||
|
||||
; get_char_address: ; gets vram address for a character at (x, y),
|
||||
; ; (n1: x n2: y -- n: $6000 + x + (64 * y))
|
||||
; ;jsr push_lit ; push 64 onto stack, low byte first
|
||||
; ;.byte 64
|
||||
; ;.byte 0
|
||||
; pha
|
||||
; lda #64
|
||||
; push ; doing this instead until `push_lit` is fixed
|
||||
; sta 0, x
|
||||
; stz 1, x
|
||||
; jsr mult ; multiply 64 with y (n2)
|
||||
; jsr plus ; add result with x (n1)
|
||||
|
||||
; ;jsr push_lit ; push vram address onto the stack
|
||||
; ;.byte $00
|
||||
; ;.byte $60
|
||||
; lda #$60
|
||||
; push
|
||||
; sta 1, x
|
||||
; stz 0, x
|
||||
; jsr plus ; add vram start address to result
|
||||
|
||||
; pla
|
||||
; rts
|
||||
|
||||
; fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1 n3: y1 n4: x2 n5: y2 -- )
|
||||
; jsr get_char_address
|
||||
|
||||
isr: ; interrupt service routine
|
||||
pha
|
||||
phx
|
||||
phy
|
||||
; jsr irq
|
||||
ply
|
||||
plx
|
||||
pla
|
||||
rti
|
||||
|
||||
.include "math.inc"
|
||||
|
||||
.org $fffc
|
||||
.word reset
|
||||
.word isr
|
||||
Binary file not shown.
+187
@@ -0,0 +1,187 @@
|
||||
; .setcpu "65C02"
|
||||
.include "./macro.inc"
|
||||
|
||||
; okay so rn i wanna set up a very basic system init, and write a few subroutines to draw characters at x,y coordinates
|
||||
n = $01 ; temporary storage for data stack operations
|
||||
|
||||
key_row = $200 ; used for character lookup when key pressed
|
||||
key_col = $201
|
||||
cursor = $202
|
||||
|
||||
char_buffer = $300 ; 256 byte character buffer
|
||||
|
||||
kb_row = $4400 ; keyboard hardware register
|
||||
kb_row_cache = $203 ; cache
|
||||
|
||||
.org $8000
|
||||
|
||||
reset:
|
||||
sei
|
||||
ldx #0; initialize data stack pointer
|
||||
|
||||
initdisplay:
|
||||
lda #0
|
||||
ldy #0
|
||||
|
||||
cleardisplay:
|
||||
sta $6000,y
|
||||
sta $6100,y
|
||||
sta $6200,y
|
||||
sta $6300,y
|
||||
sta $6400,y
|
||||
sta $6500,y
|
||||
sta $6600,y
|
||||
sta $6700,y ; this goes slightly over but it's fine
|
||||
iny
|
||||
bne cleardisplay
|
||||
cli
|
||||
|
||||
print_test:
|
||||
lda #0
|
||||
sta key_row
|
||||
lda #5
|
||||
sta key_col
|
||||
push_coords #5, #5
|
||||
|
||||
main:
|
||||
; jsr printtext
|
||||
; key_zero:
|
||||
; stz keyboard_cache, x
|
||||
; dex
|
||||
; bpl key_zero
|
||||
; fim:
|
||||
; cli
|
||||
; bra fim
|
||||
jsr print
|
||||
; jsr print
|
||||
jmp main
|
||||
|
||||
|
||||
; keyboard: ; reads keyboard registers and stores the column and row of the first key found
|
||||
; ; TODO: make this routine store up to 8 indices (for 8 key rollover)
|
||||
; ldy #0
|
||||
; .check_row: ; loop through each row
|
||||
; lda kb_row, y
|
||||
; beq .skip_row ; if row has no key pressed, skip checking which key
|
||||
; ; jmp key_down
|
||||
; sta kb_row_cache, y ; if key pressed, cache it
|
||||
; lda kb_row, y
|
||||
; cmp kb_row_cache, y ; has key changed?
|
||||
; beq key_down
|
||||
; .skip_row:
|
||||
; iny
|
||||
; cpy #5
|
||||
; bne .check_row
|
||||
; rts
|
||||
|
||||
; key_down: ; a is loaded with the row byte
|
||||
; phy
|
||||
; sty key_row ; store character row
|
||||
; ldy #0
|
||||
; .find_col: ; test each row bit, store column if key pressed
|
||||
; lsr ; test bit 7
|
||||
; bcs store_col ; if unset, don't go store character columnb
|
||||
; .skip:
|
||||
; iny
|
||||
; cpy #8
|
||||
; bne .find_col ; loop until we've checked each bit
|
||||
; rts
|
||||
|
||||
; store_col:
|
||||
; sty key_col
|
||||
; jsr print
|
||||
; rts
|
||||
|
||||
|
||||
|
||||
|
||||
print: ; x y -- prints the key indexed with key_col and key_row at position x, y
|
||||
|
||||
keymap_index:
|
||||
push
|
||||
lda key_col
|
||||
stz 1, x
|
||||
sta 0, x
|
||||
push
|
||||
lda #8
|
||||
stz 1, x
|
||||
sta 0, x
|
||||
push
|
||||
lda key_row
|
||||
stz 1, x
|
||||
sta 0, x
|
||||
jsr mult
|
||||
jsr plus
|
||||
lda 0, x
|
||||
tay
|
||||
lda keymap, y
|
||||
push
|
||||
sta 0, x
|
||||
stz 1, x
|
||||
jsr draw_char
|
||||
rts
|
||||
|
||||
keymap:
|
||||
.byte "?outrew?"
|
||||
.byte "?piygsq?"
|
||||
.byte "a??khvd?"
|
||||
.byte "42ljbfz?"
|
||||
.byte "31?mncx?"
|
||||
.byte "????? m"
|
||||
|
||||
draw:
|
||||
; push_coords #0, #0
|
||||
; push_char #$00
|
||||
; jsr draw_char
|
||||
rts
|
||||
|
||||
draw_char: ; draw a character c at (x, y) (n1: x n2: y n3: c -- )
|
||||
lda 0, x ; load a with character to draw
|
||||
pop ; and pop it off the stack
|
||||
jsr get_char_address ; calculate where to put the character in memory
|
||||
sta (0, x) ; store a at the address pointed to on the stack
|
||||
rts
|
||||
|
||||
get_char_address: ; gets vram address for a character at (x, y),
|
||||
; (n1: x n2: y -- n: $6000 + x + (64 * y))
|
||||
;jsr push_lit ; push 64 onto stack, low byte first
|
||||
;.byte 64
|
||||
;.byte 0
|
||||
pha
|
||||
lda #64
|
||||
push ; doing this instead until `push_lit` is fixed
|
||||
sta 0, x
|
||||
stz 1, x
|
||||
jsr mult ; multiply 64 with y (n2)
|
||||
jsr plus ; add result with x (n1)
|
||||
|
||||
;jsr push_lit ; push vram address onto the stack
|
||||
;.byte $00
|
||||
;.byte $60
|
||||
lda #$60
|
||||
push
|
||||
sta 1, x
|
||||
stz 0, x
|
||||
jsr plus ; add vram start address to result
|
||||
|
||||
pla
|
||||
rts
|
||||
|
||||
fill: ; fills an area from (x1, y1) to (x2, y2) will character c, (n1: c n2: x1 n3: y1 n4: x2 n5: y2 -- )
|
||||
jsr get_char_address
|
||||
|
||||
isr: ; interrupt service routine
|
||||
pha
|
||||
phx
|
||||
phy
|
||||
; jsr keyboard
|
||||
ply
|
||||
plx
|
||||
pla
|
||||
rti
|
||||
|
||||
.include "math.inc"
|
||||
|
||||
.org $fffc
|
||||
.word reset
|
||||
.word isr
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Provide the name of the rom/asm file to run"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -e
|
||||
|
||||
vasm6502_oldstyle roms/$1.asm -dotdir -wdc02 -ldots -Fbin -o roms/$1.rom;
|
||||
cargo run -- rom "roms/$1.rom";
|
||||
# hexdump -C ./cpu_dump.bin;
|
||||
@@ -0,0 +1,179 @@
|
||||
use std::{
|
||||
env,
|
||||
fs::File,
|
||||
io::{ErrorKind, Read},
|
||||
process::exit,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug)]
|
||||
pub enum ScreenType {
|
||||
Terminal,
|
||||
#[default]
|
||||
Window,
|
||||
}
|
||||
|
||||
pub struct Config {
|
||||
pub rom: Option<String>,
|
||||
pub screen: ScreenType,
|
||||
pub char_rom: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct ConfigBuilder {
|
||||
rom: Option<String>,
|
||||
char_rom: Option<String>,
|
||||
screen: ScreenType,
|
||||
}
|
||||
|
||||
impl ConfigBuilder {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
rom: None,
|
||||
char_rom: None,
|
||||
screen: ScreenType::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build(self) -> Config {
|
||||
let rom = match self.rom {
|
||||
Some(rom) => Some(rom),
|
||||
None => {
|
||||
println!("no rom was provided :(");
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
let char_rom = self.char_rom;
|
||||
let screen = self.screen;
|
||||
|
||||
Config {
|
||||
rom,
|
||||
screen,
|
||||
char_rom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn help(command: Option<String>) {
|
||||
let executable: String = env::args().next().unwrap();
|
||||
if let Some(command) = command {
|
||||
match &command as &str {
|
||||
"rom" | "--rom" | "-r" => {
|
||||
println!("{executable} {command} <path>\n\nload a rom/binary from path");
|
||||
exit(0);
|
||||
}
|
||||
"screen" | "--screen" | "-s" => {
|
||||
println!("{executable} {command} <path>\n\nload a rom/binary from path");
|
||||
exit(0);
|
||||
}
|
||||
"help" | "--help" | "-h" => {
|
||||
println!("{executable} {command} <command>\n\nshow help info for a given command");
|
||||
exit(0);
|
||||
}
|
||||
_ => {
|
||||
println!(
|
||||
"{command:?} isn't a valid command!\n\nuse `{executable} help` to see all valid commands",
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("ʕ·ᴥ·ʔ- {executable} is an emulator for george:");
|
||||
println!("https://git.augustkline.com/august/george\n");
|
||||
println!("commands:");
|
||||
println!(" help, -h, --help <command>: print help info for any command");
|
||||
println!(" rom, -r, --rom <path>: load a rom/binary from path");
|
||||
println!(" screen, -s, --screen <type>: use the \"terminal\" or \"window\" display type");
|
||||
println!("\nconfiguration:");
|
||||
println!(" george-emu searches for a `george.toml` in the current directory.\n in `george.toml` you can specify a path for the character rom\n using the key `char_rom` and the main rom/binary with the key `rom`");
|
||||
println!("\ndebugging:");
|
||||
println!(" you can pipe in a rom for george to evaluate.");
|
||||
println!(" she'll read it until she reaches a breakpoint (`0x02`) or `stp` instruction,");
|
||||
println!(" then will dump her memory to stdout <3");
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_input() -> Config {
|
||||
let executable: String = env::args().next().unwrap();
|
||||
let mut config = ConfigBuilder::new();
|
||||
|
||||
let len = env::args().len();
|
||||
|
||||
if len == 1 {
|
||||
config = match File::open("./george.toml") {
|
||||
Ok(mut file) => {
|
||||
let mut string = String::new();
|
||||
file.read_to_string(&mut string).unwrap();
|
||||
toml::from_str(string.as_str()).unwrap()
|
||||
}
|
||||
Err(_) => {
|
||||
println!("couldn't find a `george.toml` in the current directory!");
|
||||
println!("\nuse `{executable} --help` to see all config options");
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
return config.build();
|
||||
}
|
||||
|
||||
let mut args = env::args().skip(1);
|
||||
|
||||
while let Some(arg) = args.next() {
|
||||
match &arg[..] {
|
||||
"--help" | "-h" | "help" => help(args.next()),
|
||||
"--rom" | "-r" | "rom" => {
|
||||
if let Some(path) = args.next() {
|
||||
match std::fs::File::open(&path) {
|
||||
Ok(_) => {
|
||||
config.rom = Some(path);
|
||||
}
|
||||
Err(error) => {
|
||||
match error.kind() {
|
||||
ErrorKind::NotFound => {
|
||||
println!("couldn't find the rom at {path:?}");
|
||||
}
|
||||
ErrorKind::PermissionDenied => {
|
||||
println!(
|
||||
"didn't have sufficient permissions to open the rom at {path:?}"
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
println!("something went wrong! try again in a moment? really not sure why you're getting this error");
|
||||
}
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
println!("no rom specified!");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
"--screen" | "-s" | "screen" => {
|
||||
let kind = args.next();
|
||||
match kind {
|
||||
Some(kind) => match &kind as &str {
|
||||
"terminal" | "Terminal" | "TERMINAL" | "t" | "term" => {}
|
||||
"window" | "Window" | "WINDOW" | "w" | "win" => {
|
||||
config.screen = ScreenType::Window
|
||||
}
|
||||
_ => {
|
||||
println!("{kind:?} isn't a valid screen type!\n\nuse \"{executable} --help screen\" to see all valid screen types");
|
||||
exit(1);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
println!("no screen type was provided!\n\nuse \"{executable} --help screen\" to see all valid screen types");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
println!("{arg:?} isn't a valid command!\n\nuse \"{executable} help\" to see all valid commands");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
config.build()
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
use std::io::{self, stdin, IsTerminal};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod cli;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use cli::get_input;
|
||||
use georgeemu::memory::{Mem, MemHandle};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use georgeemu::GeorgeEmu;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use minifb::{Scale, ScaleMode, Window, WindowOptions};
|
||||
|
||||
use std::{fs::File, io::Read};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() -> io::Result<()> {
|
||||
use std::io::{stdout, Write};
|
||||
|
||||
use georgeemu::cpu::Cpu;
|
||||
|
||||
let mut input = stdin();
|
||||
|
||||
if !input.is_terminal() {
|
||||
let mut bytes = [0u8; 0x8000];
|
||||
input.read_exact(&mut bytes).unwrap();
|
||||
let memory = make_memory(&bytes);
|
||||
let mut cpu = Cpu::new(memory);
|
||||
let final_output = cpu.run_to_end();
|
||||
|
||||
let _ = stdout().write_all(&final_output);
|
||||
Ok(())
|
||||
} else {
|
||||
let window = Window::new(
|
||||
"ʕ·ᴥ·ʔ-☆",
|
||||
512,
|
||||
380,
|
||||
WindowOptions {
|
||||
resize: true,
|
||||
borderless: true,
|
||||
title: true,
|
||||
transparency: false,
|
||||
scale: Scale::FitScreen,
|
||||
scale_mode: ScaleMode::AspectRatioStretch,
|
||||
topmost: false,
|
||||
none: true,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let config = get_input();
|
||||
let rom = match config.rom {
|
||||
Some(path) => {
|
||||
let mut file = File::open(path).unwrap();
|
||||
let mut bin = vec![0; 0x8000];
|
||||
file.read_exact(&mut bin).unwrap();
|
||||
bin.try_into().unwrap()
|
||||
}
|
||||
None => *include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/roms/george.rom")),
|
||||
};
|
||||
let mut emu = GeorgeEmu::builder().rom(rom).window(window).build();
|
||||
|
||||
emu.run();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn make_memory(bytes: &[u8]) -> MemHandle {
|
||||
let mut memory = Mem::new();
|
||||
memory.load_bytes(bytes);
|
||||
MemHandle::new(memory)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn main() {}
|
||||
+296
@@ -0,0 +1,296 @@
|
||||
use crate::instructions::get_instruction;
|
||||
use crate::memory::{MemHandle, MemoryReader, MemoryWriter};
|
||||
use std::fmt::Display;
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::thread::sleep;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum StatusFlag {
|
||||
Negative = 0b1000_0000,
|
||||
Overflow = 0b0100_0000,
|
||||
Brk = 0b0011_0000,
|
||||
//BrkIrq = 0b0010_0000,
|
||||
Decimal = 0b0000_1000,
|
||||
IrqDisable = 0b0000_0100,
|
||||
Zero = 0b000_0010,
|
||||
Carry = 0b0000_0001,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CpuController(Sender<CpuControl>);
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum CpuControl {
|
||||
Irq,
|
||||
Nmi,
|
||||
Toggle,
|
||||
Data,
|
||||
Cycle,
|
||||
}
|
||||
|
||||
impl CpuController {
|
||||
pub fn new(sender: Sender<CpuControl>) -> Self {
|
||||
Self(sender)
|
||||
}
|
||||
pub fn irq(&self) {
|
||||
let _ = self.0.send(CpuControl::Irq);
|
||||
}
|
||||
pub fn nmi(&self) {
|
||||
let _ = self.0.send(CpuControl::Nmi);
|
||||
}
|
||||
pub fn toggle(&self) {
|
||||
let _ = self.0.send(CpuControl::Toggle);
|
||||
}
|
||||
pub fn cycle(&self) {
|
||||
let _ = self.0.send(CpuControl::Cycle);
|
||||
}
|
||||
pub fn data(&self) {
|
||||
let _ = self.0.send(CpuControl::Data);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CpuReceiver(Receiver<CpuControl>);
|
||||
impl CpuReceiver {
|
||||
pub fn new(receiver: Receiver<CpuControl>) -> Self {
|
||||
Self(receiver)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Cpu {
|
||||
pub a: u8, // Accumulator Register
|
||||
pub x: u8, // X Register
|
||||
pub y: u8, // Y Register
|
||||
pub pc: u16, // Program Counter
|
||||
pub s: u8, // Stack Pointer
|
||||
pub p: u8, // Status Register
|
||||
pub irq: bool,
|
||||
pub nmi: bool,
|
||||
pub memory: MemHandle,
|
||||
pub pending_cycles: usize,
|
||||
pub debug: bool,
|
||||
receiver: Option<CpuReceiver>,
|
||||
stopped: bool,
|
||||
cycle: bool,
|
||||
}
|
||||
|
||||
impl Display for Cpu {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{},{}", self.a, self.x)
|
||||
}
|
||||
}
|
||||
|
||||
impl MemoryReader for Cpu {
|
||||
fn read(&self, address: u16) -> u8 {
|
||||
self.memory.read(address)
|
||||
}
|
||||
}
|
||||
impl MemoryWriter for Cpu {
|
||||
fn write(&self, address: u16, data: u8) {
|
||||
self.memory.write(address, data);
|
||||
}
|
||||
}
|
||||
|
||||
impl Cpu {
|
||||
pub fn new(memory: MemHandle) -> Self {
|
||||
// reset the cpu on initialization so we don't
|
||||
// scream and cry for two days trying to figure
|
||||
// out why george isn't working <3
|
||||
let low_byte = memory.read(0xFFFC);
|
||||
let high_byte = memory.read(0xFFFD);
|
||||
let pc = (high_byte as u16) << 8 | (low_byte as u16);
|
||||
|
||||
Cpu {
|
||||
a: 0x00,
|
||||
x: 0x00,
|
||||
y: 0x00,
|
||||
pc,
|
||||
s: 0xFF,
|
||||
p: 0b0010_0100,
|
||||
irq: false,
|
||||
nmi: false,
|
||||
receiver: None,
|
||||
memory,
|
||||
debug: false,
|
||||
stopped: false,
|
||||
pending_cycles: 0,
|
||||
cycle: false,
|
||||
}
|
||||
}
|
||||
pub fn with_receiver(mut self, receiver: CpuReceiver) -> Self {
|
||||
self.receiver = Some(receiver);
|
||||
self
|
||||
}
|
||||
pub fn new_with_control(memory: MemHandle) -> (Self, CpuController) {
|
||||
let (tx, rx) = channel::<CpuControl>();
|
||||
let controller = CpuController(tx);
|
||||
let receiver = CpuReceiver(rx);
|
||||
let cpu = Cpu::new(memory).with_receiver(receiver);
|
||||
(cpu, controller)
|
||||
}
|
||||
pub fn reset(&mut self) {
|
||||
let reset_vector_pointer = self.read_word(0xFFFC);
|
||||
self.pc = reset_vector_pointer;
|
||||
self.a = 0;
|
||||
self.pending_cycles = 0;
|
||||
}
|
||||
pub fn read_word(&self, address: u16) -> u16 {
|
||||
let low_byte = self.read(address);
|
||||
let high_byte = self.read(address.wrapping_add(0x1));
|
||||
(high_byte as u16) << 8 | (low_byte as u16)
|
||||
}
|
||||
|
||||
fn stack_addr(&self) -> u16 {
|
||||
// Dunno if this is necessary, i just don't like adding the 0x0100 every time
|
||||
0x0100 + self.s as u16
|
||||
}
|
||||
|
||||
pub fn push_stack(&mut self, data: u8) {
|
||||
self.s = self.s.wrapping_sub(0x1);
|
||||
self.write(self.stack_addr(), data);
|
||||
}
|
||||
|
||||
pub fn push_stack_word(&mut self, address: u16) {
|
||||
self.s = self.s.wrapping_sub(0x1);
|
||||
self.write(self.stack_addr(), address.to_le_bytes()[1]); // Upper byte first
|
||||
self.s = self.s.wrapping_sub(0x1);
|
||||
self.write(self.stack_addr(), address.to_le_bytes()[0]); // Lower byte second
|
||||
}
|
||||
|
||||
pub fn pop_stack(&mut self) -> u8 {
|
||||
let byte = self.read(self.stack_addr());
|
||||
self.s = self.s.wrapping_add(0x1);
|
||||
byte
|
||||
}
|
||||
|
||||
pub fn pop_stack_word(&mut self) -> u16 {
|
||||
let low_byte = self.pop_stack();
|
||||
let high_byte = self.pop_stack();
|
||||
((high_byte as u16) << 8) + low_byte as u16
|
||||
}
|
||||
|
||||
pub fn set_flag(&mut self, flag: StatusFlag, value: bool) {
|
||||
self.p &= !(flag as u8);
|
||||
if value {
|
||||
self.p |= flag as u8
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_flag(&self, flag: StatusFlag) -> bool {
|
||||
(self.p & flag as u8) > 0
|
||||
}
|
||||
|
||||
pub fn is_negative(&self, value: u8) -> bool {
|
||||
value & 0b1000_0000 == 0b1000_0000
|
||||
}
|
||||
|
||||
pub fn wait_for_interrupt(&self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn interrupt(&mut self) {
|
||||
self.irq = false;
|
||||
self.push_stack_word(self.pc);
|
||||
self.push_stack(self.p);
|
||||
self.set_flag(StatusFlag::IrqDisable, true);
|
||||
self.pc = self.read_word(0xFFFE);
|
||||
}
|
||||
|
||||
pub fn toggle_stopped(&mut self) {
|
||||
self.stopped = !self.stopped;
|
||||
}
|
||||
|
||||
fn receive_control(&mut self) {
|
||||
match &self.receiver {
|
||||
Some(receiver) => {
|
||||
let control = match receiver.0.try_recv() {
|
||||
Ok(control) => control,
|
||||
Err(_error) => return, // most of the time we won't have any impending control
|
||||
// messages, just return if that's the case
|
||||
};
|
||||
match control {
|
||||
CpuControl::Nmi => self.nmi = true,
|
||||
CpuControl::Irq => self.irq = true,
|
||||
CpuControl::Toggle => {
|
||||
self.stopped = !self.stopped;
|
||||
}
|
||||
CpuControl::Cycle => self.cycle = true,
|
||||
CpuControl::Data => {
|
||||
// self
|
||||
// .state_tx
|
||||
// .send(CpuState {
|
||||
// a: self.a.clone(), // Accumulator Register
|
||||
// x: self.x.clone(), // X Register
|
||||
// y: self.y.clone(), // Y Register
|
||||
// pc: self.pc.clone(), // Program Counter
|
||||
// s: self.s.clone(), // Stack Pointer
|
||||
// p: self.p.clone(), // Status Register
|
||||
// irq: self.irq.clone(),
|
||||
// nmi: self.nmi.clone(),
|
||||
// })
|
||||
// .unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cycle(&mut self) {
|
||||
self.receive_control();
|
||||
|
||||
if self.stopped & !self.cycle {
|
||||
self.set_flag(StatusFlag::IrqDisable, true);
|
||||
return;
|
||||
}
|
||||
self.cycle = false;
|
||||
|
||||
// can't sleep in wasm
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
while self.pending_cycles != 0 {
|
||||
// roughly cycle-accurate timing
|
||||
sleep(Duration::from_nanos(500));
|
||||
self.pending_cycles -= 1;
|
||||
}
|
||||
if !self.get_flag(StatusFlag::IrqDisable) && self.irq {
|
||||
self.interrupt();
|
||||
}
|
||||
let opcode = self.read(self.pc);
|
||||
let instruction = get_instruction(opcode);
|
||||
instruction.call(self);
|
||||
}
|
||||
pub fn stop(&mut self) {
|
||||
self.stopped = true;
|
||||
}
|
||||
pub fn breakpoint(&mut self) {
|
||||
if self.debug {
|
||||
self.stop();
|
||||
}
|
||||
}
|
||||
|
||||
fn quick_cycle(&mut self) {
|
||||
if !self.get_flag(StatusFlag::IrqDisable) && self.irq {
|
||||
self.interrupt();
|
||||
}
|
||||
let opcode = self.read(self.pc);
|
||||
let instruction = get_instruction(opcode);
|
||||
instruction.call(self);
|
||||
}
|
||||
|
||||
pub fn run_to_end(&mut self) -> [u8; 0x10000] {
|
||||
self.quick_cycle();
|
||||
while self.pending_cycles != 0 {
|
||||
let opcode = self.read(self.pc);
|
||||
let instruction = get_instruction(opcode);
|
||||
match instruction.name {
|
||||
"stp" | "breakpoint" => return self.memory.dump(),
|
||||
_ => self.quick_cycle(),
|
||||
}
|
||||
}
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
.setcpu "65C02"
|
||||
.segment "CODE"
|
||||
LDA #$25
|
||||
LDY #$25
|
||||
STY $2000
|
||||
ADC $2000
|
||||
@@ -1,10 +0,0 @@
|
||||
MEMORY {
|
||||
RAM: start = $0000, size = $4000, type = rw, fill = true;
|
||||
CTRL: start = $4000, size = $2000, type = rw, fill = true;
|
||||
VRAM: start = $6000, size = $8000, type = rw, fill = true;
|
||||
ROM: start = $E000, size = $2000, type = ro, fill = true;
|
||||
}
|
||||
|
||||
SEGMENTS {
|
||||
CODE: load = "RAM", type = rw;
|
||||
}
|
||||
+2805
File diff suppressed because it is too large
Load Diff
+256
@@ -0,0 +1,256 @@
|
||||
use minifb::InputCallback;
|
||||
use minifb::Key as MKey;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use termion::event::Key;
|
||||
|
||||
use crate::memory::{MemHandle, MemoryWriter};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Keyboard {
|
||||
memory: MemHandle,
|
||||
}
|
||||
|
||||
impl Keyboard {
|
||||
pub fn new(memory: MemHandle) -> Self {
|
||||
Self { memory }
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn clear_keys(&self) {
|
||||
self.memory.write(0x4400, 0x00);
|
||||
self.memory.write(0x4401, 0x00);
|
||||
self.memory.write(0x4402, 0x00);
|
||||
self.memory.write(0x4403, 0x00);
|
||||
self.memory.write(0x4404, 0x00);
|
||||
self.memory.write(0x4405, 0x00);
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn read_keys(&self, key: Key) {
|
||||
let mut row0 = 0;
|
||||
let mut row1 = 0;
|
||||
let mut row2 = 0;
|
||||
let mut row3 = 0;
|
||||
let mut row4 = 0;
|
||||
let mut row5 = 0;
|
||||
|
||||
// these are just so the match statements are easier to read lol
|
||||
macro_rules! set_shift {
|
||||
($char:expr) => {
|
||||
match $char {
|
||||
'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M'
|
||||
| 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y'
|
||||
| 'Z' | '!' | '@' | '#' | '$' | '%' | '^' | '&' | '*' | '(' | ')' | '~'
|
||||
| '_' | '+' | '|' | '}' | '{' | '"' | ':' | '?' | '>' | '<' => {
|
||||
row2 ^= 0b1000_0000
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! set_plain {
|
||||
($char:expr) => {
|
||||
match $char {
|
||||
'w' | 'W' => row0 ^= 0b0100_0000,
|
||||
'e' | 'E' => row0 ^= 0b0010_0000,
|
||||
'r' | 'R' => row0 ^= 0b0001_0000,
|
||||
't' | 'T' => row0 ^= 0b0000_1000,
|
||||
'u' | 'U' => row0 ^= 0b0000_0100,
|
||||
'o' | 'O' => row0 ^= 0b0000_0010,
|
||||
'q' | 'Q' => row1 ^= 0b0100_0000,
|
||||
's' | 'S' => row1 ^= 0b0010_0000,
|
||||
'g' | 'G' => row1 ^= 0b0001_0000,
|
||||
'y' | 'Y' => row1 ^= 0b0000_1000,
|
||||
'i' | 'I' => row1 ^= 0b0000_0100,
|
||||
'p' | 'P' => row1 ^= 0b0000_0010,
|
||||
'd' | 'D' => row2 ^= 0b0100_0000,
|
||||
'v' | 'V' => row2 ^= 0b0010_0000,
|
||||
'h' | 'H' => row2 ^= 0b0001_0000,
|
||||
'k' | 'K' => row2 ^= 0b0000_1000,
|
||||
'\\' | '|' => row2 ^= 0b0000_0100,
|
||||
'/' | '?' => row2 ^= 0b0000_0010,
|
||||
'a' | 'A' => row2 ^= 0b0000_0001,
|
||||
'z' | 'Z' => row3 ^= 0b0100_0000,
|
||||
'f' | 'F' => row3 ^= 0b0010_0000,
|
||||
'b' | 'B' => row3 ^= 0b0001_0000,
|
||||
'j' | 'J' => row3 ^= 0b0000_1000,
|
||||
'l' | 'L' => row3 ^= 0b0000_0100,
|
||||
'2' | '@' => row3 ^= 0b0000_0010,
|
||||
'4' | '$' => row3 ^= 0b0000_0001,
|
||||
'x' | 'X' => row4 ^= 0b0100_0000,
|
||||
'c' | 'C' => row4 ^= 0b0010_0000,
|
||||
'n' | 'N' => row4 ^= 0b0001_0000,
|
||||
'm' | 'M' => row4 ^= 0b0000_1000,
|
||||
',' | '<' => row4 ^= 0b0000_0100,
|
||||
'1' | '!' => row4 ^= 0b0000_0010,
|
||||
'3' | '#' => row4 ^= 0b0000_0001,
|
||||
' ' => row5 ^= 0b0100_0000,
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
match key {
|
||||
Key::Esc => row0 ^= 0b1000_0000,
|
||||
Key::Backspace => row0 ^= 0b0000_0001,
|
||||
Key::Char('\t') => row1 ^= 0b1000_0000,
|
||||
Key::Char('\n') | Key::Char('\r') => row1 ^= 0b0000_0001,
|
||||
Key::Char(char) => {
|
||||
set_shift!(char);
|
||||
set_plain!(char);
|
||||
}
|
||||
Key::ShiftLeft | Key::ShiftUp | Key::ShiftDown | Key::ShiftRight => row2 ^= 0b1000_0000,
|
||||
Key::Ctrl(char) => {
|
||||
set_shift!(char);
|
||||
set_plain!(char);
|
||||
row3 ^= 0b1000_0000;
|
||||
}
|
||||
Key::CtrlUp | Key::CtrlLeft | Key::CtrlDown | Key::CtrlRight => {
|
||||
row3 ^= 0b1000_0000;
|
||||
}
|
||||
Key::Alt(char) => {
|
||||
set_plain!(char);
|
||||
set_shift!(char);
|
||||
row4 ^= 0b1000_0000;
|
||||
}
|
||||
Key::AltUp | Key::AltLeft | Key::AltDown | Key::AltRight => {
|
||||
row4 ^= 0b1000_0000;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.memory.write(0x4400, row0);
|
||||
self.memory.write(0x4401, row1);
|
||||
self.memory.write(0x4402, row2);
|
||||
self.memory.write(0x4403, row3);
|
||||
self.memory.write(0x4404, row4);
|
||||
self.memory.write(0x4405, row5);
|
||||
|
||||
// keeping this list around to make future changes easier
|
||||
// match key.code {
|
||||
// KeyCode::Esc => row0 ^= 0b1000_0000,
|
||||
// KeyCode::Char('w') => row0 ^= 0b0100_0000,
|
||||
// KeyCode::Char('e') => row0 ^= 0b0010_0000,
|
||||
// KeyCode::Char('r') => row0 ^= 0b0001_0000,
|
||||
// KeyCode::Char('t') => row0 ^= 0b0000_1000,
|
||||
// KeyCode::Char('u') => row0 ^= 0b0000_0100,
|
||||
// KeyCode::Char('o') => row0 ^= 0b0000_0010,
|
||||
// KeyCode::Backspace => row0 ^= 0b0000_0001,
|
||||
// KeyCode::Tab => row1 ^= 0b1000_0000,
|
||||
// KeyCode::Char('q') => row1 ^= 0b0100_0000,
|
||||
// KeyCode::Char('s') => row1 ^= 0b0010_0000,
|
||||
// KeyCode::Char('g') => row1 ^= 0b0001_0000,
|
||||
// KeyCode::Char('y') => row1 ^= 0b0000_1000,
|
||||
// KeyCode::Char('i') => row1 ^= 0b0000_0100,
|
||||
// KeyCode::Char('p') => row1 ^= 0b0000_0010,
|
||||
// KeyCode::Enter => row1 ^= 0b0000_0001,
|
||||
// KeyCode::Char('d') => row2 ^= 0b0100_0000,
|
||||
// KeyCode::Char('v') => row2 ^= 0b0010_0000,
|
||||
// KeyCode::Char('h') => row2 ^= 0b0001_0000,
|
||||
// KeyCode::Char('k') => row2 ^= 0b0000_1000,
|
||||
// KeyCode::Char('\'') => row2 ^= 0b0000_0100,
|
||||
// KeyCode::Char('/') => row2 ^= 0b0000_0010,
|
||||
// KeyCode::Char('a') => row2 ^= 0b0000_0001,
|
||||
// KeyCode::Char('z') => row3 ^= 0b0100_0000,
|
||||
// KeyCode::Char('f') => row3 ^= 0b0010_0000,
|
||||
// KeyCode::Char('b') => row3 ^= 0b0001_0000,
|
||||
// KeyCode::Char('j') => row3 ^= 0b0000_1000,
|
||||
// KeyCode::Char('l') => row3 ^= 0b0000_0100,
|
||||
// KeyCode::Char('2') => row3 ^= 0b0000_0010,
|
||||
// KeyCode::Char('4') => row3 ^= 0b0000_0001,
|
||||
// KeyCode::Char('x') => row4 ^= 0b0100_0000,
|
||||
// KeyCode::Char('c') => row4 ^= 0b0010_0000,
|
||||
// KeyCode::Char('n') => row4 ^= 0b0001_0000,
|
||||
// KeyCode::Char('m') => row4 ^= 0b0000_1000,
|
||||
// KeyCode::Char(',') => row4 ^= 0b0000_0100,
|
||||
// KeyCode::Char('1') => row4 ^= 0b0000_0010,
|
||||
// KeyCode::Char('3') => row4 ^= 0b0000_0001,
|
||||
// KeyCode::Char(' ') => row5 ^= 0b0100_0000,
|
||||
// _ => {
|
||||
// row0 = 0;
|
||||
// row1 = 0;
|
||||
// row2 = 0;
|
||||
// row3 = 0;
|
||||
// row4 = 0;
|
||||
// row5 = 0;
|
||||
// }
|
||||
// };
|
||||
}
|
||||
}
|
||||
|
||||
impl MemoryWriter for Keyboard {
|
||||
fn write(&self, address: u16, data: u8) {
|
||||
if data != 0x00 {
|
||||
// println!("wrote {:02x} to address {:04x}", data, address);
|
||||
}
|
||||
self.memory.write(address, data);
|
||||
}
|
||||
}
|
||||
|
||||
impl InputCallback for Keyboard {
|
||||
fn add_char(&mut self, _uni_char: u32) {}
|
||||
fn set_key_state(&mut self, key: MKey, _state: bool) {
|
||||
let mut row0 = 0;
|
||||
let mut row1 = 0;
|
||||
let mut row2 = 0;
|
||||
let mut row3 = 0;
|
||||
let mut row4 = 0;
|
||||
let mut row5 = 0;
|
||||
|
||||
match key {
|
||||
MKey::Escape => row0 ^= 0b1000_0000,
|
||||
MKey::W => row0 ^= 0b0100_0000,
|
||||
MKey::E => row0 ^= 0b0010_0000,
|
||||
MKey::R => row0 ^= 0b0001_0000,
|
||||
MKey::T => row0 ^= 0b0000_1000,
|
||||
MKey::U => row0 ^= 0b0000_0100,
|
||||
MKey::O => row0 ^= 0b0000_0010,
|
||||
MKey::Backspace => row0 ^= 0b0000_0001,
|
||||
MKey::Tab => row1 ^= 0b1000_0000,
|
||||
MKey::Q => row1 ^= 0b0100_0000,
|
||||
MKey::S => row1 ^= 0b0010_0000,
|
||||
MKey::G => row1 ^= 0b0001_0000,
|
||||
MKey::Y => row1 ^= 0b0000_1000,
|
||||
MKey::I => row1 ^= 0b0000_0100,
|
||||
MKey::P => row1 ^= 0b0000_0010,
|
||||
MKey::Enter => row1 ^= 0b0000_0001,
|
||||
MKey::LeftShift | MKey::RightShift => row2 ^= 0b1000_0000,
|
||||
MKey::D => row2 ^= 0b0100_0000,
|
||||
MKey::V => row2 ^= 0b0010_0000,
|
||||
MKey::H => row2 ^= 0b0001_0000,
|
||||
MKey::K => row2 ^= 0b0000_1000,
|
||||
MKey::Apostrophe => row2 ^= 0b0000_0100,
|
||||
MKey::Slash => row2 ^= 0b0000_0010,
|
||||
MKey::A => row2 ^= 0b0000_0001,
|
||||
MKey::LeftCtrl | MKey::RightCtrl => row3 ^= 0b1000_0000,
|
||||
MKey::Z => row3 ^= 0b0100_0000,
|
||||
MKey::F => row3 ^= 0b0010_0000,
|
||||
MKey::B => row3 ^= 0b0001_0000,
|
||||
MKey::J => row3 ^= 0b0000_1000,
|
||||
MKey::L => row3 ^= 0b0000_0100,
|
||||
MKey::Key2 => row3 ^= 0b0000_0010,
|
||||
MKey::Key4 => row3 ^= 0b0000_0001,
|
||||
MKey::LeftAlt | MKey::RightAlt => row4 ^= 0b1000_0000,
|
||||
MKey::X => row4 ^= 0b0100_0000,
|
||||
MKey::C => row4 ^= 0b0010_0000,
|
||||
MKey::N => row4 ^= 0b0001_0000,
|
||||
MKey::M => row4 ^= 0b0000_1000,
|
||||
MKey::Comma => row4 ^= 0b0000_0100,
|
||||
MKey::Key1 => row4 ^= 0b0000_0010,
|
||||
MKey::Key3 => row4 ^= 0b0000_0001,
|
||||
MKey::LeftSuper => row5 ^= 0b1000_0000,
|
||||
MKey::Space => row5 ^= 0b0100_0000,
|
||||
MKey::RightSuper => row5 ^= 0b0010_0000,
|
||||
_ => {}
|
||||
};
|
||||
|
||||
self.memory.write(0x4400, row0);
|
||||
self.memory.write(0x4401, row1);
|
||||
self.memory.write(0x4402, row2);
|
||||
self.memory.write(0x4403, row3);
|
||||
self.memory.write(0x4404, row4);
|
||||
self.memory.write(0x4405, row5);
|
||||
}
|
||||
}
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
pub mod cpu;
|
||||
mod instructions;
|
||||
mod keyboard;
|
||||
pub mod memory;
|
||||
mod platform;
|
||||
pub mod video;
|
||||
|
||||
use crate::cpu::Cpu;
|
||||
use crate::keyboard::Keyboard;
|
||||
use crate::memory::Mem;
|
||||
use crate::video::Renderer;
|
||||
|
||||
use cpu::CpuController;
|
||||
use memory::MemHandle;
|
||||
use minifb::Window;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use platform::native as imp;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use platform::wasm as imp;
|
||||
|
||||
pub struct GeorgeEmu(imp::GeorgeEmu);
|
||||
|
||||
impl GeorgeEmu {
|
||||
pub fn builder() -> GeorgeEmuBuilder<NoRom, NoWindow> {
|
||||
GeorgeEmuBuilder::new()
|
||||
}
|
||||
|
||||
pub fn draw(&mut self) {
|
||||
self.0.draw()
|
||||
}
|
||||
|
||||
pub fn cycle(&mut self) {
|
||||
self.0.cycle()
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn run(&mut self) {
|
||||
self.0.run()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SomeRom();
|
||||
pub struct NoRom();
|
||||
pub struct SomeWindow(Window);
|
||||
pub struct NoWindow();
|
||||
|
||||
pub struct GeorgeEmuBuilder<Rom, Window> {
|
||||
rom: Rom,
|
||||
cpu: Option<Cpu>,
|
||||
keyboard: Option<Keyboard>,
|
||||
renderer: Option<Renderer>,
|
||||
cpu_controller: Option<CpuController>,
|
||||
memory_handle: Option<MemHandle>,
|
||||
window: Window,
|
||||
}
|
||||
|
||||
impl GeorgeEmuBuilder<NoRom, NoWindow> {
|
||||
fn new() -> Self {
|
||||
GeorgeEmuBuilder {
|
||||
rom: NoRom(),
|
||||
cpu: None,
|
||||
keyboard: None,
|
||||
renderer: None,
|
||||
cpu_controller: None,
|
||||
memory_handle: None,
|
||||
window: NoWindow(),
|
||||
}
|
||||
}
|
||||
pub fn rom(self, rom: [u8; 0x8000]) -> GeorgeEmuBuilder<SomeRom, NoWindow> {
|
||||
let mut memory = Mem::new();
|
||||
memory.load_bytes(&rom);
|
||||
let memory_handle = MemHandle::new(memory);
|
||||
|
||||
let (cpu, cpu_controller) = Cpu::new_with_control(memory_handle.clone());
|
||||
|
||||
let Self {
|
||||
renderer,
|
||||
keyboard,
|
||||
window,
|
||||
..
|
||||
} = self;
|
||||
|
||||
GeorgeEmuBuilder {
|
||||
cpu: Some(cpu),
|
||||
rom: SomeRom(),
|
||||
keyboard,
|
||||
renderer,
|
||||
cpu_controller: Some(cpu_controller),
|
||||
memory_handle: Some(memory_handle),
|
||||
window,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GeorgeEmuBuilder<SomeRom, NoWindow> {
|
||||
/// Adds a minifb window to the emulator
|
||||
pub fn window(self, mut window: Window) -> GeorgeEmuBuilder<SomeRom, SomeWindow> {
|
||||
let Self {
|
||||
rom,
|
||||
cpu,
|
||||
keyboard,
|
||||
ref cpu_controller,
|
||||
ref memory_handle,
|
||||
..
|
||||
} = self;
|
||||
let renderer = Renderer::new(
|
||||
self.cpu_controller.clone().unwrap(),
|
||||
self.memory_handle.clone().unwrap(),
|
||||
);
|
||||
|
||||
window.set_input_callback(Box::new(Keyboard::new(
|
||||
self.memory_handle.as_ref().unwrap().clone(),
|
||||
)));
|
||||
|
||||
GeorgeEmuBuilder {
|
||||
cpu,
|
||||
rom,
|
||||
keyboard,
|
||||
renderer: Some(renderer),
|
||||
cpu_controller: cpu_controller.clone(),
|
||||
memory_handle: memory_handle.clone(),
|
||||
window: SomeWindow(window),
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables debug printing and breakpoint triggering
|
||||
pub fn debug(mut self) -> Self {
|
||||
self.cpu.as_mut().unwrap().debug = true;
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn build(self) -> GeorgeEmu {
|
||||
let keyboard = Keyboard::new(self.memory_handle.clone().unwrap());
|
||||
GeorgeEmu(imp::GeorgeEmu::new(
|
||||
self.cpu.unwrap(),
|
||||
self.renderer.unwrap(),
|
||||
Some(keyboard),
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl GeorgeEmuBuilder<NoRom, SomeWindow> {
|
||||
pub fn rom(self, rom: [u8; 0x8000]) -> GeorgeEmuBuilder<SomeRom, SomeWindow> {
|
||||
let mut memory = Mem::new();
|
||||
memory.load_bytes(&rom);
|
||||
let memory_handle = MemHandle::new(memory);
|
||||
|
||||
let (cpu, cpu_controller) = Cpu::new_with_control(memory_handle.clone());
|
||||
|
||||
let Self {
|
||||
renderer,
|
||||
keyboard,
|
||||
window,
|
||||
..
|
||||
} = self;
|
||||
|
||||
GeorgeEmuBuilder {
|
||||
cpu: Some(cpu),
|
||||
rom: SomeRom(),
|
||||
keyboard,
|
||||
renderer,
|
||||
cpu_controller: Some(cpu_controller),
|
||||
memory_handle: Some(memory_handle),
|
||||
window,
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables debug printing and breakpoint triggering
|
||||
pub fn debug(mut self) -> Self {
|
||||
self.cpu.as_mut().unwrap().debug = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl GeorgeEmuBuilder<SomeRom, SomeWindow> {
|
||||
/// Enables debug printing and breakpoint triggering
|
||||
pub fn debug(mut self) -> Self {
|
||||
self.cpu.as_mut().unwrap().debug = true;
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn build(self) -> GeorgeEmu {
|
||||
let keyboard = Keyboard::new(self.memory_handle.clone().unwrap());
|
||||
GeorgeEmu(imp::GeorgeEmu::new(
|
||||
self.cpu.unwrap(),
|
||||
self.renderer.unwrap(),
|
||||
None,
|
||||
Some(self.window.0),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn build(self) -> GeorgeEmu {
|
||||
GeorgeEmu(imp::GeorgeEmu::new(
|
||||
self.cpu.unwrap(),
|
||||
self.renderer.unwrap(),
|
||||
self.window.0,
|
||||
))
|
||||
}
|
||||
}
|
||||
-3460
File diff suppressed because it is too large
Load Diff
+122
@@ -0,0 +1,122 @@
|
||||
use anyhow::{bail, Result};
|
||||
use std::io::{self, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{fs::File, io::Read};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MemHandle(Arc<Mutex<Mem>>);
|
||||
impl MemHandle {
|
||||
pub fn new(memory: Mem) -> Self {
|
||||
Self(Arc::new(Mutex::new(memory)))
|
||||
}
|
||||
pub fn read(&self, address: u16) -> u8 {
|
||||
let memory = self.0.try_lock().unwrap();
|
||||
memory.read(address)
|
||||
}
|
||||
pub fn write(&self, address: u16, data: u8) {
|
||||
let mut memory = self.0.try_lock().unwrap();
|
||||
memory.write(address, data);
|
||||
}
|
||||
|
||||
pub fn dump(&self) -> [u8; 0x10000] {
|
||||
let memory = self.0.lock().unwrap();
|
||||
memory.dump()
|
||||
}
|
||||
pub fn poke(&self, address: u16) {
|
||||
let memory = self.0.lock().unwrap();
|
||||
memory.poke(address)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MemoryReader {
|
||||
fn read(&self, address: u16) -> u8;
|
||||
}
|
||||
|
||||
pub trait MemoryWriter {
|
||||
fn write(&self, address: u16, data: u8);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
// This always feels wrong instead of 0xFFFF,
|
||||
// but remember it's the number of elements,
|
||||
// not the maximum index. 0x0000 to 0xFFFF addresses
|
||||
// 0x10000 elements
|
||||
pub struct Mem([u8; 0x10000]);
|
||||
|
||||
impl Default for Mem {
|
||||
fn default() -> Self {
|
||||
let bytes = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/roms/george.rom"));
|
||||
let padding = [0; 0x8000];
|
||||
let mem: [u8; 0x10000] = {
|
||||
let mut rom: [u8; 0x10000] = [0; 0x10000];
|
||||
let (one, two) = rom.split_at_mut(padding.len());
|
||||
one.copy_from_slice(&padding);
|
||||
two.copy_from_slice(bytes);
|
||||
rom
|
||||
};
|
||||
|
||||
Self(mem)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mem {
|
||||
pub fn new() -> Self {
|
||||
Self([0; 0x10000])
|
||||
}
|
||||
pub fn dump(&self) -> [u8; 0x10000] {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn read(&self, address: u16) -> u8 {
|
||||
self.0[address as usize]
|
||||
}
|
||||
|
||||
pub fn write(&mut self, address: u16, data: u8) {
|
||||
self.0[address as usize] = data;
|
||||
}
|
||||
|
||||
pub fn load_rom<P>(&mut self, rom: P) -> Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let rom = match File::open(rom) {
|
||||
Ok(rom) => rom,
|
||||
Err(_) => bail!("rom could not be opened!"),
|
||||
};
|
||||
let bytes = rom.bytes();
|
||||
for (address, byte) in bytes.enumerate() {
|
||||
// println!("{address}");
|
||||
match byte {
|
||||
Ok(value) => self.write(address as u16 + 0x8000, value),
|
||||
Err(_) => {
|
||||
bail!("Loading rom: couldn't write byte {:#04x}", address);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn poke(&self, address: u16) {
|
||||
println!("{:02x}", self.read(address));
|
||||
}
|
||||
pub fn load_bytes(&mut self, bytes: &[u8]) {
|
||||
for (address, byte) in bytes.iter().enumerate() {
|
||||
self.write(address as u16 + 0x8000, *byte)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_from_bin(&mut self, f: File) -> Result<()> {
|
||||
let bytes = f.bytes();
|
||||
for (address, byte) in bytes.enumerate() {
|
||||
match byte {
|
||||
Ok(value) => self.write(address as u16 + 0x8000, value),
|
||||
Err(_) => {
|
||||
bail!("couldn't write byte {:#04x}", address);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+894
@@ -0,0 +1,894 @@
|
||||
vec![
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BRK(AddressingMode::Implied),
|
||||
cycles: 7,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ORA(AddressingMode::ZeroPageIndexedIndirect),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x02 }),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x03 }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::TSB(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ORA(AddressingMode::ZeroPage),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ASL(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::RMB0(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::PHP(AddressingMode::Stack),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ORA(AddressingMode::Immediate),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ASL(AddressingMode::Accumulator),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x0b }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::TSB(AddressingMode::AbsoluteA),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ORA(AddressingMode::AbsoluteA),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ASL(AddressingMode::AbsoluteA),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BBR0(AddressingMode::ProgramCounterRelativeTest),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BPL(AddressingMode::ProgramCounterRelative),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ORA(AddressingMode::AbsoluteIndexedWithY),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ORA(AddressingMode::ZeroPageIndirect),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x13 }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::TRB(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ORA(AddressingMode::ZeroPageIndexedWithX),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ASL(AddressingMode::ZeroPageIndexedWithX),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::RMB1(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::CLC(AddressingMode::Implied),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ORA(AddressingMode::AbsoluteIndexedWithY),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::INC(AddressingMode::Accumulator),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x1b }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::TRB(AddressingMode::AbsoluteA),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ORA(AddressingMode::AbsoluteIndexedWithX),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ASL(AddressingMode::AbsoluteIndexedWithX),
|
||||
cycles: 7,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BBR1(AddressingMode::ProgramCounterRelativeTest),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::JSR(AddressingMode::AbsoluteA),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::AND(AddressingMode::ZeroPageIndexedIndirect),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x22 }),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x23 }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BIT(AddressingMode::ZeroPage),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::AND(AddressingMode::ZeroPage),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ROL(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::RMB2(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::PLP(AddressingMode::Stack),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::AND(AddressingMode::Immediate),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ROL(AddressingMode::Accumulator),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x2b }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BIT(AddressingMode::AbsoluteA),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::AND(AddressingMode::AbsoluteA),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ROL(AddressingMode::AbsoluteA),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BBR2(AddressingMode::ProgramCounterRelativeTest),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BMI(AddressingMode::ProgramCounterRelative),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::AND(AddressingMode::ZeroPageIndirectIndexedWithY),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::AND(AddressingMode::ZeroPageIndirect),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x33 }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BIT(AddressingMode::ZeroPageIndexedWithX),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::AND(AddressingMode::ZeroPageIndexedWithX),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ROL(AddressingMode::ZeroPageIndexedWithX),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::RMB3(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::SEC(AddressingMode::Implied),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::AND(AddressingMode::AbsoluteIndexedWithY),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::DEC(AddressingMode::Accumulator),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x3b }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BIT(AddressingMode::AbsoluteIndexedWithX),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::AND(AddressingMode::AbsoluteIndexedWithX),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ROL(AddressingMode::AbsoluteIndexedWithX),
|
||||
cycles: 7,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BBR3(AddressingMode::ProgramCounterRelativeTest),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::RTI(AddressingMode::Implied),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::EOR(AddressingMode::ZeroPageIndexedIndirect),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x42 }),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x43 }),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x44 }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::EOR(AddressingMode::ZeroPage),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LSR(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::RMB4(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::PHA(AddressingMode::Stack),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::EOR(AddressingMode::Immediate),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LSR(AddressingMode::Accumulator),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x4b }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::JMP(AddressingMode::AbsoluteA),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::EOR(AddressingMode::AbsoluteA),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LSR(AddressingMode::AbsoluteA),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BBR4(AddressingMode::ProgramCounterRelativeTest),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BVC(AddressingMode::ProgramCounterRelative),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::EOR(AddressingMode::ZeroPageIndirectIndexedWithY),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::EOR(AddressingMode::ZeroPageIndirect),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x53 }),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x54 }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::EOR(AddressingMode::ZeroPageIndexedWithX),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LSR(AddressingMode::ZeroPageIndexedWithX),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::RMB5(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::CLI(AddressingMode::Implied),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::EOR(AddressingMode::AbsoluteIndexedWithY),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::PHY(AddressingMode::Stack),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x5b }),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x5c }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::EOR(AddressingMode::AbsoluteIndexedWithX),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LSR(AddressingMode::AbsoluteIndexedWithX),
|
||||
cycles: 7,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BBR5(AddressingMode::ProgramCounterRelativeTest),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::RTS(AddressingMode::Stack),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ADC(AddressingMode::ZeroPageIndexedIndirect),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x62 }),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x63 }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::STZ(AddressingMode::ZeroPage),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ADC(AddressingMode::ZeroPage),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ROR(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::RMB6(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::PLA(AddressingMode::Stack),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ADC(AddressingMode::Immediate),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ROR(AddressingMode::Accumulator),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x6b }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::JMP(AddressingMode::AbsoluteIndirect),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ADC(AddressingMode::AbsoluteA),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ROR(AddressingMode::AbsoluteA),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BBR6(AddressingMode::ProgramCounterRelativeTest),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BVS(AddressingMode::ProgramCounterRelative),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ADC(AddressingMode::ZeroPageIndirectIndexedWithY),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ADC(AddressingMode::ZeroPageIndirect),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x73 }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::STZ(AddressingMode::ZeroPageIndexedWithX),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ADC(AddressingMode::ZeroPageIndexedWithX),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ROR(AddressingMode::ZeroPageIndexedWithX),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::RMB7(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::SEI(AddressingMode::Implied),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ADC(AddressingMode::AbsoluteIndexedWithY),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::PLY(AddressingMode::Stack),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x7b }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::JMP(AddressingMode::AbsoluteIndexedIndirect),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ADC(AddressingMode::AbsoluteIndexedWithX),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::ROR(AddressingMode::AbsoluteIndexedWithX),
|
||||
cycles: 7,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BBR7(AddressingMode::ProgramCounterRelativeTest),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BRA(AddressingMode::ProgramCounterRelative),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::STA(AddressingMode::ZeroPageIndexedIndirect),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x82 }),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x83 }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::STY(AddressingMode::ZeroPage),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::STA(AddressingMode::ZeroPage),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::STX(AddressingMode::ZeroPage),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::SMB0(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::DEY(AddressingMode::Implied),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BIT(AddressingMode::Immediate),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::TXA(AddressingMode::Implied),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x8b }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::STY(AddressingMode::AbsoluteA),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::STA(AddressingMode::AbsoluteA),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::STX(AddressingMode::AbsoluteA),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BBS0(AddressingMode::ProgramCounterRelativeTest),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BCC(AddressingMode::ProgramCounterRelative),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::STA(AddressingMode::ZeroPageIndirectIndexedWithY),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::STA(AddressingMode::ZeroPageIndirect),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x93 }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::STY(AddressingMode::ZeroPageIndexedWithX),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::STA(AddressingMode::ZeroPageIndexedWithX),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::STX(AddressingMode::ZeroPageIndexedWithY),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::SMB1(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::TYA(AddressingMode::Implied),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::STA(AddressingMode::AbsoluteIndexedWithY),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::TXS(AddressingMode::Implied),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0x9b }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::STZ(AddressingMode::AbsoluteA),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::STA(AddressingMode::AbsoluteIndexedWithX),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::STZ(AddressingMode::AbsoluteIndexedWithX),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BBS1(AddressingMode::ProgramCounterRelativeTest),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LDY(AddressingMode::Immediate),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LDA(AddressingMode::ZeroPageIndexedIndirect),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LDX(AddressingMode::Immediate),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0xa3 }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LDY(AddressingMode::ZeroPage),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LDA(AddressingMode::ZeroPage),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LDX(AddressingMode::ZeroPage),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::SMB2(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::TAY(AddressingMode::Implied),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LDA(AddressingMode::Immediate),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::TAX(AddressingMode::Implied),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0xab }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LDY(AddressingMode::AbsoluteA),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LDA(AddressingMode::AbsoluteA),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LDX(AddressingMode::AbsoluteA),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BBS2(AddressingMode::ProgramCounterRelativeTest),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BCS(AddressingMode::ProgramCounterRelative),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LDA(AddressingMode::ZeroPageIndirectIndexedWithY),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LDA(AddressingMode::ZeroPageIndirect),
|
||||
cycles: 5, // Unsure, see https://cx16.dk/65c02/reference.html#LDA
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0xb3 }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LDY(AddressingMode::ZeroPageIndexedWithX),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LDA(AddressingMode::ZeroPageIndexedWithX),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LDX(AddressingMode::ZeroPageIndexedWithY),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::SMB3(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::CLV(AddressingMode::Implied),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LDA(AddressingMode::AbsoluteIndexedWithY),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::TSX(AddressingMode::Implied),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0xbb }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LDY(AddressingMode::AbsoluteIndexedWithX),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LDA(AddressingMode::AbsoluteIndexedWithX),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::LDX(AddressingMode::AbsoluteIndexedWithY),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BBS3(AddressingMode::ProgramCounterRelativeTest),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::CPY(AddressingMode::Immediate),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::CMP(AddressingMode::ZeroPageIndexedIndirect),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0xc2 }),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0xc3 }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::CPY(AddressingMode::ZeroPage),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::CMP(AddressingMode::ZeroPage),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::DEC(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::SMB4(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::INY(AddressingMode::Implied),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::CMP(AddressingMode::Immediate),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::DEX(AddressingMode::Implied),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::WAI(AddressingMode::Implied),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::CPY(AddressingMode::AbsoluteA),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::CMP(AddressingMode::AbsoluteA),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::DEC(AddressingMode::AbsoluteA),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BBS4(AddressingMode::ProgramCounterRelativeTest),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BNE(AddressingMode::ProgramCounterRelative),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::CMP(AddressingMode::ZeroPageIndirectIndexedWithY),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::CMP(AddressingMode::ZeroPageIndirect),
|
||||
cycles: 5, // Unsure, look here: https://cx16.dk/65c02/reference.html#CMP
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0xd3 }),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0xd4 }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::CMP(AddressingMode::ZeroPageIndexedWithX),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::DEC(AddressingMode::ZeroPageIndexedWithX),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::SMB5(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::CLD(AddressingMode::Implied),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::CMP(AddressingMode::AbsoluteIndexedWithY),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::PHX(AddressingMode::Stack),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::STP(AddressingMode::Implied),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0xdc }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::CMP(AddressingMode::AbsoluteIndexedWithX),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::DEC(AddressingMode::AbsoluteIndexedWithX),
|
||||
cycles: 7,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BBS5(AddressingMode::ProgramCounterRelativeTest),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::CPX(AddressingMode::Immediate),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::SBC(AddressingMode::ZeroPageIndexedIndirect),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0xe2 }),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0xe3 }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::CPX(AddressingMode::ZeroPage),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::SBC(AddressingMode::ZeroPage),
|
||||
cycles: 3,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::INC(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::SMB6(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::INX(AddressingMode::Implied),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::SBC(AddressingMode::Immediate),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::NOP(AddressingMode::Implied),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0xeb }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::CPX(AddressingMode::AbsoluteA),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::SBC(AddressingMode::AbsoluteA),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::INC(AddressingMode::AbsoluteA),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BBS6(AddressingMode::ProgramCounterRelativeTest),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BEQ(AddressingMode::ProgramCounterRelative),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::SBC(AddressingMode::ZeroPageIndirectIndexedWithY),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::SBC(AddressingMode::ZeroPageIndirect),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0xf3 }),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0xf4 }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::SBC(AddressingMode::ZeroPageIndexedWithX),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::INC(AddressingMode::ZeroPageIndexedWithX),
|
||||
cycles: 6,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::SMB7(AddressingMode::ZeroPage),
|
||||
cycles: 5,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::SED(AddressingMode::Implied),
|
||||
cycles: 2,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::SBC(AddressingMode::AbsoluteIndexedWithY),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::PLX(AddressingMode::Stack),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0xfb }),
|
||||
Instruction::Invalid(InvalidInstruction { opcode: 0xfc }),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::SBC(AddressingMode::AbsoluteIndexedWithX),
|
||||
cycles: 4,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::INC(AddressingMode::AbsoluteIndexedWithX),
|
||||
cycles: 7,
|
||||
}),
|
||||
Instruction::Valid(ValidInstruction {
|
||||
opcode: Opcode::BBS7(AddressingMode::ProgramCounterRelativeTest),
|
||||
cycles: 4,
|
||||
}),
|
||||
]
|
||||
@@ -0,0 +1,4 @@
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod native;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod wasm;
|
||||
@@ -0,0 +1,101 @@
|
||||
use minifb::Window;
|
||||
use std::{
|
||||
io::stdout,
|
||||
process::exit,
|
||||
thread::sleep,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use termion::{
|
||||
async_stdin, clear,
|
||||
cursor::{self, Goto},
|
||||
event::Key,
|
||||
input::TermRead,
|
||||
raw::IntoRawMode,
|
||||
};
|
||||
|
||||
use crate::{cpu::Cpu, keyboard::Keyboard, video::Renderer};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GeorgeEmu {
|
||||
cpu: Cpu,
|
||||
renderer: Renderer,
|
||||
input: Option<Keyboard>,
|
||||
window: Option<Window>,
|
||||
}
|
||||
|
||||
impl GeorgeEmu {
|
||||
pub fn new(
|
||||
mut cpu: Cpu,
|
||||
renderer: Renderer,
|
||||
input: Option<Keyboard>,
|
||||
window: Option<Window>,
|
||||
) -> Self {
|
||||
cpu.reset();
|
||||
Self {
|
||||
cpu,
|
||||
input,
|
||||
renderer,
|
||||
window,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&mut self) {
|
||||
self.renderer.render(self.window.as_mut())
|
||||
}
|
||||
|
||||
pub fn cycle(&mut self) {
|
||||
self.cpu.cycle()
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
if self.window.is_none() {
|
||||
let _ = stdout().into_raw_mode();
|
||||
print!("{}{}", cursor::Hide, clear::All,);
|
||||
}
|
||||
let clock_interval = Duration::from_nanos(500);
|
||||
let frame_interval = Duration::from_millis(16);
|
||||
let mut next_clock = Instant::now() + clock_interval;
|
||||
let mut next_frame = Instant::now() + frame_interval;
|
||||
|
||||
loop {
|
||||
let now = Instant::now();
|
||||
|
||||
if now >= next_clock {
|
||||
self.cpu.cycle();
|
||||
next_clock += clock_interval;
|
||||
}
|
||||
|
||||
if now >= next_frame {
|
||||
if self.window.is_none() {
|
||||
self.handle_input();
|
||||
}
|
||||
self.draw();
|
||||
next_frame += frame_interval;
|
||||
}
|
||||
|
||||
let next_run = std::cmp::min(next_clock, next_frame);
|
||||
let sleep_dur = next_run - now;
|
||||
sleep(sleep_dur)
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_input(&mut self) {
|
||||
let mut stdin = async_stdin().keys();
|
||||
if let Some(Ok(key)) = stdin.next() {
|
||||
match key {
|
||||
Key::Char('q') => {
|
||||
print!("{}{}{}", clear::All, cursor::Show, Goto(1, 1));
|
||||
exit(0);
|
||||
}
|
||||
Key::Char('`') => self.cpu.toggle_stopped(),
|
||||
Key::Char('\n') => self.cpu.cycle(),
|
||||
Key::Char('i') => self.cpu.interrupt(),
|
||||
_ => {
|
||||
self.input.as_mut().unwrap().read_keys(key);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.input.as_mut().unwrap().clear_keys();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
use minifb::Window;
|
||||
|
||||
use crate::{cpu::Cpu, video::Renderer};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GeorgeEmu {
|
||||
cpu: Cpu,
|
||||
renderer: Renderer,
|
||||
window: Window,
|
||||
}
|
||||
|
||||
impl GeorgeEmu {
|
||||
pub fn new(mut cpu: Cpu, renderer: Renderer, window: Window) -> Self {
|
||||
cpu.reset();
|
||||
Self {
|
||||
cpu,
|
||||
renderer,
|
||||
window,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&mut self) {
|
||||
self.renderer.render(Some(&mut self.window));
|
||||
}
|
||||
|
||||
pub fn cycle(&mut self) {
|
||||
self.cpu.cycle()
|
||||
}
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::io::{self, Write};
|
||||
use std::{env, fs::File, io::Read, path::Path};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use termion::{
|
||||
color::{self, Bg, Fg},
|
||||
cursor::Goto,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
cpu::CpuController,
|
||||
memory::{MemHandle, MemoryReader},
|
||||
};
|
||||
use minifb::Window;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
const FG_COLOR: u32 = 0xFFCC00;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
const BG_COLOR: u32 = 0x110500;
|
||||
|
||||
// Wasm colors are ABGR
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
const FG_COLOR: u32 = 0xFF00CCFF;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
const BG_COLOR: u32 = 0xFF000511;
|
||||
|
||||
const WIDTH: usize = 512;
|
||||
const HEIGHT: usize = 380;
|
||||
|
||||
// pub fn get_char_bin<P>(char_rom: Option<P>) -> [u8; 0x8000]
|
||||
// where
|
||||
// P: AsRef<Path>,
|
||||
// {
|
||||
// match char_rom {
|
||||
// Some(path) => {
|
||||
// let mut file = File::open(path).unwrap();
|
||||
// let mut bin = vec![0; 0x8000];
|
||||
// file.read_exact(&mut bin).unwrap();
|
||||
// // println!("reading char rom");
|
||||
// bin.try_into().unwrap()
|
||||
// }
|
||||
// None => *include_bytes!("./roms/cozette.rom"),
|
||||
// }
|
||||
// }
|
||||
|
||||
const CHAR_ROM: &[u8; 0x8000] = include_bytes!(concat!(env!("OUT_DIR"), "/cozette.rom"));
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
const ASCII_LOOKUP: [&str; 256] = [
|
||||
" ", "░", "▒", "▓", "♡", "♥", "⭐", "✭", "", "✦", "✨", "♀", "♂", "⚢", "⚣", "⚥", "♩", "♪",
|
||||
"♫", "♬", "ﱝ", "", "", "", "奄", "奔", "婢", "ﱜ", "ﱛ", "", "", "", " ", "!", "\"", "#",
|
||||
"$", "%", "&", "\'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6",
|
||||
"7", "8", "9", ":", ";", "<", "=", ">", "?", "@", "A", "B", "C", "D", "E", "F", "G", "H", "I",
|
||||
"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\",
|
||||
"]", "^", "_", "`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
|
||||
"p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~", "─", "│", "┌", "└",
|
||||
"├", "┤", "┬", "┴", "┼", "╭", "╮", "╯", "╰", "╱", "╲", "╳", "═", "║", "╔", "╗", "╚", "╝", "╠",
|
||||
"╣", "╦", "╩", "╬", "", "", "", "", "", "", "", "", "", "", "", "", "", "▁", "▂",
|
||||
"▃", "▄", "▅", "▆", "▇", "█", "▉", "▊", "▋", "▌", "▍", "▎", "▏", "ʕ", "·", "ᴥ", "ʔ", "▖", "▗",
|
||||
"▘", "▙", "▚", "▛", "▜", "▝", "▞", "▟", "←", "↑", "→", "↓", "⭠", "⭡", "⭢", "⭣", "⮀", "⮁", "⮂",
|
||||
"⮃", "", "", "⏳", "", "", "", "", "", "", "", "", "", "", "", "🔮", "", "",
|
||||
"◇", "◈", "🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘", "", "✔", "✘", "◆", "", "", "",
|
||||
"", "", "", "", "🎁", "", "", "", "", "⚐", "⚑", "", "", "",
|
||||
];
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Renderer {
|
||||
memory: MemHandle,
|
||||
controller: CpuController,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
pub fn new(controller: CpuController, memory: MemHandle) -> Self {
|
||||
Self { memory, controller }
|
||||
}
|
||||
|
||||
pub fn render(&self, window: Option<&mut Window>) {
|
||||
match window {
|
||||
Some(window) => {
|
||||
// the rest of this function is arcane wizardry
|
||||
// based on the specifics of george's weird
|
||||
// display and characters... don't fuck around w it
|
||||
let mut i = 0;
|
||||
let mut buffer = [0; WIDTH * HEIGHT];
|
||||
for char_row in 0..29 {
|
||||
for char_col in 0..64 {
|
||||
let ascii = self.read(0x6000 + i);
|
||||
i += 1;
|
||||
for row in 0..13 {
|
||||
let byte = CHAR_ROM[ascii as usize + (row * 0x100)];
|
||||
for bit_index in (0..8).rev() {
|
||||
let buffer_index =
|
||||
((char_row) * 13 + (row)) * 512 + (char_col * 8 + bit_index);
|
||||
if (byte << bit_index) & 0x80 == 0x80 {
|
||||
buffer[buffer_index] = FG_COLOR;
|
||||
} else {
|
||||
buffer[buffer_index] = BG_COLOR;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap();
|
||||
}
|
||||
None => {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let mut stdout = io::stdout();
|
||||
let mut i = 0;
|
||||
for char_row in 0..29 {
|
||||
for char_col in 0..64 {
|
||||
let ascii = self.read(0x6000 + i);
|
||||
i += 1;
|
||||
let char = ASCII_LOOKUP[ascii as usize];
|
||||
let _ = write!(
|
||||
// FG_COLOR = 0xFFCC00
|
||||
// BG_COLOR = 0x110500
|
||||
stdout,
|
||||
"{}{}{}{char}",
|
||||
Goto(char_col + 1, char_row + 1),
|
||||
Fg(color::Rgb(0xFF, 0xCC, 0x00)),
|
||||
Bg(color::Rgb(0x11, 0x05, 0x00))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.controller.irq();
|
||||
}
|
||||
}
|
||||
|
||||
impl MemoryReader for Renderer {
|
||||
fn read(&self, address: u16) -> u8 {
|
||||
self.memory.read(address)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user