Use Emscripten’s shell-file feature

This commit is contained in:
Maximilian Mader 2019-06-07 13:35:17 +02:00
parent 3ad106d9c5
commit 7550707562
Signed by: Max
GPG Key ID: F71D56A3151C4FB3
15 changed files with 557 additions and 180 deletions

View File

@ -51,15 +51,14 @@ endif
ifeq ($(CONF),native_release)
override CONF := release
LDFLAGS += -march=native -mtune=native
CFLAGS += -march=native -mtune=native
endif
# Set compilation and linkage flags based on target, platform and configuration
CFLAGS += -Werror -Wall -Wno-strict-aliasing -Wno-unknown-warning -Wno-unknown-warning-option -Wno-multichar -Wno-int-in-bool-context -std=gnu11 -D_GNU_SOURCE -DVERSION="$(VERSION)" -I. -D_USE_MATH_DEFINES
# CFLAGS += -DGB_INTERNAL=1 # get access to internal APIs
CFLAGS += -I$(CORE_DIR)
CFLAGS += -s WASM=1 -s USE_SDL=2 --preload-file $(BOOTROMS_DIR)@/BootROMs
CFLAGS += -s WASM=1 -s USE_SDL=2 --preload-file $(BOOTROMS_DIR)@/BootROMs -s "EXTRA_EXPORTED_RUNTIME_METHODS=['FS']"
# CFLAGS += -Wcast-align -Wover-aligned -s SAFE_HEAP=1 -s WARN_UNALIGNED=1
WASM_LDFLAGS :=
@ -67,9 +66,11 @@ LDFLAGS += -lc -lm -ldl
CFLAGS += -Wno-deprecated-declarations
ifeq ($(CONF),debug)
CFLAGS += -g4 --profiling-funcs
CFLAGS += -g -g4
CFLAGS += --cpuprofiler --memoryprofiler
else ifeq ($(CONF), release)
CFLAGS += -O3 -DNDEBUG
CFLAGS += -O3 -DNDEBUG --emit-symbol-map
CFLAGS += --llvm-lto 3 # might be unstable
else
$(error Invalid value for CONF: $(CONF). Use "debug", "release" or "native_release")
endif
@ -82,9 +83,6 @@ bootroms: $(BOOTROMS_DIR)/agb_boot.bin \
$(BOOTROMS_DIR)/sgb_boot.bin \
$(BOOTROMS_DIR)/sgb2_boot.bin
wasm: bootroms $(BIN)/SameBoy.js
all: wasm
# Get a list of our source files and their respective object file targets
CORE_SOURCES_RAW := $(shell ls $(CORE_DIR)/Core/*.c)
@ -94,6 +92,12 @@ CORE_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(CORE_SOURCES))
WASM_SOURCES := $(shell ls *.c)
WASM_OBJECTS := $(patsubst %,$(OBJ)/%.o,$(WASM_SOURCES))
WEB_SOURCES := $(shell ls ressources/.)
WEB_OBJECTS := $(patsubst %,$(BIN)/ressources/%,$(WEB_SOURCES))
wasm: bootroms $(BIN)/index.html $(WEB_OBJECTS)
all: wasm
# Automatic dependency generation
ifneq ($(filter-out clean %.bin, $(MAKECMDGOALS)),)
@ -120,12 +124,21 @@ $(OBJ)/%.c.o: %.c
-@$(MKDIR) -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
$(BIN)/SameBoy.js: $(CORE_OBJECTS) $(WASM_OBJECTS)
$(BIN)/ressources/%:
-@$(MKDIR) -p $(dir $@)
cp -r web/* $(BIN)
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) $(WASM_LDFLAGS)
cp -a $(patsubst $(BIN)/%,%,$@) $@
$(BIN)/index.html: $(CORE_OBJECTS) $(WASM_OBJECTS) index-shell.html main.js
-@$(MKDIR) -p $(dir $@)
$(CC) \
$(CFLAGS) \
$(filter %.o, $^) \
-o $@ \
--shell-file "index-shell.html" \
--post-js "main.js" \
$(LDFLAGS) \
$(WASM_LDFLAGS)
ifeq ($(CONF), release)
strip $@
endif
$(CORE_DIR)/build/bin/BootROMs/%_boot.bin:

176
wasm/index-shell.html Normal file
View File

@ -0,0 +1,176 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"/>
<title>SameBoy</title>
<style>
body {
margin: 0;
padding: 0;
}
.center {
text-align: center;
}
.canvas-wrapper {
border: 1px solid black;
display: inline-block;
}
/* the canvas *must not* have any border or padding, or mouse coords will be wrong */
.canvas-wrapper > canvas {
border: 0px none;
background-color: black;
display: block;
}
.crisp {
image-rendering: optimizeSpeed;
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: optimize-contrast;
image-rendering: crisp-edges;
image-rendering: pixelated;
-ms-interpolation-mode: nearest-neighbor;
}
#end {
display: none;
}
/* if an element has been placed after the end marker it is the CPU profiler */
#end + div > div {
margin-bottom: 10px;
margin-left: 5px;
margin-right: 5px;
margin-top: 5px;
}
/* if an element has been placed after the CPU profiler it is the memory profiler */
#end + div + div > div {
margin-bottom: 10px;
margin-left: 5px;
margin-right: 5px;
margin-top: 5px;
}
</style>
</head>
<body>
<ul id="demo-roms">
<li><a href="ressources/demos/oh.gb">Oh!</a></li>
<li><a href="ressources/demos/pocket.gb">Is that a demo in your pocket?</a></li>
<li><a href="ressources/demos/gejmboj.gb">Gejmboj</a></li>
<li><a href="ressources/demos/video.gbc">GBVP1</a></li>
<li><a href="ressources/demos/mezase.gbc">GBVP2</a></li>
<li><a href="ressources/demos/pht-mr.gbc">Mental Respirator</a></li>
<li><a href="ressources/demos/pht-pz.gbc">It came from planet Zilog</a></li>
</ul>
<input type="file" id="file" name="file" accept=".gb,.gbc,.bin">
<hr/>
<div class="center">
<div id="status">Downloading...</div>
<progress value="0" max="100" id="progress" hidden=1></progress>
</div>
<div class="center">
<div class="canvas-wrapper">
<canvas class="crisp" id="canvas" oncontextmenu="event.preventDefault()"></canvas>
</div>
</div>
<hr/>
<div class="center">
<input type="checkbox" id="resize">Resize canvas
<input type="checkbox" id="pointerLock" checked>Lock/hide mouse pointer
&nbsp;&nbsp;&nbsp;
<input type="button" value="Fullscreen" onclick="Module.requestFullscreen(document.querySelector('#pointerLock').checked, document.querySelector('#resize').checked)">
</div>
<script type='text/javascript'>
const statusElement = document.querySelector('#status');
const progressElement = document.querySelector('#progress');
var Module = {
sync_fs: function () {
console.log("Syncing file system ...");
FS.syncfs(function (err) {
if (!err) {
console.log("File system synchronized.")
}
else {
console.error(err);
}
});
},
preRun: [],
postRun: [],
print: function () {
const text = Array.prototype.slice.call(arguments).join(' ');
console.log(text);
},
printErr: function() {
const text = Array.prototype.slice.call(arguments).join(' ');
console.error(text);
},
canvas: (function() {
const canvas = document.querySelector('#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;
const m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
const 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;
}
else {
progressElement.value = null;
progressElement.max = null;
progressElement.hidden = true;
}
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');
Module.setStatus = function(text) {
if (text) Module.printErr('[post-exception status] ' + text);
};
};
</script>
{{{ SCRIPT }}}
<a href="#bottom" id="end"></a>
</body>
</html>

View File

@ -1,62 +1,20 @@
#include <emscripten.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_opengl.h>
#include <SDL2/SDL_video.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <Core/gb.h>
const char *resource_folder(void)
{
#ifdef DATA_DIR
return DATA_DIR;
#else
static const char *ret = NULL;
if (!ret) {
ret = SDL_GetBasePath();
if (!ret) {
ret = "./";
}
}
return ret;
#endif
}
char *resource_path(const char *filename)
{
static char path[1024];
snprintf(path, sizeof(path), "%s%s", resource_folder(), filename);
return path;
}
char* concat(const char *s1, const char *s2)
{
char *result = malloc(strlen(s1) + strlen(s2) + 1); // +1 for the null-terminator
if (!result) {
fprintf(stderr, "Failed to allocate memory\n");
exit(EXIT_FAILURE);
}
strcpy(result, s1);
strcat(result, s2);
return result;
}
#include "main.h"
#include "utils.h"
GB_gameboy_t gb;
#define VIDEO_WIDTH 160
#define VIDEO_HEIGHT 144
#define VIDEO_PIXELS (VIDEO_WIDTH * VIDEO_HEIGHT)
#define SGB_VIDEO_WIDTH 256
#define SGB_VIDEO_HEIGHT 224
#define SGB_VIDEO_PIXELS (SGB_VIDEO_WIDTH * SGB_VIDEO_HEIGHT)
#define FRAME_RATE 0 // let the browser schedule (usually 60 FPS), if absolutely needed define as (0x400000 / 70224.0)
SDL_Window *window;
SDL_Renderer *renderer;
SDL_Surface *screen;
SDL_Texture *texture;
SDL_PixelFormat *pixel_format;
SDL_AudioDeviceID device_id;
@ -65,45 +23,7 @@ static SDL_AudioSpec want_aspec, have_aspec;
static uint32_t pixel_buffer_1[256 * 224], pixel_buffer_2[256 * 224];
static uint32_t *active_pixel_buffer = pixel_buffer_1;
static uint32_t *previous_pixel_buffer = pixel_buffer_2;
signed short soundbuf[1024 * 2];
typedef enum {
JOYPAD_AXISES_X,
JOYPAD_AXISES_Y,
JOYPAD_AXISES_MAX
} joypad_axis_t;
typedef struct {
SDL_Scancode keys[9];
GB_color_correction_mode_t color_correction_mode;
bool blend_frames;
GB_highpass_mode_t highpass_mode;
char filter[32];
enum {
MODEL_DMG,
MODEL_CGB,
MODEL_AGB,
MODEL_SGB,
MODEL_MAX,
} model;
/* v0.11 */
uint32_t rewind_length;
SDL_Scancode keys_2[32]; /* Rewind and underclock, + padding for the future */
uint8_t joypad_configuration[32]; /* 12 Keys + padding for the future*/;
uint8_t joypad_axises[JOYPAD_AXISES_MAX];
/* v0.12 */
enum {
SGB_NTSC,
SGB_PAL,
SGB_2,
SGB_MAX
} sgb_revision;
} configuration_t;
static char *battery_save_path_ptr;
configuration_t configuration =
{
@ -147,11 +67,22 @@ configuration_t configuration =
.model = MODEL_CGB
};
// Use this function instead of GB_save_battery()
int save_battery(GB_gameboy_t *gb, const char *path) {
int result = GB_save_battery(gb, path);
fprintf(stderr, "Saving battery: \"%s\": %d\n", path, result);
EM_ASM(Module.sync_fs());
return result;
}
unsigned query_sample_rate_of_audiocontexts() {
return EM_ASM_INT({
var AudioContext = window.AudioContext || window.webkitAudioContext;
var ctx = new AudioContext();
var sr = ctx.sampleRate;
const AudioContext = window.AudioContext || window.webkitAudioContext;
const ctx = new AudioContext();
const sr = ctx.sampleRate;
ctx.close();
return sr;
});
@ -177,6 +108,18 @@ void render_texture(void *pixels, void *previous)
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
}
/*else {
static void *_pixels = NULL;
if (pixels) {
_pixels = pixels;
}
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
render_bitmap_with_shader(&shader, _pixels, previous,
GB_get_screen_width(&gb), GB_get_screen_height(&gb),
rect.x, rect.y, rect.w, rect.h);
SDL_GL_SwapWindow(window);
}*/
}
static void handle_events(GB_gameboy_t *gb) {
@ -184,7 +127,7 @@ static void handle_events(GB_gameboy_t *gb) {
}
static void vblank(GB_gameboy_t *gb) {
if (1 == 0) {
if (configuration.blend_frames) {
render_texture(active_pixel_buffer, previous_pixel_buffer);
uint32_t *temp = active_pixel_buffer;
active_pixel_buffer = previous_pixel_buffer;
@ -203,7 +146,7 @@ static uint32_t rgb_encode(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b)
return SDL_MapRGB(pixel_format, r, g, b);
}
void init() {
void init_gb() {
GB_model_t model;
model = (GB_model_t [])
@ -273,13 +216,8 @@ void init() {
error = GB_load_boot_rom(&gb, boot_rom_path);
}
void run() {
GB_run_frame(&gb);
}
int main(int argc, char **argv)
{
#define str(x) #x
int EMSCRIPTEN_KEEPALIVE init() {
#define str(x) #x
#define xstr(x) str(x)
pixel_format = (SDL_PixelFormat *) malloc(sizeof(SDL_PixelFormat));
@ -303,7 +241,7 @@ int main(int argc, char **argv)
SDL_WINDOWPOS_UNDEFINED,
VIDEO_WIDTH,
VIDEO_HEIGHT,
SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI
SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALLOW_HIGHDPI
);
if (!window) {
@ -312,6 +250,7 @@ int main(int argc, char **argv)
}
SDL_SetWindowMinimumSize(window, VIDEO_WIDTH, VIDEO_HEIGHT);
SDL_SetWindowMaximumSize(window, VIDEO_WIDTH, VIDEO_HEIGHT);
renderer = SDL_CreateRenderer(
window,
@ -324,7 +263,7 @@ int main(int argc, char **argv)
return EXIT_FAILURE;
}
SDL_Surface *screen = SDL_CreateRGBSurface(
screen = SDL_CreateRGBSurface(
0,
VIDEO_WIDTH,
VIDEO_HEIGHT,
@ -363,23 +302,63 @@ int main(int argc, char **argv)
fprintf(stderr, "Failed to open audio: %s", SDL_GetError());
}
init();
EM_ASM({
var AudioContext = window.AudioContext || window.webkitAudioContext;
var ctx = new AudioContext();
// unlock audio for iOS
if (ctx && ctx.currentTime == 0) {
var buffer = ctx.createBuffer(1, 1, 22050);
var source = ctx.createBufferSource();
source.buffer = buffer;
source.connect(ctx.destination);
source.start(0);
}
// Google audio enable work-around:
// https://github.com/emscripten-ports/SDL2/issues/57
try {
if (!Module.SDL2 || !ctx || !ctx.resume) return;
ctx.resume();
} catch (err) {}
ctx.close();
});
init_gb();
SDL_PauseAudioDevice(device_id, 0);
emscripten_set_main_loop(
run, // our main loop
FRAME_RATE,
true // infinite loop
);
return EXIT_SUCCESS;
}
int EMSCRIPTEN_KEEPALIVE load_rom(char* filename, char* battery_save_path) {
int result = GB_load_rom(&gb, filename);
if (result == 0) {
battery_save_path_ptr = battery_save_path;
GB_load_battery(&gb, battery_save_path);
save_battery(&gb, battery_save_path_ptr);
}
return result;
}
void EMSCRIPTEN_KEEPALIVE quit() {
fprintf(stderr, "Quitting ...\n");
emscripten_set_main_loop(NULL, 0, false);
GB_free(&gb);
SDL_FreeSurface(screen);
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return EXIT_SUCCESS;
}
void EMSCRIPTEN_KEEPALIVE run_frame() {
GB_run_frame(&gb);
}

49
wasm/main.h Normal file
View File

@ -0,0 +1,49 @@
#ifndef main_h
#define main_h
#define VIDEO_WIDTH 160
#define VIDEO_HEIGHT 144
#define VIDEO_PIXELS (VIDEO_WIDTH * VIDEO_HEIGHT)
#define SGB_VIDEO_WIDTH 256
#define SGB_VIDEO_HEIGHT 224
#define SGB_VIDEO_PIXELS (SGB_VIDEO_WIDTH * SGB_VIDEO_HEIGHT)
typedef enum {
JOYPAD_AXISES_X,
JOYPAD_AXISES_Y,
JOYPAD_AXISES_MAX
} joypad_axis_t;
typedef struct {
SDL_Scancode keys[9];
GB_color_correction_mode_t color_correction_mode;
bool blend_frames;
GB_highpass_mode_t highpass_mode;
char filter[32];
enum {
MODEL_DMG,
MODEL_CGB,
MODEL_AGB,
MODEL_SGB,
MODEL_MAX,
} model;
/* v0.11 */
uint32_t rewind_length;
SDL_Scancode keys_2[32]; /* Rewind and underclock, + padding for the future */
uint8_t joypad_configuration[32]; /* 12 Keys + padding for the future*/;
uint8_t joypad_axises[JOYPAD_AXISES_MAX];
/* v0.12 */
enum {
SGB_NTSC,
SGB_PAL,
SGB_2,
SGB_MAX
} sgb_revision;
} configuration_t;
#endif /* main_h */

150
wasm/main.js Normal file
View File

@ -0,0 +1,150 @@
const frame_rate = (0x400000 / 70224.0);
const ms_per_frame = 1000 / frame_rate;
let last_frame_time = 0;
const stringHash = str => {
let hash = 0;
if (str.length === 0) return hash;
for (let i = 0; i < str.length; i++) {
let chr = str.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
}
const run_frame = time => {
window.requestAnimationFrame(run_frame);
const delta = time - last_frame_time;
if (delta > ms_per_frame) {
Module._run_frame();
last_frame_time = time - (delta % ms_per_frame);
}
}
const loadRomFromMemory = (name, data) => {
const pos = name.lastIndexOf('.');
const battery_name = name.substr(0, pos < 0 ? name.length : pos) + '.sav';
try {
// try to create the virtual ROM folder
FS.mkdir('/rom');
} catch (e) { }
try {
// try to delete all previous ROM files
for (let file of FS.readdir('/rom').filter(f => f != '.' && f != '..')) {
FS.unlink(`/rom/${file}`)
}
} catch (e) { }
// create a new virtual file from memory
Module['FS_createDataFile']('/rom/', name, new Uint8Array(data), true, true);
const rom_path = allocate(intArrayFromString(`/rom/${name}`), 'i8', ALLOC_NORMAL);
const battery_path = allocate(intArrayFromString(`/persist/${battery_name}`), 'i8', ALLOC_NORMAL);
Module._load_rom(rom_path, battery_path);
// The ROM has been read into memory, we can unlink the file now
FS.unlink(`/rom/${name}`)
window.requestAnimationFrame(run_frame)
}
const loadROM = f => {
const reader = new FileReader();
reader.onload = (file => {
return event => {
loadRomFromMemory(file.name, event.target.result)
};
})(f);
reader.readAsArrayBuffer(f);
}
const loadRemoteRom = url => {
const request = new Request(url);
const name = (_ => {
const name = url.substring(url.lastIndexOf('/') + 1);
if (name.endsWith('.gb') || name.endsWith('.gbc')) {
return name
}
else if (name.length) {
return `${name}.gb`
}
return stringHash(url)
})()
return fetch(request).then(response => {
if (!response.ok) {
throw new Error('HTTP error, status = ' + response.status);
}
return response.arrayBuffer();
}).then(buf => {
loadRomFromMemory(name, buf)
})
}
const handleFileSelect = (evt, files) => {
evt.stopPropagation();
evt.preventDefault();
if (files.length) {
loadROM(files[0]);
}
}
const handleDragOver = evt => {
evt.stopPropagation();
evt.preventDefault();
evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
}
window.addEventListener('dragover', handleDragOver, false);
window.addEventListener('drop', e => {
handleFileSelect(e, e.dataTransfer.files);
}, false);
document.getElementById('file').addEventListener('change', e => {
handleFileSelect(e, e.target.files);
}, false);
Module.onRuntimeInitialized = _ => {
FS.mkdir('/persist');
FS.mount(IDBFS, { }, '/persist');
FS.syncfs(true, function (err) {
if (!err) {
console.log('Successfully loaded FS from persistent storage')
}
else {
console.error(err)
}
// Call the exported init function
Module._init();
})
};
const romClickHandler = event => {
event.stopPropagation();
event.preventDefault();
loadRemoteRom(event.target.href);
}
for (const anchor of document.querySelectorAll('#demo-roms a')) {
anchor.addEventListener('click', romClickHandler);
}

Binary file not shown.

Binary file not shown.

BIN
wasm/ressources/demos/oh.gb Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

59
wasm/utils.c Normal file
View File

@ -0,0 +1,59 @@
#include <SDL2/SDL.h>
#include <stdio.h>
#include <string.h>
#include "utils.h"
const char *resource_folder(void)
{
#ifdef DATA_DIR
return DATA_DIR;
#else
static const char *ret = NULL;
if (!ret) {
ret = SDL_GetBasePath();
if (!ret) {
ret = "./";
}
}
return ret;
#endif
}
char *resource_path(const char *filename)
{
static char path[1024];
snprintf(path, sizeof(path), "%s%s", resource_folder(), filename);
return path;
}
void replace_extension(const char *src, size_t length, char *dest, const char *ext)
{
memcpy(dest, src, length);
dest[length] = 0;
/* Remove extension */
for (size_t i = length; i--;) {
if (dest[i] == '/') break;
if (dest[i] == '.') {
dest[i] = 0;
break;
}
}
/* Add new extension */
strcat(dest, ext);
}
char* concat(const char *s1, const char *s2)
{
char *result = malloc(strlen(s1) + strlen(s2) + 1); // +1 for the null-terminator
if (!result) {
fprintf(stderr, "Failed to allocate memory\n");
exit(EXIT_FAILURE);
}
strcpy(result, s1);
strcat(result, s2);
return result;
}

10
wasm/utils.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef utils_h
#define utils_h
#include <stddef.h>
const char *resource_folder(void);
char *resource_path(const char *filename);
void replace_extension(const char *src, size_t length, char *dest, const char *ext);
char* concat(const char *s1, const char *s2);
#endif /* utils_h */

View File

@ -1,59 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>SameBoy</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
var Module = {
canvas: document.querySelector('#canvas')
};
</script>
<script src="SameBoy.js"></script>
<script>
const loadROM = f => {
const reader = new FileReader();
reader.onload = (file => {
return e => {
Module.load_rom(romName, e.target.result);
};
})(f);
reader.readAsArrayBuffer(f);
}
const handleFileSelect = (evt, files) => {
evt.stopPropagation();
evt.preventDefault();
if (files.length) {
load_rom(files[0]);
}
}
const handleDragOver = evt => {
evt.stopPropagation();
evt.preventDefault();
evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
}
window.addEventListener('dragover', handleDragOver, false);
window.addEventListener('drop', e => {
handleFileSelect(e, e.dataTransfer.files);
}, false);
document.getElementById('file').addEventListener('change', e => {
handleFileSelect(e, e.target.files);
}, false);
Module.onRuntimeInitialized = _ => {
console.log(Module)
};
</script>
</body>
</html>