mirror of
https://github.com/hsoft/collapseos.git
synced 2024-11-02 16:30:57 +11:00
Compare commits
2 Commits
9cddaf1b59
...
e1e0676191
Author | SHA1 | Date | |
---|---|---|---|
|
e1e0676191 | ||
|
b60252e330 |
@ -1,3 +1,4 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "libz80/z80.h"
|
||||
@ -19,6 +20,12 @@ typedef struct {
|
||||
IOWR iowr[0x100];
|
||||
} Machine;
|
||||
|
||||
typedef enum {
|
||||
TRI_HIGH,
|
||||
TRI_LOW,
|
||||
TRI_HIGHZ
|
||||
} Tristate;
|
||||
|
||||
Machine* emul_init();
|
||||
bool emul_step();
|
||||
bool emul_steps(unsigned int steps);
|
||||
|
@ -1,5 +1,5 @@
|
||||
EXTOBJS = ../../emul.o ../../libz80/libz80.o
|
||||
OBJS = sms.o vdp.o
|
||||
OBJS = sms.o vdp.o port.o pad.o
|
||||
TARGET = sms
|
||||
CFLAGS += `pkg-config --cflags xcb`
|
||||
LDFLAGS += `pkg-config --libs xcb`
|
||||
|
25
emul/hw/sms/README.md
Normal file
25
emul/hw/sms/README.md
Normal file
@ -0,0 +1,25 @@
|
||||
# Sega Master System emulator
|
||||
|
||||
This emulates a Sega Master system with a monochrome screen and a Genesis pad
|
||||
hooked to port A.
|
||||
|
||||
## Build
|
||||
|
||||
You need `xcb` and `pkg-config` to build this. If you have them, run `make`.
|
||||
You'll get a `sms` executable.
|
||||
|
||||
## Usage
|
||||
|
||||
Launch the emulator with `./sms /path/to/rom` (you can use the binary from the
|
||||
`sms` recipe.
|
||||
|
||||
This will show a window with the screen's content on it. The mappings to the
|
||||
pad are:
|
||||
|
||||
* Arrows
|
||||
* Z --> A
|
||||
* X --> B
|
||||
* C --> C
|
||||
* S --> Start
|
||||
|
||||
Press ESC to quit.
|
30
emul/hw/sms/pad.c
Normal file
30
emul/hw/sms/pad.c
Normal file
@ -0,0 +1,30 @@
|
||||
#include "pad.h"
|
||||
|
||||
void pad_init(Pad *pad, Tristate *TH)
|
||||
{
|
||||
pad->pressed = 0xff;
|
||||
pad->TH = TH;
|
||||
}
|
||||
|
||||
void pad_setbtn(Pad *pad, PAD_BTN btn, bool pressed)
|
||||
{
|
||||
if (pressed) {
|
||||
pad->pressed &= ~(1 << btn);
|
||||
} else {
|
||||
pad->pressed |= (1 << btn);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t pad_rd(Pad *pad)
|
||||
{
|
||||
uint8_t res;
|
||||
if (*pad->TH == TRI_LOW) { // TH selected
|
||||
// A and START shifted in from bits 7:6 into 5:4
|
||||
res = (pad->pressed & 0xf) | ((pad->pressed & 0xc0) >> 2);
|
||||
} else {
|
||||
res = pad->pressed & 0x3f;
|
||||
}
|
||||
// Bits 7:6 are always high
|
||||
res |= 0b11000000;
|
||||
return res;
|
||||
}
|
23
emul/hw/sms/pad.h
Normal file
23
emul/hw/sms/pad.h
Normal file
@ -0,0 +1,23 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "port.h"
|
||||
|
||||
typedef enum {
|
||||
PAD_BTN_UP = 0,
|
||||
PAD_BTN_DOWN = 1,
|
||||
PAD_BTN_LEFT = 2,
|
||||
PAD_BTN_RIGHT = 3,
|
||||
PAD_BTN_B = 4,
|
||||
PAD_BTN_C = 5,
|
||||
PAD_BTN_A = 6,
|
||||
PAD_BTN_START = 7
|
||||
} PAD_BTN;
|
||||
|
||||
typedef struct {
|
||||
uint8_t pressed;
|
||||
Tristate *TH;
|
||||
} Pad;
|
||||
|
||||
void pad_init(Pad *pad, Tristate *TH);
|
||||
void pad_setbtn(Pad *pad, PAD_BTN btn, bool pressed);
|
||||
uint8_t pad_rd(Pad *pad);
|
70
emul/hw/sms/port.c
Normal file
70
emul/hw/sms/port.c
Normal file
@ -0,0 +1,70 @@
|
||||
#include "port.h"
|
||||
|
||||
void ports_init(Ports *ports)
|
||||
{
|
||||
ports->ctl = 0xff;
|
||||
ports->TRA = TRI_HIGHZ;
|
||||
ports->THA = TRI_HIGHZ;
|
||||
ports->TRB = TRI_HIGHZ;
|
||||
ports->THB = TRI_HIGHZ;
|
||||
}
|
||||
|
||||
uint8_t ports_ctl_rd(Ports *ports)
|
||||
{
|
||||
return ports->ctl;
|
||||
}
|
||||
|
||||
void ports_ctl_wr(Ports *ports, uint8_t val)
|
||||
{
|
||||
ports->ctl = val;
|
||||
ports->TRA = TRI_HIGHZ;
|
||||
ports->THA = TRI_HIGHZ;
|
||||
ports->TRB = TRI_HIGHZ;
|
||||
ports->THB = TRI_HIGHZ;
|
||||
if ((val & 0x01) == 0) {
|
||||
ports->TRA = ((val & 0x10) == 0) ? TRI_LOW : TRI_HIGH;
|
||||
}
|
||||
if ((val & 0x02) == 0) {
|
||||
ports->THA = ((val & 0x20) == 0) ? TRI_LOW : TRI_HIGH;
|
||||
}
|
||||
if ((val & 0x04) == 0) {
|
||||
ports->TRB = ((val & 0x40) == 0) ? TRI_LOW : TRI_HIGH;
|
||||
}
|
||||
if ((val & 0x08) == 0) {
|
||||
ports->THB = ((val & 0x80) == 0) ? TRI_LOW : TRI_HIGH;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ports_A_rd(Ports *ports)
|
||||
{
|
||||
// Bits 7:6 are port B's Down/Up
|
||||
// Bits 5:0 are port A's TR/TL/R/L/D/U
|
||||
uint8_t res = 0xff;
|
||||
if (ports->portA_rd != NULL) {
|
||||
res &= ports->portA_rd() | 0b11000000;
|
||||
}
|
||||
if (ports->portB_rd != NULL) {
|
||||
res &= (ports->portB_rd() << 6) | 0b00111111;
|
||||
}
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
uint8_t ports_B_rd(Ports *ports)
|
||||
{
|
||||
// Bit 7: Port B's TH
|
||||
// Bit 6: Port A's TH
|
||||
// Bit 5: unused
|
||||
// Bit 4: unused (reset button)
|
||||
// Bits 3:0 are port B's TR/TL/R/L
|
||||
uint8_t res = 0xff;
|
||||
if (ports->portA_rd != NULL) {
|
||||
res &= ports->portA_rd() | 0b10111111;
|
||||
}
|
||||
if (ports->portB_rd != NULL) {
|
||||
uint8_t portb = ports->portB_rd();
|
||||
res &= (portb << 1) | 0b01111111; // TH
|
||||
res &= (portb >> 2) | 0b11110000; // TR/TL/R/L
|
||||
}
|
||||
return res;
|
||||
}
|
21
emul/hw/sms/port.h
Normal file
21
emul/hw/sms/port.h
Normal file
@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include "../../emul.h"
|
||||
|
||||
// Each port is a bitmask of each pin's status. 1 means high.
|
||||
// From Bit 0 to 6: up, down, left, right, TL, TR, TH
|
||||
|
||||
typedef struct {
|
||||
uint8_t ctl;
|
||||
Tristate TRA;
|
||||
Tristate THA;
|
||||
Tristate TRB;
|
||||
Tristate THB;
|
||||
IORD portA_rd;
|
||||
IORD portB_rd;
|
||||
} Ports;
|
||||
|
||||
void ports_init(Ports *ports);
|
||||
uint8_t ports_ctl_rd(Ports *ports);
|
||||
void ports_ctl_wr(Ports *ports, uint8_t val);
|
||||
uint8_t ports_A_rd(Ports *ports);
|
||||
uint8_t ports_B_rd(Ports *ports);
|
@ -6,10 +6,15 @@
|
||||
|
||||
#include "../../emul.h"
|
||||
#include "vdp.h"
|
||||
#include "port.h"
|
||||
#include "pad.h"
|
||||
|
||||
#define RAMSTART 0xc000
|
||||
#define VDP_CMD_PORT 0xbf
|
||||
#define VDP_DATA_PORT 0xbe
|
||||
#define PORTS_CTL_PORT 0x3f
|
||||
#define PORTS_IO1_PORT 0xdc
|
||||
#define PORTS_IO2_PORT 0xdd
|
||||
#define MAX_ROMSIZE 0x8000
|
||||
|
||||
static xcb_connection_t *conn;
|
||||
@ -26,6 +31,8 @@ static xcb_rectangle_t rectangles[VDP_SCREENW*VDP_SCREENH];
|
||||
static Machine *m;
|
||||
static VDP vdp;
|
||||
static bool vdp_changed;
|
||||
static Ports ports;
|
||||
static Pad pad;
|
||||
|
||||
static uint8_t iord_vdp_cmd()
|
||||
{
|
||||
@ -37,6 +44,21 @@ static uint8_t iord_vdp_data()
|
||||
return vdp_data_rd(&vdp);
|
||||
}
|
||||
|
||||
static uint8_t iord_ports_io1()
|
||||
{
|
||||
return ports_A_rd(&ports);
|
||||
}
|
||||
|
||||
static uint8_t iord_ports_io2()
|
||||
{
|
||||
return ports_B_rd(&ports);
|
||||
}
|
||||
|
||||
static uint8_t iord_pad()
|
||||
{
|
||||
return pad_rd(&pad);
|
||||
}
|
||||
|
||||
static void iowr_vdp_cmd(uint8_t val)
|
||||
{
|
||||
vdp_cmd_wr(&vdp, val);
|
||||
@ -48,6 +70,11 @@ static void iowr_vdp_data(uint8_t val)
|
||||
vdp_data_wr(&vdp, val);
|
||||
}
|
||||
|
||||
static void iowr_ports_ctl(uint8_t val)
|
||||
{
|
||||
ports_ctl_wr(&ports, val);
|
||||
}
|
||||
|
||||
void create_window()
|
||||
{
|
||||
uint32_t mask;
|
||||
@ -99,6 +126,7 @@ void draw_pixels()
|
||||
int innerh = psize * VDP_SCREENH;
|
||||
int innerx = (geom->width - innerw) / 2;
|
||||
int innery = (geom->height - innerh) / 2;
|
||||
free(geom);
|
||||
int drawcnt = 0;
|
||||
for (int i=0; i<VDP_SCREENW; i++) {
|
||||
for (int j=0; j<VDP_SCREENH; j++) {
|
||||
@ -131,6 +159,15 @@ void event_loop()
|
||||
emul_steps(100);
|
||||
draw_pixels();
|
||||
}
|
||||
// A low tech way of checking when the window was closed. The proper way
|
||||
// involving WM_DELETE is too complicated.
|
||||
xcb_get_geometry_reply_t *geom;
|
||||
geom = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL);
|
||||
if (geom == NULL) {
|
||||
return; // window has been closed.
|
||||
} else {
|
||||
free(geom);
|
||||
}
|
||||
xcb_generic_event_t *e = xcb_poll_for_event(conn);
|
||||
if (!e) {
|
||||
continue;
|
||||
@ -140,7 +177,34 @@ void event_loop()
|
||||
case XCB_KEY_RELEASE:
|
||||
case XCB_KEY_PRESS: {
|
||||
xcb_key_press_event_t *ev = (xcb_key_press_event_t *)e;
|
||||
if (ev->detail == 0x09) return;
|
||||
bool ispressed = e->response_type == XCB_KEY_PRESS;
|
||||
switch (ev->detail) {
|
||||
case 0x09: return; // ESC
|
||||
case 0x27: // S
|
||||
pad_setbtn(&pad, PAD_BTN_START, ispressed);
|
||||
break;
|
||||
case 0x34: // Z
|
||||
pad_setbtn(&pad, PAD_BTN_A, ispressed);
|
||||
break;
|
||||
case 0x35: // X
|
||||
pad_setbtn(&pad, PAD_BTN_B, ispressed);
|
||||
break;
|
||||
case 0x36: // C
|
||||
pad_setbtn(&pad, PAD_BTN_C, ispressed);
|
||||
break;
|
||||
case 0x62:
|
||||
pad_setbtn(&pad, PAD_BTN_UP, ispressed);
|
||||
break;
|
||||
case 0x64:
|
||||
pad_setbtn(&pad, PAD_BTN_LEFT, ispressed);
|
||||
break;
|
||||
case 0x66:
|
||||
pad_setbtn(&pad, PAD_BTN_RIGHT, ispressed);
|
||||
break;
|
||||
case 0x68:
|
||||
pad_setbtn(&pad, PAD_BTN_DOWN, ispressed);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case XCB_EXPOSE: {
|
||||
@ -180,10 +244,16 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
vdp_init(&vdp);
|
||||
vdp_changed = false;
|
||||
ports_init(&ports);
|
||||
ports.portA_rd = iord_pad;
|
||||
pad_init(&pad, &ports.THA);
|
||||
m->iord[VDP_CMD_PORT] = iord_vdp_cmd;
|
||||
m->iord[VDP_DATA_PORT] = iord_vdp_data;
|
||||
m->iord[PORTS_IO1_PORT] = iord_ports_io1;
|
||||
m->iord[PORTS_IO2_PORT] = iord_ports_io2;
|
||||
m->iowr[VDP_CMD_PORT] = iowr_vdp_cmd;
|
||||
m->iowr[VDP_DATA_PORT] = iowr_vdp_data;
|
||||
m->iowr[PORTS_CTL_PORT] = iowr_ports_ctl;
|
||||
conn = xcb_connect(NULL, NULL);
|
||||
screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
|
||||
create_window();
|
||||
|
@ -199,6 +199,7 @@ void draw_pixels()
|
||||
int innerh = psize * 64;
|
||||
int innerx = (geom->width - innerw) / 2;
|
||||
int innery = (geom->height - innerh) / 2;
|
||||
free(geom);
|
||||
int drawcnt = 0;
|
||||
for (int i=0; i<96; i++) {
|
||||
for (int j=0; j<64; j++) {
|
||||
@ -231,6 +232,15 @@ void event_loop()
|
||||
emul_steps(100);
|
||||
draw_pixels();
|
||||
}
|
||||
// A low tech way of checking when the window was closed. The proper way
|
||||
// involving WM_DELETE is too complicated.
|
||||
xcb_get_geometry_reply_t *geom;
|
||||
geom = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, win), NULL);
|
||||
if (geom == NULL) {
|
||||
return; // window has been closed.
|
||||
} else {
|
||||
free(geom);
|
||||
}
|
||||
xcb_generic_event_t *e = xcb_poll_for_event(conn);
|
||||
if (!e) {
|
||||
continue;
|
||||
|
Loading…
Reference in New Issue
Block a user