243 lines
7.1 KiB
Rust
243 lines
7.1 KiB
Rust
|
|
use mem::Mem;
|
|
use util::Xorshift;
|
|
|
|
static GR_TXMODE: int = 1;
|
|
static GR_MIXMODE: int = 2;
|
|
static GR_PAGE1: int = 4;
|
|
static GR_HIRES: int = 8;
|
|
|
|
static HW_LO: u16 = 0xC000;
|
|
static ROM_LO: u16 = 0xD000;
|
|
static ROM_LEN: u16 = 0x3000;
|
|
|
|
pub trait Peripheral
|
|
{
|
|
fn doIO(&mut self, addr: u16, val: u8) -> u8;
|
|
fn doHighIO(&mut self, addr: u16, val: u8) -> u8;
|
|
}
|
|
|
|
struct LangCardState
|
|
{
|
|
// language card switches
|
|
auxRAMselected: bool,
|
|
auxRAMbank: u8,
|
|
writeinhibit: bool,
|
|
|
|
// value to add when reading & writing each of these banks
|
|
// bank 1 is D000-FFFF, bank 2 is D000-DFFF
|
|
bank1rdoffset: int,
|
|
bank2rdoffset: int,
|
|
bank1wroffset: int,
|
|
bank2wroffset: int,
|
|
}
|
|
|
|
impl LangCardState
|
|
{
|
|
fn new(auxRAMselected: bool, auxRAMbank: u8, writeinhibit: bool) -> LangCardState {
|
|
LangCardState {
|
|
auxRAMselected: auxRAMselected,
|
|
auxRAMbank: auxRAMbank,
|
|
writeinhibit: writeinhibit,
|
|
// reset language card constants
|
|
// 0x3000 = map 0xd000-0xffff -> 0x10000-0x12fff
|
|
// -0x1000 = map 0xd000-0xdfff -> 0xc000-0xcfff
|
|
bank1rdoffset: if auxRAMselected { 0x3000 } else { 0x0 },
|
|
bank2rdoffset: if auxRAMselected { if auxRAMbank==2 { -0x1000 } else { 0x3000 } } else { 0x0 },
|
|
bank1wroffset: if !writeinhibit { 0x3000 } else { 0x0 },
|
|
bank2wroffset: if !writeinhibit { if auxRAMbank==2 { -0x1000 } else { 0x3000 } } else { 0x0 },
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct AppleII
|
|
{
|
|
mem: [u8, ..0x13000],
|
|
|
|
slots: [Option<~Peripheral>, ..8],
|
|
|
|
debugflags: int,
|
|
kbdlatch: u8,
|
|
grswitch: u16,
|
|
soundstate: bool,
|
|
aux: LangCardState,
|
|
nreads: u16 // counts # of reads for noise() fn
|
|
}
|
|
|
|
impl Mem for AppleII
|
|
{
|
|
fn loadb(&mut self, addr: u16) -> u8
|
|
{
|
|
debug!("Read {:x}", addr);
|
|
self.nreads += 1;
|
|
let val =
|
|
// see if it's from main memory (0x0000-0xbfff)
|
|
if (addr < HW_LO) {
|
|
self.mem[addr] & 0xff
|
|
// see if it came from the ROM/LC area (0xd000-0xffff)
|
|
} else if (addr >= ROM_LO) {
|
|
if (addr >= 0xe000) {
|
|
self.mem[addr as int + self.aux.bank1rdoffset] & 0xff
|
|
} else {
|
|
self.mem[addr as int + self.aux.bank2rdoffset] & 0xff
|
|
}
|
|
}
|
|
// it must be an I/O location (0xc000-0xcfff)
|
|
else if (addr < HW_LO + 0x100) {
|
|
self.doIO(addr, 0)
|
|
} else {
|
|
match self.slots[(addr >> 8) & 7] {
|
|
None => self.noise(),
|
|
Some(ref mut p) => p.doHighIO(addr, 0) // TODO: maybe have optional value, or new method
|
|
}
|
|
};
|
|
debug!("Read {:x} = {:x}", addr, val);
|
|
return val;
|
|
}
|
|
|
|
fn storeb(&mut self, addr: u16, val: u8)
|
|
{
|
|
debug!("write {:x} = {:x}", addr, val);
|
|
// see if it's from main memory (0x0000-0xbfff)
|
|
if (addr < HW_LO)
|
|
{
|
|
self.mem[addr] = val;
|
|
//dirty[addr >> 7] = true;
|
|
}
|
|
// see if it came from the ROM/LC area (0xd000-0xffff)
|
|
else if (addr >= ROM_LO && /* auxRAMselected && */ !self.aux.writeinhibit)
|
|
{
|
|
if (addr >= 0xe000) {
|
|
self.mem[addr as int + self.aux.bank1wroffset] = val;
|
|
} else {
|
|
self.mem[addr as int + self.aux.bank2wroffset] = val;
|
|
}
|
|
}
|
|
// it must be an I/O location (0xc000-0xcfff)
|
|
else if (addr < HW_LO + 0x100) {
|
|
self.doIO(addr, val);
|
|
} else {
|
|
match self.slots[(addr >> 8) & 7] {
|
|
None => (), // no-op
|
|
Some(ref mut p) => { p.doHighIO(addr, val); }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl AppleII
|
|
{
|
|
pub fn new() -> AppleII { AppleII {
|
|
mem: [ 0, ..0x13000 ],
|
|
// TODO: slots: [ None, ..8 ],
|
|
// https://gist.github.com/carl-eastlund/6264938
|
|
slots: [ None, None, None, None, None, None, None, None ],
|
|
aux: LangCardState::new(false, 1, true),
|
|
debugflags: 0,
|
|
kbdlatch: 0,
|
|
grswitch: 0,
|
|
soundstate: false,
|
|
nreads: 0
|
|
} }
|
|
|
|
pub fn set_slot(&mut self, slot: uint, p: ~Peripheral)
|
|
{
|
|
self.slots[slot] = Some(p);
|
|
}
|
|
|
|
fn noise(&mut self) -> u8 { self.mem[self.nreads & 0xffff] }
|
|
|
|
fn setGrSwitch(&mut self, addr: u16)
|
|
{
|
|
// graphics
|
|
if ((addr & 1) != 0) {
|
|
self.grswitch |= 1 << ((addr >> 1) & 0x07);
|
|
} else {
|
|
self.grswitch &= !(1 << ((addr >> 1) & 0x07));
|
|
}
|
|
debug!("switch {} grswitch = {}", addr, self.grswitch);
|
|
}
|
|
|
|
fn setAnnunciator(&mut self, addr: u16)
|
|
{
|
|
// nothing yet
|
|
}
|
|
|
|
fn fakeJoystick(&mut self, addr: u16) -> u8
|
|
{
|
|
// tapein, joystick, buttons
|
|
match addr & 7 {
|
|
1..3 => self.noise() & 0x7f, // buttons (off)
|
|
4..5 => self.noise() | 0x80, // joystick
|
|
_ => self.noise()
|
|
}
|
|
}
|
|
|
|
fn doIO(&mut self, addr: u16, val: u8) -> u8
|
|
{
|
|
debug!("doIO({:x}, {:x})", addr, val);
|
|
let slot = (addr >> 4) & 0x0f;
|
|
match slot {
|
|
0 => self.kbdlatch, // keyboard
|
|
1 => { self.clearStrobe(); self.noise() } // reset kbd strobe
|
|
3 => { self.soundstate = !self.soundstate; self.noise() } // speaker
|
|
5 => { if ((addr & 0x0f) < 8) { self.setGrSwitch(addr); } else { self.setAnnunciator(addr); } self.noise() }
|
|
6 => self.fakeJoystick(addr),
|
|
7 if (addr == 0xc070) => self.noise() | 0x80, // joystick reset
|
|
8 => { self.doLanguageCardIO(addr); self.noise() }
|
|
9..15 => match self.slots[slot-8] {
|
|
None => self.noise(),
|
|
Some(ref mut p) => p.doIO(addr, val)
|
|
},
|
|
_ => self.noise()
|
|
}
|
|
}
|
|
|
|
fn clearStrobe(&mut self)
|
|
{
|
|
self.kbdlatch &= 0x7f;
|
|
debug!("Clear strobe");
|
|
}
|
|
|
|
pub fn keyPressed(&mut self, keycode: u8)
|
|
{
|
|
let mut key = (keycode | 0x80) & 0xff;
|
|
// since we're an Apple II+, we don't do lowercase
|
|
if (key >= 0xe1 && key <= 0xfa) { key -= 0x20; }
|
|
self.kbdlatch = key;
|
|
debug!("Key pressed: {}", key);
|
|
}
|
|
|
|
fn doLanguageCardIO(&mut self, addr:u16)
|
|
{
|
|
self.aux = match addr & 0xf
|
|
{
|
|
// Select aux RAM bank 2, write protected.
|
|
0|4 => LangCardState::new(true, 2, true),
|
|
// Select ROM, write enable aux RAM bank 2.
|
|
1|5 => LangCardState::new(false, 2, false),
|
|
// Select ROM, write protect aux RAM (either bank).
|
|
2|6|10|14 => LangCardState::new(false, self.aux.auxRAMbank, true),
|
|
// Select aux RAM bank 2, write enabled.
|
|
3|7 => LangCardState::new(true, 2, false),
|
|
// Select aux RAM bank 1, write protected.
|
|
8|12 => LangCardState::new(true, 1, false),
|
|
// Select ROM, write enable aux RAM bank 1.
|
|
9|13 => LangCardState::new(false, 1, false),
|
|
// Select aux RAM bank 1, write enabled.
|
|
11|15 => LangCardState::new(true, 1, false),
|
|
// TODO: shouldn't need this
|
|
_ => fail!()
|
|
}
|
|
}
|
|
|
|
pub fn read_roms(&mut self)
|
|
{
|
|
use std::io::File;
|
|
use std::vec::bytes::copy_memory;
|
|
let ap2rom = File::open(&Path::new("apple2.rom")).read_bytes(0x3000);
|
|
copy_memory(self.mem.mut_slice(0xd000, 0xd000+0x3000), ap2rom);
|
|
debug!("loaded apple2.rom");
|
|
}
|
|
}
|