mirror of
https://github.com/hsoft/collapseos.git
synced 2024-11-02 14:30:55 +11:00
Compare commits
2 Commits
9cddaf1b59
...
e1e0676191
Author | SHA1 | Date | |
---|---|---|---|
|
e1e0676191 | ||
|
b60252e330 |
@ -1,3 +1,4 @@
|
|||||||
|
#pragma once
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include "libz80/z80.h"
|
#include "libz80/z80.h"
|
||||||
@ -19,6 +20,12 @@ typedef struct {
|
|||||||
IOWR iowr[0x100];
|
IOWR iowr[0x100];
|
||||||
} Machine;
|
} Machine;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
TRI_HIGH,
|
||||||
|
TRI_LOW,
|
||||||
|
TRI_HIGHZ
|
||||||
|
} Tristate;
|
||||||
|
|
||||||
Machine* emul_init();
|
Machine* emul_init();
|
||||||
bool emul_step();
|
bool emul_step();
|
||||||
bool emul_steps(unsigned int steps);
|
bool emul_steps(unsigned int steps);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
EXTOBJS = ../../emul.o ../../libz80/libz80.o
|
EXTOBJS = ../../emul.o ../../libz80/libz80.o
|
||||||
OBJS = sms.o vdp.o
|
OBJS = sms.o vdp.o port.o pad.o
|
||||||
TARGET = sms
|
TARGET = sms
|
||||||
CFLAGS += `pkg-config --cflags xcb`
|
CFLAGS += `pkg-config --cflags xcb`
|
||||||
LDFLAGS += `pkg-config --libs 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 "../../emul.h"
|
||||||
#include "vdp.h"
|
#include "vdp.h"
|
||||||
|
#include "port.h"
|
||||||
|
#include "pad.h"
|
||||||
|
|
||||||
#define RAMSTART 0xc000
|
#define RAMSTART 0xc000
|
||||||
#define VDP_CMD_PORT 0xbf
|
#define VDP_CMD_PORT 0xbf
|
||||||
#define VDP_DATA_PORT 0xbe
|
#define VDP_DATA_PORT 0xbe
|
||||||
|
#define PORTS_CTL_PORT 0x3f
|
||||||
|
#define PORTS_IO1_PORT 0xdc
|
||||||
|
#define PORTS_IO2_PORT 0xdd
|
||||||
#define MAX_ROMSIZE 0x8000
|
#define MAX_ROMSIZE 0x8000
|
||||||
|
|
||||||
static xcb_connection_t *conn;
|
static xcb_connection_t *conn;
|
||||||
@ -26,6 +31,8 @@ static xcb_rectangle_t rectangles[VDP_SCREENW*VDP_SCREENH];
|
|||||||
static Machine *m;
|
static Machine *m;
|
||||||
static VDP vdp;
|
static VDP vdp;
|
||||||
static bool vdp_changed;
|
static bool vdp_changed;
|
||||||
|
static Ports ports;
|
||||||
|
static Pad pad;
|
||||||
|
|
||||||
static uint8_t iord_vdp_cmd()
|
static uint8_t iord_vdp_cmd()
|
||||||
{
|
{
|
||||||
@ -37,6 +44,21 @@ static uint8_t iord_vdp_data()
|
|||||||
return vdp_data_rd(&vdp);
|
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)
|
static void iowr_vdp_cmd(uint8_t val)
|
||||||
{
|
{
|
||||||
vdp_cmd_wr(&vdp, val);
|
vdp_cmd_wr(&vdp, val);
|
||||||
@ -48,6 +70,11 @@ static void iowr_vdp_data(uint8_t val)
|
|||||||
vdp_data_wr(&vdp, val);
|
vdp_data_wr(&vdp, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void iowr_ports_ctl(uint8_t val)
|
||||||
|
{
|
||||||
|
ports_ctl_wr(&ports, val);
|
||||||
|
}
|
||||||
|
|
||||||
void create_window()
|
void create_window()
|
||||||
{
|
{
|
||||||
uint32_t mask;
|
uint32_t mask;
|
||||||
@ -99,6 +126,7 @@ void draw_pixels()
|
|||||||
int innerh = psize * VDP_SCREENH;
|
int innerh = psize * VDP_SCREENH;
|
||||||
int innerx = (geom->width - innerw) / 2;
|
int innerx = (geom->width - innerw) / 2;
|
||||||
int innery = (geom->height - innerh) / 2;
|
int innery = (geom->height - innerh) / 2;
|
||||||
|
free(geom);
|
||||||
int drawcnt = 0;
|
int drawcnt = 0;
|
||||||
for (int i=0; i<VDP_SCREENW; i++) {
|
for (int i=0; i<VDP_SCREENW; i++) {
|
||||||
for (int j=0; j<VDP_SCREENH; j++) {
|
for (int j=0; j<VDP_SCREENH; j++) {
|
||||||
@ -131,6 +159,15 @@ void event_loop()
|
|||||||
emul_steps(100);
|
emul_steps(100);
|
||||||
draw_pixels();
|
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);
|
xcb_generic_event_t *e = xcb_poll_for_event(conn);
|
||||||
if (!e) {
|
if (!e) {
|
||||||
continue;
|
continue;
|
||||||
@ -140,7 +177,34 @@ void event_loop()
|
|||||||
case XCB_KEY_RELEASE:
|
case XCB_KEY_RELEASE:
|
||||||
case XCB_KEY_PRESS: {
|
case XCB_KEY_PRESS: {
|
||||||
xcb_key_press_event_t *ev = (xcb_key_press_event_t *)e;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case XCB_EXPOSE: {
|
case XCB_EXPOSE: {
|
||||||
@ -180,10 +244,16 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
vdp_init(&vdp);
|
vdp_init(&vdp);
|
||||||
vdp_changed = false;
|
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_CMD_PORT] = iord_vdp_cmd;
|
||||||
m->iord[VDP_DATA_PORT] = iord_vdp_data;
|
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_CMD_PORT] = iowr_vdp_cmd;
|
||||||
m->iowr[VDP_DATA_PORT] = iowr_vdp_data;
|
m->iowr[VDP_DATA_PORT] = iowr_vdp_data;
|
||||||
|
m->iowr[PORTS_CTL_PORT] = iowr_ports_ctl;
|
||||||
conn = xcb_connect(NULL, NULL);
|
conn = xcb_connect(NULL, NULL);
|
||||||
screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
|
screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
|
||||||
create_window();
|
create_window();
|
||||||
|
@ -199,6 +199,7 @@ void draw_pixels()
|
|||||||
int innerh = psize * 64;
|
int innerh = psize * 64;
|
||||||
int innerx = (geom->width - innerw) / 2;
|
int innerx = (geom->width - innerw) / 2;
|
||||||
int innery = (geom->height - innerh) / 2;
|
int innery = (geom->height - innerh) / 2;
|
||||||
|
free(geom);
|
||||||
int drawcnt = 0;
|
int drawcnt = 0;
|
||||||
for (int i=0; i<96; i++) {
|
for (int i=0; i<96; i++) {
|
||||||
for (int j=0; j<64; j++) {
|
for (int j=0; j<64; j++) {
|
||||||
@ -231,6 +232,15 @@ void event_loop()
|
|||||||
emul_steps(100);
|
emul_steps(100);
|
||||||
draw_pixels();
|
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);
|
xcb_generic_event_t *e = xcb_poll_for_event(conn);
|
||||||
if (!e) {
|
if (!e) {
|
||||||
continue;
|
continue;
|
||||||
|
Loading…
Reference in New Issue
Block a user