view blastem.c @ 103:a71544cd01ea

Don't pre-emptively translate code at interrupt vectors as some PD ROMs have these pointing at junk. Need some kind of heuristic for detecting garbage if I'm going to translate them ahead of time by default.
author Mike Pavone <pavone@retrodev.com>
date Thu, 27 Dec 2012 22:48:54 -0800
parents dd3c680c618c
children 11d72e471104
line wrap: on
line source

#include "68kinst.h"
#include "m68k_to_x86.h"
#include "mem.h"
#include "vdp.h"
#include "render.h"
#include "blastem.h"
#include <stdio.h>
#include <stdlib.h>

#define CARTRIDGE_WORDS 0x200000
#define RAM_WORDS 32 * 1024
#define MCLKS_PER_68K 7
//TODO: Figure out the exact value for this
#define MCLKS_PER_FRAME (MCLKS_LINE*262)
#define CYCLE_NEVER 0xFFFFFFFF

uint16_t cart[CARTRIDGE_WORDS];
uint16_t ram[RAM_WORDS];

io_port gamepad_1;
io_port gamepad_2;

#ifndef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif

int load_rom(char * filename)
{
	FILE * f = fopen(filename, "rb");
	if (!f) {
		return 0;
	}
	fseek(f, 0, SEEK_END);
	long filesize = ftell(f);
	fseek(f, 0, SEEK_SET);
	fread(cart, 2, MIN(filesize/2, CARTRIDGE_WORDS), f);
	fclose(f);
	for(unsigned short * cur = cart; cur - cart < (filesize/2); ++cur)
	{
		*cur = (*cur >> 8) | (*cur << 8);
	}
	//TODO: Mirror ROM
	return 1;
}

uint16_t read_dma_value(uint32_t address)
{
	//addresses here are word addresses (i.e. bit 0 corresponds to A1), so no need to do div by 2
	if (address < 0x200000) {
		return cart[address];
	} else if(address >= 0x700000) {
		return ram[address & 0x7FFF];
	}
	//TODO: Figure out what happens when you try to DMA from weird adresses like IO or banked Z80 area
	return 0;
}

#define VINT_CYCLE ((MCLKS_LINE * 226)/MCLKS_PER_68K)

m68k_context * sync_components(m68k_context * context)
{
	//TODO: Handle sync targets smaller than a single frame
	vdp_context * v_context = context->next_context;
	uint32_t mclks = context->current_cycle * MCLKS_PER_68K;
	if (mclks >= MCLKS_PER_FRAME) {
		//printf("reached frame end | 68K Cycles: %d, MCLK Cycles: %d\n", context->current_cycle, mclks);
		vdp_run_context(v_context, MCLKS_PER_FRAME);
		wait_render_frame(v_context);
		mclks -= MCLKS_PER_FRAME;
		vdp_adjust_cycles(v_context, MCLKS_PER_FRAME);
		io_adjust_cycles(&gamepad_1, context->current_cycle, MCLKS_PER_FRAME/MCLKS_PER_68K);
		io_adjust_cycles(&gamepad_2, context->current_cycle, MCLKS_PER_FRAME/MCLKS_PER_68K);
		context->current_cycle -= MCLKS_PER_FRAME/MCLKS_PER_68K;
		if (mclks) {
			vdp_run_context(v_context, mclks);
		}
		if (v_context->regs[REG_MODE_2] & 0x20 && ((context->status & 0x7) < 6)) {
			if (context->int_cycle > VINT_CYCLE) {
				context->int_cycle = VINT_CYCLE;
				context->int_num = 6;
				if (context->int_cycle < context->sync_cycle) {
					context->target_cycle = context->int_cycle;
				}
			}
		} else {
			context->int_cycle = 0xFFFFFFFF;
			context->target_cycle = context->sync_cycle;
		}
	} else {
		//printf("running VDP for %d cycles\n", mclks - v_context->cycles);
		vdp_run_context(v_context, mclks);
		if (v_context->regs[REG_MODE_2] & 0x20 && ((context->status & 0x7) < 6)) {
			if (context->int_cycle > VINT_CYCLE) {
				context->int_cycle = VINT_CYCLE;
				context->int_num = 6;
				if (context->int_cycle < context->sync_cycle && context->int_cycle < context->current_cycle) {
					context->target_cycle = context->int_cycle;
				}
			}
			if (context->int_cycle <= context->current_cycle) {
				context->int_cycle = CYCLE_NEVER;
				context->target_cycle = context->sync_cycle;
			}
		} else {
			context->int_cycle = CYCLE_NEVER;
			context->target_cycle = context->sync_cycle;
		}
	}
	return context;
}

m68k_context * vdp_port_write(uint32_t vdp_port, m68k_context * context, uint16_t value)
{
	//printf("vdp_port write: %X, value: %X, cycle: %d\n", vdp_port, value, context->current_cycle);
	sync_components(context);
	vdp_context * v_context = context->next_context;
	if (vdp_port < 0x10) {
		if (vdp_port < 4) {
			vdp_data_port_write(v_context, value);
		} else if(vdp_port < 8) {
			int blocked = vdp_control_port_write(v_context, value);
			if (blocked) {
				while(v_context->flags & FLAG_DMA_RUN) {
					vdp_run_dma_done(v_context, MCLKS_PER_FRAME);
					if (v_context->cycles >= MCLKS_PER_FRAME) {
						wait_render_frame(v_context);
						vdp_adjust_cycles(v_context, MCLKS_PER_FRAME);
						io_adjust_cycles(&gamepad_1, v_context->cycles/MCLKS_PER_68K, MCLKS_PER_FRAME/MCLKS_PER_68K);
						io_adjust_cycles(&gamepad_2, v_context->cycles/MCLKS_PER_68K, MCLKS_PER_FRAME/MCLKS_PER_68K);
					}
				}
				context->current_cycle = v_context->cycles / MCLKS_PER_68K;
			} else {
				if (v_context->regs[REG_MODE_2] & 0x20 && ((context->status & 0x7) < 6)) {
					if (context->int_cycle > VINT_CYCLE) {
						context->int_cycle = VINT_CYCLE;
						context->int_num = 6;
						if (context->int_cycle < context->sync_cycle) {
							context->target_cycle = context->int_cycle;
						}
					}
				} else {
					context->int_cycle = 0xFFFFFFFF;
					context->target_cycle = context->sync_cycle;
				}
			}
		} else {
			printf("Illegal write to HV Counter port %X\n", vdp_port);
			exit(1);
		}
		context->current_cycle = v_context->cycles/MCLKS_PER_68K;
	} else if (vdp_port < 0x18) {
		//TODO: Implement PSG
	} else {
		//TODO: Implement undocumented test register(s)
	}
	return context;
}

m68k_context * vdp_port_read(uint32_t vdp_port, m68k_context * context)
{
	sync_components(context);
	vdp_context * v_context = context->next_context;
	if (vdp_port < 0x10) {
		if (vdp_port < 4) {
			context->value = vdp_data_port_read(v_context);
		} else if(vdp_port < 8) {
			context->value = vdp_control_port_read(v_context);
		} else {
			//TODO: Implement H/V counter
			context->value = 0;
		}
		context->current_cycle = v_context->cycles/MCLKS_PER_68K;
	} else {
		printf("Illegal read from PSG or test register port %X\n", vdp_port);
		exit(1);
	}
	return context;
}

#define TH 0x40
#define TH_TIMEOUT 8000
#define Z80_ACK_DELAY 3 //TODO: Calculate this on the fly based on how synced up the Z80 and 68K clocks are

uint8_t reset = 1;
uint8_t busreq = 0;
uint8_t busack = 0;
uint32_t busack_cycle = CYCLE_NEVER;
uint8_t new_busack = 0;

void io_adjust_cycles(io_port * pad, uint32_t current_cycle, uint32_t deduction)
{
	/*uint8_t control = pad->control | 0x80;
	uint8_t th = control & pad->output;
	if (pad->input[GAMEPAD_TH0] || pad->input[GAMEPAD_TH1]) {
		printf("adjust_cycles | control: %X, TH: %X, GAMEPAD_TH0: %X, GAMEPAD_TH1: %X, TH Counter: %d, Timeout: %d, Cycle: %d\n", control, th, pad->input[GAMEPAD_TH0], pad->input[GAMEPAD_TH1], pad->th_counter,pad->timeout_cycle, current_cycle);
	}*/
	if (current_cycle >= pad->timeout_cycle) {
		pad->th_counter = 0;
	} else {
		pad->timeout_cycle -= deduction;
	}
	if (busack_cycle < CYCLE_NEVER && current_cycle < busack_cycle) {
		busack_cycle -= deduction;
	}
}

void io_data_write(io_port * pad, m68k_context * context, uint8_t value)
{
	if (pad->control & TH) {
		//check if TH has changed
		if ((pad->output & TH) ^ (value & TH)) {
			if (context->current_cycle >= pad->timeout_cycle) {
				pad->th_counter = 0;
			}
			if (!(value & TH)) {
				pad->th_counter++;
			}
			pad->timeout_cycle = context->current_cycle + TH_TIMEOUT;
		}
	}
	pad->output = value;
}

void io_data_read(io_port * pad, m68k_context * context)
{
	uint8_t control = pad->control | 0x80;
	uint8_t th = control & pad->output;
	uint8_t input;
	if (context->current_cycle >= pad->timeout_cycle) {
		pad->th_counter = 0;
	}
	/*if (pad->input[GAMEPAD_TH0] || pad->input[GAMEPAD_TH1]) {
		printf("io_data_read | control: %X, TH: %X, GAMEPAD_TH0: %X, GAMEPAD_TH1: %X, TH Counter: %d, Timeout: %d, Cycle: %d\n", control, th, pad->input[GAMEPAD_TH0], pad->input[GAMEPAD_TH1], pad->th_counter,pad->timeout_cycle, context->current_cycle);
	}*/
	if (th) {
		if (pad->th_counter == 2) {
			input = pad->input[GAMEPAD_EXTRA];
		} else {
			input = pad->input[GAMEPAD_TH1];
		}
	} else {
		if (pad->th_counter == 2) {
			input = pad->input[GAMEPAD_TH0] | 0xF;
		} else if(pad->th_counter == 3) {
			input = pad->input[GAMEPAD_TH0]  & 0x30;
		} else {
			input = pad->input[GAMEPAD_TH0] | 0xC;
		}
	}
	context->value = ((~input) & (~control)) | (pad->output & control);
	/*if (pad->input[GAMEPAD_TH0] || pad->input[GAMEPAD_TH1]) {
		printf ("value: %X\n", context->value);
	}*/
}

m68k_context * io_write(uint32_t location, m68k_context * context, uint8_t value)
{
	if (location < 0x100) {
		switch(location/2)
		{
		case 0x1:
			io_data_write(&gamepad_1, context, value);
			break;
		case 0x2:
			io_data_write(&gamepad_2, context, value);
			break;
		case 0x3://PORT C Data
			break;
		case 0x4:
			gamepad_1.control = value;
			break;
		case 0x5:
			gamepad_2.control = value;
			break;
		}
	} else {
		if (location == 0x1100) {
			if (busack_cycle > context->current_cycle) {
				busack = new_busack;
				busack_cycle = CYCLE_NEVER;
			}
			if (value & 1) {
				busreq = 1;
				if(!reset) {
					busack_cycle = context->current_cycle + Z80_ACK_DELAY;
					new_busack = 1;
				}
			} else {
				busreq = 0;
				busack_cycle = CYCLE_NEVER;
				busack = 0;
			}
		} else if (location == 0x1200) {
			if (value & 1) {
				if (reset && busreq) {
					new_busack = 1;
					busack_cycle = context->current_cycle + Z80_ACK_DELAY;
				}
				reset = 0;
			} else {
				busack = 0;
				reset = 1;
			}
		}
	}
	return context;
}

m68k_context * io_write_w(uint32_t location, m68k_context * context, uint16_t value)
{
	if (location < 0x100) {
		switch(location/2)
		{
		case 0x1:
			io_data_write(&gamepad_1, context, value);
			break;
		case 0x2:
			io_data_write(&gamepad_2, context, value);
			break;
		case 0x3://PORT C Data
			break;
		case 0x4:
			gamepad_1.control = value;
			break;
		case 0x5:
			gamepad_2.control = value;
			break;
		}
	} else {
		//printf("IO Write of %X to %X\n", value, location);
		if (location == 0x1100) {
			if (busack_cycle > context->current_cycle) {
				busack = new_busack;
				busack_cycle = CYCLE_NEVER;
			}
			if (value & 0x100) {
				busreq = 1;
				if(!reset) {
					busack_cycle = context->current_cycle + Z80_ACK_DELAY;
					new_busack = 1;
				}
			} else {
				busreq = 0;
				busack_cycle = CYCLE_NEVER;
				busack = 0;
			}
		} else if (location == 0x1200) {
			if (value & 0x100) {
				if (reset && busreq) {
					new_busack = 1;
					busack_cycle = context->current_cycle + Z80_ACK_DELAY;
				}
				reset = 0;
			} else {
				busack = 0;
				reset = 1;
			}
		}
	}
	return context;
}

m68k_context * io_read(uint32_t location, m68k_context * context)
{
	if (location < 0x100) {
		switch(location/2)
		{
		case 0x0:
			//version bits should be 0 for now since we're not emulating TMSS
			//Not sure about the other bits
			context->value = 0;
			break;
		case 0x1:
			io_data_read(&gamepad_1, context);
			break;
		case 0x2:
			io_data_read(&gamepad_2, context);
			break;
		case 0x3://PORT C Data
			break;
		case 0x4:
			context->value = gamepad_1.control;
			break;
		case 0x5:
			context->value = gamepad_2.control;
			break;
		}
	} else {
		if (location == 0x1100) {
			if (busack_cycle > context->current_cycle) {
				busack = new_busack;
				busack_cycle = CYCLE_NEVER;
			}
			context->value = (!reset) && busack;
			//printf("Byte read of BUSREQ returned %d\n", context->value);
		} else if (location == 0x1200) {
			context->value = !reset;
		} else {
			printf("Byte read of unknown IO location: %X\n", location);
		}
	}
	return context;
}

m68k_context * io_read_w(uint32_t location, m68k_context * context)
{
	if (location < 0x100) {
		switch(location/2)
		{
		case 0x0:
			//version bits should be 0 for now since we're not emulating TMSS
			//Not sure about the other bits
			context->value = 0;
			break;
		case 0x1:
			io_data_read(&gamepad_1, context);
			break;
		case 0x2:
			io_data_read(&gamepad_2, context);
			break;
		case 0x3://PORT C Data
			break;
		case 0x4:
			context->value = gamepad_1.control;
			break;
		case 0x5:
			context->value = gamepad_2.control;
			break;
		case 0x6:
			//PORT C Control
			context->value = 0;
			break;
		}
		context->value = context->value | (context->value << 8);
		//printf("Word read to %X returned %d\n", location, context->value);
	} else {
		if (location == 0x1100) {
			if (busack_cycle > context->current_cycle) {
				busack = new_busack;
				busack_cycle = CYCLE_NEVER;
			}
			context->value = ((!reset) && busack) << 8;
			//printf("Word read of BUSREQ returned %d\n", context->value);
		} else if (location == 0x1200) {
			context->value = (!reset) << 8;
		} else {
			printf("Word read of unknown IO location: %X\n", location);
		}
	}
	return context;
}

int main(int argc, char ** argv)
{
	if (argc < 2) {
		fputs("Usage: blastem FILENAME\n", stderr);
		return 1;
	}
	if(!load_rom(argv[1])) {
		fprintf(stderr, "Failed to open %s for reading\n", argv[1]);
		return 1;
	}
	int width = 320;
	int height = 240;
	if (argc > 2) {
		width = atoi(argv[2]);
		if (argc > 3) {
			height = atoi(argv[3]);
		} else {
			height = (width/320) * 240;
		}
	}
	render_init(width, height);
	
	x86_68k_options opts;
	m68k_context context;
	vdp_context v_context;
	
	init_x86_68k_opts(&opts);
	init_68k_context(&context, opts.native_code_map, &opts);
	init_vdp_context(&v_context);
	context.next_context = &v_context;
	//cartridge ROM
	context.mem_pointers[0] = cart;
	context.target_cycle = context.sync_cycle = MCLKS_PER_FRAME/MCLKS_PER_68K;
	//work RAM
	context.mem_pointers[1] = ram;
	uint32_t address;
	/*address = cart[0x68/2] << 16 | cart[0x6A/2];
	translate_m68k_stream(address, &context);
	address = cart[0x70/2] << 16 | cart[0x72/2];
	translate_m68k_stream(address, &context);
	address = cart[0x78/2] << 16 | cart[0x7A/2];
	translate_m68k_stream(address, &context);*/
	address = cart[2] << 16 | cart[3];
	translate_m68k_stream(address, &context);
	m68k_reset(&context);
	return 0;
}