-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for emulation in Genesis Plus GX and emscripten
Also fixes bugs in preceding refactors
- Loading branch information
1 parent
e1fcdcd
commit 94f7b3c
Showing
6 changed files
with
642 additions
and
173 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
commit 4c23028aa3a829d67910dba2ab9acff6e1a9c603 | ||
commit 169ef692633bd8b89ff0f714b6606dbe5a494889 | ||
Author: Joey Parrish <[email protected]> | ||
Date: Wed Mar 27 07:39:28 2024 -0700 | ||
|
||
|
@@ -21,7 +21,7 @@ index 0000000..8f39f7f | |
+zdis | ||
+kinetoscope | ||
diff --git a/Makefile b/Makefile | ||
index 0dddc9f..dbe9c28 100644 | ||
index 0dddc9f..9a776a5 100644 | ||
--- a/Makefile | ||
+++ b/Makefile | ||
@@ -23,11 +23,11 @@ ifeq ($(CPU),i686) | ||
|
@@ -105,7 +105,7 @@ index 0dddc9f..dbe9c28 100644 | |
%.o : %.m | ||
$(CC) $(CFLAGS) -c -o $@ $< | ||
|
||
+kinetoscope.o : kinetoscope/emulator-patches/kinetoscope.c | ||
+kinetoscope.o : kinetoscope/emulator-patches/kinetoscope.c kinetoscope/emulator-patches/fetch.c | ||
+ $(CC) $(CFLAGS) -c -o $@ $< | ||
+ | ||
%.png : %.xcf | ||
|
@@ -278,7 +278,7 @@ index c91273c..f637add 100755 | |
|
||
def gchannel(Val): | ||
diff --git a/romdb.c b/romdb.c | ||
index 79afa76..f7ca40d 100644 | ||
index 79afa76..b5e7c9a 100644 | ||
--- a/romdb.c | ||
+++ b/romdb.c | ||
@@ -14,6 +14,7 @@ | ||
|
@@ -289,7 +289,7 @@ index 79afa76..f7ca40d 100644 | |
|
||
#define DOM_TITLE_START 0x120 | ||
#define DOM_TITLE_END 0x150 | ||
@@ -338,6 +339,46 @@ void add_memmap_header(rom_info *info, uint8_t *rom, uint32_t size, memmap_chunk | ||
@@ -338,6 +339,45 @@ void add_memmap_header(rom_info *info, uint8_t *rom, uint32_t size, memmap_chunk | ||
warning("ROM uses MegaWiFi, but it is disabled\n"); | ||
} | ||
return; | ||
|
@@ -304,6 +304,8 @@ index 79afa76..f7ca40d 100644 | |
+ info->map[0].end = 0x400000; | ||
+ info->map[0].mask = 0x1FFFFF; | ||
+ info->map[0].flags = MMAP_READ; | ||
+ // Returns the address of the buffer that emulates the SRAM banks (2MB). | ||
+ info->map[0].buffer = kinetoscope_init(); | ||
+ | ||
+ // In hardware, this whole range will trigger the /TIME signal. | ||
+ info->map[1].start = 0xA13000; | ||
|
@@ -316,6 +318,11 @@ index 79afa76..f7ca40d 100644 | |
+ // 0x...40, ... would all map to the same port. | ||
+ info->map[1].mask = 0x00001F; | ||
+ | ||
+ info->map[1].write_16 = kinetoscope_write_16; | ||
+ info->map[1].write_8 = kinetoscope_write_8; | ||
+ info->map[1].read_16 = kinetoscope_read_16; | ||
+ info->map[1].read_8 = kinetoscope_read_8; | ||
+ | ||
+ info->map[2].start = 0x000000; | ||
+ info->map[2].end = 0x200000; | ||
+ if (rom_end < info->map[2].end) { | ||
|
@@ -324,14 +331,6 @@ index 79afa76..f7ca40d 100644 | |
+ info->map[2].mask = 0x1FFFFF; | ||
+ info->map[2].flags = MMAP_READ; | ||
+ info->map[2].buffer = rom; | ||
+ | ||
+ info->map[1].write_16 = kinetoscope_write_16; | ||
+ info->map[1].write_8 = kinetoscope_write_8; | ||
+ info->map[1].read_16 = kinetoscope_read_16; | ||
+ info->map[1].read_8 = kinetoscope_read_8; | ||
+ | ||
+ // Returns the address of the buffer that emulates the SRAM banks (2MB). | ||
+ info->map[0].buffer = kinetoscope_init(); | ||
+ return; | ||
} else if (has_ram_header(rom, size)) { | ||
uint32_t ram_start = read_ram_header(info, rom); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
// Kinetoscope: A Sega Genesis Video Player | ||
// | ||
// Copyright (c) 2024 Joey Parrish | ||
// | ||
// See MIT License in LICENSE.txt | ||
|
||
// Emulation of Kinetoscope video streaming hardware. | ||
// Separate implementation of fetching, with Curl+pthread for native and with | ||
// emscripten_fetch for web. | ||
// An emscripten build requires -s FETCH=1 at link time. | ||
|
||
#if defined(__EMSCRIPTEN__) | ||
# include <emscripten/fetch.h> | ||
#else | ||
# include <curl/curl.h> | ||
# include <pthread.h> | ||
#endif | ||
|
||
#include <stdint.h> | ||
#include <stdio.h> | ||
|
||
// bytes written buffer, num_things, thing_size, context | ||
typedef size_t (*WriteCallback)(char*, size_t, size_t, void *); | ||
// ok, status, context | ||
typedef void (*DoneCallback)(bool, int, void *); | ||
// terminated error string | ||
typedef void (*ReportError)(const char* buf); | ||
|
||
typedef struct FetchContext { | ||
void* user_ctx; | ||
char* url; | ||
WriteCallback write_callback; | ||
DoneCallback done_callback; | ||
#if !defined(__EMSCRIPTEN__) | ||
CURL* handle; | ||
#endif | ||
} FetchContext; | ||
|
||
#if defined(__EMSCRIPTEN__) | ||
static void fetch_with_emscripten_success(emscripten_fetch_t* fetch) { | ||
FetchContext* ctx = (FetchContext*)fetch->userData; | ||
|
||
int http_code = fetch->status; | ||
printf("Kinetoscope: url = %s, http status = %d\n", ctx->url, http_code); | ||
|
||
bool ok = http_code == 200 || http_code == 206; | ||
if (ok) { | ||
ctx->write_callback((char*)fetch->data, fetch->numBytes, 1, ctx->user_ctx); | ||
} | ||
|
||
if (ctx->done_callback) { | ||
ctx->done_callback(ok, http_code, ctx->user_ctx); | ||
} | ||
|
||
free(ctx->url); | ||
free(ctx); | ||
emscripten_fetch_close(fetch); | ||
} | ||
|
||
static void fetch_with_emscripten_error(emscripten_fetch_t* fetch) { | ||
FetchContext* ctx = (FetchContext*)fetch->userData; | ||
|
||
printf("Kinetoscope: url = %s, error!\n", ctx->url); | ||
ctx->done_callback(/* ok= */ false, /* http_code= */ 0, ctx->user_ctx); | ||
|
||
free(ctx->url); | ||
free(ctx); | ||
emscripten_fetch_close(fetch); | ||
} | ||
#else | ||
static void* fetch_with_curl_in_pthread(void* thread_ctx) { | ||
FetchContext* ctx = (FetchContext*)thread_ctx; | ||
|
||
CURLcode res = curl_easy_perform(ctx->handle); | ||
long http_code = 0; | ||
curl_easy_getinfo(ctx->handle, CURLINFO_RESPONSE_CODE, &http_code); | ||
curl_easy_cleanup(ctx->handle); | ||
|
||
printf("Kinetoscope: url = %s, CURLcode = %d, http status = %ld\n", | ||
ctx->url, res, http_code); | ||
if (res != CURLE_OK) { | ||
printf("Curl error: %s\n", curl_easy_strerror(res)); | ||
} | ||
|
||
bool ok = res == CURLE_OK && (http_code == 200 || http_code == 206); | ||
ctx->done_callback(ok, http_code, ctx->user_ctx); | ||
|
||
free(ctx->url); | ||
free(ctx); | ||
pthread_exit(NULL); | ||
return NULL; | ||
} | ||
#endif | ||
|
||
static void fetch_range_async(const char* url, size_t first_byte, size_t size, | ||
WriteCallback write_callback, | ||
DoneCallback done_callback, | ||
void* user_ctx) { | ||
char range[32]; | ||
bool use_range = false; | ||
if (size != (size_t)-1) { | ||
size_t last_byte = first_byte + size - 1; | ||
snprintf(range, 32, "%zd-%zd", first_byte, last_byte); | ||
// snprintf doesn't guarantee a terminator when it overflows. | ||
range[31] = '\0'; | ||
use_range = true; | ||
} | ||
|
||
FetchContext* ctx = (FetchContext*)malloc(sizeof(FetchContext)); | ||
ctx->user_ctx = user_ctx; | ||
ctx->url = strdup(url); | ||
ctx->write_callback = write_callback; | ||
ctx->done_callback = done_callback; | ||
|
||
#if !defined(__EMSCRIPTEN__) | ||
CURL* handle = curl_easy_init(); | ||
ctx->handle = handle; | ||
|
||
curl_easy_setopt(handle, CURLOPT_URL, url); | ||
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_callback); | ||
curl_easy_setopt(handle, CURLOPT_WRITEDATA, ctx); | ||
if (use_range) { | ||
curl_easy_setopt(handle, CURLOPT_RANGE, range); | ||
} | ||
|
||
pthread_t thread; | ||
pthread_create(&thread, NULL, fetch_with_curl_in_pthread, ctx); | ||
#else | ||
emscripten_fetch_attr_t fetch_attributes; | ||
emscripten_fetch_attr_init(&fetch_attributes); | ||
|
||
strcpy(fetch_attributes.requestMethod, "GET"); | ||
fetch_attributes.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; | ||
|
||
const char* headers[] = { "Range", range, NULL }; | ||
fetch_attributes.requestHeaders = headers; | ||
|
||
fetch_attributes.userData = ctx; | ||
fetch_attributes.onsuccess = fetch_with_emscripten_success; | ||
fetch_attributes.onerror = fetch_with_emscripten_error; | ||
|
||
emscripten_fetch(&fetch_attributes, url); | ||
#endif | ||
} |
Oops, something went wrong.