Add emscripten, shortcuts

Added:
* New palettes.
* Ghosting (reduce flickering).
* Emscripten.
* Const update rate 60.
* Drag & drop.
* Gamepad support.
* Keyboard Shortcuts: reset, palette, window size, ghosting, mute audio.

Fixes:
* Screen X offset (from MAME). Example: 'Bubble World'.
* Revert cycles (there was a problem with the pause in 'Hero Kid').
* Loading a rom after loaded 32K rom.
* Save/load savestate (new extension is '.svst').
This commit is contained in:
infvaL 2018-10-08 00:39:15 +03:00
parent b9ba068a26
commit 932d57a895
29 changed files with 1835 additions and 347 deletions

View File

@ -23,7 +23,7 @@ void controls_state_write(uint8 type, uint8 data)
return;
else
controls_state = 0;
if (type)
controls_state |= data;
else

View File

@ -8,19 +8,64 @@
#ifdef NDS
#include <nds.h>
#endif
#ifdef _ODSDL_
#include "../platform/opendingux/shared.h"
#endif
static uint16 *supervision_palette;
static uint8 gpu_regs[4];
#ifdef NDS
#define RGB555(R,G,B) ((((int)(B))<<10)|(((int)(G))<<5)|(((int)(R)))|(1<<15))
#else
#define RGB555(R,G,B) ((((int)(B))<<10)|(((int)(G))<<5)|(((int)(R))))
#endif
const static uint8 palettes[COLOUR_SCHEME_COUNT][12] = {
{
252, 252, 252,
168, 168, 168,
84, 84, 84,
0, 0, 0,
},
{
252, 154, 0,
168, 102, 0,
84, 51, 0,
0, 0, 0,
},
{
50, 227, 50,
34, 151, 34,
17, 76, 17,
0, 0, 0,
},
{
0, 154, 252,
0, 102, 168,
0, 51, 84,
0, 0, 0,
},
{
224, 248, 208,
136, 192, 112,
52, 104, 86,
8, 24, 32,
},
{
0x70, 0xb0, 0x78,
0x48, 0x98, 0x90,
0x24, 0x58, 0x60,
0x08, 0x24, 0x30,
},
};
static uint16 *supervision_palette;
#define SB_MAX (GHOSTING_MAX + 1)
static int ghostCount = 0;
static uint8 *screenBuffers[SB_MAX];
static uint8 screenBufferStartX[SB_MAX];
static void add_ghosting(uint32 scanline, uint16 *backbuffer, uint8 start_x, uint8 end_x);
void gpu_init(void)
{
supervision_palette = (uint16*)malloc(4 * sizeof(int16));
@ -28,101 +73,42 @@ void gpu_init(void)
void gpu_reset(void)
{
#if defined(GP2X)
supervision_palette[3] = gp2x_video_RGB_color16( 0, 0, 0);
supervision_palette[2] = gp2x_video_RGB_color16( 85, 85, 85);
supervision_palette[1] = gp2x_video_RGB_color16(170,170,170);
supervision_palette[0] = gp2x_video_RGB_color16(170,170,170);
#elif defined(NDS)
supervision_palette[3] = RGB555( 0, 0, 0);
supervision_palette[2] = RGB555(10,10,10);
supervision_palette[1] = RGB555(20,20,20);
supervision_palette[0] = RGB555(30,30,30);
#elif defined(_ODSDL_)
supervision_palette[3] = PIX_TO_RGB(actualScreen->format, 0, 0, 0);
supervision_palette[2] = PIX_TO_RGB(actualScreen->format, 85, 85, 85);
supervision_palette[1] = PIX_TO_RGB(actualScreen->format, 170, 170, 170);
supervision_palette[0] = PIX_TO_RGB(actualScreen->format, 240, 240, 240);
#else
supervision_palette[3] = RGB555( 0, 0, 0);
supervision_palette[2] = RGB555(10,10,10);
supervision_palette[1] = RGB555(20,20,20);
supervision_palette[0] = RGB555(30,30,30);
#endif
memset(gpu_regs, 0, 4);
gpu_set_colour_scheme(COLOUR_SCHEME_DEFAULT);
gpu_set_ghosting(0);
}
void gpu_set_colour_scheme(int colourScheme)
{
float greenf = 1;
float bluef = 1;
float redf = 1;
switch (colourScheme) {
case COLOUR_SCHEME_DEFAULT:
break;
case COLOUR_SCHEME_AMBER:
greenf = 0.61f;
bluef = 0.00f;
redf = 1.00f;
break;
case COLOUR_SCHEME_GREEN:
greenf = 0.90f;
bluef = 0.20f;
redf = 0.20f;
break;
case COLOUR_SCHEME_BLUE:
greenf = 0.30f;
bluef = 0.75f;
redf = 0.30f;
break;
default:
colourScheme = 0;
break;
float c[12];
int i;
for (i = 0; i < 12; i++) {
c[i] = palettes[colourScheme][i] / 255.0f;
}
#if defined(GP2X)
supervision_palette[3] = gp2x_video_RGB_color16( 0*redf, 0*greenf, 0*bluef);
supervision_palette[2] = gp2x_video_RGB_color16( 85*redf, 85*greenf, 85*bluef);
supervision_palette[1] = gp2x_video_RGB_color16(170*redf,170*greenf,170*bluef);
supervision_palette[0] = gp2x_video_RGB_color16(255*redf,255*greenf,255*bluef);
#elif defined(NDS)
supervision_palette[3] = RGB555( 0*redf, 0*greenf, 0*bluef);
supervision_palette[2] = RGB555(10*redf,10*greenf,10*bluef);
supervision_palette[1] = RGB555(20*redf,20*greenf,20*bluef);
supervision_palette[0] = RGB555(30*redf,30*greenf,30*bluef);
supervision_palette[3] = gp2x_video_RGB_color16(255*c[9],255*c[10],255*c[11]);
supervision_palette[2] = gp2x_video_RGB_color16(255*c[6],255*c[ 7],255*c[ 8]);
supervision_palette[1] = gp2x_video_RGB_color16(255*c[3],255*c[ 4],255*c[ 5]);
supervision_palette[0] = gp2x_video_RGB_color16(255*c[0],255*c[ 1],255*c[ 2]);
#elif defined(_ODSDL_)
int p11 = (int)85 * redf; int p12 = (int)85 * greenf; int p13 = (int)85 * bluef;
int p21 = (int)170 * redf; int p22 = (int)170 * greenf; int p23 = (int)170 * bluef;
int p31 = (int)255 * redf; int p32 = (int)255 * greenf; int p33 = (int)255 * bluef;
supervision_palette[3] = PIX_TO_RGB(actualScreen->format, 0, 0, 0);
supervision_palette[2] = PIX_TO_RGB(actualScreen->format, p11, p12, p13);
supervision_palette[1] = PIX_TO_RGB(actualScreen->format, p21, p22, p23);
supervision_palette[0] = PIX_TO_RGB(actualScreen->format, p31, p32, p33);
supervision_palette[3] = PIX_TO_RGB(actualScreen->format, (int)(255*c[9]),(int)(255*c[10]),(int)(255*c[11]));
supervision_palette[2] = PIX_TO_RGB(actualScreen->format, (int)(255*c[6]),(int)(255*c[ 7]),(int)(255*c[ 8]));
supervision_palette[1] = PIX_TO_RGB(actualScreen->format, (int)(255*c[3]),(int)(255*c[ 4]),(int)(255*c[ 5]));
supervision_palette[0] = PIX_TO_RGB(actualScreen->format, (int)(255*c[0]),(int)(255*c[ 1]),(int)(255*c[ 2]));
#else
supervision_palette[3] = RGB555( 0*redf, 0*greenf, 0*bluef);
supervision_palette[2] = RGB555(10*redf,10*greenf,10*bluef);
supervision_palette[1] = RGB555(20*redf,20*greenf,20*bluef);
supervision_palette[0] = RGB555(30*redf,30*greenf,30*bluef);
supervision_palette[3] = RGB555(31*c[9],31*c[10],31*c[11]);
supervision_palette[2] = RGB555(31*c[6],31*c[ 7],31*c[ 8]);
supervision_palette[1] = RGB555(31*c[3],31*c[ 4],31*c[ 5]);
supervision_palette[0] = RGB555(31*c[0],31*c[ 1],31*c[ 2]);
#endif
}
void gpu_write(uint32 addr, uint8 data)
{
gpu_regs[(addr&0x03)] = data;
}
uint8 gpu_read(uint32 addr)
{
return gpu_regs[(addr&0x03)];
}
void gpu_render_scanline(uint32 scanline, uint16 *backbuffer)
{
uint8 *vram_line = &(memorymap_getUpperRamPointer())[(gpu_regs[2] >> 2) + (scanline*0x30)];
uint8 *m_reg = memorymap_getRegisters();
uint8 *vram_line = memorymap_getUpperRamPointer() + (m_reg[XPOS] / 4 + m_reg[YPOS] * 0x30) + (scanline * 0x30);
uint8 x;
for (x =0; x < 160; x += 4)
for (x = 0; x < 160; x += 4)
{
uint8 b = *(vram_line++);
backbuffer[3] = supervision_palette[((b >> 6) & 0x03)];
@ -135,18 +121,110 @@ void gpu_render_scanline(uint32 scanline, uint16 *backbuffer)
void gpu_render_scanline_fast(uint32 scanline, uint16 *backbuffer)
{
uint8 *vram_line = &(memorymap_getUpperRamPointer())[scanline];
uint8 x;
uint32 *buf = (uint32 *)backbuffer;
uint8 *vram_line = memorymap_getUpperRamPointer() + scanline;
uint8 x, j, b;
uint8 *m_reg = memorymap_getRegisters();
int start_x = 3 - (m_reg[XPOS] & 3);
int end_x = (163 < (m_reg[XSIZE] | 3) ? 163 : (m_reg[XSIZE] | 3));
//if (start_x != 3) printf("start_x = %d\n", start_x);
//if (end_x != 163) printf("end_x = %d\n", end_x);
for (x = start_x; x < end_x; x += 4) {
uint8 b = *(vram_line++);
*(buf++) = (supervision_palette[((b >> 2) & 0x03)] << 16) | (supervision_palette[((b >> 0) & 0x03)]);
*(buf++) = (supervision_palette[((b >> 6) & 0x03)] << 16) | (supervision_palette[((b >> 4) & 0x03)]);
uint8 start_x = m_reg[XPOS] & 3; //3 - (m_reg[XPOS] & 3);
uint8 end_x = (163 < (m_reg[XSIZE] | 3) ? 163 : (m_reg[XSIZE] | 3)) - 3; //(163 < (m_reg[XSIZE] | 3) ? 163 : (m_reg[XSIZE] | 3));
//if (start_x != 0) printf("start_x = %u\n", start_x);
//if (end_x != 160) printf("end_x = %u\n", end_x);
j = start_x;
// #1
if (j & 3) {
b = *vram_line++;
b >>= (j & 3) * 2;
}
for (x = 0; x < end_x; x++, j++) {
if (!(j & 3)) {
b = *(vram_line++);
}
backbuffer[x] = supervision_palette[b & 3];
b >>= 2;
}
// #2
/*for (x = 0; x < end_x; x++, j++) {
b = vram_line[j >> 2];
backbuffer[x] = supervision_palette[(b >> ((j & 3) * 2)) & 3];
}*/
if (ghostCount != 0) {
add_ghosting(scanline, backbuffer, start_x, end_x);
}
}
void gpu_set_ghosting(int frameCount)
{
int i;
if (frameCount < 0)
ghostCount = 0;
else if (frameCount > GHOSTING_MAX)
ghostCount = GHOSTING_MAX;
else
ghostCount = frameCount;
if (ghostCount != 0) {
if (screenBuffers[0] == NULL) {
for (i = 0; i < SB_MAX; i++) {
screenBuffers[i] = malloc(160 * 160 / 4);
}
}
for (i = 0; i < SB_MAX; i++) {
memset(screenBuffers[i], 0, 160 * 160 / 4);
}
}
else {
for (i = 0; i < SB_MAX; i++) {
free(screenBuffers[i]);
screenBuffers[i] = NULL;
}
}
}
static void add_ghosting(uint32 scanline, uint16 *backbuffer, uint8 start_x, uint8 end_x)
{
static int curSB = 0;
static int lineCount = 0;
uint8 *vram_line = memorymap_getUpperRamPointer() + scanline;
uint8 x, i, j;
screenBufferStartX[curSB] = start_x;
memset(screenBuffers[curSB] + lineCount * 160 / 4, 0, 160 / 4);
for (j = start_x, x = 0; x < end_x; x++, j++) {
uint8 b = vram_line[j >> 2];
int pixInd = (x + lineCount * 160) / 4;
uint8 innerInd = (j & 3) * 2;
uint8 c = (b >> innerInd) & 3;
if (c == 0) {
for (i = 0; i < ghostCount; i++) {
uint8 sbInd = (curSB + (SB_MAX - 1) - i) % SB_MAX;
innerInd = ((screenBufferStartX[sbInd] + x) & 3) * 2;
c = (screenBuffers[sbInd][pixInd] >> innerInd) & 3;
if (c != 0) {
#if defined(GP2X) || defined(_ODSDL_)
backbuffer[x] = supervision_palette[3 - 3 * i / ghostCount];
#else
uint8 r = (supervision_palette[c] >> 0) & 31;
uint8 g = (supervision_palette[c] >> 5) & 31;
uint8 b = (supervision_palette[c] >> 10) & 31;
r = r + (((supervision_palette[0] >> 0) & 31) - r) * i / ghostCount;
g = g + (((supervision_palette[0] >> 5) & 31) - g) * i / ghostCount;
b = b + (((supervision_palette[0] >> 10) & 31) - b) * i / ghostCount;
backbuffer[x] = RGB555(r, g, b);
#endif
break;
}
}
}
else {
screenBuffers[curSB][pixInd] |= c << innerInd;
}
}
if (lineCount == 159) {
curSB = (curSB + 1) % SB_MAX;
}
lineCount = (lineCount + 1) % 160;
}

View File

@ -3,12 +3,24 @@
#include "supervision.h"
enum
{
COLOUR_SCHEME_DEFAULT,
COLOUR_SCHEME_AMBER,
COLOUR_SCHEME_GREEN,
COLOUR_SCHEME_BLUE,
COLOUR_SCHEME_BGB,
COLOUR_SCHEME_YOUTUBE,
COLOUR_SCHEME_COUNT
};
#define GHOSTING_MAX 8
void gpu_init(void);
void gpu_reset(void);
void gpu_set_colour_scheme(int colourScheme);
void gpu_write(uint32 addr, uint8 data);
uint8 gpu_read(uint32 addr);
void gpu_render_scanline(uint32 scanline, uint16 *backbuffer);
void gpu_render_scanline_fast(uint32 scanline, uint16 *backbuffer);
void gpu_set_ghosting(int frameCount);
#endif

View File

@ -13,45 +13,25 @@
/** changes to this file. **/
/*************************************************************/
/* FIXME: need to add cycle data for 65C02 - uso. */
static byte Cycles[256] =
{
//7,6,2,8,3,3,5,5,3,2,2,2,4,4,6,6,
//2,5,2,8,4,4,6,6,2,4,2,7,5,5,7,7,
//6,6,2,8,3,3,5,5,4,2,2,2,4,4,6,6,
//2,5,2,8,4,4,6,6,2,4,2,7,5,5,7,7,
//6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,6,
//2,5,2,8,4,4,6,6,2,4,2,7,5,5,7,7,
//6,6,2,8,3,3,5,5,4,2,2,2,5,4,6,6,
//2,5,2,8,4,4,6,6,2,4,2,7,5,5,7,7,
//2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,
//2,6,2,6,4,4,4,4,2,5,2,5,5,5,5,5,
//2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,
//2,5,2,5,4,4,4,4,2,4,2,5,4,4,4,4,
//2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,
//2,5,2,8,4,4,6,6,2,4,2,7,5,5,7,7,
//2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,
//2,5,2,8,4,4,6,6,2,4,2,7,5,5,7,7
//https://github.com/mamedev/historic-mess/blob/master/src/emu/cpu/m6502/t6502.c
//https://github.com/mamedev/historic-mess/blob/master/src/emu/cpu/m6502/t65c02.c
//0 1 2 3 4 5 6 7 8 9 a b c d e f
7,6,2,8,3,3,5,5,3,2,2,2,2,4,6,5, // 0
2,5,3,8,3,4,6,5,2,4,2,7,4,4,7,5, // 1
6,6,2,8,3,3,5,5,4,2,2,2,4,4,6,5, // 2
2,5,3,8,4,4,6,5,2,4,2,7,4,4,7,5, // 3
6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,5, // 4
2,5,3,8,4,4,6,5,2,4,3,7,5,4,7,5, // 5
6,6,2,8,2,3,5,5,4,2,2,2,5,4,6,5, // 6
2,5,3,8,4,4,6,5,2,4,4,7,2,4,7,5, // 7
2,6,2,6,3,3,3,5,2,2,2,2,4,4,4,5, // 8
2,6,4,6,4,4,4,5,2,5,2,5,4,5,5,5, // 9
2,6,2,6,3,3,3,5,2,2,2,2,4,4,4,5, // a
2,5,3,5,4,4,4,5,2,4,2,5,4,4,4,5, // b
2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,5, // c
2,5,3,8,4,4,6,5,2,4,3,7,5,4,7,5, // d
2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,5, // e
2,5,3,8,4,4,6,5,2,4,4,7,5,4,7,5 // f
//0 1 2 3 4 5 6 7 8 9 a b c d e f
7,6,2,8,3,3,5,5,3,2,2,2,4,4,6,6,
2,5,2,8,4,4,6,6,2,4,2,7,5,5,7,7,
6,6,2,8,3,3,5,5,4,2,2,2,4,4,6,6,
2,5,2,8,4,4,6,6,2,4,2,7,5,5,7,7,
6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,6,
2,5,2,8,4,4,6,6,2,4,2,7,5,5,7,7,
6,6,2,8,3,3,5,5,4,2,2,2,5,4,6,6,
2,5,2,8,4,4,6,6,2,4,2,7,5,5,7,7,
2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,
2,6,2,6,4,4,4,4,2,5,2,5,5,5,5,5,
2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,
2,5,2,5,4,4,4,4,2,4,2,5,4,4,4,4,
2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,
2,5,2,8,4,4,6,6,2,4,2,7,5,5,7,7,
2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,
2,5,2,8,4,4,6,6,2,4,2,7,5,5,7,7
};
byte ZNTable[256] =

View File

@ -65,7 +65,6 @@ uint8 memorymap_registers_read(uint32 Addr)
case 0x02:
case 0x03:
break;
//return gpu_read(Addr);
case 0x20:
return controls_read();
case 0x21:
@ -101,7 +100,6 @@ void memorymap_registers_write(uint32 Addr, uint8 Value)
case 0x01:
case 0x02:
case 0x03:
gpu_write(Addr, Value);
break;
case 0x23:
timer_write(Value);
@ -114,19 +112,19 @@ void memorymap_registers_write(uint32 Addr, uint8 Value)
return;
case 0x10: case 0x11: case 0x12: case 0x13:
case 0x14: case 0x15: case 0x16: case 0x17:
soundport_w(((Addr & 0x4) >> 2), Addr & 3, Value);
sound_soundport_w(((Addr & 0x4) >> 2), Addr & 3, Value);
break;
case 0x28:
case 0x29:
case 0x2a:
svision_noise_w(Addr & 0x07, Value);
sound_noise_w(Addr & 0x07, Value);
break;
case 0x18:
case 0x19:
case 0x1a:
case 0x1b:
case 0x1c:
svision_sounddma_w(Addr & 0x07, Value);
sound_sounddma_w(Addr & 0x07, Value);
break;
}
}
@ -180,16 +178,17 @@ byte Rd6502(register word Addr)
return 0xff;
}
void memorymap_load(uint8 *rom, uint32 size)
void memorymap_load(uint8 **rom, uint32 size)
{
memorymap_programRomSize = size;
memorymap_programRom = rom;
memorymap_programRom = *rom;
if (memorymap_programRomSize == 32768) {
uint8 *tmp = (uint8 *)malloc(0x10000);
memcpy(tmp + 0x0000, memorymap_programRom, 0x8000);
memcpy(tmp + 0x8000, memorymap_programRom, 0x8000);
free(memorymap_programRom);
*rom = tmp;
memorymap_programRom = tmp;
memorymap_programRomSize = 0x10000;
}
@ -224,3 +223,35 @@ uint8 *memorymap_getRomPointer(void)
{
return memorymap_programRom;
}
void memorymap_save_state(FILE *fp)
{
fwrite(memorymap_regs, 1, 0x2000, fp);
fwrite(memorymap_lowerRam, 1, 0x2000, fp);
fwrite(memorymap_upperRam, 1, 0x2000, fp);
uint32 offset = 0;
offset = memorymap_lowerRomBank - memorymap_programRom;
fwrite(&offset, 1, sizeof(offset), fp);
offset = memorymap_upperRomBank - memorymap_programRom;
fwrite(&offset, 1, sizeof(offset), fp);
fwrite(&dma_finished, 1, sizeof(dma_finished), fp);
fwrite(&timer_shot, 1, sizeof(timer_shot), fp);
}
void memorymap_load_state(FILE *fp)
{
fread(memorymap_regs, 1, 0x2000, fp);
fread(memorymap_lowerRam, 1, 0x2000, fp);
fread(memorymap_upperRam, 1, 0x2000, fp);
uint32 offset = 0;
fread(&offset, 1, sizeof(offset), fp);
memorymap_lowerRomBank = memorymap_programRom + offset;
fread(&offset, 1, sizeof(offset), fp);
memorymap_upperRomBank = memorymap_programRom + offset;
fread(&dma_finished, 1, sizeof(dma_finished), fp);
fread(&timer_shot, 1, sizeof(timer_shot), fp);
}

View File

@ -2,6 +2,7 @@
#define __MEMORYMAP_H__
#include "supervision.h"
#include <stdio.h>
enum
{
@ -18,7 +19,10 @@ void memorymap_init(void);
void memorymap_reset(void);
uint8 memorymap_registers_read(uint32 Addr);
void memorymap_registers_write(uint32 Addr, uint8 Value);
void memorymap_load(uint8 *rom, uint32 size);
void memorymap_load(uint8 **rom, uint32 size);
void memorymap_save_state(FILE *fp);
void memorymap_load_state(FILE *fp);
uint8 *memorymap_getUpperRamPointer(void);
uint8 *memorymap_getLowerRamPointer(void);

View File

@ -40,6 +40,13 @@ typedef struct {
} SVISION_DMA;
SVISION_DMA m_dma;
void sound_reset(void)
{
memset(m_channel, 0, sizeof(m_channel));
memset(&m_noise, 0, sizeof(m_noise) );
memset(&m_dma, 0, sizeof(m_dma) );
}
void sound_stream_update(uint8 *stream, int len)
{
int i, j;
@ -48,14 +55,14 @@ void sound_stream_update(uint8 *stream, int len)
uint8 *left = stream + 0;
uint8 *right = stream + 1;
for (i = 0; i < len >> 1; i++, left+=2, right+=2) {
for (i = 0; i < len >> 1; i++, left += 2, right += 2) {
s = 0;
*left = *right = 0;
for (channel = m_channel, j = 0; j < 2; j++, channel++) {
if (channel->size != 0) {
if (channel->on || channel->count) {
int on = FALSE;
BOOL on = FALSE;
switch (channel->waveform) {
case 0:
on = channel->pos <= (28 * channel->size) >> 5;
@ -71,7 +78,7 @@ void sound_stream_update(uint8 *stream, int len)
on = channel->pos <= (9 * channel->size) >> 5;
break;
}
s = on ? channel->volume : 0; // << 8 : 0;
s = on ? channel->volume /*<< 8*/ : 0;
if (j == 0) {
*right += s;
}
@ -87,13 +94,13 @@ void sound_stream_update(uint8 *stream, int len)
if (m_noise.on && (m_noise.play || m_noise.count)) {
s = (m_noise.value ? 1 /*<< 8*/ : 0) * m_noise.volume;
int b1, b2;
if (m_noise.left)
*left += s;
if (m_noise.right)
*right += s;
m_noise.pos += m_noise.step;
if (m_noise.pos >= 1.0) {
BOOL b1, b2;
switch (m_noise.type) {
case SVISION_NOISE_Type7Bit:
m_noise.value = m_noise.state & 0x40 ? 1 : 0;
@ -152,7 +159,7 @@ void sound_decrement(void)
m_noise.count--;
}
void soundport_w(int which, int offset, int data)
void sound_soundport_w(int which, int offset, int data)
{
SVISION_CHANNEL *channel = &m_channel[which];
unsigned short size;
@ -182,7 +189,7 @@ void soundport_w(int which, int offset, int data)
}
}
void svision_sounddma_w(int offset, int data)
void sound_sounddma_w(int offset, int data)
{
m_dma.reg[offset] = data;
switch (offset) {
@ -208,7 +215,7 @@ void svision_sounddma_w(int offset, int data)
}
}
void svision_noise_w(int offset, int data)
void sound_noise_w(int offset, int data)
{
m_noise.reg[offset] = data;
switch (offset) {

View File

@ -5,14 +5,15 @@
#define BPS 44100
void sound_reset(void);
/*!
* Generate U8, 2 channels.
* \param len in bytes.
*/
void sound_stream_update(uint8 *stream, int len);
void sound_decrement(void);
void soundport_w(int which, int offset, int data);
void svision_sounddma_w(int offset, int data);
void svision_noise_w(int offset, int data);
void sound_soundport_w(int which, int offset, int data);
void sound_sounddma_w(int offset, int data);
void sound_noise_w(int offset, int data);
#endif

View File

@ -13,21 +13,17 @@
#include "./m6502/m6502.h"
#define COLOUR_SCHEME_DEFAULT 0
#define COLOUR_SCHEME_AMBER 1
#define COLOUR_SCHEME_GREEN 2
#define COLOUR_SCHEME_BLUE 3
void supervision_init(void);
void supervision_reset(void);
void supervision_done(void);
void supervision_exec(uint16 *backbuffer);
BOOL supervision_load(uint8 *rom, uint32 romSize);
BOOL supervision_load(uint8 **rom, uint32 romSize);
BOOL supervision_update_input(void);
void supervision_set_colour_scheme(int ws_colourScheme);
void supervision_set_colour_scheme(int colourScheme);
void supervision_set_ghosting(int frameCount);
M6502 *supervision_get6502regs(void);
int sv_loadState(const char *statepath, int id);
int sv_saveState(const char *statepath, int id);
int supervision_save_state(const char *statepath, int id);
int supervision_load_state(const char *statepath, int id);
#endif

View File

@ -31,3 +31,15 @@ void timer_exec(uint32 cycles)
}
}
}
void timer_save_state(FILE *fp)
{
fwrite(&timer_cycles, 1, sizeof(timer_cycles), fp);
fwrite(&timer_activated, 1, sizeof(timer_activated), fp);
}
void timer_load_state(FILE *fp)
{
fread(&timer_cycles, 1, sizeof(timer_cycles), fp);
fread(&timer_activated, 1, sizeof(timer_activated), fp);
}

View File

@ -2,9 +2,13 @@
#define __TIMER_H__
#include "supervision.h"
#include <stdio.h>
void timer_reset(void);
void timer_write(uint8 data);
void timer_exec(uint32 cycles);
void timer_save_state(FILE *fp);
void timer_load_state(FILE *fp);
#endif

View File

@ -19,7 +19,6 @@
#endif
static M6502 m6502_registers;
static BOOL irq = FALSE;
void m6502_set_irq_line(BOOL assertLine)
@ -39,27 +38,19 @@ byte Loop6502(register M6502 *R)
void supervision_init(void)
{
memorymap_init();
gpu_init();
}
BOOL supervision_load(uint8 *rom, uint32 romSize)
{
memorymap_load(rom, romSize);
supervision_reset();
return TRUE;
memorymap_init();
}
void supervision_reset(void)
{
memorymap_reset();
gpu_reset();
timer_reset();
controls_reset();
gpu_reset();
memorymap_reset();
sound_reset();
timer_reset();
Reset6502(&m6502_registers);
irq = FALSE;
}
@ -67,14 +58,12 @@ void supervision_done(void)
{
}
void supervision_set_colour_scheme(int sv_colourScheme)
BOOL supervision_load(uint8 **rom, uint32 romSize)
{
gpu_set_colour_scheme(sv_colourScheme);
}
memorymap_load(rom, romSize);
supervision_reset();
M6502 *supervision_get6502regs(void)
{
return &m6502_registers;
return TRUE;
}
BOOL supervision_update_input(void)
@ -82,23 +71,39 @@ BOOL supervision_update_input(void)
return controls_update();
}
void supervision_set_colour_scheme(int colourScheme)
{
gpu_set_colour_scheme(colourScheme);
}
void supervision_set_ghosting(int frameCount)
{
gpu_set_ghosting(frameCount);
}
M6502 *supervision_get6502regs(void)
{
return &m6502_registers;
}
void supervision_exec(uint16 *backbuffer)
{
uint32 supervision_scanline, scan = 0;
uint8 *m_reg = memorymap_getRegisters();
//if (!((m_reg[BANK] >> 3) & 1)) { printf("ndraw "); }
scan = m_reg[XPOS] / 4 + m_reg[YPOS] * 0x30;
for (supervision_scanline = 0; supervision_scanline < 160; supervision_scanline++)
{
m6502_registers.ICount = 512;
for (supervision_scanline = 0; supervision_scanline < 160; supervision_scanline++) {
m6502_registers.ICount = 512;
timer_exec(m6502_registers.ICount);
Run6502(&m6502_registers);
}
//if (!((m_reg[BANK] >> 3) & 1)) { printf("LCD off\n"); }
scan = m_reg[XPOS] / 4 + m_reg[YPOS] * 0x30;
for (supervision_scanline = 0; supervision_scanline < 160; supervision_scanline++) {
#ifdef NDS
gpu_render_scanline(supervision_scanline, backbuffer);
backbuffer += 160+96;
backbuffer += 160 + 96;
#else
//gpu_render_scanline(supervision_scanline, backbuffer);
gpu_render_scanline_fast(scan, backbuffer);
@ -109,84 +114,70 @@ void supervision_exec(uint16 *backbuffer)
scan = 0; // SSSnake
}
if (Rd6502(0x2026)&0x01)
if (Rd6502(0x2026) & 0x01)
Int6502(supervision_get6502regs(), INT_NMI);
sound_decrement();
}
int sv_loadState(const char *statepath, int id)
int supervision_save_state(const char *statepath, int id)
{
FILE *fp;
char newPath[256];
strcpy(newPath, statepath);
sprintf(newPath + strlen(newPath) - 3, ".s%d", id);
sprintf(newPath + strlen(newPath), ".svst");
#ifdef GP2X
gp2x_printf(0,10,220,"newPath = %s",newPath);
gp2x_video_RGB_flip(0);
#endif
#ifdef NDS
iprintf("\nnewPath = %s",newPath);
#endif
fp = fopen(newPath, "rb");
if (fp) {
fread(&m6502_registers, 1, sizeof(m6502_registers), fp);
fread(memorymap_programRom, 1, sizeof(memorymap_programRom), fp);
fread(memorymap_lowerRam, 1, 0x2000, fp);
fread(memorymap_upperRam, 1, 0x2000, fp);
fread(memorymap_lowerRomBank, 1, sizeof(memorymap_lowerRomBank), fp);
fread(memorymap_upperRomBank, 1, sizeof(memorymap_upperRomBank), fp);
fread(memorymap_regs, 1, 0x2000, fp);
fclose(fp);
}
#ifdef GP2X
sleep(1);
#endif
return 1;
}
int sv_saveState(const char *statepath, int id)
{
FILE *fp;
char newPath[256];
strcpy(newPath, statepath);
sprintf(newPath + strlen(newPath) - 3, ".s%d", id);
#ifdef GP2X
gp2x_printf(0,10,220,"newPath = %s",newPath);
gp2x_video_RGB_flip(0);
#endif
#ifdef NDS
iprintf("\nnewPath = %s",newPath);
#endif
fp = fopen(newPath, "wb");
if (fp) {
fwrite(&m6502_registers, 1, sizeof(m6502_registers), fp);
fwrite(memorymap_programRom, 1, sizeof(memorymap_programRom), fp);
fwrite(memorymap_lowerRam, 1, 0x2000, fp);
fwrite(memorymap_upperRam, 1, 0x2000, fp);
fwrite(memorymap_lowerRomBank, 1, sizeof(memorymap_lowerRomBank), fp);
fwrite(memorymap_upperRomBank, 1, sizeof(memorymap_upperRomBank), fp);
fwrite(memorymap_regs, 1, 0x2000, fp);
fwrite(&m6502_registers, 1, sizeof(m6502_registers), fp);
fwrite(&irq, 1, sizeof(irq), fp);
memorymap_save_state(fp);
timer_save_state(fp);
fflush(fp);
fclose(fp);
#ifdef GP2X
sync();
#endif
}
#ifdef GP2X
sleep(1);
#endif
return 1;
}
int supervision_load_state(const char *statepath, int id)
{
FILE *fp;
char newPath[256];
strcpy(newPath, statepath);
sprintf(newPath + strlen(newPath), ".svst");
#ifdef GP2X
gp2x_video_RGB_flip(0);
#endif
fp = fopen(newPath, "rb");
if (fp) {
sound_reset();
fread(&m6502_registers, 1, sizeof(m6502_registers), fp);
fread(&irq, 1, sizeof(irq), fp);
memorymap_load_state(fp);
timer_load_state(fp);
fclose(fp);
}
#ifdef GP2X
sleep(1);
#endif
return 1;
}

View File

@ -154,7 +154,7 @@ int main(int argc, char *argv[])
if(romname!=NULL){
loadROM(romname);
supervision_load((uint8*)buffer, (uint32)buffer_size);
supervision_load(&buffer, (uint32)buffer_size);
} else {
handleFileMenu(); // File menu
}

View File

@ -269,7 +269,7 @@ void handleFileMenu(void)
RESIZE();
loadROM(FileList[curFile + virtualFile].fName);
textClear();
supervision_load((uint8*)buffer, (uint32)buffer_size);
supervision_load(&buffer, (uint32)buffer_size);
textClear();
return;
}
@ -496,8 +496,8 @@ void handleMainMenu(void)
case MMOPTION_RESTART: RESIZE(); supervision_reset(); textClear(); return;
case MMOPTION_SELECTOR: handleFileMenu(); return;
case MMOPTION_OPTIONS: handleOptionsMenu(); textClear(); return;
case MMOPTION_SAVESTATE: sv_saveState(romname,saveSlot); textClear();return;
case MMOPTION_LOADSTATE: sv_loadState(romname,saveSlot); textClear();return;
case MMOPTION_SAVESTATE: supervision_save_state(romname,saveSlot); textClear();return;
case MMOPTION_LOADSTATE: supervision_load_state(romname,saveSlot); textClear();return;
case MMOPTION_EXIT: exitMenu(); break;
default: return;
}

View File

@ -121,7 +121,7 @@ void CheckKeys(void)
/*if(keys & KEY_START && keys & KEY_SELECT) {
dotextmenu();
loadROM();
supervision_load((uint8*)buffer, (uint32)buffer_size);
supervision_load(&buffer, (uint32)buffer_size);
iprintf("\nLoad Rom Seccessfully\n"); }*/
}
@ -148,7 +148,7 @@ int main()
iprintf("\nFailed to init fat");
}
supervision_load((uint8*)buffer, (uint32)buffer_size);
supervision_load(&buffer, (uint32)buffer_size);
iprintf("\nLoad Rom Seccessfully\n");
while(1)

View File

@ -136,7 +136,7 @@ int main(int argc, char *argv[])
supervision_init(); //Init the emulator
if(romname) loadROM(romname); else loadROM("rom.sv");
supervision_load((uint8*)buffer, (uint32)buffer_size);
supervision_load(&buffer, (uint32)buffer_size);
while(1)
{

View File

@ -6,21 +6,36 @@
#include <stdlib.h>
#include <string.h>
#include <SDL.h>
#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#endif
#include "../../common/supervision.h"
#include "../../common/sound.h"
#include <SDL.h>
#define OR_DIE(cond) \
if (cond) { \
fprintf(stderr, "[Error] SDL: %s\n", SDL_GetError()); \
exit(1); \
}
SDL_bool paused = SDL_FALSE;
#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
uint16_t screenBuffer[160 * 160];
#define SCREEN_W 160
#define SCREEN_H 160
typedef enum {
MENUSTATE_NONE,
MENUSTATE_DROP_ROM,
MENUSTATE_EMULATION,
MENUSTATE_PAUSE,
MENUSTATE_SET_KEY
} MenuState;
SDL_bool done = SDL_FALSE;
uint16_t screenBuffer[SCREEN_W * SCREEN_H];
SDL_Window *sdlScreen;
SDL_Renderer *sdlRenderer;
SDL_Texture *sdlTexture;
@ -28,97 +43,337 @@ SDL_Texture *sdlTexture;
uint8_t *buffer;
uint32_t bufferSize = 0;
SDL_GameController *controller = NULL;
SDL_bool IsFullscreen(void);
void ToggleFullscreen(void);
uint64_t startCounter = 0;
SDL_bool isRefreshRate60 = SDL_FALSE;
void InitCounter(void);
SDL_bool NeedUpdate(void);
void DrawDropROM(void);
int nextMenuState = 0;
MenuState menuStates[4];
void PushMenuState(MenuState state);
void PopMenuState(void);
MenuState GetMenuState(void);
void Reset(void);
int currentPalette = 0;
void NextPalette(void);
int windowScale = 4;
void IncreaseWindowSize(void);
void DecreaseWindowSize(void);
void SaveState(void);
void LoadState(void);
int audioVolume = SDL_MIX_MAXVOLUME;
void SetVolume(int volume);
void MuteAudio(void);
int currentGhosting = 0;
void IncreaseGhosting(void);
void DecreaseGhosting(void);
char *keysNames[] = {
"Right",
"Left",
"Down",
"Up",
"B",
"A",
"Select",
"Start",
"Toggle Fullscreen",
"Reset",
"Save State",
"Load State",
"Next Palette",
"Decrease Window Size",
"Increase Window Size",
"Decrease Ghosting",
"Increase Ghosting",
"Mute Audio",
};
int keysMapping[] = {
SDL_SCANCODE_RIGHT,
SDL_SCANCODE_LEFT,
SDL_SCANCODE_DOWN,
SDL_SCANCODE_UP,
SDL_SCANCODE_X, // B
SDL_SCANCODE_C, // A
SDL_SCANCODE_Z, // Select
SDL_SCANCODE_SPACE, // Start
SDL_SCANCODE_RETURN,
SDL_SCANCODE_TAB,
SDL_SCANCODE_1,
SDL_SCANCODE_2,
SDL_SCANCODE_P,
SDL_SCANCODE_MINUS,
SDL_SCANCODE_EQUALS,
SDL_SCANCODE_LEFTBRACKET,
SDL_SCANCODE_RIGHTBRACKET,
SDL_SCANCODE_M,
};
void (*keysFuncs[])(void) = {
ToggleFullscreen,
Reset,
SaveState,
LoadState,
NextPalette,
DecreaseWindowSize,
IncreaseWindowSize,
DecreaseGhosting,
IncreaseGhosting,
MuteAudio,
};
int setButton = -1;
void SetKey(int button);
char romName[64];
void SetRomName(const char *path)
{
const char *p = path + strlen(path);
while (p != path) {
if (*p == '\\' || *p == '/') {
p++;
break;
}
p--;
}
strncpy(romName, p, sizeof(romName));
romName[sizeof(romName) - 1] = '\0';
}
int LoadROM(const char *filename)
{
if (buffer != 0)
if (buffer != NULL) {
free(buffer);
buffer = NULL;
}
FILE *romfile = fopen(filename, "rb");
SDL_RWops *romfile = SDL_RWFromFile(filename, "rb");
if (romfile == NULL) {
printf("fopen(): Unable to open file!\n");
fprintf(stderr, "SDL_RWFromFile(): Unable to open file!\n");
return 1;
}
fseek(romfile, 0, SEEK_END);
bufferSize = ftell(romfile);
fseek(romfile, 0, SEEK_SET);
bufferSize = (uint32_t)SDL_RWsize(romfile);
buffer = (uint8_t *)malloc(bufferSize);
fread(buffer, bufferSize, 1, romfile);
if (fclose(romfile) == EOF) {
printf("fclose(): Unable to close file!\n");
SDL_RWread(romfile, buffer, bufferSize, 1);
if (SDL_RWclose(romfile) != 0) {
fprintf(stderr, "SDL_RWclose(): Unable to close file!\n");
return 1;
}
SetRomName(filename);
return 0;
}
//int LoadROM(const char *filename)
//{
// if (buffer != NULL) {
// free(buffer);
// buffer = NULL;
// }
//
// FILE *romfile = fopen(filename, "rb");
// if (romfile == NULL) {
// printf("fopen(): Unable to open file!\n");
// return 1;
// }
// fseek(romfile, 0, SEEK_END);
// bufferSize = ftell(romfile);
// fseek(romfile, 0, SEEK_SET);
//
// buffer = (uint8_t *)malloc(bufferSize);
//
// fread(buffer, bufferSize, 1, romfile);
//
// if (fclose(romfile) == EOF) {
// printf("fclose(): Unable to close file!\n");
// return 1;
// }
// SetRomName(filename);
// return 0;
//}
void LoadBuffer(void)
{
supervision_load(&buffer, bufferSize);
MenuState prevState = GetMenuState();
PopMenuState();
PushMenuState(MENUSTATE_EMULATION);
if (prevState == MENUSTATE_PAUSE) { // Focus wasn't gained
PushMenuState(MENUSTATE_PAUSE);
}
supervision_set_colour_scheme(currentPalette);
supervision_set_ghosting(currentGhosting);
}
void AudioCallback(void *userdata, uint8_t *stream, int len)
{
// The alternative of SDL_PauseAudio()
//if (GetMenuState() != MENUSTATE_EMULATION) {
// SDL_memset(stream, 0, len);
// return;
//}
// U8 to F32
sound_stream_update(stream, len / 4);
float *s = (float*)(stream + len) - 1;
for (int i = len / 4 - 1; i >= 0; i--) {
// 127 - max
*s-- = stream[i] / 127.0f * audioVolume / (float)SDL_MIX_MAXVOLUME;
}
// U8 or S8
/*sound_stream_update(stream, len);
for (int i = 0; i < len; i++) {
stream[i] = (uint8_t)(stream[i] * audioVolume / (float)SDL_MIX_MAXVOLUME);
}*/
}
void HandleInput(void)
{
uint8_t controls_state = 0;
const uint8_t *keystate = SDL_GetKeyboardState(NULL);
if (keystate[SDL_SCANCODE_RIGHT]) controls_state |= 0x01;
if (keystate[SDL_SCANCODE_LEFT]) controls_state |= 0x02;
if (keystate[SDL_SCANCODE_DOWN]) controls_state |= 0x04;
if (keystate[SDL_SCANCODE_UP]) controls_state |= 0x08;
if (keystate[SDL_SCANCODE_X]) controls_state |= 0x10; // B
if (keystate[SDL_SCANCODE_C]) controls_state |= 0x20; // A
if (keystate[SDL_SCANCODE_Z]) controls_state |= 0x40; // Select
if (keystate[SDL_SCANCODE_SPACE]) controls_state |= 0x80; // Start
if (keystate[keysMapping[0]]) controls_state |= 0x01;
if (keystate[keysMapping[1]]) controls_state |= 0x02;
if (keystate[keysMapping[2]]) controls_state |= 0x04;
if (keystate[keysMapping[3]]) controls_state |= 0x08;
if (keystate[keysMapping[4]]) controls_state |= 0x10; // B
if (keystate[keysMapping[5]]) controls_state |= 0x20; // A
if (keystate[keysMapping[6]]) controls_state |= 0x40; // Select
if (keystate[keysMapping[7]]) controls_state |= 0x80; // Start
if (SDL_GameControllerGetAttached(controller)) {
if (SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_RIGHT)) controls_state |= 0x01;
if (SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_LEFT)) controls_state |= 0x02;
if (SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_DOWN)) controls_state |= 0x04;
if (SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_UP)) controls_state |= 0x08;
if (SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_A)) controls_state |= 0x10;
if (SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_X)) controls_state |= 0x20;
if (SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_BACK)) controls_state |= 0x40;
if (SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_START)) controls_state |= 0x80;
// 31130/32768 == 0.95
if (SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTX) > 31130) controls_state |= 0x01;
if (SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTX) < -31130) controls_state |= 0x02;
if (SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTY) > 31130) controls_state |= 0x04;
if (SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTY) < -31130) controls_state |= 0x08;
}
controls_state_write(0, controls_state);
}
void AudioCallback(void *userdata, uint8_t *stream, int len)
{
//SDL_memset(stream, 0, len);
sound_stream_update(stream, len/4);
// U8 to F32
int i;
float *s = (float*)(stream + len) - 1;
for (i = len/4 - 1; i >= 0; i--) {
*s-- = stream[i] / 255.0f;
}
}
void Render(void)
{
SDL_RenderClear(sdlRenderer);
SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL);
SDL_RenderPresent(sdlRenderer);
}
void PollEvents(void)
{
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
paused = SDL_TRUE;
done = SDL_TRUE;
}
else if (event.type == SDL_KEYDOWN) {
switch (event.key.keysym.sym) {
case SDLK_RETURN:
ToggleFullscreen();
break;
case SDLK_1:
sv_saveState("rom ", 0);
break;
case SDLK_2:
sv_loadState("rom ", 0);
if (GetMenuState() == MENUSTATE_SET_KEY) {
if (event.key.keysym.scancode != SDL_SCANCODE_ESCAPE)
keysMapping[setButton] = event.key.keysym.scancode;
PopMenuState();
SDL_PauseAudio(0);
continue;
}
for (int i = 0; i < COUNT_OF(keysFuncs); i++) {
if (keysMapping[i + 8] == event.key.keysym.scancode) {
keysFuncs[i]();
break;
}
}
}
else if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) {
if (!IsFullscreen())
SDL_SetWindowSize(sdlScreen, (event.window.data1 + 80) / 160 * 160, (event.window.data2 + 80) / 160 * 160);
// SDL_CONTROLLERBUTTONDOWN doesn't work
else if (event.type == SDL_JOYBUTTONDOWN) {
if (SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_LEFTSHOULDER)) {
SaveState();
}
else if (SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER)) {
LoadState();
}
}
else if (event.type == SDL_WINDOWEVENT) {
switch (event.window.event) {
case SDL_WINDOWEVENT_RESIZED:
if (!IsFullscreen()) {
SDL_SetWindowSize(sdlScreen,
(event.window.data1 + SCREEN_W / 2) / SCREEN_W * SCREEN_W,
(event.window.data2 + SCREEN_H / 2) / SCREEN_H * SCREEN_H);
}
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
if (GetMenuState() == MENUSTATE_SET_KEY) PopMenuState();
PushMenuState(MENUSTATE_PAUSE);
SDL_PauseAudio(1);
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
if (GetMenuState() == MENUSTATE_PAUSE) PopMenuState();
SDL_PauseAudio(0);
break;
}
}
else if (event.type == SDL_DROPFILE) {
if (LoadROM(event.drop.file) == 0) {
LoadBuffer();
}
SDL_free(event.drop.file);
}
else if (event.type == SDL_JOYDEVICEADDED) {
if (SDL_IsGameController(event.jdevice.which)) {
controller = SDL_GameControllerOpen(event.jdevice.which);
if (!controller) {
fprintf(stderr, "Could not open gamecontroller %i: %s\n", event.jdevice.which, SDL_GetError());
}
}
}
}
}
void Loop(void)
{
PollEvents();
HandleInput();
while (NeedUpdate()) {
switch (GetMenuState()) {
case MENUSTATE_EMULATION:
supervision_exec(screenBuffer);
break;
case MENUSTATE_DROP_ROM:
DrawDropROM();
break;
case MENUSTATE_PAUSE:
case MENUSTATE_SET_KEY:
break;
default:
break;
}
}
// Draw
SDL_UpdateTexture(sdlTexture, NULL, screenBuffer, SCREEN_W * sizeof(uint16_t));
SDL_RenderClear(sdlRenderer);
SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL);
SDL_RenderPresent(sdlRenderer);
#ifdef __EMSCRIPTEN__
if (done) {
emscripten_cancel_main_loop();
}
#endif
}
int main(int argc, char *argv[])
{
OR_DIE(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK) < 0);
@ -126,64 +381,62 @@ int main(int argc, char *argv[])
sdlScreen = SDL_CreateWindow("Potator (SDL2)",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
160*3, 160*3,
SCREEN_W * windowScale, SCREEN_H * windowScale,
SDL_WINDOW_RESIZABLE);
OR_DIE(sdlScreen == NULL);
sdlRenderer = SDL_CreateRenderer(sdlScreen, -1, SDL_RENDERER_PRESENTVSYNC);
OR_DIE(sdlRenderer == NULL);
SDL_RenderSetLogicalSize(sdlRenderer, 160, 160);
SDL_RenderSetLogicalSize(sdlRenderer, SCREEN_W, SCREEN_H);
sdlTexture = SDL_CreateTexture(sdlRenderer,
SDL_PIXELFORMAT_RGB555,
SDL_PIXELFORMAT_BGR555,
SDL_TEXTUREACCESS_STREAMING,
160, 160);
SCREEN_W, SCREEN_H);
OR_DIE(sdlTexture == NULL);
SDL_AudioSpec audio_spec;
SDL_memset(&audio_spec, 0, sizeof(SDL_AudioSpec));
SDL_zero(audio_spec);
audio_spec.freq = BPS;
audio_spec.channels = 2;
audio_spec.samples = 512;
audio_spec.format = AUDIO_F32; // Problem with U8
audio_spec.format = AUDIO_F32; // Or AUDIO_S8. Problem with AUDIO_U8
audio_spec.callback = AudioCallback;
audio_spec.userdata = NULL;
//SDL_OpenAudio(&audio_spec, NULL);
SDL_AudioDeviceID devid = SDL_OpenAudioDevice(NULL, 0, &audio_spec, NULL, 0);
OR_DIE(devid == 0);
OR_DIE(SDL_OpenAudio(&audio_spec, NULL) == -1);
//SDL_AudioDeviceID devid = SDL_OpenAudioDevice(NULL, 0, &audio_spec, NULL, 0);
//OR_DIE(devid == 0);
if (argc <= 1) {
LoadROM("rom.sv");
}
else {
LoadROM(argv[1]);
printf("# Controls\n");
for (int i = 0; i < COUNT_OF(keysNames); i++) {
printf("%20s: %s\n", keysNames[i], SDL_GetScancodeName(keysMapping[i]));
}
printf("\n");
supervision_init();
supervision_load(buffer, bufferSize);
//SDL_PauseAudio(0);
SDL_PauseAudioDevice(devid, 0);
while (!paused) {
PollEvents();
HandleInput();
// Emulate
supervision_exec(screenBuffer);
// Draw
SDL_UpdateTexture(sdlTexture, NULL, screenBuffer, 160 * sizeof(uint16_t));
Render();
PushMenuState(MENUSTATE_DROP_ROM);
if (LoadROM(argc <= 1 ? "rom.sv" : argv[1]) == 0) {
LoadBuffer();
}
SDL_PauseAudio(0);
//SDL_PauseAudioDevice(devid, 0);
InitCounter();
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop(Loop, 0, 1);
#else
while (!done) {
Loop();
}
#endif
supervision_done();
//SDL_CloseAudio();
SDL_CloseAudioDevice(devid);
SDL_CloseAudio();
//SDL_CloseAudioDevice(devid);
SDL_DestroyTexture(sdlTexture);
SDL_DestroyRenderer(sdlRenderer);
@ -193,9 +446,11 @@ int main(int argc, char *argv[])
return 0;
}
#define FULLSCREEN_FLAG SDL_WINDOW_FULLSCREEN_DESKTOP
SDL_bool IsFullscreen(void)
{
return 0 != (SDL_GetWindowFlags(sdlScreen) & SDL_WINDOW_FULLSCREEN_DESKTOP);
return 0 != (SDL_GetWindowFlags(sdlScreen) & FULLSCREEN_FLAG);
}
void ToggleFullscreen(void)
@ -203,9 +458,11 @@ void ToggleFullscreen(void)
static int mouseX;
static int mouseY;
static SDL_bool cursorInWindow = SDL_FALSE;
#ifdef __EMSCRIPTEN__
return;
#endif
if (!IsFullscreen()) {
SDL_SetWindowFullscreen(sdlScreen, SDL_WINDOW_FULLSCREEN_DESKTOP);
SDL_SetWindowFullscreen(sdlScreen, FULLSCREEN_FLAG);
SDL_ShowCursor(SDL_DISABLE);
int x, y;
@ -219,7 +476,7 @@ void ToggleFullscreen(void)
SDL_SetWindowFullscreen(sdlScreen, 0);
SDL_ShowCursor(SDL_ENABLE);
// Don't move cursor
// Don't move cursor. Bug?
if (cursorInWindow) {
SDL_WarpMouseInWindow(sdlScreen, mouseX, mouseY);
}
@ -229,3 +486,187 @@ void ToggleFullscreen(void)
cursorInWindow = SDL_FALSE;
}
}
void InitCounter(void)
{
SDL_DisplayMode current;
SDL_GetCurrentDisplayMode(0, &current);
isRefreshRate60 = (current.refresh_rate == 60);
startCounter = SDL_GetPerformanceCounter();
}
SDL_bool NeedUpdate(void)
{
static double elapsedCounter = 0.0;
static SDL_bool result = SDL_FALSE;
if (isRefreshRate60) {
result = !result;
}
else {
// New frame
if (!result) {
uint64_t now = SDL_GetPerformanceCounter();
elapsedCounter += (double)((now - startCounter) * 1000) / SDL_GetPerformanceFrequency();
startCounter = now;
}
result = elapsedCounter >= 16.666;
if (result) {
elapsedCounter -= 16.666;
}
}
return result;
}
void DrawDropROM(void)
{
static uint8_t fade = 0;
char dropRom[] = {
"## ## ### ## ## ### # #"
"# # # # # # # # # # # # ###"
"# # ## # # ## ## # # # #"
"## # # ### # # # ### # #"
};
uint8_t f = (fade < 32) ? fade : 63 - fade;
uint16_t color = (f << 0) | (f << 5) | (f << 10);
fade = (fade + 1) % 64;
int width = 28, height = 4;
int scale = 4, start = (SCREEN_W - width * scale) / 2 + SCREEN_W * (SCREEN_H - height * scale) / 2;
for (int j = 0; j < height * scale; j++) {
for (int i = 0; i < width * scale; i++) {
if (dropRom[i / scale + width * (j / scale)] == '#')
screenBuffer[start + i + SCREEN_W * j] = color;
}
}
}
void PushMenuState(MenuState state)
{
if (nextMenuState == 0 || (nextMenuState > 0 && menuStates[nextMenuState - 1] != state)) {
menuStates[nextMenuState] = state;
nextMenuState++;
}
}
void PopMenuState(void)
{
if (nextMenuState > 0)
nextMenuState--;
}
MenuState GetMenuState(void)
{
if (nextMenuState > 0)
return menuStates[nextMenuState - 1];
return MENUSTATE_NONE;
}
// Emscripten
void UploadROM(void *newBuffer, int newBufferSize, const char *fileName)
{
bufferSize = newBufferSize;
if (buffer != NULL) {
free(buffer);
buffer = NULL;
}
buffer = (uint8_t *)malloc(bufferSize);
memcpy(buffer, newBuffer, bufferSize);
SetRomName(fileName);
LoadBuffer();
}
void Reset(void)
{
if (GetMenuState() == MENUSTATE_EMULATION)
LoadBuffer();
}
void NextPalette(void)
{
currentPalette = (currentPalette + 1) % COLOUR_SCHEME_COUNT;
supervision_set_colour_scheme(currentPalette);
}
void IncreaseWindowSize(void)
{
if (IsFullscreen())
return;
SDL_DisplayMode dm;
SDL_GetDesktopDisplayMode(0, &dm);
if (SCREEN_W * (windowScale + 1) <= dm.w && SCREEN_H * (windowScale + 1) <= dm.h) {
windowScale++;
SDL_SetWindowSize(sdlScreen, SCREEN_W * windowScale, SCREEN_H * windowScale);
}
}
void DecreaseWindowSize(void)
{
if (IsFullscreen())
return;
if (windowScale > 1) {
windowScale--;
SDL_SetWindowSize(sdlScreen, SCREEN_W * windowScale, SCREEN_H * windowScale);
}
}
void SaveState(void)
{
if (GetMenuState() == MENUSTATE_EMULATION)
supervision_save_state(romName, 0);
}
void LoadState(void)
{
if (GetMenuState() == MENUSTATE_EMULATION)
supervision_load_state(romName, 0);
}
void SetVolume(int volume)
{
if (volume < 0)
audioVolume = 0;
else if (volume > SDL_MIX_MAXVOLUME)
audioVolume = SDL_MIX_MAXVOLUME; // 128
else
audioVolume = volume;
}
void MuteAudio(void)
{
static int lastVolume = -1;
if (lastVolume == -1) {
lastVolume = audioVolume;
audioVolume = 0;
}
else {
audioVolume = lastVolume;
lastVolume = -1;
}
}
void IncreaseGhosting(void)
{
if (currentGhosting < GHOSTING_MAX) {
currentGhosting++;
supervision_set_ghosting(currentGhosting);
}
}
void DecreaseGhosting(void)
{
if (currentGhosting > 0) {
currentGhosting--;
supervision_set_ghosting(currentGhosting);
}
}
void SetKey(int button)
{
setButton = button;
PushMenuState(MENUSTATE_SET_KEY);
SDL_PauseAudio(1);
}

View File

@ -0,0 +1,10 @@
set(SDL2_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}/include")
# Support both 32 and 64 bit builds
if (${CMAKE_SIZEOF_VOID_P} EQUAL 8)
set(SDL2_LIBRARY "${CMAKE_CURRENT_LIST_DIR}/lib/x64/SDL2.lib;${CMAKE_CURRENT_LIST_DIR}/lib/x64/SDL2main.lib")
else ()
set(SDL2_LIBRARY "${CMAKE_CURRENT_LIST_DIR}/lib/x86/SDL2.lib;${CMAKE_CURRENT_LIST_DIR}/lib/x86/SDL2main.lib")
endif ()
string(STRIP "${SDL2_LIBRARY}" SDL2_LIBRARY)

View File

@ -260,7 +260,7 @@ LRESULT CALLBACK WndProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam)
}
loadROM(filename);
supervision_load((UINT8*)buffer, (UINT32)buffer_size);
supervision_load(&buffer, (UINT32)buffer_size);
execute=TRUE;
}
break;

View File

@ -0,0 +1,49 @@
# Emscripten makefile
# Based on https://github.com/aardappel/lobster/blob/master/dev/emscripten/Makefile
CC = emcc
# use -g4 for maximum debug info.
OPTLEVEL = -g4 -O0
CFLAGS = $(OPTLEVEL)
CFLAGS += -Wall -s USE_SDL=2
CSRCS = \
../SDL2/main.c \
../../common/m6502/m6502.c \
$(wildcard ../../common/*.c)
COBJS := $(patsubst %.c,%.o,$(CSRCS))
EXPORTED_FUNCTIONS = \
'_main', \
'_UploadROM', \
'_SaveState', \
'_LoadState', \
'_NextPalette', \
'_IncreaseWindowSize', \
'_DecreaseWindowSize', \
'_SetVolume', \
'_SetKey', \
'_MuteAudio', \
'_supervision_set_ghosting'
.PHONY: potator clean clean2 release all default
# add -s ASSERTIONS=2 when troubleshooting.
#EMCC_WASM_BACKEND=1
potator: $(COBJS)
$(CC) $(CFLAGS) $(COBJS) \
-s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall']" \
-s "EXPORTED_FUNCTIONS=[$(EXPORTED_FUNCTIONS)]" \
-o index.html --shell-file potator_shell.html
clean clean2:
-$(RM) $(COBJS)
release: OPTLEVEL = -O2
release: clean potator clean2
all: potator
default: all

View File

@ -0,0 +1,872 @@
<!doctype html>
<html lang="en-us">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Emscripten-Generated Code</title>
<style>
.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
textarea.emscripten { font-family: monospace; width: 80%; }
div.emscripten { text-align: center; }
div.emscripten_border { /*border: 1px solid black;*/ }
/* the canvas *must not* have any border or padding, or mouse coords will be wrong */
canvas.emscripten { border: 0px none; background-color: black; }
.spinner {
height: 50px;
width: 50px;
margin: 0px auto;
-webkit-animation: rotation .8s linear infinite;
-moz-animation: rotation .8s linear infinite;
-o-animation: rotation .8s linear infinite;
animation: rotation 0.8s linear infinite;
border-left: 10px solid rgb(0,150,240);
border-right: 10px solid rgb(0,150,240);
border-bottom: 10px solid rgb(0,150,240);
border-top: 10px solid rgb(100,0,200);
border-radius: 100%;
background-color: rgb(200,100,250);
}
@-webkit-keyframes rotation {
from {-webkit-transform: rotate(0deg);}
to {-webkit-transform: rotate(360deg);}
}
@-moz-keyframes rotation {
from {-moz-transform: rotate(0deg);}
to {-moz-transform: rotate(360deg);}
}
@-o-keyframes rotation {
from {-o-transform: rotate(0deg);}
to {-o-transform: rotate(360deg);}
}
@keyframes rotation {
from {transform: rotate(0deg);}
to {transform: rotate(360deg);}
}
body {
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
margin: 0px;
}
h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
}
.main-button, canvas {
image-rendering: pixelated;
image-rendering: crisp-edges;
image-rendering: -moz-crisp-edges;
image-rendering: -webkit-crisp-edges;
background-color: inherit;
}
.main-button {
width: 48px;
height: 36px;
box-sizing: content-box;
background-size: contain;
background-repeat: no-repeat;
background-origin: content-box;
border: 2px solid #aaa;
margin: 1px;
padding: 3px;
cursor: pointer;
}
.main-button:hover {
border-color: black;
}
#fileb {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAMCAMAAABcOc2zAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAB5QTFRFvALakJCQ2gIFAjraHtoC+MBI8IgAgq7I+Kgw////JqDhkgAAAAp0Uk5T////////////ALLMLM8AAABJSURBVHjaXI5BDoAwDMPC2s3t/z+MADFafLSiJJrRmFqzsbSCQlxijOHuhkl6BDhuZnoT7MQnuEXpoHb8VujHUHaRSo4CeQowALRTBDws+boxAAAAAElFTkSuQmCC');
}
#savestateb {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAMCAMAAABcOc2zAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAxQTFRFZmZmmZmZAAAA////ugmYcAAAAAR0Uk5T////AEAqqfQAAAA4SURBVHjajIvRCgAgCAO38///OVCLCB+ag9PhFCXoRZ8Bqfvj3EPFUdOweCT0GLOdULMCD5UlwADBvAGarVk1DAAAAABJRU5ErkJggg==');
}
#loadstateb {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAMCAMAAABcOc2zAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAxQTFRFZmZmmZmZAAAA////ugmYcAAAAAR0Uk5T////AEAqqfQAAAA5SURBVHjafIvRCgAwCAIv+/9/HizbQ2wzwTyQLEl+6N6E0014TH4gss4RaAgxrFB7B84CcZksAQYAwRIBmu1bY5MAAAAASUVORK5CYII=');
}
#muteb {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAMCAMAAABcOc2zAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAlQTFRFZmZmAAAA////UkbnHwAAAEJJREFUeNp0zgEKACAIA8DN/z86l8swaFDBsUrEE/jk2Q2kl4FkFQSsqDAhvoCMADDsz+tRNKSEKrowBsSdtLMEGAB8ZgExwXIzcQAAAABJRU5ErkJggg==');
}
#muteab {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAMCAMAAABcOc2zAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAlQTFRFZmZmAAAA////UkbnHwAAADBJREFUeNpiYEIDDFCaEU2AEU2AEVmAEQJIEWAAAkZ0QxnQBYAi6A5jQBeAA4AAAwCHogFSzpCvUAAAAABJRU5ErkJggg==')
}
#decreaseb {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAMCAMAAABcOc2zAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRFAAAAAAAApWe5zwAAAAJ0Uk5T/wDltzBKAAAAJElEQVR42mJgwACMMABmUUkA1XhkFYwYAiAOfhUYhoIBQIABADFuAHvO4HrHAAAAAElFTkSuQmCC');
}
#increaseb {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAMCAMAAABcOc2zAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRFAAAAAAAApWe5zwAAAAJ0Uk5T/wDltzBKAAAAJUlEQVR42mJgwACMMABmUUkA1XhkFWA1KAIgDn4VGIaCAUCAAQAxAAB51Z56IgAAAABJRU5ErkJggg==');
}
#nextpaletteb {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAMCAMAAABcOc2zAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABhQTFRFJEc7uc2rb6Bag71qMWJSABAY3vbNAAAAjQpJGgAAACdJREFUeNpiYIQCJihgYGSDAGYIoI4AAwsEsEIAO3UEoIAdCgACDAAfcgL57TF56gAAAABJRU5ErkJggg==');
}
#fullscreenb {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAMCAMAAABcOc2zAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRFAAAAAAAApWe5zwAAAAJ0Uk5T/wDltzBKAAAAIUlEQVR42mJggABGRgYGOBMZkCXAiAYwBahhC7rTAQIMADvEAJWS8qCXAAAAAElFTkSuQmCC');
}
.page-header {
display: inline-block;
padding-bottom: 9px;
margin: 40px 0 20px;
border-bottom: 2px solid #eee;
}
/* https://www.w3schools.com/cSS/css_tooltip.asp */
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 120px;
background-color: #444;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px;
position: absolute;
z-index: 1;
bottom: 110%;
left: 50%;
transform: translateX(-50%);
}
.tooltip .tooltiptext::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: #444 transparent transparent transparent;
}
.tooltip:hover .tooltiptext {
visibility: visible;
transition: visibility 0s linear 0.5s; /* https://stackoverflow.com/q/36242258 */
}
.tooltip .tooltipaudio {
visibility: hidden;
width: 120px;
background-color: #4449;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 6px;
position: absolute;
z-index: 1;
bottom: 100%;
left: 50%;
transform: rotate(-90deg) translateX(-21px);
transform-origin: 0px 20px;
height: 40px;
box-sizing: border-box;
}
.tooltip:hover .tooltipaudio {
visibility: visible;
}
.tooltipinfo {
border: 1px solid black;
border-radius: 100%;
width: 1em;
height: 1em;
text-align: center;
cursor: help;
}
/* https://www.w3schools.com/howto/howto_js_rangeslider.asp */
.slider {
-webkit-appearance: none;
width: 100%;
height: 10px;
border-radius: 5px;
background: #d3d3d3;
outline: none;
margin: 8px 0;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
/*box-sizing: content-box;*/
background-color: white;
width: 24px;
height: 24px;
border: 0px solid #888;
border-radius: 100%;
cursor: pointer;
}
.slider::-moz-range-thumb {
background-color: white;
width: 24px;
height: 24px;
border: 0px solid #888;
border-radius: 100%;
cursor: pointer;
}
.slider::-moz-range-track {
background-color: transparent;
}
#ghostslider::-webkit-slider-thumb {
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAxQTFRF////AAAAnJyc////cfZmagAAAAR0Uk5T////AEAqqfQAAABzSURBVHjanJFREgARDEOTuv+dt2ip4sPmo0PeKBWUizBWbNqAeqKaCO6LyQmSPwiyrySA1lys9iMGSNCrDFAtAF4DkD/gfIcsr+rPwjaGTeJzvAJeQDrCK1i+nSGSGJTuOmUCtW+lxYOfmRstMcGTPgEGAKVGBNcv/z1ZAAAAAElFTkSuQmCC');
}
#ghostslider::-moz-range-thumb {
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAxQTFRF////AAAAnJyc////cfZmagAAAAR0Uk5T////AEAqqfQAAABzSURBVHjanJFREgARDEOTuv+dt2ip4sPmo0PeKBWUizBWbNqAeqKaCO6LyQmSPwiyrySA1lys9iMGSNCrDFAtAF4DkD/gfIcsr+rPwjaGTeJzvAJeQDrCK1i+nSGSGJTuOmUCtW+lxYOfmRstMcGTPgEGAKVGBNcv/z1ZAAAAAElFTkSuQmCC');
}
#ghost {
box-sizing: border-box;
width: 158px;
height: 46px;
padding: 8px 4px;
margin: 1px;
border: 2px solid #aaa;
}
#ghost:hover {
border-color: black;
}
/* autohotkey.com */
kbd {
border: 1px solid #ccc;
border-radius: 3px;
box-shadow: 0 1px 0 rgba(0,0,0,.2),0 0 0 2px #fff inset;
color: #333;
display: inline-block;
font-family: Consolas,Courier New,monospace;
line-height: 1.4em;
margin: 0 .1em;
margin-top: 0px;
margin-bottom: 0px;
padding: 0 .5em;
text-shadow: 0 1px 0 #fff;
white-space: nowrap;
background-color: white;
}
table {
border-collapse: collapse;
width: 100%;
}
td, th {
border: 1px solid #dddddd;
text-align: left;
padding: 2px 8px;
}
.ps-bg {
display: inline-block;
background-color: #484848;
border-radius: 100%;
width: 20px;
height: 20px;
position: relative;
vertical-align: text-bottom;
}
.ps-square {
display: inline-block;
border: solid 2px #e398c9;
width: 8px;
height: 8px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.ps-cross {
display: inline-block;
width: 18px;
height: 18px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(45deg);
/* https://stackoverflow.com/a/17359874 */
background: linear-gradient(to bottom, transparent 44%,
#a2abd6 44%,
#a2abd6 56%,
transparent 56%),
linear-gradient(to right, transparent 44%,
#a2abd6 44%,
#a2abd6 56%,
transparent 56%);
}
.ps-select {
display: inline-block;
background-color: #484848;
border-radius: 2px;
width: 20px;
height: 12px;
}
.ps-start {
display: inline-block;
border-radius: 2px;
width: 0;
height: 0;
border-top: 6px solid transparent;
border-left: 20px solid #484848;
border-bottom: 6px solid transparent;
}
.ps-up, .ps-right, .ps-down, .ps-left {
display: inline-block;
width: 12px;
height: 12px;
background-color: #484848;
position: relative;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
transform: translateY(-2px);
}
.ps-right {
transform: rotate(90deg) translateY(-2px);
margin: 0 1px 0 2px;
}
.ps-down {
transform: rotate(180deg) translateY(-2px);
}
.ps-left {
transform: rotate(-90deg) translateY(-2px);
margin: 0 2px 0 1px;
}
.ps-up::after, .ps-right::after, .ps-down::after, .ps-left::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
margin-left: -50%;
border-width: 6px;
border-style: solid;
border-color: #484848 transparent transparent transparent;
}
#dropzone {
position: fixed;
left: 0px;
top: 0px;
z-index: 666;
width: 100%;
height: 100%;
background-color: rgb(0, 128, 192);
opacity: 0.5;
display: none;
}
.preview-td {
background-color: #111;
padding: 0px;
width: 0px;
text-align: center;
}
.preview-img {
width: 80px;
height: 80px;
vertical-align: middle;
}
.preview-img:hover {
transform: scale(2) translateX(-20px);
}
</style>
</head>
<body>
<div style="text-align: center;">
<h1 class="page-header">Potator - Watara Supervision Emulator</h1>
</div>
<div class="emscripten_border" style="position: relative; background: #111;" ondblclick="toggleFullscreen()">
<figure style="position: absolute; top: 50%; left: 50%; margin: 0; transform: translate(-50%, -50%);" id="spinner"><div class="spinner"></div></figure>
<canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()" width="640" height="640"></canvas>
</div>
<div class="emscripten" style="text-align: left; width: 640px; margin: 3px auto;">
<div class="tooltip" style="vertical-align: top;">
<label>
<div id="fileb" class="main-button"></div>
<input type="file" onchange="onChange(event)" accept=".bin, .sv, .ws" style="display: none;">
</label>
<span class="tooltiptext">Load ROM</span>
</div><!-- delete space
--><div class="tooltip" style="vertical-align: top;">
<button id="savestateb" class="main-button" onclick="Module.ccall('SaveState', 'void', ['void'])"></button>
<span class="tooltiptext">Save state <kbd>1</kbd></span>
</div><!-- delete space
--><div class="tooltip" style="vertical-align: top;">
<button id="loadstateb" class="main-button" onclick="Module.ccall('LoadState', 'void', ['void'])"></button>
<span class="tooltiptext">Load state <kbd>2</kbd></span>
</div><!-- delete space
--><div class="tooltip" id="ghost">
<input id="ghostslider" class="slider" type="range" min="0" max="8" value="0" step="1">
<span class="tooltiptext" style="width: 200px;">Ghosting (reduce flickering)</span>
</div>
<div style="float: right;">
<div class="tooltip">
<button id="muteb" class="main-button" onclick="this.id = (this.id=='muteb'?'muteab':'muteb'); Module.ccall('MuteAudio', 'void', ['void'])"></button>
<span class="tooltipaudio">
<input id="volumeslider" class="slider" type="range" min="0" max="128" value="128" step="1">
</span>
</div><!-- delete space
--><div class="tooltip">
<button id="nextpaletteb" class="main-button" onclick="Module.ccall('NextPalette', 'void', ['void'])"></button>
<span class="tooltiptext">Next palette <kbd>P</kbd></span>
</div><!-- delete space
--><div class="tooltip">
<button id="decreaseb" class="main-button" onclick="Module.ccall('DecreaseWindowSize', 'void', ['void'])"></button>
<span class="tooltiptext">Decrease window size <kbd>-_</kbd></span>
</div><!-- delete space
--><div class="tooltip">
<button id="increaseb" class="main-button" onclick="Module.ccall('IncreaseWindowSize', 'void', ['void'])"></button>
<span class="tooltiptext">Increase window size <kbd>=+</kbd></span>
</div><!-- delete space
--><div class="tooltip">
<button id="fullscreenb" class="main-button" onclick="Module.requestFullscreen(true, false)"></button>
<span class="tooltiptext">Fullscreen</span>
</div>
</div>
<h2>Controls</h2>
<table>
<tr>
<th>Action</th>
<th>Keyboard</th>
<th>Gamepad</th>
</tr>
<tr>
<td>Right</td>
<td><span class="keyName">Arrow Right</span><button style="float: right;" onclick="setKey(this, 0)">Change</button></td>
<td><span class="ps-right"></span>, Axis LeftX+</td>
</tr>
<tr>
<td>Left</td>
<td><span class="keyName">Arrow Left</span><button style="float: right;" onclick="setKey(this, 1)">Change</button></td>
<td><span class="ps-left"></span>, Axis LeftX-</td>
</tr>
<tr>
<td>Down</td>
<td><span class="keyName">Arrow Down</span><button style="float: right;" onclick="setKey(this, 2)">Change</button></td>
<td><span class="ps-down"></span>, Axis LeftY+</td>
</tr>
<tr>
<td>Up</td>
<td><span class="keyName">Arrow Up</span><button style="float: right;" onclick="setKey(this, 3)">Change</button></td>
<td><span class="ps-up"></span>, Axis LeftY-</td>
</tr>
<tr>
<td>B</td>
<td><span class="keyName">Key X</span><button style="float: right;" onclick="setKey(this, 4)">Change</button></td>
<td><span class="ps-bg" style="text-align: center;"><span style="color: #91c85c; vertical-align: middle;">A</span></span> / <span class="ps-bg"><span class="ps-cross"></span></span></td>
</tr>
<tr>
<td>A</td>
<td><span class="keyName">Key C</span><button style="float: right;" onclick="setKey(this, 5)">Change</button></td>
<td><span class="ps-bg" style="text-align: center;"><span style="color: #0098d9; vertical-align: middle;">X</span></span> / <span class="ps-bg"><span class="ps-square"></span></span></td>
</tr>
<tr>
<td>Select</td>
<td><span class="keyName">Key Z</span><button style="float: right;" onclick="setKey(this, 6)">Change</button></td>
<td>Back / <span class="ps-select"></span></td>
</tr>
<tr>
<td>Start</td>
<td><span class="keyName">Space</span><button style="float: right;" onclick="setKey(this, 7)">Change</button></td>
<td>Start / <span class="ps-start"></span></td>
</tr>
<tr>
<td></td>
<td><span class="keyName"></span></td>
<td></td>
</tr>
<tr>
<td>Reset</td>
<td><span class="keyName">Tab</span><button style="float: right;" onclick="setKey(this, 9)">Change</button></td>
<td></td>
</tr>
<tr>
<td>Save state</td>
<td><span class="keyName">Digit 1</span><button style="float: right;" onclick="setKey(this, 10)">Change</button></td>
<td>Left bumper / L1</td>
</tr>
<tr>
<td>Load state</td>
<td><span class="keyName">Digit 2</span><button style="float: right;" onclick="setKey(this, 11)">Change</button></td>
<td>Right bumper/ R1</td>
</tr>
<tr>
<td>Next palette</td>
<td><span class="keyName">Key P</span><button style="float: right;" onclick="setKey(this, 12)">Change</button></td>
<td></td>
</tr>
<tr>
<td>Window size -</td>
<td><span class="keyName">Minus</span><button style="float: right;" onclick="setKey(this, 13)">Change</button></td>
<td></td>
</tr>
<tr>
<td>Window size +</td>
<td><span class="keyName">Equal</span><button style="float: right;" onclick="setKey(this, 14)">Change</button></td>
<td></td>
</tr>
<tr>
<td>Ghosting -</td>
<td><span class="keyName">Bracket Left</span><button style="float: right;" onclick="setKey(this, 15)">Change</button></td>
<td></td>
</tr>
<tr>
<td>Ghosting +</td>
<td><span class="keyName">Bracket Right</span><button style="float: right;" onclick="setKey(this, 16)">Change</button></td>
<td></td>
</tr>
<tr>
<td>Mute</td>
<td><span class="keyName">Key M</span><button style="float: right;" onclick="setKey(this, 17)">Change</button></td>
<td></td>
</tr>
</table>
<h2>Savestates <span class="tooltip tooltipinfo" style="font-size: 0.7em;"><span style="font-weight: bolder;">!</span><span class="tooltiptext" style="width: 360px">The savestates exist in-memory. They are lost when the page is reloaded.</span></span></h2>
<div style="margin: 10px auto;">
<button onclick="refreshSavestates()">Refresh</button>
<div style="display: inline;">
<label for="ss_upload"><button onclick="parentNode.click()">Upload</button></label>
<input type="file" id="ss_upload" onchange="uploadSavestate(event)" style="display: none;">
</div>
<!-- Lost preview-img's hover -->
<div style="position: absolute; height: 10px; width: 80px; z-index: 1;"></div>
</div>
<table><tbody id="savestates"></tbody></table>
<h2>Information</h2>
Source code: <a href="https://github.com/infval/potator">GitHub</a>.
</div>
<div class="emscripten" id="status" style="position: fixed; top: 0px; background-color: #eee; padding: 8px;">Downloading...</div>
<div class="emscripten"><progress value="0" max="100" id="progress" style="display: none;"></progress></div>
<textarea class="emscripten" id="output" rows="8" style="display: none;"></textarea>
<script type='text/javascript'>
var statusElement = document.getElementById('status');
var progressElement = document.getElementById('progress');
var spinnerElement = document.getElementById('spinner');
var Module = {
preRun: [],
postRun: [],
print: (function() {
var element = document.getElementById('output');
if (element) element.value = ''; // clear browser cache
return function(text) {
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
// These replacements are necessary if you render to raw HTML
//text = text.replace(/&/g, "&amp;");
//text = text.replace(/</g, "&lt;");
//text = text.replace(/>/g, "&gt;");
//text = text.replace('\n', '<br>', 'g');
console.log(text);
if (element) {
element.value += text + "\n";
element.scrollTop = element.scrollHeight; // focus on bottom
}
};
})(),
printErr: function(text) {
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
if (0) { // XXX disabled for safety typeof dump == 'function') {
dump(text + '\n'); // fast, straight to the real console
} else {
console.error(text);
}
},
canvas: (function() {
var canvas = document.getElementById('canvas');
// As a default initial behavior, pop up an alert when webgl context is lost. To make your
// application robust, you may want to override this behavior before shipping!
// See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false);
return canvas;
})(),
setStatus: function(text) {
if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };
if (text === Module.setStatus.last.text) return;
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
var now = Date.now();
if (m && now - Module.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon
Module.setStatus.last.time = now;
Module.setStatus.last.text = text;
if (m) {
text = m[1];
progressElement.value = parseInt(m[2])*100;
progressElement.max = parseInt(m[4])*100;
progressElement.hidden = false;
spinnerElement.hidden = false;
} else {
progressElement.value = null;
progressElement.max = null;
progressElement.hidden = true;
if (!text) {
spinnerElement.style.display = 'none';
statusElement.style.display = 'none';
}
}
statusElement.innerHTML = text;
},
totalDependencies: 0,
monitorRunDependencies: function(left) {
this.totalDependencies = Math.max(this.totalDependencies, left);
Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
}
};
Module.setStatus('Downloading...');
window.onerror = function() {
Module.setStatus('Exception thrown, see JavaScript console');
spinnerElement.style.display = 'none';
Module.setStatus = function(text) {
if (text) Module.printErr('[post-exception status] ' + text);
};
};
</script>
<div id="dropzone"></div>
<script>
'use strict';
function onChange(event) {
handleFiles(event.target.files);
}
function handleFiles(files) {
var file = files[0];
var reader = new FileReader();
reader.onload = function(event) {
//var buffer = Module._malloc(event.target.result.byteLength);
//Module.writeArrayToMemory(new Uint8Array(event.target.result), buffer);
//Module.ccall('UploadROM', 'void', ['number', 'number'], [buffer, event.target.result.byteLength]);
//Module._free(buffer);
var arr = new Uint8Array(event.target.result);
Module.ccall('UploadROM', 'void', ['array', 'number', 'string'], [arr, arr.length, file.name]);
};
reader.readAsArrayBuffer(file);
}
var dropzone = document.getElementById("dropzone");
window.addEventListener("dragover", dragover, false);
dropzone.addEventListener("dragleave", dragleave, false);
dropzone.addEventListener("drop", drop, false);
function dragover(e) {
e.stopPropagation();
e.preventDefault();
if (e.target.id != "dropzone") {
dropzone.style.display = "block";
}
}
function dragleave(e) {
e.stopPropagation();
e.preventDefault();
if (e.target.id == "dropzone") {
dropzone.style.display = "none";
}
}
function drop(e) {
e.stopPropagation();
e.preventDefault();
dropzone.style.display = "none";
handleFiles(e.dataTransfer.files);
}
function createSlider(slider, oninput) {
slider.addEventListener("input", oninput, false);
slider.addEventListener("mouseup", (e) => {
e.target.blur(); // FIX (Firefox), https://github.com/emscripten-ports/SDL2/issues/41
}, false);
}
createSlider(document.getElementById("volumeslider"),
(e) => { Module.ccall('SetVolume', 'void', ['number'], [e.target.value]); });
createSlider(document.getElementById("ghostslider"),
(e) => { Module.ccall('supervision_set_ghosting', 'void', ['number'], [e.target.value]); });
var newButtonId = -1;
var ghostSlider = document.getElementById("ghostslider");
var muteButton = document.getElementById("muteb");
var keyCodes = [].map.call(document.getElementsByClassName("keyName"), (e) => e.textContent.replace(/ /g, ''));
document.addEventListener("keydown", (e) => {
switch (e.code) {
case keyCodes[15]:
ghostSlider.value -= 1;
break;
case keyCodes[16]:
ghostSlider.value -= -1;
break;
case keyCodes[17]:
muteButton.id = (muteButton.id == 'muteb' ? 'muteab' : 'muteb');
break;
}
endSetKey(e);
});
function setKey(element, buttonId) {
if (newButtonId == -1) {
element.textContent = "Press key";
newButtonId = buttonId;
Module.ccall('SetKey', 'void', ['int'], [newButtonId]);
pauseAudio(1);
}
}
function endSetKey(keyboardEvent) {
if (newButtonId != -1) {
let keyNames = document.getElementsByClassName("keyName");
if (keyboardEvent && keyboardEvent.code != "Escape") {
keyCodes[newButtonId] = keyboardEvent.code;
let newKeyName = keyboardEvent.code.replace(/([a-z])([\dA-Z])/g, '$1 $2');
keyNames[newButtonId].textContent = newKeyName;
}
keyNames[newButtonId].nextElementSibling.textContent = "Change";
newButtonId = -1;
if (keyboardEvent) pauseAudio(0);
}
}
window.addEventListener("blur", function() {
pauseAudio(1);
endSetKey();
}, false);
window.addEventListener("focus", function() {
pauseAudio(0);
}, false);
// FIX (Chrome, ...?), project.js -> ASM_CONSTS, https://github.com/emscripten-ports/SDL2/blob/master/src/audio/emscripten/SDL_emscriptenaudio.c
function pauseAudio(on) {
if (typeof SDL2 === 'undefined') return;
if (on) {
SDL2.audio.scriptProcessorNode.disconnect();
}
else {
SDL2.audio.scriptProcessorNode['connect'](SDL2.audioContext['destination']);
}
}
function refreshSavestates() {
var fileNames = FS.readdir(".").filter((e) => e.search(/\.svst$/) != -1);
var ss = document.getElementById("savestates");
ss.innerHTML = fileNames.map((e) =>
'<tr>\
<td>\
' + e + '<button onclick="saveFile(\'' + e + '\')" style="float: right;">Download</button>\
</td>\
</tr>'
).join('');
for (let i = 0; i < ss.children.length; i++) {
let telem = document.createElement('td');
let image = document.createElement('img');
telem.setAttribute("class", "preview-td");
image.setAttribute("class", "preview-img");
drawPreview(fileNames[i], image);
telem.appendChild(image);
ss.children[i].insertBefore(telem, ss.children[i].firstChild);
}
}
function saveFile(fileName) {
saveAs(new Blob([FS.readFile(fileName)], {type: 'application/octet-stream'}), fileName);
}
// https://stackoverflow.com/q/23451726
function saveAs(blob, fileName) {
var url = window.URL.createObjectURL(blob);
var anchorElem = document.createElement("a");
anchorElem.style = "display: none";
anchorElem.href = url;
anchorElem.download = fileName;
document.body.appendChild(anchorElem);
anchorElem.click();
document.body.removeChild(anchorElem);
// On Edge, revokeObjectURL should be called only after
// a.click() has completed, atleast on EdgeHTML 15.15048
setTimeout(function() {
window.URL.revokeObjectURL(url);
}, 1000);
}
function uploadSavestate(event) {
var file = event.target.files[0];
var reader = new FileReader();
reader.onload = function(event) {
FS.writeFile(file.name, new Uint8Array(event.target.result));
refreshSavestates();
};
reader.readAsArrayBuffer(file);
}
function drawPreview(fileName, dstImage) {
const WIDTH = 160, HEIGHT = 160;
function setPixel(imageData, x, y, r, g, b) {
var i = (x + y * d.width) * 4;
imageData.data[i+0] = r;
imageData.data[i+1] = g;
imageData.data[i+2] = b;
imageData.data[i+3] = 255;
}
function setPixelPalette(imageData, x, y, index) {
const c = [240, 160, 80, 0];
setPixel(imageData, x, y, c[index], c[index], c[index]);
}
var canvas = document.getElementById('canvas-preview');
var ctx = canvas.getContext('2d');
var d = ctx.createImageData(WIDTH, HEIGHT);
var f = FS.readFile(fileName);
var startRegs = 36 + 4;
var startUpperRam = startRegs + 0x2000 * 2;
var startVRAM = startUpperRam + f[startRegs + 2] / 4 + f[startRegs + 3] * 0x30;
for (let y = 0; y < HEIGHT; y++) {
for (let x = 0; x < WIDTH / 4; x++) {
let b = f[startVRAM + y*0x30 + x];
setPixelPalette(d, x*4 + 0, y, (b >> 0)&3);
setPixelPalette(d, x*4 + 1, y, (b >> 2)&3);
setPixelPalette(d, x*4 + 2, y, (b >> 4)&3);
setPixelPalette(d, x*4 + 3, y, (b >> 6)&3);
}
}
ctx.putImageData(d, 0, 0);
dstImage.src = canvas.toDataURL();
}
function toggleFullscreen() {
if (Browser.isFullscreen)
Module.canvas.exitFullscreen();
else
Module.requestFullscreen(true, false);
}
</script>
<canvas id="canvas-preview" width="160" height="160" style="border: 1px solid black; position: fixed; right: 0px; top: 0px; display: none;"></canvas>
<div style="height: 64px; margin-top: 32px; background-color: #fcfcfc; border-top: 2px solid #eee;">
</div>
{{{ SCRIPT }}}
</body>
</html>

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -167,7 +167,7 @@ unsigned char potatorLoadROM(char* filename) {
fread(rom_buffer, 1, rom_size, romfile);
fclose(romfile);
supervision_load(rom_buffer, rom_size);
supervision_load(&rom_buffer, rom_size);
// Compute game CRC
gameCRC = crc32(0, rom_buffer, rom_size);

View File

@ -911,7 +911,7 @@ void menuSaveState(void) {
strcpy(szFile, gameName);
strcpy(strrchr(szFile, '.'), ".sta");
print_string("Saving...", COLOR_OK, COLOR_BG, 8,240-5 -10*3);
sv_saveState(szFile,1);
supervision_save_state(szFile,1);
print_string("Save OK",COLOR_OK,COLOR_BG, 8+10*8,240-5 -10*3);
screen_flip();
screen_waitkey();
@ -926,7 +926,7 @@ void menuLoadState(void) {
strcpy(szFile, gameName);
strcpy(strrchr(szFile, '.'), ".sta");
print_string("Loading...", COLOR_OK, COLOR_BG, 8,240-5 -10*3);
sv_loadState(szFile,1);
supervision_load_state(szFile,1);
print_string("Load OK",COLOR_OK,COLOR_BG, 8+10*8,240-5 -10*3);
screen_flip();
screen_waitkey();