COLECOVISIONエミュレータをつくる その2

Spinner controlに対応したyaCOLECOVISIONできました。TURBOで遊べます。

http://atariage.com/forums/topic/196556-spinner-control-hook-up/

f:id:tanam:20160815184059p:image:w360

FAQ

Q1.COLECOVISIONって何?

A1.Enriさんのサイトで確認ください。

http://www43.tok2.com/home/cmpslv/Coleco/EnrCol.htm

Q2.どこで手に入るの?

A2.国内ではSAMMLERさんで購入できます。

http://retro-game-os.com/

Q3.エミュレータはすでにあるのでは?

A3.はい、blueMSXがとても良いです。yaCOLECOVISIONではハンドルコントローラーに対応してSEGA TURBOで遊ぶのがゴールです。

http://bluemsx.msxblue.com/jindex.htm

resource.h


// COLECO COLECOVISION
#ifdef _COLECO
#define MENU_POS_CONTROL                0
#define MENU_POS_CART1                  1
#define MENU_POS_SCREEN                 2
#define MENU_POS_SOUND                  3
#define MENU_POS_INPUT                  4
#endif

vm.h


// COLECO COLECOVISION
#ifdef _COLECO
#include "coleco/coleco.h"
#endif

coleco.h

/*
	COLECO COLECOVISION Emulator 'yaCOLECOVISION'

	Author : tanam
	Date   : 2016.08.14-

	[ virtual machine ]
*/

#ifndef _COLECO_H_
#define _COLECO_H_

#define DEVICE_NAME		"COLECO COLECOVISION"
#define CONFIG_NAME		"coleco"

// device informations for virtual machine
#define FRAMES_PER_SEC		60
#define LINES_PER_FRAME		262
#define CPU_CLOCKS		3579545
#define SCREEN_WIDTH		256
#define SCREEN_HEIGHT		192
#define TMS9918A_VRAM_SIZE	0x4000
#define TMS9918A_LIMIT_SPRITES

// device informations for win32
#define USE_CART1
#define USE_SOUND_VOLUME	2
#define USE_JOYSTICK
#define USE_DEBUGGER
#define USE_STATE

#include "../../common.h"
#include "../../fileio.h"

#ifdef USE_SOUND_VOLUME
static const _TCHAR *sound_device_caption[] = {
	_T("PSG"),
};
#endif

class EMU;
class DEVICE;
class EVENT;

class IO;
class SN76489AN;
class TMS9918A;
class Z80;

class KEYBOARD;
class MEMORY;

class VM
{
protected:
	EMU* emu;
	
	// devices
	EVENT* event;
	
	IO* io;
	SN76489AN* psg;
	TMS9918A* vdp;
	Z80* cpu;
	
	KEYBOARD* key;
	MEMORY* memory;
	
public:
	// ----------------------------------------
	// initialize
	// ----------------------------------------
	
	VM(EMU* parent_emu);
	~VM();
	
	// ----------------------------------------
	// for emulation class
	// ----------------------------------------
	
	// drive virtual machine
	void reset();
	void run();
	
#ifdef USE_DEBUGGER
	// debugger
	DEVICE *get_cpu(int index);
#endif
	
	// draw screen
	void draw_screen();
	
	// sound generation
	void initialize_sound(int rate, int samples);
	uint16_t* create_sound(int* extra_frames);
	int get_sound_buffer_ptr();
#ifdef USE_SOUND_VOLUME
	void set_sound_device_volume(int ch, int decibel_l, int decibel_r);
#endif
	
	// user interface
	void open_cart(int drv, const _TCHAR* file_path);
	void close_cart(int drv);
	bool is_cart_inserted(int drv);
	bool is_frame_skippable();
	
	void update_config();
	void save_state(FILEIO* state_fio);
	bool load_state(FILEIO* state_fio);
	
	// ----------------------------------------
	// for each device
	// ----------------------------------------
	
	// devices
	DEVICE* get_device(int id);
	DEVICE* dummy;
	DEVICE* first_device;
	DEVICE* last_device;
};

#endif

coleco.cpp

/*
	COLECO COLECOVISION Emulator 'yaCOLECOVISION'

	Author : tanam
	Date   : 2016.08.14-

	[ virtual machine ]
*/

#include "coleco.h"
#include "../../emu.h"
#include "../device.h"
#include "../event.h"

#include "../io.h"
#include "../sn76489an.h"
#include "../tms9918a.h"
#include "../z80.h"

#ifdef USE_DEBUGGER
#include "../debugger.h"
#endif

#include "keyboard.h"
#include "memory.h"

// ----------------------------------------------------------------------------
// initialize
// ----------------------------------------------------------------------------

VM::VM(EMU* parent_emu) : emu(parent_emu)
{
	// create devices
	first_device = last_device = NULL;
	dummy = new DEVICE(this, emu);	// must be 1st device
	event = new EVENT(this, emu);	// must be 2nd device
	
	io = new IO(this, emu);
	psg = new SN76489AN(this, emu);
	vdp = new TMS9918A(this, emu);
	cpu = new Z80(this, emu);
	
	key = new KEYBOARD(this, emu);
	memory = new MEMORY(this, emu);
	
	// set contexts
	event->set_context_cpu(cpu);
	event->set_context_sound(psg);
	
	vdp->set_context_irq(cpu, SIG_CPU_NMI, 1);
	key->set_context_cpu(cpu);
	
	// cpu bus
	cpu->set_context_mem(memory);
	cpu->set_context_io(io);
	cpu->set_context_intr(dummy);
#ifdef USE_DEBUGGER
	cpu->set_context_debugger(new DEBUGGER(this, emu));
#endif
	
	// i/o bus
	io->set_iomap_range_w(0x80, 0x9f, key);
	io->set_iomap_range_rw(0xbe, 0xbf, vdp);
	io->set_iomap_range_w(0xc0, 0xdf, key);
	io->set_iomap_range_r(0xfc, 0xff, key);
	io->set_iomap_range_w(0xff, 0xff, psg);
	
	// initialize all devices
	for(DEVICE* device = first_device; device; device = device->next_device) {
		device->initialize();
	}
}

VM::~VM()
{
	// delete all devices
	for(DEVICE* device = first_device; device;) {
		DEVICE *next_device = device->next_device;
		device->release();
		delete device;
		device = next_device;
	}
}

DEVICE* VM::get_device(int id)
{
	for(DEVICE* device = first_device; device; device = device->next_device) {
		if(device->this_device_id == id) {
			return device;
		}
	}
	return NULL;
}

// ----------------------------------------------------------------------------
// debugger
// ----------------------------------------------------------------------------

#ifdef USE_DEBUGGER
DEVICE *VM::get_cpu(int index)
{
	if(index == 0) {
		return cpu;
	}
	return NULL;
}
#endif

// ----------------------------------------------------------------------------
// drive virtual machine
// ----------------------------------------------------------------------------

void VM::reset()
{
	// reset all devices
	for(DEVICE* device = first_device; device; device = device->next_device) {
		device->reset();
	}
}

void VM::run()
{
	event->drive();
}

// ----------------------------------------------------------------------------
// draw screen
// ----------------------------------------------------------------------------

void VM::draw_screen()
{
	vdp->draw_screen();
}

// ----------------------------------------------------------------------------
// soud manager
// ----------------------------------------------------------------------------

void VM::initialize_sound(int rate, int samples)
{
	// init sound manager
	event->initialize_sound(rate, samples);
	
	// init sound gen
	psg->initialize_sound(rate, 3579545, 8000);
}

uint16_t* VM::create_sound(int* extra_frames)
{
	return event->create_sound(extra_frames);
}

int VM::get_sound_buffer_ptr()
{
	return event->get_sound_buffer_ptr();
}

#ifdef USE_SOUND_VOLUME
void VM::set_sound_device_volume(int ch, int decibel_l, int decibel_r)
{
	if(ch == 0) {
		psg->set_volume(0, decibel_l, decibel_r);
	}
}
#endif

// ----------------------------------------------------------------------------
// user interface
// ----------------------------------------------------------------------------

void VM::open_cart(int drv, const _TCHAR* file_path)
{
	if(drv == 0) {
		memory->open_cart(file_path);
		reset();
	}
}

void VM::close_cart(int drv)
{
	if(drv == 0) {
		memory->close_cart();
		reset();
	}
}

bool VM::is_cart_inserted(int drv)
{
	if(drv == 0) {
		return memory->is_cart_inserted();
	} else {
		return false;
	}
}

bool VM::is_frame_skippable()
{
	return event->is_frame_skippable();
}

void VM::update_config()
{
	for(DEVICE* device = first_device; device; device = device->next_device) {
		device->update_config();
	}
}

#define STATE_VERSION	1

void VM::save_state(FILEIO* state_fio)
{
	state_fio->FputUint32(STATE_VERSION);
	
	for(DEVICE* device = first_device; device; device = device->next_device) {
		device->save_state(state_fio);
	}
}

bool VM::load_state(FILEIO* state_fio)
{
	if(state_fio->FgetUint32() != STATE_VERSION) {
		return false;
	}
	for(DEVICE* device = first_device; device; device = device->next_device) {
		if(!device->load_state(state_fio)) {
			return false;
		}
	}
	return true;
}

memory.h

/*
	COLECO COLECOVISION Emulator 'yaCOLECOVISION'

	Author : tanam
	Date   : 2016.08.14-

	[ memory ]
*/

#ifndef _MEMORY_H_
#define _MEMORY_H_

#include "../vm.h"
#include "../../emu.h"
#include "../device.h"

class MEMORY : public DEVICE
{
private:
	// memory
	uint8_t cart[0x8000];
	uint8_t ipl[0x2000];
	uint8_t ram[0x10000];
	
	uint8_t wdmy[0x10000];
	uint8_t rdmy[0x10000];
	uint8_t* wbank[16];
	uint8_t* rbank[16];
	
	bool inserted;
	
public:
	MEMORY(VM* parent_vm, EMU* parent_emu) : DEVICE(parent_vm, parent_emu) {}
	~MEMORY() {}
	
	// common functions
	void initialize();
	void write_data8(uint32_t addr, uint32_t data);
	uint32_t read_data8(uint32_t addr);
	void save_state(FILEIO* state_fio);
	bool load_state(FILEIO* state_fio);
	
	// unique functions
	void open_cart(const _TCHAR* file_path);
	void close_cart();
	bool is_cart_inserted()
	{
		return inserted;
	}
};

#endif

memory.cpp

/*
	COLECO COLECOVISION Emulator 'yaCOLECOVISION'

	Author : tanam
	Date   : 2016.08.14-

	[ memory ]
*/

#include "memory.h"

#define SET_BANK(s, e, w, r) { \
	int sb = (s) >> 12, eb = (e) >> 12; \
	for(int i = sb; i <= eb; i++) { \
		if((w) == wdmy) { \
			wbank[i] = wdmy; \
		} else { \
			wbank[i] = (w) + 0x1000 * (i - sb); \
		} \
		if((r) == rdmy) { \
			rbank[i] = rdmy; \
		} else { \
			rbank[i] = (r) + 0x1000 * (i - sb); \
		} \
	} \
}

void MEMORY::initialize()
{
	memset(cart, 0xff, sizeof(cart));
	memset(ipl, 0xff, sizeof(ipl));
	memset(ram, 0, sizeof(ram));
	memset(rdmy, 0xff, sizeof(rdmy));
	
	// load ipl
	FILEIO* fio = new FILEIO();
	if(fio->Fopen(create_local_path(_T("COLECO.ROM")), FILEIO_READ_BINARY)) {
		fio->Fread(ipl, sizeof(ipl), 1);
		fio->Fclose();
	}
	delete fio;
	
	// set memory map
	SET_BANK(0x0000, 0x1fff, wdmy, ipl);
	SET_BANK(0x2000, 0x5fff, wdmy, rdmy);
	SET_BANK(0x6000, 0x7fff, ram,  ram);
	SET_BANK(0x8000, 0xffff, wdmy, cart);
	
	inserted = false;
}

void MEMORY::write_data8(uint32_t addr, uint32_t data)
{
	addr &= 0xffff;
	wbank[addr >> 12][addr & 0xfff] = data;
}

uint32_t MEMORY::read_data8(uint32_t addr)
{
	addr &= 0xffff;
	return rbank[addr >> 12][addr & 0xfff];
}

void MEMORY::open_cart(const _TCHAR* file_path)
{
	FILEIO* fio = new FILEIO();
	
	if(fio->Fopen(file_path, FILEIO_READ_BINARY)) {
		memset(cart, 0xff, sizeof(cart));
		fio->Fread(cart, sizeof(cart), 1);
		fio->Fclose();
		inserted = true;
		
		// set memory map
		SET_BANK(0x8000, 0xffff, wdmy, cart);
	}
	delete fio;
}

void MEMORY::close_cart()
{
	memset(cart, 0xff, sizeof(cart));
	inserted = false;
	
	// set memory map
	SET_BANK(0x0000, 0x1fff, wdmy, ipl);
	SET_BANK(0x2000, 0x5fff, wdmy, rdmy);
	SET_BANK(0x6000, 0x7fff, ram,  ram);
	SET_BANK(0x8000, 0xffff, wdmy, cart);
}

#define STATE_VERSION	1

void MEMORY::save_state(FILEIO* state_fio)
{
	state_fio->FputUint32(STATE_VERSION);
	state_fio->FputInt32(this_device_id);
	
	state_fio->Fwrite(ram, sizeof(ram), 1);
	state_fio->FputBool(inserted);
}

bool MEMORY::load_state(FILEIO* state_fio)
{
	if(state_fio->FgetUint32() != STATE_VERSION) {
		return false;
	}
	if(state_fio->FgetInt32() != this_device_id) {
		return false;
	}
	state_fio->Fread(ram, sizeof(ram), 1);
	inserted = state_fio->FgetBool();
	
	// post process
	if(inserted) {
		SET_BANK(0x8000, 0xffff, wdmy, cart);
	} else {
		SET_BANK(0x0000, 0x1fff, wdmy, ipl);
		SET_BANK(0x2000, 0x5fff, wdmy, rdmy);
		SET_BANK(0x6000, 0x7fff, ram,  ram);
		SET_BANK(0x8000, 0xffff, wdmy, cart);
	}
	return true;
}

keyboard.h

/*
	COLECO COLECOVISION Emulator 'yaCOLECOVISION'

	Author : tanam
	Date   : 2016.08.14-

	[ keyboard ]
*/

#ifndef _KEYBOARD_H_
#define _KEYBOARD_H_

#include "../vm.h"
#include "../../emu.h"
#include "../device.h"

class KEYBOARD : public DEVICE
{
private:
	DEVICE *d_cpu;
	
	const uint8_t* key_stat;
	const uint32_t* joy_stat;
	bool tenkey;
public:
	KEYBOARD(VM* parent_vm, EMU* parent_emu) : DEVICE(parent_vm, parent_emu) {}
	~KEYBOARD() {}
	
	// common functions
	void initialize();
	void event_frame();
	uint32_t read_io8(uint32_t addr);
	void write_io8(uint32_t addr, uint32_t data);
	void save_state(FILEIO* state_fio);
	bool load_state(FILEIO* state_fio);	

	// unique functions
	void set_context_cpu(DEVICE* device)
	{
		d_cpu = device;
	}
};

#endif

keyboard.cpp

/*
	COLECO COLECOVISION Emulator 'yaCOLECOVISION'

	Author : tanam
	Date   : 2016.08.14-

	[ keyboard ]
*/

#include "keyboard.h"

void KEYBOARD::initialize()
{
	key_stat = emu->get_key_buffer();
	joy_stat = emu->get_joy_buffer();
	// register event to update the key status
	register_frame_event(this);
}

void KEYBOARD::event_frame()
{
	if (joy_stat[0] & 0x04 || joy_stat[0] & 0x08) {
		d_cpu->write_signal(SIG_CPU_IRQ, 1, 1);
	}
}

void KEYBOARD::write_io8(uint32_t addr, uint32_t data)
{
	if ((addr & 0x000000ff)==0x80) {
		tenkey=true;
	}
	if ((addr & 0x000000ff)==0xc0) {
		tenkey=false;
	}
	return;
}

uint32_t KEYBOARD::read_io8(uint32_t addr)
{
	// Controller 1
	if ((addr & 0x000000ff)==0xfc) {
		uint8_t button=0xf0;
		if (joy_stat[0] & 0x10) button=0xb0;			// F2
		if (!tenkey) {
			uint32_t joystick=0xff;
			if (joy_stat[0] & 0x01) joystick &= 0xfe;	// U
			if (joy_stat[0] & 0x02) joystick &= 0xfb;	// D
			if (joy_stat[0] & 0x04) {
				joystick &= 0x67;	// L
			}
			if (joy_stat[0] & 0x08) {
				joystick &= 0x4d;	// R
			}
			if (joy_stat[0] & 0x20) joystick &= 0xbf;	// F1
			return joystick;
		}
		if (key_stat[0x31] & 0x80)
			return (button | 0x0d); // 1
		if (key_stat[0x32] & 0x80)
			return (button | 0x07); // 2
		if (key_stat[0x33] & 0x80)
			return (button | 0x0c); // 3
		if (key_stat[0x34] & 0x80)
			return (button | 0x02); // 4
		if (key_stat[0x35] & 0x80)
			return (button | 0x03); // 5
		if (key_stat[0x36] & 0x80)
			return (button | 0x0e); // 6
		if (key_stat[0x37] & 0x80)
			return (button | 0x05); // 7
		if (key_stat[0x38] & 0x80)
			return (button | 0x01); // 8
		if (key_stat[0x39] & 0x80)
			return (button | 0x0b); // 9
		if (key_stat[0x30] & 0x80)
			return (button | 0x0a); // 0
		if (key_stat[0xbd] & 0x80)
			return (button | 0x09); // * '-'
		if (key_stat[0xde] & 0x80)
			return (button | 0x06); // # '^'
		if (key_stat[0x43] & 0x80)
			return (button | 0x08); // F3 'c'
		if (key_stat[0x56] & 0x80)
			return (button | 0x04); // F4 'v'
		return (button | 0x0f);
	}
	// Controller 2
	if ((addr & 0x000000ff)==0xff) {
		uint8_t button=0xf0;
		if (joy_stat[1] & 0x10) button=0xb0;			// F2
		if (!tenkey) {
			uint32_t joystick=0xff;
			if (joy_stat[1] & 0x01) joystick &= 0xfe;	// U
			if (joy_stat[1] & 0x02) joystick &= 0xfb;	// D
			if (joy_stat[1] & 0x04) joystick &= 0xf7;	// L
			if (joy_stat[1] & 0x08) joystick &= 0xfd;	// R
			if (joy_stat[1] & 0x20) joystick &= 0xbf;	// F1
			return joystick;
		}
		if (key_stat[0x51] & 0x80)
			return (button | 0x0d); // 1 'q'
		if (key_stat[0x57] & 0x80)
			return (button | 0x07); // 2 'w'
		if (key_stat[0x45] & 0x80)
			return (button | 0x0c); // 3 'e'
		if (key_stat[0x52] & 0x80)
			return (button | 0x02); // 4 'r'
		if (key_stat[0x54] & 0x80)
			return (button | 0x03); // 5 't'
		if (key_stat[0x59] & 0x80)
			return (button | 0x0e); // 6 'y'
		if (key_stat[0x55] & 0x80)
			return (button | 0x05); // 7 'u'
		if (key_stat[0x49] & 0x80)
			return (button | 0x01); // 8 'i'
		if (key_stat[0x4f] & 0x80)
			return (button | 0x0b); // 9 'o'
		if (key_stat[0x50] & 0x80)
			return (button | 0x0a); // 0 'p'
		if (key_stat[0xc0] & 0x80)
			return (button | 0x09); // * '@'
		if (key_stat[0xdb] & 0x80)
			return (button | 0x06); // # '['
		if (key_stat[0xbc] & 0x80)
			return (button | 0x08); // F3 ','
		if (key_stat[0xbe] & 0x80)
			return (button | 0x04); // F4 '.'
		return (button | 0x0f);
	}
	return 0x0ff;
}

#define STATE_VERSION	1

void KEYBOARD::save_state(FILEIO* state_fio)
{
	state_fio->FputUint32(STATE_VERSION);
	state_fio->FputInt32(this_device_id);	
}

bool KEYBOARD::load_state(FILEIO* state_fio)
{
	if(state_fio->FgetUint32() != STATE_VERSION) {
		return false;
	}
	if(state_fio->FgetInt32() != this_device_id) {
		return false;
	}
	return true;
}