1
0
mirror of https://github.com/pevans/erc-c.git synced 2024-10-14 09:23:54 +00:00
erc-c/src/apple2.c
2018-01-09 15:56:48 -06:00

369 lines
9.5 KiB
C

/*
* apple2.c
*
* Here we have support for the apple2 machine. I suspect that we will
* need to break this file up into components in the future...
*/
#include "apple2.h"
#include "apple2.draw.h"
#include "apple2.mem.h"
#include "mos6502.enums.h"
#include "mos6502.dis.h"
#include "objstore.h"
#include "option.h"
#include "vm_segment.h"
/*
* This is the memory address where an apple program can find the value
* of the key that was last pressed.
*/
#define LAST_KEY 0xC000
/*
* This is the address in memory where you can find whether a key is
* currently pressed or not.
*/
#define ANY_KEY_DOWN 0xC010
/*
* Create the basic apple2 structure.
*/
apple2 *
apple2_create(int width, int height)
{
apple2 *mach;
int err;
objstore_init();
mach = malloc(sizeof(apple2));
if (mach == NULL) {
return NULL;
}
// Forward set these to NULL in case we fail to build the machine
// properly; that way, we won't try to free garbage data
mach->rom = NULL;
mach->ram2 = NULL;
mach->sysfont = NULL;
mach->screen = NULL;
mach->drive1 = NULL;
mach->drive2 = NULL;
mach->cpu = mos6502_create();
if (mach->cpu == NULL) {
log_critical("Could not create CPU!");
apple2_free(mach);
return NULL;
}
// Our memory is that which is owned by the CPU.
mach->memory = mach->cpu->memory;
// Set the read/write mappers for everything
apple2_mem_map(mach);
// Initliaze our system ROM and separate bank-switched block of RAM
mach->rom = vm_segment_create(APPLE2_ROM_SIZE);
mach->ram2 = vm_segment_create(APPLE2_RAM2_SIZE);
if (mach->rom == NULL || mach->ram2 == NULL) {
log_critical("Could not initialize ROM / RAM2!");
apple2_free(mach);
return NULL;
}
if (apple2_mem_init_peripheral_rom(mach) != OK) {
log_critical("Could not initialize disk2 ROM");
apple2_free(mach);
return NULL;
}
if (apple2_mem_init_sys_rom(mach) != OK) {
log_critical("Could not initialize apple2 ROM");
apple2_free(mach);
return NULL;
}
// Our two drives -- we create both of them, even if we intend to
// use only one.
mach->drive1 = apple2_dd_create();
mach->drive2 = apple2_dd_create();
if (mach->drive1 == NULL || mach->drive2 == NULL) {
log_critical("Could not create disk drives!");
apple2_free(mach);
return NULL;
}
// Let's build our screen abstraction!
mach->screen = vm_screen_create();
if (mach->screen == NULL) {
log_critical("Screen creation failed!");
apple2_free(mach);
return NULL;
}
// We still need to add a window, since we want to render some
// graphics.
err = vm_screen_add_window(mach->screen, width, height);
if (err != OK) {
log_critical("Window creation failed!");
apple2_free(mach);
return NULL;
}
// Default to full color
apple2_set_color(mach, COLOR_FULL);
// We default to lo-res mode.
apple2_set_video(mach, VIDEO_LORES);
// By default we should have ROM be the addressable last 12k of
// memory.
apple2_set_memory(mach, MEMORY_BANK_ROM);
// Let's install our bitmap font.
mach->sysfont = vm_bitfont_create(mach->screen,
objstore_apple2_sysfont(),
APPLE2_SYSFONT_SIZE,
7, 8, // 7 pixels wide, 8 pixels tall
0x7f); // 7-bit values only
if (mach->sysfont == NULL) {
apple2_free(mach);
log_critical("Could not initialize apple2: bad font");
return NULL;
}
return mach;
}
/*
* Change the memory mode of the apple2 to a given mode.
*/
void
apple2_set_memory(apple2 *mach, int mode)
{
mach->memory_mode = mode;
}
/*
* Return true if we are in a state that the apple2 would consider
* double resolution. (In practice, this refers to horizontal screen
* density; vertical screen density per-pixel is unchanged.)
*/
bool
apple2_is_double_video(apple2 *mach)
{
return
mach->video_mode == VIDEO_DOUBLE_HIRES ||
mach->video_mode == VIDEO_DOUBLE_LORES ||
mach->video_mode == VIDEO_80COL_TEXT;
}
/*
* Try to "boot" the apple2 machine. Look for input sources indicated in
* the option system and load those into our disk drives.
*/
int
apple2_boot(apple2 *mach)
{
FILE *stream;
int err;
// Do we have any disks?
stream = option_get_input(1);
if (stream) {
err = apple2_dd_insert(mach->drive1, stream);
if (err != OK) {
log_critical("Unable to insert disk1 into drive");
return err;
}
}
stream = option_get_input(2);
if (stream) {
err = apple2_dd_insert(mach->drive2, stream);
if (err != OK) {
log_critical("Unable to insert disk2 into drive");
return err;
}
}
if (option_flag(OPTION_FLASH)) {
mos6502_flash_memory(mach->cpu, mach->drive1->data);
}
if (option_flag(OPTION_DISASSEMBLE)) {
mos6502_dis_scan(mach->cpu, stdout, 0, mach->cpu->memory->size);
}
// Run the reset routine to get the machine ready to go.
apple2_reset(mach);
return OK;
}
/*
* This function marks out the procedures that happen when the machine
* is reset. A reset can happen at a cold boot, but it can also happen
* after the computer is already operational.
*/
void
apple2_reset(apple2 *mach)
{
mach->cpu->P = MOS_INTERRUPT;
mach->cpu->PC = vm_segment_get16(mach->memory, 0xFFFC);
mach->cpu->S = 0;
}
/*
* This function will clear the 8th bit, which is the "strobe" bit, from
* the position in memory where the value of the last key that was
* pressed is held.
*/
void
apple2_clear_strobe(apple2 *mach)
{
vm_8bit ch;
ch = vm_segment_get(mach->memory, LAST_KEY);
vm_segment_set(mach->memory, LAST_KEY, ch & 0x7F);
}
/*
* Free the memory reserved for an apple2 struct.
*/
void
apple2_free(apple2 *mach)
{
if (mach->cpu) {
mos6502_free(mach->cpu);
}
if (mach->rom) {
vm_segment_free(mach->rom);
}
if (mach->ram2) {
vm_segment_free(mach->ram2);
}
if (mach->sysfont) {
vm_bitfont_free(mach->sysfont);
}
if (mach->drive1) {
apple2_dd_free(mach->drive1);
}
if (mach->drive2) {
apple2_dd_free(mach->drive2);
}
if (mach->screen) {
vm_screen_free(mach->screen);
}
// NOTE: we do _NOT_ want to clear the memory field of mach, as it's
// co-owned with the cpu struct that we just freed above.
free(mach);
}
/*
* Emulate the notion of a pressed key in the apple2 with a given
* character.
*/
void
apple2_press_key(apple2 *mach, vm_8bit ch)
{
// The apple2 can only handle ASCII values of 0 through 127.
// However, the eigth bit is called the "strobe" bit, and is treated
// specially. In particular, the strobe bit is 1 if a key was
// pressed down, and remains 1 until you reset it by reading from
// the clear-strobe location.
ch = ch | 0x80;
// This is the location in memory where a program will expect to
// find the value of the last key that was pressed.
vm_segment_set(mach->memory, LAST_KEY, ch);
// This area is a combination of flags; the eighth bit here is the
// "any-key-down" flag, which is a bit of a mouthful. It's 1 if a
// key is pressed, and 0 if not. The effect of reading this bit will
// also _clear_ the strobe bit in the $C000 address (above).
vm_segment_set(mach->memory, ANY_KEY_DOWN, 0x80);
}
/*
* This function will clear the value of the any-key-down switch/flag.
*/
void
apple2_release_key(apple2 *mach)
{
vm_segment_set(mach->memory, ANY_KEY_DOWN, 0);
}
/*
* The run loop is the function that essentially waits for user input
* and continues to present the apple2 abstraction for you to use. At
* some point the user will indicate they are done, whereby
* vm_screen_active() will no longer be true and we exit.
*/
void
apple2_run_loop(apple2 *mach)
{
if (option_flag(OPTION_DISASSEMBLE)) {
return;
}
while (vm_screen_active(mach->screen)) {
mos6502_dis_opcode(mach->cpu, stdout, mach->cpu->PC);
mos6502_execute(mach->cpu, mos6502_next_byte(mach->cpu));
vm_screen_refresh(mach->screen);
}
}
/*
* Set the color mode of the apple2, which is to say if we are emulating
* a monochromatic display, or full color, or just black-and-white.
*/
void
apple2_set_color(apple2 *mach, int mode)
{
mach->color_mode = mode;
// FIXME: doing this should force us to redraw everything in the
// correct color interpretation
}
/*
* Set the video mode of the display. This would be the type of
* resolution (text by which number of columns, lo-res, hi-res, etc.)
*/
void
apple2_set_video(apple2 *mach, int mode)
{
int width, height;
mach->video_mode = mode;
// In the traditional video modes that Apple II first came in, you
// would have a maximum width of 280 pixels. (In lo-res, you have
// fewer pixels, but that is something we have to handle in our
// drawing functions rather than by changing the logical size.)
width = 280;
height = 192;
// In double video modes, the width is effectively doubled, but the
// height is untouched.
if (mach->video_mode == VIDEO_DOUBLE_LORES ||
mach->video_mode == VIDEO_DOUBLE_HIRES
) {
width = 560;
}
vm_screen_set_logical_coords(mach->screen, width, height);
}