Compare commits

...

10 Commits

Author SHA1 Message Date
O_WRONLY
2a2e2cbc34 Create README.md 2024-11-04 07:09:33 +09:00
db41ae86e3 add print 2024-08-12 16:57:37 +09:00
d82b0a8ccd remove unused variable 2024-07-24 16:04:17 +09:00
8c700685ab fix save bug 2024-07-23 17:05:23 +09:00
05a3ada827 fix bug of WX,WY
now can render window
2024-07-23 14:15:56 +09:00
5de3efefb5 fix wran 2024-07-22 21:42:24 +09:00
3181653d92 replace coroutine to setjmp 2024-07-22 21:41:10 +09:00
O_WRONLY
aea52f9e63
Merge pull request #4 from Hojun-Cho/dev/save
dev/save snapshot and save feature for battery cartidge
2024-07-18 11:30:47 +00:00
bc76c36ed4 add loadsave
loadsave: only for BAT features cartidge
[load|save] state: for all cartidge. It's like snapshot
2024-07-18 20:24:34 +09:00
4661fc35bb add load and save function 2024-07-17 15:12:33 +09:00
23 changed files with 658 additions and 1031 deletions

2
.gitignore vendored
View File

@ -6,6 +6,8 @@
*.ko
*.obj
*.elf
.*.swp
*.gb
# Linker output
*.ilk

View File

@ -1,18 +1,10 @@
CC=tcc
CFLAGS= -c -Wall -g -Wextra
SRC:=$(wildcard *.c)
OBJ:=$(SRC:.c=.o)
CFLAGS= -Wall -g
all: gb
clean:
make clean -C co/
rm -f $(OBJ)
rm -f *.o
rm -f gb
%.o: %.c gb.h
$(CC) $(CFLAGS) -o $@ $<
gb: $(OBJ)
make all -C co/
$(CC) co/*.o *.o -lucontext -lSDL2 -g -o gb
$(CC) jmp.S *.c -lSDL2 -o gb $(CFLAGS)

4
README.md Normal file
View File

@ -0,0 +1,4 @@
# gameboy emulator
* only for x86-64
* dependency: sdl2

View File

@ -1,15 +0,0 @@
CC=tcc
CFLAGS= -c -Wall -g
SRC:=$(wildcard *.c)
OBJ:=$(SRC:.c=.o)
all: co
clean:
rm -f $(OBJ)
%.o: %.c
$(CC) $(CFLAGS) -o $@ $<
co: $(OBJ)

View File

@ -1,2 +0,0 @@
# coroutine library
https://github.com/Hojun-Cho/coroutine

View File

@ -1,356 +0,0 @@
#include "taskimpl.h"
Channel*
newchan(int elemsize, int bufsize)
{
Channel* c;
c = malloc(sizeof *c + bufsize * elemsize);
if (c == 0) {
exit(1);
}
*c = (Channel){
.elemsize = elemsize,
.bufsize = bufsize,
.nbuf = 0,
.buf = (uchar*)&c[1],
};
return c;
}
void
deletechan(Channel* c)
{
if (c == 0)
return;
free(c->name);
free(c->asend.a);
free(c->arecv.a);
free(c);
}
static void
addarray(Altarray* a, Alt* alt)
{
if (a->n == a->m) {
a->m += 16;
a->a = realloc(a->a, a->m * sizeof a->a[0]);
if (a->a == nil) {
exit(1);
}
}
a->a[a->n++] = alt;
}
static void
amove(void* dst, void* src, uint n)
{
if (dst) {
if (src == nil)
memset(dst, 0, n);
else
memmove(dst, src, n);
}
}
static void
delarray(Altarray* a, int i)
{
--a->n;
a->a[i] = a->a[a->n];
}
static enum chan_op
otherop(enum chan_op op)
{
return (CHANSND + CHANRCV) - op;
}
static Altarray*
chanarray(Channel* c, enum chan_op op)
{
switch (op) {
default:
return nil;
case CHANSND:
return &c->asend;
case CHANRCV:
return &c->arecv;
}
}
/*
* 3 players: sender, reciver, 'channel itself'
* if chann.empty
* send(send to reciver);
*/
static void
altcopy(Alt* s, Alt* r)
{
Channel* c;
uchar* cp;
if (s == nil && r == nil)
return;
assert(s != nil); /* sender must not nil */
c = s->c;
if (s->op == CHANRCV) {
Alt* t = s;
s = r;
r = t;
}
assert(s == nil || s->op == CHANSND); /* sender */
assert(r == nil || r->op == CHANRCV); /* reciver */
/* Channel is unbufferd */
if (s && r && c->nbuf == 0) {
amove(r->v, s->v, c->elemsize);
return;
}
if (r) {
/* recive buffred data first */
cp = c->buf + c->off * c->elemsize;
amove(r->v, cp, c->elemsize);
--c->nbuf;
if (++c->off == c->bufsize)
c->off = 0;
}
if (s) {
cp = c->buf + (c->off + c->nbuf) % c->bufsize * c->elemsize;
amove(cp, s->v, c->elemsize);
++c->nbuf;
}
}
static void
altdeque(Alt* a)
{
Altarray* ar;
ar = chanarray(a->c, a->op);
if (ar == nil) {
exit(1);
}
for (uint i = 0; i < ar->n; ++i) {
if (ar->a[i] == a) {
delarray(ar, i);
return;
}
}
/* can't find self in altdq */
exit(1);
}
static void
altqueue(Alt* a)
{
Altarray* ar;
ar = chanarray(a->c, a->op);
addarray(ar, a);
}
static void
altalldeque(Alt* a)
{
for (int i = 0; a[i].op != CHANEND && a[i].op != CHANNOBLK; i++) {
if (a[i].op != CHANNOP)
altdeque(&a[i]);
}
}
static void
altexec(Alt* a)
{
Altarray* ar;
Channel* c;
c = a->c;
ar = chanarray(c, otherop(a->op));
if (ar && ar->n) {
int i = rand() % ar->n; /* find any other palyer */
Alt* other = ar->a[i];
altcopy(a, other); /* copy data to other's buffer */
altalldeque(other->xalt); /* delete because now request is completed */
other->xalt[0].xalt = other;
taskready(other->task);
} else
altcopy(a, nil); /* if channel is unbufferd then do nothing */
}
static int
altcanexec(Alt* a)
{
Channel* c;
if (a->op == CHANNOP)
return 0;
c = a->c;
if (c->bufsize == 0) {
Altarray* ar = chanarray(c, otherop(a->op));
return ar && ar->n;
}
switch (a->op) {
default:
return 0;
case CHANSND:
return c->nbuf < c->bufsize;
case CHANRCV:
return c->nbuf > 0;
}
}
int
chanalt(Alt* a)
{
int i, j, ncan, n, canblock;
Task* t;
assertstack(512);
for (i = 0; a[i].op != CHANEND && a[i].op != CHANNOBLK; ++i)
;
n = i;
canblock = (a[i].op == CHANEND);
t = taskrunning;
for (i = 0; i < n; ++i) {
a[i].task = t;
a[i].xalt = a;
}
ncan = 0;
for (i = 0; i < n; i++) {
if (altcanexec(&a[i])) {
ncan++;
}
}
if (ncan) {
j = rand() % ncan;
for (i = 0; i < n; ++i) {
if (altcanexec(&a[i])) {
if (j-- == 0) {
altexec(&a[i]);
return i;
}
}
}
}
if (canblock == 0)
return -1;
for (i = 0; i < n; ++i) {
if (a[i].op != CHANNOP)
altqueue(&a[i]);
}
taskswitch();
return a[0].xalt - a; /* calculate offset */
}
static int
_chanop(Channel* c, int op, void* p, int canblock)
{
Alt ar[2];
ar[0] = (Alt){
.c = c,
.op = op,
.v = p,
};
ar[1] = (Alt){
.op = canblock ? CHANEND : CHANNOBLK,
};
if (chanalt(ar) < 0)
return -1;
return 1;
}
int
chansend(Channel* c, void* v)
{
return _chanop(c, CHANSND, v, 1);
}
int
chanbsend(Channel* c, void* v)
{
return _chanop(c, CHANSND, v, 0);
}
int
chanrecv(Channel* c, void* v)
{
return _chanop(c, CHANRCV, v, 1);
}
int
chanbrecv(Channel* c, void* v)
{
return _chanop(c, CHANSND, v, 0);
}
int
chansendp(Channel* c, void* v)
{
return _chanop(c, CHANSND, &v, 1);
}
void*
chanrecvp(Channel* c)
{
void* v;
_chanop(c, CHANRCV, &v, 1);
return v;
}
int
channbsendp(Channel* c, void* v)
{
return _chanop(c, CHANSND, (void*)&v, 0);
}
void*
channbrecvp(Channel* c)
{
void* v;
_chanop(c, CHANRCV, &v, 0);
return v;
}
int
chansendul(Channel* c, ulong v)
{
return _chanop(c, CHANSND, &v, 1);
}
ulong
chanrecvul(Channel* c)
{
ulong v;
_chanop(c, CHANRCV, &v, 1);
return v;
}
int
channbsendul(Channel* c, ulong v)
{
return _chanop(c, CHANSND, &v, 0);
}
int
channbrecvul(Channel* c, ulong v)
{
return _chanop(c, CHANRCV, &v, 0);
}
int
channbsend(Channel* c, void* v)
{
return _chanop(c, CHANSND, v, 0);
}
int
channbrecv(Channel* c, void* v)
{
return _chanop(c, CHANRCV, v, 0);
}

View File

@ -1,60 +0,0 @@
#include "taskimpl.h"
int
_qlock(QLock* l, int block)
{
if (l->owner == nil) {
l->owner = taskrunning;
return 1;
}
if (!block)
return 0;
addtask(&l->waiting, taskrunning);
taskswitch(); /* wait until own lock */
if (l->owner != taskrunning) {
/* TODO: */
exit(1);
}
return 1;
}
void
qlock(QLock* l)
{
_qlock(l, 1);
}
int
canqlock(QLock* l)
{
return _qlock(l, 0);
}
void
qunlock(QLock* l)
{
Task* ready;
if (l->owner == nil) {
/* TODO: */
exit(1);
}
l->owner = ready = l->waiting.head;
if (l->owner != nil) {
deltask(&l->waiting, ready);
taskready(ready);
}
}
QLock*
newqlock()
{
QLock* l;
l = malloc(sizeof *l);
if (l == nil)
exit(1);
l->owner = 0;
l->waiting = (Tasklist){ 0 };
return l;
}

View File

@ -1,54 +0,0 @@
#include "taskimpl.h"
void
tasksleep(Rendez* r)
{
addtask(&r->waiting, taskrunning);
if (r->l)
qunlock(r->l);
taskswitch();
if (r->l)
qlock(r->l);
}
static int
_taskwakeup(Rendez* r, int all)
{
int i;
Task* t;
for (i = 0;; ++i) {
if (i == 1 && !all)
break;
if ((t = r->waiting.head) == nil)
break;
deltask(&r->waiting, t);
taskready(t);
}
return i;
}
int
taskwakeup(Rendez* r)
{
return _taskwakeup(r, 0);
}
int
taskwakeupall(Rendez* r)
{
return _taskwakeup(r, 1);
}
Rendez*
newrendez(QLock* l)
{
Rendez* r;
r = malloc(sizeof *r);
if (r == nil)
exit(1);
r->l = l;
r->waiting = (Tasklist){ 0 };
return r;
}

253
co/task.c
View File

@ -1,253 +0,0 @@
#include "taskimpl.h"
#include <stdio.h>
int taskcount;
int taskidgen;
int tasknswitch;
int taskexitval;
Task* taskrunning;
ucontext_t taskschedcontext;
Tasklist taskrunqueue;
Task** alltask;
int nalltask;
static void
contextswitch(ucontext_t* from, ucontext_t* to);
void
assertstack(uint n);
static void
taskinfo(int s)
{
int i;
Task* t;
char* extra;
for (i = 0; i < nalltask; ++i) {
t = alltask[i];
if (t == taskrunning)
extra = " (running)";
else if (t->ready)
extra = " (ready)";
else
extra = "";
printf("%s\n", extra);
}
}
static void
taskstart(uint x, uint y)
{
Task* t;
ulong z;
z = x << 16;
z <<= 16;
z |= y;
t = (Task*)z;
t->startfn(t->startarg);
taskexit(0);
}
static Task*
taskalloc(void (*fn)(void*), void* arg, uint stk)
{
Task* t;
sigset_t zero;
uint x, y;
ulong z;
if ((t = malloc(sizeof *t + stk)) == nil) {
exit(1);
}
*t = (Task){
.stk = (uchar*)(&t[1]),
.stksize = stk,
.id = ++taskidgen,
.startfn = fn,
.startarg = arg,
};
sigemptyset(&zero);
sigprocmask(SIG_BLOCK, &zero, &t->uc.uc_sigmask);
if (getcontext(&t->uc)) {
exit(1);
}
t->uc.uc_stack.ss_sp = t->stk + 8;
t->uc.uc_stack.ss_size = t->stksize - 64;
z = (ulong)t;
y = z;
z >>= 16;
x = z >> 16;
makecontext(&t->uc, (void (*)())taskstart, 2, x, y);
return t;
}
int
taskcreate(void (*fn)(void*), void* arg, uint stk)
{
int id;
Task* t;
t = taskalloc(fn, arg, stk);
taskcount++;
id = t->id;
if (nalltask % 64 == 0) {
alltask = realloc(alltask, (nalltask + 64) * sizeof(alltask[0]));
if (alltask == 0) {
exit(1);
}
}
t->alltaskslot = nalltask;
alltask[nalltask++] = t;
taskready(t);
return id;
}
void
taskready(Task* t)
{
t->ready = 1;
addtask(&taskrunqueue, t);
}
void
taskswitch(void)
{
assertstack(0);
contextswitch(&taskrunning->uc, &taskschedcontext);
}
int
taskyield(void)
{
int n;
n = tasknswitch;
taskready(taskrunning);
taskswitch();
return tasknswitch - n - 1;
}
void
taskexit(int val)
{
taskexitval = val;
taskrunning->exiting = 1;
taskswitch();
}
void
taskexitall(int val)
{
exit(val);
}
void
deltask(Tasklist* l, Task* t)
{
if (t->prev)
t->prev->next = t->next;
else
l->head = t->next;
if (t->next)
t->next->prev = t->prev;
else
l->tail = t->prev;
}
void
addtask(Tasklist* l, Task* t)
{
if (l->tail) {
l->tail->next = t;
t->prev = l->tail;
} else {
l->head = t;
t->prev = nil;
}
l->tail = t;
t->next = nil;
}
static void
contextswitch(ucontext_t* from, ucontext_t* to)
{
if (swapcontext(from, to) < 0) {
printf("swapcontext is fail\n");
exit(1);
}
}
void
assertstack(uint n)
{
Task* t;
t = taskrunning;
if ((uchar*)&t <= (uchar*)t->stk || (uchar*)&t - (uchar*)t->stk < 256 + n) {
/* satck over flow */
exit(1);
}
}
static void
taskscheduler(void)
{
int i;
Task* t;
for (;;) {
if (taskcount == 0)
exit(taskexitval);
t = taskrunqueue.head;
if (t == nil) {
/* nothing to do */
exit(1);
}
deltask(&taskrunqueue, t); /* delete from runqueue */
t->ready = 0;
taskrunning = t;
tasknswitch++;
contextswitch(&taskschedcontext, &t->uc);
taskrunning = nil; /* ready for next task */
if (t->exiting) {
taskcount--;
i = t->alltaskslot;
alltask[i] = alltask[--nalltask];
alltask[i]->alltaskslot = i;
free(t);
}
}
}
static char* argv0;
static int taskargc;
static char** taskargv;
int mainstacksize;
static void
taskmainstart(void* v)
{
taskmain(taskargc, taskargv);
}
int
main(int argc, char** argv)
{
struct sigaction sa, osa;
memset(&sa, 0, sizeof(sigaction));
sa.sa_handler = taskinfo;
sa.sa_flags = SA_RESTART;
sigaction(SIGQUIT, &sa, &osa);
/*sigaction(SIGINFO, &sa, &osa);*/
argv0 = argv[0];
taskargc = argc;
taskargv = argv;
mainstacksize = 256 * 1024;
taskcreate(taskmainstart, nil, mainstacksize);
taskscheduler();
exit(0);
}

138
co/task.h
View File

@ -1,138 +0,0 @@
#ifndef _TASK_H_
#define _TASK_H_
typedef struct Task Task;
typedef struct Tasklist Tasklist;
struct Tasklist
{
Task* head;
Task* tail;
};
typedef struct QLock
{
Task* owner;
Tasklist waiting;
} QLock;
void
taskmain(int argc, char** argv);
int
taskyield(void);
void
taskexitall(int val);
int
taskcreate(void (*fn)(void*), void* arg, unsigned int stk);
void
taskready(Task* t);
void
taskexit(int val);
void
taskswitch(void);
void
assertstack(unsigned int n);
void
qlock(QLock*);
int
canqlock(QLock*);
void
qunlock(QLock*);
QLock*
newqlock();
typedef struct Rendez Rendez;
struct Rendez
{
QLock* l;
Tasklist waiting;
};
void
tasksleep(Rendez*);
int
taskwakeup(Rendez*);
int
taskwakeupall(Rendez*);
Rendez*
newrendez(QLock* l);
extern Task* taskrunning;
extern int taskcount;
typedef struct Alt Alt;
typedef struct Altarray Altarray;
typedef struct Channel Channel;
enum chan_op
{
CHANEND,
CHANSND,
CHANRCV,
CHANNOP,
CHANNOBLK,
};
struct Alt
{
Channel* c;
void* v;
enum chan_op op;
Task* task;
Alt* xalt;
};
struct Altarray
{
Alt** a;
unsigned int n;
unsigned int m;
};
struct Channel
{
unsigned int bufsize;
unsigned int elemsize;
unsigned char* buf;
unsigned int nbuf;
unsigned int off;
Altarray asend;
Altarray arecv;
char* name;
};
Channel*
newchan(int elemsize, int bufsize);
void
deletechan(Channel* c);
int
chansend(Channel* c, void* v);
int
chanbsend(Channel* c, void* v);
int
chanrecv(Channel* c, void* v);
int
chanbrecv(Channel* c, void* v);
int
chansendp(Channel* c, void* v);
void*
chanrecvp(Channel* c);
int
channbsend(Channel* c, void* v);
int
channbsendp(Channel* c, void* v);
void*
channbrecvp(Channel* c);
int
channbrecv(Channel* c, void* v);
int
chansendul(Channel* c, unsigned long v);
unsigned long
chanrecvul(Channel* c);
int
channbsendul(Channel* c, unsigned long v);
int
channbrecvul(Channel* c, unsigned long v);
#endif

View File

@ -1,44 +0,0 @@
#include "task.h"
#include <assert.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <ucontext.h>
#define nil ((void*)0)
#define nelem(x) (sizeof(x) / sizeof((x)[0]))
typedef unsigned long ulong;
typedef unsigned int uint;
typedef unsigned char uchar;
typedef unsigned long long uvlong;
typedef long long vlong;
enum
{
STACK = 8192,
};
struct Task
{
Task* next;
Task* prev;
Task* allnext;
Task* allprev;
ucontext_t uc;
uvlong alarmtime;
uint id;
uchar* stk;
uint stksize;
int exiting;
int alltaskslot;
int ready;
void (*startfn)(void*);
void* startarg;
};
void
deltask(Tasklist* l, Task* t);
void
addtask(Tasklist* l, Task* t);

7
cpu.c
View File

@ -1,5 +1,5 @@
#include "gb.h"
#include <stdio.h>
#include "print.h"
#define BC() ((u16)r[rB] << 8 | r[rC])
#define DE() ((u16)r[rD] << 8 | r[rE])
@ -30,10 +30,13 @@ u8 r[8], ime;
u16 pc, sp, curpc;
int halt;
Var cpuvars[] = {ARR(r), VAR(ime), VAR(pc), VAR(curpc), VAR(sp), VAR(halt),
{nil, 0, 0}};
void
state(void)
{
printf("A:%02X F:%02X B:%02X C:%02X D:%02X E:%02X H:%02X L:%02X SP:%04X "
print("A:%02X F:%02X B:%02X C:%02X D:%02X E:%02X H:%02X L:%02X SP:%04X "
"PC:%04X PCMEM:%02X,%02X,%02X,%02X\n",
r[rA],
r[rF],

19
error.c
View File

@ -1,30 +1,29 @@
#include "gb.h"
#include <stdarg.h>
#include <stdio.h>
#include "print.h"
#include <stdlib.h>
#include <string.h>
void
panic(const char* fmt, ...)
panic(char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "panic:");
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
fprint(2, "panic:");
vfprint(2, fmt, ap);
fprint(2, "\n");
exit(1);
}
void
error(const char* fmt, ...)
error(char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "panic:");
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
fprint(2, "panic:");
vfprint(2, fmt, ap);
fprint(2, "\n");
}
void*

10
eui.c
View File

@ -9,7 +9,7 @@ u8* pic;
SDL_Renderer* renderer;
SDL_Texture* bitmapTex;
static void
void
render()
{
SDL_UpdateTexture(bitmapTex, nil, pic, 160 * sizeof(u32));
@ -29,6 +29,8 @@ joypadevent(void*_)
case SDL_KEYUP: keys = 0; break;
case SDL_KEYDOWN:
switch (evt.key.keysym.scancode) {
case SDL_SCANCODE_F1: savereq = 1; break;
case SDL_SCANCODE_F2: loadreq = 1; break;
case SDL_SCANCODE_X: keys = GB_KEY_A; break;
case SDL_SCANCODE_Z: keys = GB_KEY_B; break;
case SDL_SCANCODE_UP: keys = GB_KEY_UP; break;
@ -45,12 +47,6 @@ joypadevent(void*_)
return;
}
void
flush()
{
render();
}
void
initwindow(int scale)
{

3
ev.c
View File

@ -1,8 +1,11 @@
#include "gb.h"
Event evhblank, evjoypad;
Event *events[NEVENT] = {&evhblank, &evjoypad};
Event* elist;
Var evvars[] = {{nil, 0, 0}};
void
addevent(Event* ev, int time)
{

146
gb.c
View File

@ -1,40 +1,95 @@
#include "gb.h"
#include "co/task.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
int cpuhalt;
int backup;
int savereq, loadreq;
int savefd = -1;
u64 clock;
u8 mbc, feat, mode;
int saveframes;
const char *romname;
u8 mbc, mode;
void
writeback(void)
{
if(saveframes == 0)
saveframes = 15;
}
void
flushback(void)
{
if(savefd >= 0)
pwrite(savefd, back, nback, 0);
saveframes = 0;
}
static void
_flushback(void)
{
flushback();
close(savefd);
}
static void
loadsave(const char *file)
{
u8 *buf, *p;
buf = xalloc(strlen(file) + 4);
strcpy(buf, file);
p = strrchr(buf, '.');
if(p == nil)
p = buf + strlen(buf);
strcpy(p, ".sav");
savefd = open(buf, O_RDWR);
if(savefd == -1){
savefd = open(buf, O_RDWR|O_CREAT, 0664);
if(savefd == -1)
error("Can't load save file '%s'", file);
back = xalloc(nback);
if(write(savefd, back, nback)!= nback)
error("Can't Write %d byte to savefile", nback);
atexit(_flushback);
free(buf);
return;
}
back = xalloc(nback);
if(read(savefd, back, nback) != nback)
error("savefile size is not matched\n");
atexit(_flushback);
free(buf);
}
static void
loadrom(const char* file)
{
FILE* f;
int rc;
int feat;
int fd;
long sz;
static u8 mbctab[31] = { 0, 1, 1, 1, -1, 2, 2, -1, 0, 0, -1, 6, 6, 6, -1, 3,
3, 3, 3, 3, -1, 4, 4, 4, -1, 5, 5, 5, 5, 5, 5 };
static u8 feattab[31] = {
0, 0, FEATRAM, FEATRAM|FEATBAT, 0, FEATRAM, FEATRAM|FEATBAT, 0,
FEATRAM, FEATRAM|FEATBAT, 0, 0, FEATRAM, FEATRAM|FEATBAT, 0, FEATTIM|FEATBAT,
FEATTIM|FEATRAM|FEATBAT, 0, FEATRAM, FEATRAM|FEATBAT, 0, 0, FEATRAM, FEATRAM|FEATBAT,
0, 0, FEATRAM, FEATRAM|FEATBAT, 0, FEATRAM, FEATRAM|FEATBAT,
};
static u8 feattab[31] = {
0, 0, FEATRAM, FEATRAM|FEATBAT, 0, FEATRAM, FEATRAM|FEATBAT, 0,
FEATRAM, FEATRAM|FEATBAT, 0, 0, FEATRAM, FEATRAM|FEATBAT, 0, FEATTIM|FEATBAT,
FEATTIM|FEATRAM|FEATBAT, 0, FEATRAM, FEATRAM|FEATBAT, 0, 0, FEATRAM, FEATRAM|FEATBAT,
0, 0, FEATRAM, FEATRAM|FEATBAT, 0, FEATRAM, FEATRAM|FEATBAT,
};
f = fopen(file, "r");
if (f == nil)
fd = open(file, O_RDONLY);
if (fd == nil)
panic("can't open %s", file);
fseek(f, 0, SEEK_END);
sz = ftell(f);
if (sz < 0 || sz > 32 * 1024 * 1024)
panic("bad size %d", sz);
fseek(f, 0, SEEK_SET);
sz = lseek(fd, 0, SEEK_END);
if(sz <= 0 || sz > 32*1024*1024)
panic("invalid file size %d", sz);
lseek(fd, 0, SEEK_SET);
nrom = sz;
rom = xalloc(nrom);
if (fread(rom, 1, nrom, f) != nrom)
panic("siz is different %z", nrom);
fclose(f);
if((rc = read(fd, rom, nrom)) != nrom)
panic("rom size is not matched %d\n", rc);
close(fd);
if (rom[0x147] > 0x1F)
panic("bad cartidge type %d\n", rom[0x147]);
mbc = mbctab[rom[0x147]];
@ -48,7 +103,6 @@ loadrom(const char* file)
default: panic("Unkown Ram size %d\n", rom[0x149]);
}
}
back = xalloc(nback);
if(nback == 0)
nbackbank = 1;
else
@ -58,18 +112,40 @@ loadrom(const char* file)
break;
default: panic("unsupported mbc %d", mbc);
}
if ((rom[0x143] & 0x80) != 0 && (mode & FORCEDMG) == 0)
mode = CGB | COL;
if (rom[0x143] & 0x80 != 0)
panic("unsupported color");
if((feat & FEATBAT) != 0)
loadsave(file);
}
void
flush()
{
static char *savestatename;
if (savestatename == 0){
int len = strlen(romname);
savestatename = xalloc(len + 4);
strncpy(savestatename, romname, len);
strcpy(savestatename + len, "-state.save");
}
render();
if(saveframes > 0 && -- saveframes == 0)
flushback();
if(savereq){
savestate(savestatename);
savereq = 0;
}
if(loadreq){
loadstate(savestatename);
loadreq = 0;
}
}
static void
colinit(void)
{
union
{
u8 c[4];
u32 l;
} c;
union {u8 c[4]; u32 l;}c;
c.c[3] = 0;
for (int i = 0; i < 4; i++) {
@ -78,16 +154,16 @@ colinit(void)
}
}
void
taskmain(int argc, char* argv[])
int
main(int argc, char* argv[])
{
loadrom(romname = argv[1]);
colinit();
loadrom(argv[1]);
initwindow(5);
initevent();
meminit();
reset();
taskcreate(pputask, 0, 32768);
ppuinit();
for (;;) {
int t = step();

53
gb.h
View File

@ -1,5 +1,9 @@
#include <stdint.h>
#define nelem(x) (sizeof(x)/sizeof(x[0]))
#define VAR(a) {&a, sizeof(a), 1}
#define ARR(a) {a, sizeof(*a), nelem(a)}
#define nil ((void*)0)
#define JOYPAD_CYCLE (30)
@ -128,6 +132,14 @@ enum
GB_KEY_START = 0x80
};
enum { NEVENT = 2 };
enum
{
REG_RIP = 7,
REG_RSP = 6,
};
typedef uint8_t u8;
typedef uint16_t u16;
typedef int8_t i8;
@ -136,6 +148,7 @@ typedef int32_t i32;
typedef uint64_t u64;
typedef int64_t i64;
typedef struct Event Event;
typedef struct Var Var;
struct Event
{
@ -145,6 +158,15 @@ struct Event
void* aux;
};
struct Var
{
void *a;
int s, n;
};
#define VAR(a) {&a, sizeof(a), 1}
#define ARR(a) {a, sizeof(*a), nelem(a)}
extern u8 *rom, *back, reg[256], oam[256];
extern u8 vram[16384];
extern int nrom, nback, nbackbank;
@ -152,17 +174,18 @@ extern u32 pal[64];
extern u8 dma;
extern u32 divclock;
extern u64 clock;
extern u8 ppuy, ppustate, ppuw;
extern u8 ppuy, ppustate;
extern u8 keys;
extern int prish;
extern u8 mode;
extern u8 mbc, feat;
extern u8 mbc;
extern Event* elist;
extern Event evhblank, evjoypad;
extern u32 moncols[4];
extern u32 white;
extern u8* pic;
extern int (*mapper)(int, int);
extern Event *events[NEVENT];
extern int savereq, loadreq;
/* joypad */
void
@ -192,7 +215,7 @@ hblanktick(void*);
void
ppusync(void);
void
pputask(void*);
ppuinit(void);
/* cpu */
int
@ -202,14 +225,28 @@ reset(void);
/* graphic */
void
flush();
void
initwindow(int scale);
void
render();
/* save */
void
putvars(Var *v);
void
getvars(Var *v);
void
flush();
int
savestate(const char *fname);
int
loadstate(const char *fname);
void
writeback(void);
/* error */
void
error(const char*, ...);
error(char*, ...);
void
panic(const char*, ...);
panic(char*, ...);
void*
xalloc(long);

30
jmp.S Normal file
View File

@ -0,0 +1,30 @@
.global psetjmp
.type psetjmp,@function
psetjmp:
mov %rbx,(%rdi) /* rdi is jmp_buf, move registers onto it */
mov %rbp,8(%rdi)
mov %r12,16(%rdi)
mov %r13,24(%rdi)
mov %r14,32(%rdi)
mov %r15,40(%rdi)
lea 8(%rsp),%rdx /* this is our rsp WITHOUT current ret addr */
mov %rdx,48(%rdi)
mov (%rsp),%rdx /* save return addr ptr for new rip */
mov %rdx,56(%rdi)
xor %eax,%eax /* always return 0 */
ret
.global plongjmp
.type plongjmp,@function
plongjmp:
xor %eax,%eax
cmp $1,%esi /* CF = val ? 0 : 1 */
adc %esi,%eax /* eax = val + !val */
mov (%rdi),%rbx /* rdi is the jmp_buf, restore regs from it */
mov 8(%rdi),%rbp
mov 16(%rdi),%r12
mov 24(%rdi),%r13
mov 32(%rdi),%r14
mov 40(%rdi),%r15
mov 48(%rdi),%rsp
jmp *56(%rdi)

31
mem.c
View File

@ -9,17 +9,18 @@ u8* rom;
u8 *romb, *vramb, *wramb, *eramb;
u8 wram[32768], vram[16384], oam[256], reg[256];
u8* back;
u8 palm[128];
u32 pal[64];
int nrom, nback, nbackbank;
u64 clock;
u32 divclock;
int prish;
u8 dma;
u32 moncols[4];
u32 white;
int (*mappers[])(int, int) = { mbc0, mbc1 };
int (*mapper)(int, int);
Var memvars[] = {ARR(wram), ARR(vram), ARR(oam), ARR(reg), VAR(clock), VAR(divclock), {nil, 0, 0}};
static u8
regread(u16 a)
{
@ -70,7 +71,6 @@ regwrite(u16 a, u8 v)
ppusync();
if ((~v & reg[a] & LCDEN) != 0) {
ppuy = 0;
ppuw = 0;
ppustate = 0;
delevent(&evhblank);
}
@ -159,6 +159,7 @@ memwrite(u16 a, u8 v)
eramb[a - 0xa000] = v;
else
mapper(a, v);
writeback();
return;
case 12: case 14:
wram[a & 0xFFF] = v;
@ -198,29 +199,29 @@ static int
mbc1(int a, int v)
{
static u8 ramen, b0, b1, romram;
static Var mbc1vars[] = {VAR(ramen), VAR(b0), VAR(b1), VAR(romram),
{nil, 0,0}};
u16 b;
if (a < 0) {
switch(a) {
case INIT: return 0;
case SAVE:
case RSTR:
break;
case READ:
return -1;
case SAVE: putvars(mbc1vars); break;
case RSTR: getvars(mbc1vars); break;
case READ: return -1;
default: panic("MBC1 does not have function of %d", a);
}
}
switch (a >> 13) {
case 0: ramen = (v & 0xF) == 0xA; break;
case 1: v &= 0x1F; b0 = v != 0 ? v : 1; break;
case 2: b1 = v & 3; b1 % nbackbank; break;
case 2: b1 = v & 3; b1 %= nbackbank; break;
case 3: romram = v & 1; break;
}
b = b0;
if (romram == 0)
b |= b1 << 5;
b %= nrom >> 14; /* 32KB => 2bank */
b %= nrom >> 14;
romb = rom + (b << 14);
if (ramen) {
if (romram)
@ -234,13 +235,9 @@ mbc1(int a, int v)
}
void
meminit(void)
meminit()
{
union
{
u8 c[4];
u32 l;
} c;
union {u8 c[4]; u32 l;}c;
c.c[0] = c.c[1] = c.c[2] = 0;
c.c[3] = 1;
@ -248,14 +245,12 @@ meminit(void)
c.l >>= 1;
c.c[0] = c.c[1] = c.c[2] = 0xff;
c.c[3] = 0;
white = c.l;
romb = rom + 0x4000;
wramb = wram + 0x1000;
vramb = vram;
mapper = mappers[mbc];
mapper(INIT, 0);
reg[LCDC] = 0x91;
reg[IF] = 0xE0;
reg[SVBK] = 0xff;

52
ppu.c
View File

@ -1,15 +1,17 @@
#include "co/task.h"
#include "gb.h"
#include <string.h>
#define ryield() {if(psetjmp(renderjmp) == 0) plongjmp(mainjmp, 1);}
#define myield() {if(psetjmp(mainjmp) == 0) plongjmp(renderjmp, 1);}
#define eat(nc) \
if (cyc <= nc) { \
for (i = 0; i < nc; i++) \
if (--cyc == 0) \
taskyield(); \
ryield(); \
} else \
cyc -= nc;
typedef void *jmp_buf[10];
typedef struct sprite sprite;
struct sprite
{
@ -31,19 +33,28 @@ enum
TILSPR = 0x04,
};
u8 ppustate, ppuy, ppuw;
jmp_buf mainjmp,renderjmp;
u8 ppustate, ppuy;
u64 hblclock, rendclock;
static int cyc, ppux, ppux0;
sprite spr[10], *sprm;
void
pputask(void* _)
Var ppuvars[] = {VAR(ppustate), VAR(ppuy), VAR(hblclock), VAR(rendclock),
{nil, 0, 0}};
extern int psetjmp(jmp_buf buf);
extern void plongjmp(jmp_buf buf, int v);
static void
ppurender(void)
{
int x, y, i, n, m, win;
u16 ta, ca, chr;
u8 tile;
u32 sr[8], *picp;
ryield();
for (;;) {
eat(6 * 2);
m = 168 + (reg[SCX] & 7);
@ -79,7 +90,7 @@ pputask(void* _)
if (cyc <= 2 * 8) {
for (i = 0; i < 2 * 8; ++i)
if (--cyc == 0)
taskyield();
ryield();
y = ppuy + reg[SCY] << 1 & 14;
ta = 0x1800 | reg[LCDC] << 7 & 0x400 | ppuy + reg[SCY] << 2 & 0x3E0 |
ta & 0x1F;
@ -99,13 +110,13 @@ pputask(void* _)
} while (m > 8);
if (win == -1) {
win = 1;
ta = 0x1800 | reg[LCDC] << 4 & 0x400 | ppuw - reg[WY] << 2 & 0x3E0;
y = ppuw - reg[WY] << 1 & 14;
cyc += 2;
ta = 0x1800 | reg[LCDC] << 4 & 0x400 | ppuy - reg[WY] << 2 & 0x3E0;
y = ppuy - reg[WY] << 1 & 14;
cyc += 12;
m = 175 - reg[WX];
goto restart;
}
taskyield();
ryield();
}
}
@ -182,11 +193,11 @@ sprites(void)
for (; x < x1; ++x) {
attr = picp[x] >> prish;
chr = q->chr;
if ((chr & ((q->t & SPRXFL) != 0 ? 0x0101 : 0x8080)) != 0 &&
if((chr & ((q->t & SPRXFL) != 0 ? 0x0101 : 0x8080)) != 0 &&
(attr & TILSPR) == 0 &&
((mode & COL) != 0 && (reg[LCDC] & BGPRI) == 0 ||
(attr & TILCOL0) != 0 ||
(attr & TILPRI) == 0 && (q->t & SPRPRI) == 0)) {
(attr & TILPRI) == 0 && (q->t & SPRPRI) == 0)){
if ((q->t & SPRXFL) == 0)
picp[x] = pal[q->pal | chr >> 15 | chr >> 6 & 2] | TILSPR << prish;
else
@ -208,7 +219,7 @@ ppusync(void)
return;
cyc = clock - rendclock;
if (cyc != 0)
taskyield();
myield();
sprites();
rendclock = clock;
}
@ -234,8 +245,6 @@ hblanktick(void* _)
switch (ppustate) {
case 0:
hblclock = clock + evhblank.time;
if (reg[WX] <= 166 && reg[WY] <= 143)
ppuw++;
if (++ppuy == 144) {
ppustate = 1;
if (reg[STAT] & IRQM1)
@ -254,11 +263,8 @@ hblanktick(void* _)
break;
case 1:
hblclock = clock + evhblank.time;
if (reg[WX] <= 166 && reg[WY] <= 143)
ppuw++;
if (++ppuy == 154) {
ppuy = 0;
ppuw = 0;
ppustate = 2;
if (reg[STAT] & IRQM2)
reg[IF] |= IRQLCDS;
@ -284,3 +290,13 @@ hblanktick(void* _)
break;
}
}
void
ppuinit(void)
{
static u8 stk[8192];
renderjmp[REG_RSP] = stk + sizeof(stk) - 64;
renderjmp[REG_RIP] = ppurender;
myield();
}

222
print.c Normal file
View File

@ -0,0 +1,222 @@
#include "print.h"
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
enum
{
FlagLong = 1<<0,
FlagLongLong = 1<<1,
FlagUnsigned = 1<<2,
};
static char*
printstr(char *dst, char *edst, char *s, int sz)
{
int l, n, isneg;
isneg = 0;
if(sz < 0){
sz = -sz;
isneg = 1;
}
if(dst >= edst)
return dst;
n = l = strlen(s);
if(n < sz)
n = sz;
if(n >= edst - dst)
n = (edst - dst) - 1;
if(l > n)
l = n;
if(isneg){
memmove(dst, s, l);
if(n - l)
memset(dst + l, ' ', n - l);
}else{
if(n - l)
memset(dst, ' ', n - l);
memmove(dst + n - l, s, l);
}
return dst + n;
}
char*
vseprint(char *dst, char *edst, char *fmt, va_list arg)
{
int fl, sz, sign, base;
char *p, *w;
char cbuf[2];
w = dst;
for(p = fmt; *p && w < edst - 1; p++){
switch(*p){
default:
*w++ = *p;
break;
case '%':
sign = 1;
fl = sz = 0;
for(p++; *p; p++){
switch(*p){
case '-': sign = -1; break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
sz = sz * 10 + *p - '0';
break;
case 'l': if(fl & FlagLong)fl |= FlagLongLong;break;
case 'u': fl |= FlagUnsigned; break;
case 'i': case 'd':
base = 10; goto num;
case 'o': base = 8; goto num;
case 'p': case 'x': case 'X':
base = 16;
goto num;
num:
{
static char digits[] = "0123456789abcdef";
char buf[30], *p;
int neg, zero;
unsigned long long luv;
if(fl & FlagLongLong){
if(fl & FlagUnsigned)
luv = va_arg(arg, unsigned long long);
else
luv = va_arg(arg, long long);
}else{
if(fl & FlagLong){
if(fl & FlagUnsigned)
luv = va_arg(arg, unsigned long);
else
luv = va_arg(arg, long);
}else{
if(fl & FlagUnsigned)
luv = va_arg(arg, unsigned int);
else
luv = va_arg(arg, int);
}
}
p = buf + sizeof(buf);
neg = zero = 0;
if((fl & FlagUnsigned) == 0 && (long long)luv < 0){
neg = 1;
luv = -luv;
}
if(luv == 0)
zero = 1;
*--p = 0;
while(luv){
*--p = digits[luv % base];
luv /= base;
}
if(base == 16){
*--p = 'x';
*--p = '0';
}
if(base == 8 || zero)
*--p = '0';
w = printstr(w, edst,p, sz * sign);
goto break2;
}
case 'c':
cbuf[0] = va_arg(arg, int);
cbuf[1] = 0;
w = printstr(w, edst, cbuf, sz * sign);
goto break2;
case 's':
w = printstr(w, edst, va_arg(arg, char*), sz*sign);
goto break2;
case 'r':
w = printstr(w, edst, strerror(errno), sz*sign);
goto break2;
default:
p = "error";
goto break2;
}
}
break2:
break;
}
}
assert(w < edst);
*w = 0;
return dst;
}
char*
vsnprint(char *dst, unsigned int n, char *fmt, va_list arg)
{
return vseprint(dst, dst + n, fmt, arg);
}
char*
snprint(char *dst, unsigned int n, char *fmt, ...)
{
va_list arg;
va_start(arg, fmt);
vsnprint(dst, n, fmt, arg);
va_end(arg);
return dst;
}
char*
seprint(char* dst, char* edst, char* fmt, ...)
{
va_list arg;
va_start(arg, fmt);
vseprint(dst, edst, fmt, arg);
va_end(arg);
return dst;
}
int
vfprint(int fd, char *fmt, va_list arg)
{
char buf[1024];
vseprint(buf, buf + sizeof buf, fmt, arg);
return write(fd, buf, strlen(buf));
}
int
vprint(char *fmt, va_list arg)
{
return vfprint(1, fmt, arg);
}
int
fprint(int fd, char *fmt, ...)
{
int n;
va_list arg;
va_start(arg, fmt);
n = vfprint(fd, fmt, arg);
va_end(arg);
return n;
}
int
print(char *fmt, ...)
{
int n;
va_list arg;
va_start(arg, fmt);
n = vprint(fmt, arg);
va_end(arg);
return n;
}
char*
strecpy(char *dst, char *edst, char *src)
{
*printstr(dst, edst, src, 0) = 0;
return dst;
}

11
print.h Normal file
View File

@ -0,0 +1,11 @@
#include <stdarg.h>
char* vseprint(char *dst, char *edst, char *fmt, va_list arg);
char* vsnprint(char *dst, unsigned int n, char *fmt, va_list arg);
char* snprint(char *dst, unsigned int n, char *fmt, ...);
char* seprint(char* dst, char* edst, char* fmt, ...);
int vfprint(int fd, char *fmt, va_list arg);
int vprint(char *fmt, va_list arg);
int fprint(int fd, char *fmt, ...);
int print(char *fmt, ...);
char* strecpy(char *dst, char *edst, char *src);

163
save.c Normal file
View File

@ -0,0 +1,163 @@
#include "gb.h"
#include <stdio.h>
extern Var cpuvars[], ppuvars[], memvars[], evvars[];
static FILE *fp;
void
putevents(void)
{
u8 i, j;
Event *e;
for(i = 0; i < NEVENT; ++i)
if(elist == events[i]) /* find head */
break;
if(i == NEVENT && elist != nil)
error("Unkown event in chain [%p]", elist);
fputc(i, fp);
for(i = 0; i < NEVENT; ++i){
e = events[i];
fputc(e->time & 0x000000ff, fp);
fputc(e->time & 0x0000ff00, fp);
fputc(e->time & 0x00ff0000, fp);
fputc(e->time & 0xff000000, fp);
for(j = 0; j < NEVENT; ++j)
if(e->next == events[j]) /* find next */
break;
if(j == NEVENT && e->next != nil)
error("Unkown event in chain [%p]", e->next);
fputc(j, fp);
}
}
void
putvars(Var *v)
{
int n;
u16 *p;
u32 *q;
for(; v->a != nil; v++){
switch(v->s){
case 1:
fwrite(v->a, 1, v->n, fp);
break;
case 2:
n = v->n;
p = v->a;
while(n--){
fputc(*p & 0xff, fp);
fputc(*p++ >> 8, fp);
}
break;
case 4:
n = v->n;
q = v->a;
while(n--){
fputc(*q, fp);
fputc(*q >> 8, fp);
fputc(*q >> 16, fp);
fputc(*q >> 24, fp);
}
break;
}
}
}
void
getvars(Var *v)
{
int n;
u16 *p, w;
u32 *q, l;
for(; v->a != nil; v++){
switch(v->s){
case 1: fread(v->a, v->n, 1, fp); break;
case 2:
n = v->n;
p = v->a;
while(n--){
w = fgetc(fp);
w |= fgetc(fp) << 8;
*p++ = w;
}
break;
case 4:
n = v->n;
q = v->a;
while(n--){
l = fgetc(fp);
l |= fgetc(fp) << 8;
l |= fgetc(fp) << 16;
l |= fgetc(fp) << 24;
*q++ = l;
}
break;
}
}
}
int
savestate(const char *fname)
{
fp = fopen(fname, "w");
if(fp == 0){
error("Can't open '%s'", fname);
return -1;
}
putvars(cpuvars);
putvars(ppuvars);
putvars(memvars);
putvars(evvars);
putevents();
mapper(SAVE, 0);
fclose(fp);
fp = 0;
return 0;
}
static void
getevents(void)
{
int i, j;
Event *e;
i = fgetc(fp);
if(i >= NEVENT)
elist = nil;
else
elist = events[i];
for(i = 0; i < NEVENT; ++i){
e = events[i];
e->time = fgetc(fp);
e->time |= fgetc(fp) << 8;
e->time |= fgetc(fp) << 16;
e->time |= fgetc(fp) << 24;
j = fgetc(fp);
if(j >= NEVENT)
e->next = nil;
else
e->next = events[j];
}
}
int
loadstate(const char *fname)
{
fp = fopen(fname, "r");
if(fp == 0){
error("Can't load '%s'", fname);
return -1;
}
getvars(cpuvars);
getvars(ppuvars);
getvars(memvars);
getvars(evvars);
getevents();
mapper(RSTR, 0);
fclose(fp);
fp = 0;
return 0;
}