diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..512e2df
Binary files /dev/null and b/.DS_Store differ
diff --git a/.gitignore b/.gitignore
index 85a74d7..9b33691 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,4 +24,5 @@ snap/
*.iso
*.mcd
*.rom
-*.o
\ No newline at end of file
+*.o
+psxe.app
\ No newline at end of file
diff --git a/Info.plist b/Info.plist
index 5cfc5cf..a284091 100644
--- a/Info.plist
+++ b/Info.plist
@@ -1,32 +1,26 @@
-
- CFBundleDevelopmentRegion
- en
- CFBundleExecutable
- psxe
- CFBundleIdentifier
- psxe.app.0
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- psxe
- CFBundlePackageType
- APPL
- CFBundleShortVersionString
- 0.1-alpha
- CFBundleSignature
- psxe
- CFBundleVersion
- 0.1-alpha
- LSApplicationCategoryType
- public.app-category.games
- LSMinimumSystemVersion
- 10.6
- NSHighResolutionCapable
-
- NSPrincipalClass
- NSApplication
-
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ psxe
+ CFBundleIdentifier
+ psxe.app.0
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ psxe
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 0.10.4-alpha
+ CFBundleVersion
+ 0.10.4-alpha
+ LSMinimumSystemVersion
+ 10.9
+ NSHighResolutionCapable
+
+
diff --git a/Makefile b/Makefile
index 81289c2..4a0e3db 100644
--- a/Makefile
+++ b/Makefile
@@ -1,35 +1,65 @@
.ONESHELL:
-CFLAGS := -g -DLOG_USE_COLOR `sdl2-config --cflags --libs`
-CFLAGS += -Ofast -Wno-overflow -Wall -pedantic -Wno-address-of-packed-member -flto
-
-PLATFORM := $(shell uname -s)
-
-ifeq ($(PLATFORM),Darwin)
- CFLAGS += -mmacosx-version-min=10.9 -Wno-newline-eof
-endif
-
-VERSION_TAG := $(shell git describe --always --tags --abbrev=0)
-COMMIT_HASH := $(shell git rev-parse --short HEAD)
-OS_INFO := $(shell uname -rmo)
-
-SOURCES := $(wildcard psx/*.c)
+CFLAGS := -g -DLOG_USE_COLOR `sdl2-config --cflags --libs`
+CFLAGS += -Ofast -Wno-overflow -Wall -pedantic -Wno-address-of-packed-member -flto
+
+PLATFORM := $(shell uname -s)
+
+ifeq ($(PLATFORM),Darwin)
+ CFLAGS += -mmacosx-version-min=10.9 -Wno-newline-eof
+endif
+
+SHARED_EXT := .so
+SHARED_LDFLAGS := -shared
+SHARED_CFLAGS := $(CFLAGS) -D__DLL_BUILD -fPIC
+
+ifeq ($(PLATFORM),Darwin)
+ SHARED_EXT := .dylib
+ SHARED_LDFLAGS := -dynamiclib
+endif
+
+VERSION_TAG := $(shell git describe --always --tags --abbrev=0)
+COMMIT_HASH := $(shell git rev-parse --short HEAD)
+OS_INFO := $(shell uname -rmo)
+
+SOURCES := $(wildcard psx/*.c)
SOURCES += $(wildcard psx/dev/*.c)
-SOURCES += $(wildcard psx/dev/cdrom/*.c)
-SOURCES += $(wildcard psx/input/*.c)
-SOURCES += $(wildcard psx/disc/*.c)
-SOURCES += $(wildcard frontend/*.c)
-
-bin/psxe frontend/main.c:
- mkdir -p bin
-
- gcc $(SOURCES) -o bin/psxe \
- -I"." \
- -I"psx" \
- -DOS_INFO="$(OS_INFO)" \
- -DREP_VERSION="$(VERSION_TAG)" \
- -DREP_COMMIT_HASH="$(COMMIT_HASH)" \
- $(CFLAGS)
-
-clean:
- rm -rf "bin"
+SOURCES += $(wildcard psx/dev/cdrom/*.c)
+SOURCES += $(wildcard psx/input/*.c)
+SOURCES += $(wildcard psx/disc/*.c)
+SOURCES += $(wildcard frontend/*.c)
+SHARED_SOURCES := $(filter-out frontend/main.c,$(SOURCES))
+
+BIN_DIR := bin
+BIN := $(BIN_DIR)/psxe
+SHARED_BIN := $(BIN_DIR)/libpsxe$(SHARED_EXT)
+
+.PHONY: all clean shared
+
+all: $(BIN)
+
+$(BIN_DIR):
+ mkdir -p $(BIN_DIR)
+
+$(BIN): $(SOURCES) | $(BIN_DIR)
+ gcc $(SOURCES) -o $(BIN) \
+ -I"." \
+ -I"psx" \
+ -DOS_INFO="$(OS_INFO)" \
+ -DREP_VERSION="$(VERSION_TAG)" \
+ -DREP_COMMIT_HASH="$(COMMIT_HASH)" \
+ $(CFLAGS)
+
+shared: $(SHARED_BIN)
+
+$(SHARED_BIN): $(SHARED_SOURCES) | $(BIN_DIR)
+ gcc $(SHARED_LDFLAGS) $(SHARED_SOURCES) -o $(SHARED_BIN) \
+ -I"." \
+ -I"psx" \
+ -DOS_INFO="$(OS_INFO)" \
+ -DREP_VERSION="$(VERSION_TAG)" \
+ -DREP_COMMIT_HASH="$(COMMIT_HASH)" \
+ $(SHARED_CFLAGS)
+
+clean:
+ rm -rf "$(BIN_DIR)"
diff --git a/README.md b/README.md
index 96ba4c1..a44e617 100644
--- a/README.md
+++ b/README.md
@@ -12,11 +12,11 @@ A simple and portable Sony PlayStation emulator and emulation library written in


-## Running
-You can download the latest automated build for your platform on Releases. If your system isn't supported, you can easily build the emulator from source, instructions on "Building" below.
-
-In order to run the emulator, you will need a BIOS file. You can either get one from the internet or [dump it from your own console](https://www.youtube.com/watch?v=u8eHp0COcBo).
-
+## Running
+You can download the latest automated build for your platform on Releases. If your system isn't supported, you can easily build the emulator from source, instructions on "Building" below.
+
+In order to run the emulator, you will need a BIOS file. You can either get one from the internet or [dump it from your own console](https://www.youtube.com/watch?v=u8eHp0COcBo).
+
Most BIOS versions are confirmed to work.
Use the `-b` or `--bios` setting to configure the BIOS file.
@@ -72,12 +72,47 @@ cd psxe
make clean && make
```
-### macOS
-```
-git clone https://github.com/allkern/psxe
-cd psxe
-./build.sh
-```
+### macOS
+```
+git clone https://github.com/allkern/psxe
+cd psxe
+./build.sh
+```
+
+## Configuration
+
+A `settings.toml` is generated on first run next to the executable. CLI flags always override file settings for a session.
+
+### Settings file keys
+- `psxe_version` (string, managed by PSXE)
+
+`[bios]`
+- `search_path` (string): directory to search for BIOS files
+- `preferred_model` (string): default model (e.g. `SCPH-1001`)
+- `override_file` (string): explicit BIOS path; leave empty to use search/model
+
+`[console]`
+- `region` (string): `ntsc`, `pal`, or `auto`
+
+`[video]`
+- `texture_scale_mode` (bool): enable SDL texture scale mode; toggle bilinear via F4 when supported
+
+### CLI flags (override settings)
+`psxe [options] path-to-cdrom`
+- `-a, --use-args` : ignore settings file
+- `-b, --bios=` : BIOS file
+- `-B, --bios-folder` : BIOS search folder
+- `-c, --console-source=` : auto | null | kernel | atcons
+- `-e, --exp-rom=` : expansion ROM file
+- `-L, --log-level=` : 0=trace .. 5=fatal
+- `-M, --model=` : console model (SCPH-XXXX)
+- `-r, --region=` : console region
+- `-s, --scale=` : display scaling factor
+- `-S, --settings-file=` : settings file path
+- `-q, --quiet` : silence logs
+- `-x, --exe=` : PS-X EXE to boot
+- `--cdrom=` : CDROM image
+- `-h/--help` | `--help-model` | `--help-region` | `-v/--version` : info helpers
## Acknowledgements
This project uses external open source code that can be found on the following GitHub repos:
diff --git a/build.sh b/build.sh
index f1838e5..e99e2cc 100755
--- a/build.sh
+++ b/build.sh
@@ -1,19 +1,36 @@
#!/bin/sh
-# Build emulator
-make clean && make
+# Build options:
+# BUILD_DLL=1 -> build shared library (libpsxe.* with __DLL_BUILD)
+# BUILD_DLL_ONLY=1 -> build only the shared library
+# BUNDLE_APP=1 -> bundle macOS .app (uses executable build)
-# Create bundle filesystem
-mkdir -p psxe.app/Contents/MacOS/Libraries
+make clean
-# Move executable to folder
-mv bin/psxe psxe.app/Contents/MacOS
+if [ "${BUILD_DLL_ONLY:-0}" != "0" ]; then
+ make shared
+else
+ make
+ if [ "${BUILD_DLL:-0}" != "0" ]; then
+ make shared
+ fi
-# Make executable
-chmod 777 psxe.app/Contents/MacOS/psxe
+ if [ "${BUNDLE_APP:-0}" != "0" ]; then
+ # Create bundle filesystem
+ mkdir -p psxe.app/Contents/MacOS/Libraries
-# Bundle required dylibs
-dylibbundler -b -x ./psxe.app/Contents/MacOS/psxe -d ./psxe.app/Contents/Libraries/ -p @executable_path/../Libraries/ -cd
+ # Copy executable to bundle folder (keep bin/psxe intact)
+ cp bin/psxe psxe.app/Contents/MacOS
-# Move plist to Contents folder
-mv Info.plist psxe.app/Contents/Info.plist
\ No newline at end of file
+ # Make executable
+ chmod 777 psxe.app/Contents/MacOS/psxe
+
+ # Bundle required dylibs
+ dylibbundler -b -x ./psxe.app/Contents/MacOS/psxe -d ./psxe.app/Contents/Libraries/ -p @executable_path/../Libraries/ -cd
+
+ # Copy plist to Contents folder (keep a copy in repo for subsequent builds)
+ cp Info.plist psxe.app/Contents/Info.plist
+ else
+ echo "Skipping macOS .app bundle. Set BUNDLE_APP=1 to build the application bundle."
+ fi
+fi
diff --git a/frontend/config.c b/frontend/config.c
index fbdb49c..b0ea055 100644
--- a/frontend/config.c
+++ b/frontend/config.c
@@ -19,11 +19,11 @@ static const char* g_version_text =
"This is free software; see the source for copying conditions. There is NO\n"
"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n";
-static const char g_default_settings[] =
- "# Settings file generated by PSXE\n"
- "\n"
- "# Don't change please, reserved for future use\n"
- "psxe_version = \"" STR(REP_VERSION) "\"\n"
+static const char g_default_settings[] =
+ "# Settings file generated by PSXE\n"
+ "\n"
+ "# Don't change please, reserved for future use\n"
+ "psxe_version = \"" STR(REP_VERSION) "\"\n"
"\n"
"# BIOS-related settings:\n"
"[bios]\n"
@@ -31,9 +31,13 @@ static const char g_default_settings[] =
" preferred_model = \"SCPH-1001\"\n"
" override_file = \"\"\n"
"\n"
- "# Console settings\n"
- "[console]\n"
- " region = \"auto\"\n";
+ "# Console settings\n"
+ "[console]\n"
+ " region = \"auto\"\n"
+ "\n"
+ "# Video settings\n"
+ "[video]\n"
+ " texture_scale_mode = false\n";
static const char* g_models_text =
"Available console models:\n"
@@ -78,8 +82,8 @@ void psxe_cfg_destroy(psxe_config_t* cfg) {
free(cfg);
}
-void psxe_cfg_load_defaults(psxe_config_t* cfg) {
- cfg->bios = "bios.bin";
+void psxe_cfg_load_defaults(psxe_config_t* cfg) {
+ cfg->bios = "bios.bin";
cfg->bios_search = "bios";
cfg->exe = NULL;
cfg->help_model = 0;
@@ -91,25 +95,39 @@ void psxe_cfg_load_defaults(psxe_config_t* cfg) {
cfg->settings_path = NULL;
cfg->use_args = 0;
cfg->version = 0;
- cfg->log_level = LOG_FATAL;
- cfg->quiet = 0;
- cfg->cd_path = NULL;
- cfg->exp_path = NULL;
-}
+ cfg->log_level = LOG_FATAL;
+ cfg->quiet = 0;
+ cfg->cd_path = NULL;
+ cfg->exp_path = NULL;
+ cfg->texture_scale_mode = 0;
+}
-void psxe_cfg_load(psxe_config_t* cfg, int argc, const char* argv[]) {
- log_set_level(LOG_INFO);
+void psxe_cfg_load(psxe_config_t* cfg, int argc, const char* argv[]) {
+ log_set_level(LOG_INFO);
+
+ // macOS .app bundles can inject a "-psn_*" argument; drop it before parsing
+ const char** argv_filtered = (const char**)malloc(sizeof(char*) * argc);
+ int argc_filtered = 0;
+
+ for (int i = 0; i < argc; i++) {
+ if (strncmp(argv[i], "-psn_", 5) == 0)
+ continue;
+
+ argv_filtered[argc_filtered++] = argv[i];
+ }
- int use_args = 0;
- int version = 0;
+ int use_args = 0;
+ int version = 0;
int help_model = 0;
int help_region = 0;
int log_level = 0;
- int quiet = 0;
- int console_source = 0;
- int scale = 0;
- const char* settings_path = NULL;
- const char* bios = NULL;
+ int quiet = 0;
+ int console_source = 0;
+ int scale = 0;
+ int texture_scale_mode = 0;
+ const char* settings_path = NULL;
+ const char* bios = NULL;
+ int bios_from_cli = 0;
const char* bios_search = NULL;
const char* model = NULL;
const char* exe = NULL;
@@ -130,7 +148,7 @@ void psxe_cfg_load(psxe_config_t* cfg, int argc, const char* argv[]) {
OPT_BOOLEAN ('v', "version" , &version , "Display version and build information", NULL, 0, 0),
OPT_GROUP("Basic options"),
OPT_BOOLEAN ('a', "use-args" , &use_args , "Ignore settings file, use CLI args instead", NULL, 0, 0),
- OPT_STRING ('b', "bios" , &bios , "Specify a BIOS file (ignores -B, -M)", NULL, 0, 0),
+ OPT_STRING ('b', "bios" , &bios , "Specify a BIOS file (ignores -B, -M)", NULL, 0, 0),
OPT_BOOLEAN ('B', "bios-folder" , &bios_search , "Specify a BIOS search folder", NULL, 0, 0),
OPT_STRING ('c', "console-source", &console_source, "Select console source (auto, null, kernel, atcons)"),
OPT_STRING ('e', "exp-rom" , &exp_path , "Specify an expansion ROM file"),
@@ -147,10 +165,13 @@ void psxe_cfg_load(psxe_config_t* cfg, int argc, const char* argv[]) {
struct argparse argparse;
- argparse_init(&argparse, options, usages, 0);
- argparse_describe(&argparse, NULL, g_desc_text);
-
- argc = argparse_parse(&argparse, argc, argv);
+ argparse_init(&argparse, options, usages, 0);
+ argparse_describe(&argparse, NULL, g_desc_text);
+
+ argc = argparse_parse(&argparse, argc_filtered, argv_filtered);
+
+ if (bios)
+ bios_from_cli = 1;
if (help_model) {
printf("%s\n", g_models_text);
@@ -201,10 +222,10 @@ void psxe_cfg_load(psxe_config_t* cfg, int argc, const char* argv[]) {
toml_table_t* conf = toml_parse_file(settings, error, sizeof(error));
if (!conf) {
- log_error("Couldn't parse settings file");
-
- exit(1);
- }
+ log_error("Couldn't parse settings file");
+
+ exit(1);
+ }
toml_datum_t s_version = toml_string_in(conf, "psxe_version");
@@ -227,27 +248,36 @@ void psxe_cfg_load(psxe_config_t* cfg, int argc, const char* argv[]) {
if (s_bios_preferred_model.ok)
model = s_bios_preferred_model.u.s;
- toml_datum_t s_bios_override_file = toml_string_in(s_bios_table, "override_file");
-
- if (s_bios_override_file.ok)
- bios = s_bios_override_file.u.s;
+ toml_datum_t s_bios_override_file = toml_string_in(s_bios_table, "override_file");
+
+ if (!bios_from_cli && s_bios_override_file.ok && s_bios_override_file.u.s[0])
+ bios = s_bios_override_file.u.s;
}
toml_table_t* s_console_table = toml_table_in(conf, "console");
- if (s_console_table) {
- toml_datum_t s_console_region = toml_string_in(s_console_table, "region");
-
- if (s_console_region.ok)
- region = s_console_region.u.s;
- }
-
- psxe_version = s_version.u.s;
+ if (s_console_table) {
+ toml_datum_t s_console_region = toml_string_in(s_console_table, "region");
+
+ if (s_console_region.ok)
+ region = s_console_region.u.s;
+ }
+
+ toml_table_t* s_video_table = toml_table_in(conf, "video");
+
+ if (s_video_table) {
+ toml_datum_t s_texture_scale_mode = toml_bool_in(s_video_table, "texture_scale_mode");
+
+ if (s_texture_scale_mode.ok)
+ texture_scale_mode = s_texture_scale_mode.u.b;
+ }
+
+ psxe_version = s_version.u.s;
log_info("Settings file parsed. PSXE version: %s", psxe_version);
- fclose(settings);
- }
+ fclose(settings);
+ }
if (argc) {
if (argc > 1) {
@@ -283,12 +313,18 @@ void psxe_cfg_load(psxe_config_t* cfg, int argc, const char* argv[]) {
if (psxe_version)
cfg->psxe_version = psxe_version;
- if (exp_path)
- cfg->exp_path = exp_path;
-
- if (scale)
- cfg->scale = scale;
-}
+ if (exp_path)
+ cfg->exp_path = exp_path;
+
+ if (scale)
+ cfg->scale = scale;
+
+ cfg->texture_scale_mode = texture_scale_mode;
+
+ log_info("Using BIOS path: %s", cfg->bios ? cfg->bios : "(none)");
+
+ free(argv_filtered);
+}
// To-do: Implement BIOS searching
char* psxe_cfg_get_bios_path(psxe_config_t* cfg) {
@@ -296,4 +332,4 @@ char* psxe_cfg_get_bios_path(psxe_config_t* cfg) {
}
#undef STR1
-#undef STR
\ No newline at end of file
+#undef STR
diff --git a/frontend/config.h b/frontend/config.h
index 81f565d..70fef76 100644
--- a/frontend/config.h
+++ b/frontend/config.h
@@ -10,16 +10,17 @@ typedef struct {
int use_args;
int version;
int help_model;
- int help_region;
- int log_level;
- int quiet;
- int console_source;
- int scale;
- const char* snap_path;
- const char* settings_path;
- const char* bios;
- const char* bios_search;
- const char* model;
+ int help_region;
+ int log_level;
+ int quiet;
+ int console_source;
+ int scale;
+ int texture_scale_mode;
+ const char* snap_path;
+ const char* settings_path;
+ const char* bios;
+ const char* bios_search;
+ const char* model;
const char* exe;
const char* region;
const char* psxe_version;
@@ -34,4 +35,4 @@ void psxe_cfg_load(psxe_config_t*, int, const char**);
char* psxe_cfg_get_bios_path(psxe_config_t*);
void psxe_cfg_destroy(psxe_config_t*);
-#endif
\ No newline at end of file
+#endif
diff --git a/frontend/main.c b/frontend/main.c
index 4c2de72..e13328b 100644
--- a/frontend/main.c
+++ b/frontend/main.c
@@ -37,21 +37,26 @@ int main(int argc, const char* argv[]) {
log_set_level(cfg->log_level);
- psx_t* psx = psx_create();
- psx_init(psx, cfg->bios, cfg->exp_path);
+ psx_t* psx = psx_create();
+ int init_result = psx_init(psx, cfg->bios, cfg->exp_path);
+
+ if (init_result) {
+ log_fatal("Failed to initialize PSX (code %d). Check BIOS/expansion paths and settings.", init_result);
+ return 1;
+ }
psx_cdrom_t* cdrom = psx_get_cdrom(psx);
// To-do: Set CDROM firmware version and region based
// on CLI options
- if (cfg->cd_path)
- psx_cdrom_open(cdrom, cfg->cd_path);
-
- psxe_screen_t* screen = psxe_screen_create();
- psxe_screen_init(screen, psx);
- psxe_screen_set_scale(screen, cfg->scale);
- psxe_screen_reload(screen);
+ if (cfg->cd_path)
+ psx_cdrom_open(cdrom, cfg->cd_path);
+
+ psxe_screen_t* screen = psxe_screen_create();
+ psxe_screen_init(screen, psx, cfg);
+ psxe_screen_set_scale(screen, cfg->scale);
+ psxe_screen_reload(screen);
SDL_Init(SDL_INIT_AUDIO);
@@ -126,4 +131,4 @@ int main(int argc, const char* argv[]) {
psxe_screen_destroy(screen);
return 0;
-}
\ No newline at end of file
+}
diff --git a/frontend/screen.c b/frontend/screen.c
index 6547c20..ff30692 100644
--- a/frontend/screen.c
+++ b/frontend/screen.c
@@ -1,7 +1,8 @@
-#include "screen.h"
-
-#include "input/sda.h"
-#include "input/guncon.h"
+#include "screen.h"
+
+#include "input/sda.h"
+#include "input/guncon.h"
+#include "../psx/log.h"
uint32_t screen_get_button(SDL_Keycode k) {
if (k == SDLK_x ) return PSXI_SW_SDA_CROSS;
@@ -66,16 +67,30 @@ int screen_get_base_width(psxe_screen_t* screen) {
return 320;
}
-psxe_screen_t* psxe_screen_create(void) {
- return (psxe_screen_t*)malloc(sizeof(psxe_screen_t));
-}
-
-void psxe_screen_init(psxe_screen_t* screen, psx_t* psx) {
- memset(screen, 0, sizeof(psxe_screen_t));
-
- if (screen->debug_mode) {
- screen->width = PSX_GPU_FB_WIDTH;
- screen->height = PSX_GPU_FB_HEIGHT;
+psxe_screen_t* psxe_screen_create(void) {
+ return (psxe_screen_t*)malloc(sizeof(psxe_screen_t));
+}
+
+static void psxe_screen_apply_texture_scale_mode(psxe_screen_t* screen) {
+#if SDL_VERSION_ATLEAST(2, 0, 12)
+ if (!screen->texture_scale_mode)
+ return;
+
+ SDL_ScaleMode scale_mode = screen->bilinear ? SDL_ScaleModeLinear : SDL_ScaleModeNearest;
+
+ if (SDL_SetTextureScaleMode(screen->texture, scale_mode) < 0) {
+ log_warn("SDL_SetTextureScaleMode failed, disabling texture_scale_mode: %s", SDL_GetError());
+ screen->texture_scale_mode = 0;
+ }
+#endif
+}
+
+void psxe_screen_init(psxe_screen_t* screen, psx_t* psx, const psxe_config_t* cfg) {
+ memset(screen, 0, sizeof(psxe_screen_t));
+
+ if (screen->debug_mode) {
+ screen->width = PSX_GPU_FB_WIDTH;
+ screen->height = PSX_GPU_FB_HEIGHT;
} else {
screen->width = 320;
screen->height = 240;
@@ -83,23 +98,29 @@ void psxe_screen_init(psxe_screen_t* screen, psx_t* psx) {
screen->scale = 1;
screen->open = 1;
- screen->format = SDL_PIXELFORMAT_BGR555;
- screen->psx = psx;
- screen->pad = psx_get_pad(psx);
-
- screen->texture_width = PSX_GPU_FB_WIDTH;
- screen->texture_height = PSX_GPU_FB_HEIGHT;
+ screen->format = SDL_PIXELFORMAT_BGR555;
+ screen->psx = psx;
+ screen->pad = psx_get_pad(psx);
+ screen->texture_scale_mode = cfg ? cfg->texture_scale_mode : 0;
+ screen->bilinear = screen->texture_scale_mode ? SDL_ScaleModeLinear : SDL_ScaleModeNearest;
+
+ screen->texture_width = PSX_GPU_FB_WIDTH;
+ screen->texture_height = PSX_GPU_FB_HEIGHT;
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER);
SDL_SetRenderDrawColor(screen->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
- screen->controller = screen_find_controller();
-}
-
-void psxe_screen_reload(psxe_screen_t* screen) {
- if (screen->texture) SDL_DestroyTexture(screen->texture);
- if (screen->renderer) SDL_DestroyRenderer(screen->renderer);
- if (screen->window) SDL_DestroyWindow(screen->window);
+ screen->controller = screen_find_controller();
+}
+
+void psxe_screen_set_window_handle(psxe_screen_t* screen, void* native_handle) {
+ screen->external_window = native_handle;
+}
+
+void psxe_screen_reload(psxe_screen_t* screen) {
+ if (screen->texture) SDL_DestroyTexture(screen->texture);
+ if (screen->renderer) SDL_DestroyRenderer(screen->renderer);
+ if (screen->window) SDL_DestroyWindow(screen->window);
if (screen->debug_mode) {
screen->width = PSX_GPU_FB_WIDTH;
@@ -113,14 +134,30 @@ void psxe_screen_reload(psxe_screen_t* screen) {
screen->height = 240;
}
}
-
- screen->window = SDL_CreateWindow(
- "psxe " STR(REP_VERSION) "-" STR(REP_COMMIT_HASH),
- SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
- screen->width * screen->scale,
- screen->height * screen->scale,
- 0
- );
+
+#ifndef __DLL_BUILD
+ screen->window = SDL_CreateWindow(
+ "psxe " STR(REP_VERSION) "-" STR(REP_COMMIT_HASH),
+ SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
+ screen->width * screen->scale,
+ screen->height * screen->scale,
+ 0
+ );
+#else
+ if (!screen->external_window) {
+ log_error("DLL build requires external window handle before reload");
+ screen->open = 0;
+ return;
+ }
+
+ screen->window = SDL_CreateWindowFrom(screen->external_window);
+
+ if (!screen->window) {
+ log_error("Failed to create SDL window from external handle: %s", SDL_GetError());
+ screen->open = 0;
+ return;
+ }
+#endif
screen->renderer = SDL_CreateRenderer(
screen->window,
@@ -128,16 +165,14 @@ void psxe_screen_reload(psxe_screen_t* screen) {
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC
);
- screen->texture = SDL_CreateTexture(
- screen->renderer,
- screen->format,
- SDL_TEXTUREACCESS_STREAMING,
- screen->texture_width, screen->texture_height
- );
-
-#if SDL_VERSION_ATLEAST(2, 0, 12)
- SDL_SetTextureScaleMode(screen->texture, screen->bilinear);
-#endif
+ screen->texture = SDL_CreateTexture(
+ screen->renderer,
+ screen->format,
+ SDL_TEXTUREACCESS_STREAMING,
+ screen->texture_width, screen->texture_height
+ );
+
+ psxe_screen_apply_texture_scale_mode(screen);
// Check for retina displays
int width = 0, height = 0;
@@ -511,19 +546,17 @@ void psxe_gpu_dmode_event_cb(psx_gpu_t* gpu) {
SDL_DestroyTexture(screen->texture);
- screen->texture = SDL_CreateTexture(
- screen->renderer,
- screen->format,
- SDL_TEXTUREACCESS_STREAMING,
- screen->texture_width, screen->texture_height
- );
-
-#if SDL_VERSION_ATLEAST(2, 0, 12)
- SDL_SetTextureScaleMode(screen->texture, screen->bilinear);
-#endif
-
- SDL_SetWindowSize(screen->window, screen->width, screen->height);
-}
+ screen->texture = SDL_CreateTexture(
+ screen->renderer,
+ screen->format,
+ SDL_TEXTUREACCESS_STREAMING,
+ screen->texture_width, screen->texture_height
+ );
+
+ psxe_screen_apply_texture_scale_mode(screen);
+
+ SDL_SetWindowSize(screen->window, screen->width, screen->height);
+}
void psxe_gpu_vblank_event_cb(psx_gpu_t* gpu) {
psxe_screen_t* screen = gpu->udata[0];
@@ -531,4 +564,4 @@ void psxe_gpu_vblank_event_cb(psx_gpu_t* gpu) {
psxe_screen_update(screen);
psxe_gpu_vblank_timer_event_cb(gpu);
-}
\ No newline at end of file
+}
diff --git a/frontend/screen.h b/frontend/screen.h
index e50fbc5..a7f87ab 100644
--- a/frontend/screen.h
+++ b/frontend/screen.h
@@ -1,8 +1,9 @@
#ifndef SCREEN_H
#define SCREEN_H
-#include "../psx/psx.h"
-#include "common.h"
+#include "../psx/psx.h"
+#include "common.h"
+#include "config.h"
#include
@@ -22,29 +23,32 @@ typedef struct {
unsigned int width, height, scale;
unsigned int image_width, image_height;
unsigned int image_xoff, image_yoff;
- unsigned int format;
- unsigned int texture_width, texture_height;
-
- int bilinear;
- int fullscreen;
- int vertical_mode;
- int debug_mode;
- int open;
+ unsigned int format;
+ unsigned int texture_width, texture_height;
+
+ void* external_window;
+ int texture_scale_mode;
+ int bilinear;
+ int fullscreen;
+ int vertical_mode;
+ int debug_mode;
+ int open;
SDL_GameController* controller;
} psxe_screen_t;
-psxe_screen_t* psxe_screen_create(void);
-void psxe_screen_init(psxe_screen_t*, psx_t*);
-void psxe_screen_reload(psxe_screen_t*);
-int psxe_screen_is_open(psxe_screen_t*);
-void psxe_screen_update(psxe_screen_t*);
-void psxe_screen_destroy(psxe_screen_t*);
-void psxe_screen_set_scale(psxe_screen_t*, unsigned int);
-void psxe_screen_toggle_debug_mode(psxe_screen_t*);
+psxe_screen_t* psxe_screen_create(void);
+void psxe_screen_init(psxe_screen_t*, psx_t*, const psxe_config_t*);
+void psxe_screen_reload(psxe_screen_t*);
+int psxe_screen_is_open(psxe_screen_t*);
+void psxe_screen_update(psxe_screen_t*);
+void psxe_screen_destroy(psxe_screen_t*);
+void psxe_screen_set_scale(psxe_screen_t*, unsigned int);
+void psxe_screen_set_window_handle(psxe_screen_t*, void* native_handle);
+void psxe_screen_toggle_debug_mode(psxe_screen_t*);
// GPU event handlers
void psxe_gpu_dmode_event_cb(psx_gpu_t*);
void psxe_gpu_vblank_event_cb(psx_gpu_t*);
-#endif
\ No newline at end of file
+#endif
diff --git a/psx/dev/bios.c b/psx/dev/bios.c
index deb9345..5258c1b 100644
--- a/psx/dev/bios.c
+++ b/psx/dev/bios.c
@@ -1,13 +1,14 @@
#include "bios.h"
-#include "../log.h"
-
-#include
-#include
-#include
-
-psx_bios_t* psx_bios_create(void) {
- return (psx_bios_t*)malloc(sizeof(psx_bios_t));
-}
+#include "../log.h"
+
+#include
+#include
+#include
+#include
+
+psx_bios_t* psx_bios_create(void) {
+ return (psx_bios_t*)malloc(sizeof(psx_bios_t));
+}
void psx_bios_init(psx_bios_t* bios) {
memset(bios, 0, sizeof(psx_bios_t));
@@ -17,14 +18,16 @@ void psx_bios_init(psx_bios_t* bios) {
bios->bus_delay = 18;
}
-int psx_bios_load(psx_bios_t* bios, const char* path) {
- if (!path)
- return 0;
-
- FILE* file = fopen(path, "rb");
-
- if (!file)
- return 1;
+int psx_bios_load(psx_bios_t* bios, const char* path) {
+ if (!path)
+ return 0;
+
+ FILE* file = fopen(path, "rb");
+
+ if (!file) {
+ log_error("Failed to open BIOS at '%s': %s", path, strerror(errno));
+ return 1;
+ }
// Almost all PS1 BIOS ROMs are 512 KiB in size.
// There's (at least) one exception, and that is SCPH-5903.
@@ -40,8 +43,10 @@ int psx_bios_load(psx_bios_t* bios, const char* path) {
bios->buf = malloc(size);
bios->io_size = size;
- if (!fread(bios->buf, 1, size, file))
- return 2;
+ if (!fread(bios->buf, 1, size, file)) {
+ log_error("Failed to read BIOS at '%s': %s", path, strerror(errno));
+ return 2;
+ }
fclose(file);
@@ -75,4 +80,4 @@ void psx_bios_write8(psx_bios_t* bios, uint32_t offset, uint8_t value) {
void psx_bios_destroy(psx_bios_t* bios) {
free(bios->buf);
free(bios);
-}
\ No newline at end of file
+}
diff --git a/psx/dev/cdrom/cdrom.c b/psx/dev/cdrom/cdrom.c
index 5ddd259..61db4ad 100644
--- a/psx/dev/cdrom/cdrom.c
+++ b/psx/dev/cdrom/cdrom.c
@@ -4,6 +4,7 @@
#include
#include "cdrom.h"
+#include "../../log.h"
typedef void (*cdrom_cmd_func)(psx_cdrom_t* cdrom);
@@ -194,11 +195,21 @@ int psx_cdrom_open(psx_cdrom_t* cdrom, const char* path) {
cdrom->disc_type = psx_disc_open(cdrom->disc, path);
if (cdrom->disc_type == CDT_ERROR) {
+ log_error("Failed to open CD image: %s", path);
psx_cdrom_close(cdrom);
return 0;
}
+ if (cdrom->disc_type != CDT_LICENSED) {
+ log_error("CD image not recognized as a licensed PSX disc (type=%d). Use a proper BIN/CUE rip.", cdrom->disc_type);
+ psx_cdrom_close(cdrom);
+
+ return 0;
+ }
+
+ log_info("CD image type: Licensed");
+
return 1;
}
@@ -759,4 +770,4 @@ void psx_cdrom_write32(psx_cdrom_t* cdrom, uint32_t addr, uint32_t value) {
void psx_cdrom_write16(psx_cdrom_t* cdrom, uint32_t addr, uint32_t value) {
assert("16-bit CDROM writes are not supported" && 0);
-}
\ No newline at end of file
+}
diff --git a/psx/dev/cdrom/disc.c b/psx/dev/cdrom/disc.c
index af5b5fd..2341c12 100644
--- a/psx/dev/cdrom/disc.c
+++ b/psx/dev/cdrom/disc.c
@@ -1,8 +1,10 @@
#include
#include
+#include
#include "disc.h"
#include "cue.h"
+#include "../../log.h"
#define MSF_TO_LBA(m, s, f) ((m * 4500) + (s * 75) + f)
@@ -13,8 +15,67 @@ const char* disc_cd_extensions[] = {
0
};
+typedef struct {
+ FILE* file;
+ uint32_t sector_size;
+ uint32_t sector_count;
+} raw_disc_t;
+
+static int raw_read(void* udata, uint32_t lba, void* buf) {
+ raw_disc_t* raw = (raw_disc_t*)udata;
+
+ if (!raw || !raw->file)
+ return 0;
+
+ if (lba >= raw->sector_count)
+ return 0;
+
+ if (fseek(raw->file, (long long)lba * raw->sector_size, SEEK_SET))
+ return 0;
+
+ size_t to_read = raw->sector_size;
+ memset(buf, 0, CD_SECTOR_SIZE);
+
+ return fread(buf, 1, to_read, raw->file) == to_read ? TS_DATA : 0;
+}
+
+static int raw_query(void* udata, uint32_t lba) {
+ raw_disc_t* raw = (raw_disc_t*)udata;
+
+ return (raw && (lba < raw->sector_count)) ? TS_DATA : TS_FAR;
+}
+
+static int raw_get_track_number(void* udata, uint32_t lba) {
+ (void)udata; (void)lba;
+ return 1;
+}
+
+static int raw_get_track_count(void* udata) {
+ (void)udata;
+ return 1;
+}
+
+static uint32_t raw_get_track_lba(void* udata, int track) {
+ (void)udata; (void)track;
+ return 0;
+}
+
+static void raw_destroy(void* udata) {
+ raw_disc_t* raw = (raw_disc_t*)udata;
+
+ if (raw && raw->file)
+ fclose(raw->file);
+
+ free(raw);
+}
+
psx_disc_t* psx_disc_create(void) {
- return malloc(sizeof(psx_disc_t));
+ psx_disc_t* disc = malloc(sizeof(psx_disc_t));
+
+ if (disc)
+ memset(disc, 0, sizeof(psx_disc_t));
+
+ return disc;
}
int disc_get_extension(const char* path) {
@@ -81,12 +142,49 @@ int psx_disc_open_as(psx_disc_t* disc, const char* path, int type) {
if (cue_load(cue, LD_FILE))
return CDT_ERROR;
} break;
+
+ case CD_EXT_BIN:
+ case CD_EXT_ISO: {
+ FILE* f = fopen(path, "rb");
+
+ if (!f)
+ return CDT_ERROR;
+
+ fseek(f, 0, SEEK_END);
+ long long size = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ raw_disc_t* raw = (raw_disc_t*)malloc(sizeof(raw_disc_t));
+
+ raw->file = f;
+ raw->sector_size = (type == CD_EXT_BIN) ? CD_SECTOR_SIZE : 2048;
+ raw->sector_count = (uint32_t)(size / raw->sector_size);
+
+ disc->udata = raw;
+ disc->read_sector = raw_read;
+ disc->query_sector = raw_query;
+ disc->get_track_number = raw_get_track_number;
+ disc->get_track_count = raw_get_track_count;
+ disc->get_track_lba = raw_get_track_lba;
+ disc->destroy = raw_destroy;
+ } break;
+
+ default:
+ return CDT_ERROR;
+ }
+
+ if (!disc->read_sector) {
+ log_error("Unsupported or failed to open disc image: %s", path);
+ return CDT_ERROR;
}
return disc_get_cd_type(disc);
}
int psx_disc_read(psx_disc_t* disc, uint32_t lba, void* buf) {
+ if (!disc->read_sector)
+ return 0;
+
return disc->read_sector(disc->udata, lba, buf);
}
@@ -110,4 +208,4 @@ void psx_disc_destroy(psx_disc_t* disc) {
disc->destroy(disc->udata);
free(disc);
-}
\ No newline at end of file
+}