mirror of
https://github.com/ARMSX2/ARMSX1.git
synced 2026-08-24 16:53:35 -07:00
checkpoint portable cached CPU SDL acceleration and media support
This commit is contained in:
@@ -10,7 +10,7 @@ ifeq ($(WASM_TARGET),wasm)
|
||||
SDL_CFLAGS := -sUSE_SDL=2
|
||||
SDL_LIBS_DYNAMIC :=
|
||||
SDL_LIBS_STATIC :=
|
||||
WASM_LDFLAGS := -sUSE_SDL=2 -sALLOW_MEMORY_GROWTH=1 -sASSERTIONS=1 -sFORCE_FILESYSTEM=1 -sFULL_ES3=1 -sMIN_WEBGL_VERSION=2 -sMAX_WEBGL_VERSION=2 -sNO_EXIT_RUNTIME=0 -sEXPORTED_RUNTIME_METHODS='["FS","ccall","cwrap"]' -sEXPORTED_FUNCTIONS='["_main","_psxe_run","_external_main","_psxe_wasm_on_file","_psxe_enqueue_launch_argument"]' --preload-file icons@/icons
|
||||
WASM_LDFLAGS := -sUSE_SDL=2 -sALLOW_MEMORY_GROWTH=1 -sASSERTIONS=1 -sFORCE_FILESYSTEM=1 -sFULL_ES3=1 -sMIN_WEBGL_VERSION=2 -sMAX_WEBGL_VERSION=2 -sNO_EXIT_RUNTIME=0 -sEXPORTED_RUNTIME_METHODS='["FS","ccall","cwrap"]' -sEXPORTED_FUNCTIONS='["_main","_psxe_run","_external_main","_psxe_wasm_on_file","_psxe_wasm_on_error","_psxe_enqueue_launch_argument"]' --preload-file icons@/icons
|
||||
endif
|
||||
|
||||
SDL_CONFIG ?= sdl2-config
|
||||
@@ -25,9 +25,7 @@ SDL_LIBS_DYNAMIC ?= $(shell $(SDL_CONFIG) --libs 2>/dev/null)
|
||||
SDL_LIBS_STATIC ?= $(shell $(SDL_CONFIG) --static-libs 2>/dev/null || $(SDL_CONFIG) --libs --static 2>/dev/null)
|
||||
SDL_STATIC ?= 1
|
||||
WASM_LDFLAGS ?=
|
||||
USE_HARDWARE ?= 0
|
||||
USE_CHD ?= 0
|
||||
USE_CONNECTIVITY ?= 0
|
||||
USE_CHD ?= 1
|
||||
HW_DEBUG ?= 0
|
||||
|
||||
PLATFORM := $(shell uname -s)
|
||||
@@ -48,7 +46,7 @@ SDKROOT ?=
|
||||
CONTROLLER_GENERIC ?= 0
|
||||
FSUI_DIR := third_party/fsui-lib
|
||||
FSUI_BUILD_DIR ?= build/fsui/native
|
||||
WASM_HTML_POSTPROCESS := $(FSUI_DIR)/cmake/postprocess_web_html.cmake
|
||||
WASM_HTML_POSTPROCESS := web/postprocess_web_html.cmake
|
||||
FSUI_LINK_SYSTEM_GL ?= 1
|
||||
|
||||
ifeq ($(PSVITA_TARGET),1)
|
||||
@@ -115,7 +113,7 @@ ifeq ($(USE_CHD),1)
|
||||
LIBCHDR_BUILD_DIR := build/libchdr/native
|
||||
endif
|
||||
|
||||
LIBCHDR_INCLUDE_FLAGS := -I$(LIBCHDR_DIR)/include
|
||||
LIBCHDR_INCLUDE_FLAGS := -I$(LIBCHDR_DIR)/include -I$(LIBCHDR_DIR)/deps/miniz-3.1.1
|
||||
LIBCHDR_INPUTS := $(shell find $(LIBCHDR_DIR) -type f 2>/dev/null)
|
||||
LIBCHDR_ARCHIVE := $(LIBCHDR_BUILD_DIR)/libchdr-static.a
|
||||
LIBCHDR_LIBS := \
|
||||
@@ -128,7 +126,7 @@ ifeq ($(USE_CHD),1)
|
||||
LIBCHDR_CONFIGURE := emcmake cmake
|
||||
endif
|
||||
|
||||
LIBCHDR_CMAKE_ARGS := -S $(LIBCHDR_DIR) -B $(LIBCHDR_BUILD_DIR) -DBUILD_SHARED_LIBS=OFF -DCHDR_WANT_RAW_DATA_SECTOR=ON -DCHDR_WANT_SUBCODE=ON -DCMAKE_BUILD_TYPE=Release
|
||||
LIBCHDR_CMAKE_ARGS := -S $(LIBCHDR_DIR) -B $(LIBCHDR_BUILD_DIR) -DBUILD_SHARED_LIBS=OFF -DCHDR_WANT_RAW_DATA_SECTOR=ON -DCHDR_WANT_SUBCODE=ON -DMINIZ_ARCHIVE_APIS=ON -DMINIZ_STDIO=ON -DCMAKE_BUILD_TYPE=Release
|
||||
ifneq ($(strip $(CC)),)
|
||||
LIBCHDR_CMAKE_ARGS += -DCMAKE_C_COMPILER=$(CC)
|
||||
endif
|
||||
@@ -155,25 +153,17 @@ ifeq ($(USE_CHD),1)
|
||||
CHD_LINK_LIBS := $(LIBCHDR_LIBS)
|
||||
endif
|
||||
|
||||
CONNECTIVITY_COMPILE_DEFS :=
|
||||
ifeq ($(USE_CONNECTIVITY),1)
|
||||
CONNECTIVITY_COMPILE_DEFS := -DUSE_CONNECTIVITY
|
||||
endif
|
||||
|
||||
BASE_CXXFLAGS = -std=c++20 $(BASE_CFLAGS) $(FSUI_INCLUDE_FLAGS) $(FSUI_COMPILE_DEFS)
|
||||
BASE_CFLAGS += $(LIBCHDR_INCLUDE_FLAGS) $(CHD_COMPILE_DEFS)
|
||||
BASE_CXXFLAGS += $(LIBCHDR_INCLUDE_FLAGS) $(CHD_COMPILE_DEFS)
|
||||
BASE_CFLAGS += $(CONNECTIVITY_COMPILE_DEFS)
|
||||
|
||||
ifeq ($(CONTROLLER_GENERIC),1)
|
||||
BASE_CFLAGS += -DCONTROLLER_GENERIC
|
||||
BASE_CXXFLAGS += -DCONTROLLER_GENERIC
|
||||
endif
|
||||
|
||||
ifeq ($(USE_HARDWARE),1)
|
||||
BASE_CFLAGS += -DUSE_HARDWARE
|
||||
BASE_CXXFLAGS += -DUSE_HARDWARE
|
||||
endif
|
||||
BASE_CFLAGS += -DUSE_HARDWARE
|
||||
BASE_CXXFLAGS += -DUSE_HARDWARE
|
||||
|
||||
ifeq ($(HW_DEBUG),1)
|
||||
BASE_CFLAGS += -DHW_DEBUG
|
||||
@@ -273,9 +263,7 @@ C_SOURCES := $(wildcard psx/*.c) \
|
||||
frontend/diagnostics.c \
|
||||
frontend/toml.c
|
||||
C_SOURCES := $(filter-out psx/dev/cdrom/chd.c,$(C_SOURCES))
|
||||
ifeq ($(USE_HARDWARE),1)
|
||||
C_SOURCES += frontend/gpu_hw.c
|
||||
endif
|
||||
ifeq ($(USE_CHD),1)
|
||||
C_SOURCES += psx/dev/cdrom/chd.c
|
||||
endif
|
||||
@@ -284,7 +272,7 @@ C_SOURCES += frontend/vita_sdl_stubs.c
|
||||
endif
|
||||
C_SOURCES_SHARED := $(C_SOURCES)
|
||||
|
||||
CPP_SOURCES := frontend/main.cpp
|
||||
CPP_SOURCES := frontend/archive.cpp frontend/main.cpp
|
||||
|
||||
FSUI_LIBS := \
|
||||
$(FSUI_BUILD_DIR)/libfsui-donor.a \
|
||||
@@ -355,7 +343,7 @@ endif
|
||||
SDL_LIBS := $(if $(filter 1,$(SDL_STATIC)),$(SDL_LIBS_STATIC),$(SDL_LIBS_DYNAMIC))
|
||||
SDL_LIBS_SHARED := $(SDL_LIBS_DYNAMIC)
|
||||
|
||||
.PHONY: all clean shared wasm psvita-lib
|
||||
.PHONY: all clean shared wasm psvita-lib test test-cpu test-gpu test-chd test-zip test-sdl-runtime disc-probe
|
||||
|
||||
all: $(BIN)
|
||||
|
||||
@@ -365,6 +353,65 @@ wasm: $(BIN)
|
||||
|
||||
psvita-lib: $(VITA_NATIVE_LIB)
|
||||
|
||||
TEST_CORE_SOURCES := $(wildcard psx/*.c) \
|
||||
$(wildcard psx/dev/*.c) \
|
||||
$(wildcard psx/dev/cdrom/*.c) \
|
||||
$(wildcard psx/input/*.c)
|
||||
TEST_CORE_SOURCES := $(filter-out psx/dev/cdrom/chd.c,$(TEST_CORE_SOURCES))
|
||||
TEST_CPU_BIN := build/tests/cpu_differential
|
||||
TEST_GPU_BIN := build/tests/gpu_renderer_parity
|
||||
TEST_CHD_BIN := build/tests/chd_logic
|
||||
TEST_ZIP_BIN := build/tests/zip_integration
|
||||
TEST_SDL_BIN := build/tests/sdl_renderer_smoke
|
||||
DISC_PROBE_BIN := build/tests/disc_probe
|
||||
|
||||
$(TEST_CPU_BIN): tests/cpu_differential.c $(TEST_CORE_SOURCES)
|
||||
mkdir -p $(dir $@)
|
||||
$(CC) -std=c11 -O2 -g -DPSXE_DIAG_STDIO_DISABLE -I. -Ipsx $^ -lm -o $@
|
||||
|
||||
test-cpu: $(TEST_CPU_BIN)
|
||||
./$(TEST_CPU_BIN)
|
||||
|
||||
$(TEST_GPU_BIN): tests/gpu_renderer_parity.c psx/dev/gpu.c frontend/gpu_hw.c
|
||||
mkdir -p $(dir $@)
|
||||
$(CC) -std=c11 -O2 -g -DUSE_HARDWARE -DPSXE_DIAG_STDIO_DISABLE -I. -Ipsx $(SDL_CFLAGS) $^ -lm -o $@
|
||||
|
||||
test-gpu: $(TEST_GPU_BIN)
|
||||
./$(TEST_GPU_BIN)
|
||||
|
||||
$(TEST_CHD_BIN): tests/chd_logic.c $(CHD_BUILD_DEPS)
|
||||
mkdir -p $(dir $@)
|
||||
$(CC) -std=c11 -O2 -g -DUSE_CHD -DPSXE_DIAG_STDIO_DISABLE -I. -Ipsx $(LIBCHDR_INCLUDE_FLAGS) $< $(CHD_LINK_LIBS) -lm -o $@
|
||||
|
||||
test-chd: $(TEST_CHD_BIN)
|
||||
./$(TEST_CHD_BIN)
|
||||
|
||||
$(TEST_ZIP_BIN): tests/zip_integration.cpp frontend/archive.cpp $(CHD_BUILD_DEPS)
|
||||
mkdir -p $(dir $@)
|
||||
$(CXX) -std=c++20 -O0 -DUSE_CHD -I. $(LIBCHDR_INCLUDE_FLAGS) \
|
||||
tests/zip_integration.cpp frontend/archive.cpp $(CHD_LINK_LIBS) -o $@
|
||||
|
||||
test-zip: $(TEST_ZIP_BIN)
|
||||
python3 tests/validate_zip.py ./$(TEST_ZIP_BIN)
|
||||
|
||||
$(TEST_SDL_BIN): tests/sdl_renderer_smoke.c
|
||||
mkdir -p $(dir $@)
|
||||
$(CC) -std=c11 -O2 -g $(SDL_CFLAGS) $< $(SDL_LIBS) -o $@
|
||||
|
||||
test-sdl-runtime: $(TEST_SDL_BIN)
|
||||
./$(TEST_SDL_BIN)
|
||||
|
||||
$(DISC_PROBE_BIN): tests/disc_probe.c psx/dev/cdrom/disc.c psx/dev/cdrom/cue.c psx/dev/cdrom/list.c psx/dev/cdrom/chd.c $(CHD_BUILD_DEPS)
|
||||
mkdir -p $(dir $@)
|
||||
$(CC) -std=c11 -O2 -g -DUSE_CHD -DPSXE_DIAG_STDIO_DISABLE -I. -Ipsx $(LIBCHDR_INCLUDE_FLAGS) \
|
||||
tests/disc_probe.c psx/dev/cdrom/disc.c psx/dev/cdrom/cue.c psx/dev/cdrom/list.c psx/dev/cdrom/chd.c \
|
||||
$(CHD_LINK_LIBS) -lm -o $@
|
||||
|
||||
disc-probe: $(DISC_PROBE_BIN)
|
||||
|
||||
test:
|
||||
python3 tests/run_validation.py
|
||||
|
||||
$(BIN_DIR):
|
||||
mkdir -p $(BIN_DIR)
|
||||
|
||||
|
||||
@@ -18,13 +18,29 @@ Disc image support is:
|
||||
- `BIN/CUE`
|
||||
- single-track `BIN`
|
||||
- `ISO`
|
||||
- `CHD`, including mixed-mode metadata, pregaps/postgaps, audio byte order, and Q subchannel data
|
||||
- `ZIP` archives containing a supported game image and its companion files
|
||||
|
||||
Not implemented yet:
|
||||
- save states
|
||||
- netplay
|
||||
- texture packs
|
||||
- a separate hardware renderer backend
|
||||
- CHD support, which is still stubbed in-tree
|
||||
|
||||
## Accuracy and Acceleration
|
||||
|
||||
ARMSX has two portable CPU engines:
|
||||
|
||||
- `cached` is the default. It caches decoded instruction handlers, but still performs a real bus fetch and opcode check for every emulated instruction. Writes invalidate matching entries, including cached/uncached address aliases.
|
||||
- `interpreter` is the reference path and remains selectable for diagnostics.
|
||||
|
||||
Neither engine emits native code, uses executable memory, or relies on host threading behavior. Select the engine in Settings, with `--cpu-engine=cached|interpreter`, or with `ARMSX_CPU_ENGINE`.
|
||||
|
||||
The GPU always uses the accurate software PlayStation rasterizer and keeps its 16-bit VRAM authoritative. Presentation has two SDL2 modes:
|
||||
|
||||
- `SDL software`, the default
|
||||
- `SDL accelerated`, opt-in through Settings or `ARMSX_GPU_BACKEND=sdl-accelerated`
|
||||
|
||||
The accelerated mode uploads only changed VRAM scanlines to an SDL texture. It does not replace PS1 rasterization with host triangles, so switching presentation modes does not change emulated GPU results. If an accelerated SDL renderer is unavailable, ARMSX falls back to SDL software rendering.
|
||||
|
||||
## Supported Targets
|
||||
- Desktop: macOS, Linux, Windows
|
||||
@@ -55,6 +71,8 @@ The app opens an FSUI shell where you can choose BIOS files, start discs, change
|
||||
|
||||
Settings are stored under SDL's pref path, usually `SDL_GetPrefPath("nanodata", "armsx")`, in `settings.toml`.
|
||||
|
||||
On WebAssembly, **Start File**, **Start Disc**, and **Open Game Folder** use the browser's permission-gated file and directory pickers. Selected files are copied into the Emscripten virtual filesystem for the current session. Directory access falls back to a multi-file directory input where the File System Access API is unavailable. USB mass-storage drives are intentionally handled through these file pickers; WebUSB does not expose protected mass-storage-class devices as a general filesystem.
|
||||
|
||||
## Building
|
||||
### Linux Desktop
|
||||
Install SDL2 development packages, then run:
|
||||
@@ -115,6 +133,8 @@ Requires Emscripten:
|
||||
./build.sh wasm
|
||||
```
|
||||
|
||||
Serve the generated `bin/wasm` directory over HTTP(S); browser file APIs are not available from a raw `file://` URL.
|
||||
|
||||
### PSVita
|
||||
Requires VITASDK:
|
||||
|
||||
@@ -125,6 +145,30 @@ Requires VITASDK:
|
||||
## Configuration
|
||||
`settings.toml` is generated on first run. CLI flags always override file settings for the current session. Some internal fields still use historical `psxe_*` names for compatibility.
|
||||
|
||||
The generated defaults are:
|
||||
|
||||
```toml
|
||||
[cpu]
|
||||
execution_mode = "cached"
|
||||
|
||||
[gpu]
|
||||
backend = "software"
|
||||
```
|
||||
|
||||
Legacy renderer names such as `hardware` are accepted when reading old settings and normalize to `sdl-accelerated` when saved.
|
||||
|
||||
## Validation
|
||||
|
||||
Run the deterministic source and subsystem matrix:
|
||||
|
||||
```sh
|
||||
python3 tests/run_validation.py
|
||||
```
|
||||
|
||||
The cases cover source invariants, cached/reference CPU differential execution and self-modifying code, GPU VRAM parity, CHD layout/subchannel logic, ZIP extraction and traversal rejection, and browser file-selection rules. `make disc-probe IMAGE=/path/to/game.cue` performs a read-only disc-open and sector-read smoke test.
|
||||
|
||||
`make test-sdl-runtime` is an optional headed host check that creates an accelerated SDL2 renderer, uploads a BGR555 texture, and presents it. It is intentionally not part of the deterministic matrix because graphical CI runners may not have a display or accelerated SDL driver.
|
||||
|
||||
## Acknowledgements
|
||||
ARMSX uses:
|
||||
- `argparse.c`
|
||||
@@ -132,3 +176,4 @@ ARMSX uses:
|
||||
- `tomlc99`
|
||||
- SDL2
|
||||
- FSUI donor / ImGui
|
||||
- libchdr
|
||||
|
||||
@@ -13,8 +13,7 @@ set -e
|
||||
|
||||
MODE="$1"
|
||||
BUILD_JOBS="${BUILD_JOBS:-4}"
|
||||
export USE_CHD="${USE_CHD:-0}"
|
||||
export USE_CONNECTIVITY="${USE_CONNECTIVITY:-0}"
|
||||
export USE_CHD="${USE_CHD:-1}"
|
||||
|
||||
build_fsui_native() {
|
||||
if [ "$(uname -s)" = "Darwin" ]; then
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
#include "archive.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cctype>
|
||||
#include <system_error>
|
||||
#include <vector>
|
||||
|
||||
#ifdef USE_CHD
|
||||
extern "C" {
|
||||
#include "miniz.h"
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace armsx {
|
||||
namespace {
|
||||
|
||||
std::string ToLower(std::string value) {
|
||||
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char ch) {
|
||||
return static_cast<char>(std::tolower(ch));
|
||||
});
|
||||
return value;
|
||||
}
|
||||
|
||||
bool IsExePath(const std::filesystem::path& path) {
|
||||
const std::string ext = ToLower(path.extension().string());
|
||||
return ext == ".exe" || ext == ".ps-exe" || ext == ".psexe";
|
||||
}
|
||||
|
||||
#ifdef USE_CHD
|
||||
bool SafeArchiveRelativePath(const std::filesystem::path& path) {
|
||||
if (path.empty() || path.is_absolute() || path.has_root_name() || path.has_root_directory()) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& component : path) {
|
||||
if (component == "..") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int LaunchCandidatePriority(const std::filesystem::path& path) {
|
||||
const std::string ext = ToLower(path.extension().string());
|
||||
if (ext == ".cue") return 0;
|
||||
if (ext == ".chd") return 1;
|
||||
if (ext == ".iso") return 2;
|
||||
if (ext == ".img") return 3;
|
||||
if (ext == ".bin") return 4;
|
||||
if (IsExePath(path)) return 5;
|
||||
return 100;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
bool IsZipPath(const std::filesystem::path& path) {
|
||||
return ToLower(path.extension().string()) == ".zip";
|
||||
}
|
||||
|
||||
#ifdef USE_CHD
|
||||
std::optional<std::filesystem::path> ExtractZipLaunchCandidate(
|
||||
const std::filesystem::path& archive_path,
|
||||
std::string& error
|
||||
) {
|
||||
mz_zip_archive archive{};
|
||||
if (!mz_zip_reader_init_file(&archive, archive_path.string().c_str(), 0)) {
|
||||
error = "The ZIP archive could not be opened.";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
struct ArchiveGuard {
|
||||
mz_zip_archive* archive;
|
||||
~ArchiveGuard() { mz_zip_reader_end(archive); }
|
||||
} guard{&archive};
|
||||
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
constexpr mz_uint64 kMaxArchiveBytes = 1536ull * 1024ull * 1024ull;
|
||||
#else
|
||||
constexpr mz_uint64 kMaxArchiveBytes = 8ull * 1024ull * 1024ull * 1024ull;
|
||||
#endif
|
||||
|
||||
std::error_code ec;
|
||||
const auto nonce = static_cast<unsigned long long>(
|
||||
std::chrono::steady_clock::now().time_since_epoch().count()
|
||||
);
|
||||
const std::filesystem::path output_root =
|
||||
std::filesystem::temp_directory_path(ec) /
|
||||
"armsx-zip" /
|
||||
(archive_path.stem().string() + "-" + std::to_string(nonce));
|
||||
if (ec) {
|
||||
error = "ARMSX could not resolve temporary ZIP storage.";
|
||||
return std::nullopt;
|
||||
}
|
||||
std::filesystem::create_directories(output_root, ec);
|
||||
if (ec) {
|
||||
error = "ARMSX could not create temporary ZIP storage.";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
mz_uint64 total_uncompressed = 0;
|
||||
std::vector<std::filesystem::path> candidates;
|
||||
const mz_uint file_count = mz_zip_reader_get_num_files(&archive);
|
||||
if (file_count > 4096u) {
|
||||
error = "The ZIP contains too many entries.";
|
||||
return std::nullopt;
|
||||
}
|
||||
for (mz_uint index = 0; index < file_count; ++index) {
|
||||
mz_zip_archive_file_stat stat{};
|
||||
if (!mz_zip_reader_file_stat(&archive, index, &stat)) {
|
||||
error = "The ZIP central directory is invalid.";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const mz_uint filename_size = mz_zip_reader_get_filename(&archive, index, nullptr, 0);
|
||||
if (filename_size < 2u || filename_size > 4096u) {
|
||||
error = "The ZIP contains an invalid file name.";
|
||||
return std::nullopt;
|
||||
}
|
||||
std::vector<char> filename(filename_size);
|
||||
if (mz_zip_reader_get_filename(&archive, index, filename.data(), filename_size) != filename_size) {
|
||||
error = "The ZIP file name could not be decoded.";
|
||||
return std::nullopt;
|
||||
}
|
||||
const std::filesystem::path relative =
|
||||
std::filesystem::path(filename.data()).lexically_normal();
|
||||
if (!SafeArchiveRelativePath(relative)) {
|
||||
error = "The ZIP contains an unsafe path.";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const std::filesystem::path destination = output_root / relative;
|
||||
if (mz_zip_reader_is_file_a_directory(&archive, index)) {
|
||||
std::filesystem::create_directories(destination, ec);
|
||||
if (ec) {
|
||||
error = "ARMSX could not create a ZIP output directory.";
|
||||
return std::nullopt;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (mz_zip_reader_is_file_encrypted(&archive, index)) {
|
||||
error = "Encrypted ZIP archives are not supported.";
|
||||
return std::nullopt;
|
||||
}
|
||||
if (!mz_zip_reader_is_file_supported(&archive, index)) {
|
||||
error = "The ZIP uses an unsupported compression method.";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (stat.m_uncomp_size > kMaxArchiveBytes ||
|
||||
total_uncompressed > (kMaxArchiveBytes - stat.m_uncomp_size)) {
|
||||
error = "The ZIP expands beyond ARMSX's safety limit.";
|
||||
return std::nullopt;
|
||||
}
|
||||
total_uncompressed += stat.m_uncomp_size;
|
||||
|
||||
std::filesystem::create_directories(destination.parent_path(), ec);
|
||||
if (ec || !mz_zip_reader_extract_to_file(
|
||||
&archive, index, destination.string().c_str(), 0)) {
|
||||
error = "A file could not be extracted from the ZIP archive.";
|
||||
return std::nullopt;
|
||||
}
|
||||
if (LaunchCandidatePriority(destination) < 100) {
|
||||
candidates.push_back(destination);
|
||||
}
|
||||
}
|
||||
|
||||
if (candidates.empty()) {
|
||||
error = "The ZIP does not contain a supported PlayStation image or executable.";
|
||||
return std::nullopt;
|
||||
}
|
||||
std::stable_sort(candidates.begin(), candidates.end(), [](const auto& lhs, const auto& rhs) {
|
||||
return LaunchCandidatePriority(lhs) < LaunchCandidatePriority(rhs);
|
||||
});
|
||||
return candidates.front();
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace armsx
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef ARMSX_FRONTEND_ARCHIVE_H
|
||||
#define ARMSX_FRONTEND_ARCHIVE_H
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace armsx {
|
||||
|
||||
bool IsZipPath(const std::filesystem::path& path);
|
||||
|
||||
#ifdef USE_CHD
|
||||
std::optional<std::filesystem::path> ExtractZipLaunchCandidate(
|
||||
const std::filesystem::path& archive_path,
|
||||
std::string& error
|
||||
);
|
||||
#endif
|
||||
|
||||
} // namespace armsx
|
||||
|
||||
#endif
|
||||
+25
-2
@@ -50,6 +50,10 @@ static const char g_default_settings[] =
|
||||
"[console]\n"
|
||||
" region = \"auto\"\n"
|
||||
"\n"
|
||||
"# CPU settings\n"
|
||||
"[cpu]\n"
|
||||
" execution_mode = \"cached\" # cached | interpreter\n"
|
||||
"\n"
|
||||
"# Runtime settings\n"
|
||||
"[runtime]\n"
|
||||
" display_scale = 3\n"
|
||||
@@ -72,7 +76,7 @@ static const char g_default_settings[] =
|
||||
#endif
|
||||
"\n"
|
||||
#ifdef USE_HARDWARE
|
||||
" gpu_backend = \"software\" # software | hardware\n"
|
||||
" gpu_backend = \"software\" # software | sdl-accelerated\n"
|
||||
#endif
|
||||
" texture_scale_mode = false\n"
|
||||
" debug_panel = false\n"
|
||||
@@ -385,6 +389,7 @@ void psxe_cfg_load_defaults(psxe_config_t* cfg) {
|
||||
cfg->help_model = 0;
|
||||
cfg->help_region = 0;
|
||||
cfg->model = "scph1001";
|
||||
cfg->cpu_engine = 1;
|
||||
cfg->scale = 3;
|
||||
cfg->psxe_version = STR(REP_VERSION);
|
||||
cfg->region = "ntsc";
|
||||
@@ -431,6 +436,7 @@ void psxe_cfg_load(psxe_config_t* cfg, int argc, const char* argv[]) {
|
||||
int log_level = 0;
|
||||
int quiet = cfg->quiet;
|
||||
int console_source = 0;
|
||||
int cpu_engine = cfg->cpu_engine;
|
||||
int scale = 0;
|
||||
int vsync_enabled = cfg->vsync_enabled;
|
||||
#ifdef USE_HARDWARE
|
||||
@@ -451,6 +457,7 @@ void psxe_cfg_load(psxe_config_t* cfg, int argc, const char* argv[]) {
|
||||
const char* psxe_version = NULL;
|
||||
const char* cd_path = NULL;
|
||||
const char* exp_path = NULL;
|
||||
const char* cpu_engine_name = NULL;
|
||||
|
||||
static const char *const usages[] = {
|
||||
"psxe [options] path-to-cdrom",
|
||||
@@ -467,6 +474,7 @@ void psxe_cfg_load(psxe_config_t* cfg, int argc, const char* argv[]) {
|
||||
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 (0 , "cpu-engine" , &cpu_engine_name, "Select CPU engine (cached or interpreter)", NULL, 0, 0),
|
||||
OPT_STRING ('e', "exp-rom" , &exp_path , "Specify an expansion ROM file"),
|
||||
OPT_INTEGER ('L', "log-level" , &log_level , "Set log level"),
|
||||
OPT_STRING ('M', "model" , &model , "Specify console model (SPCH-XXXX)", NULL, 0, 0),
|
||||
@@ -587,6 +595,17 @@ void psxe_cfg_load(psxe_config_t* cfg, int argc, const char* argv[]) {
|
||||
region = s_console_region.u.s;
|
||||
}
|
||||
|
||||
toml_table_t* s_cpu_table = toml_table_in(conf, "cpu");
|
||||
|
||||
if (s_cpu_table && !cpu_engine_name) {
|
||||
toml_datum_t s_cpu_engine = toml_string_in(s_cpu_table, "execution_mode");
|
||||
|
||||
if (s_cpu_engine.ok && s_cpu_engine.u.s) {
|
||||
cpu_engine = !strcasecmp(s_cpu_engine.u.s, "interpreter") ? 0 : 1;
|
||||
free(s_cpu_engine.u.s);
|
||||
}
|
||||
}
|
||||
|
||||
toml_table_t* s_video_table = toml_table_in(conf, "video");
|
||||
|
||||
if (s_video_table) {
|
||||
@@ -601,7 +620,7 @@ void psxe_cfg_load(psxe_config_t* cfg, int argc, const char* argv[]) {
|
||||
if (s_gpu_backend.ok && s_gpu_backend.u.s) {
|
||||
const char* backend = s_gpu_backend.u.s;
|
||||
|
||||
if (!strcasecmp(backend, "hardware") || !strcasecmp(backend, "hardware (experimental)") || !strcasecmp(backend, "hw") || !strcasecmp(backend, "hw-renderer")) {
|
||||
if (!strcasecmp(backend, "sdl-accelerated") || !strcasecmp(backend, "hardware") || !strcasecmp(backend, "hardware (experimental)") || !strcasecmp(backend, "hw") || !strcasecmp(backend, "hw-renderer")) {
|
||||
gpu_backend = 1;
|
||||
} else {
|
||||
gpu_backend = 0;
|
||||
@@ -719,10 +738,14 @@ void psxe_cfg_load(psxe_config_t* cfg, int argc, const char* argv[]) {
|
||||
if (exp_path)
|
||||
cfg->exp_path = exp_path;
|
||||
|
||||
if (cpu_engine_name)
|
||||
cpu_engine = !strcasecmp(cpu_engine_name, "interpreter") ? 0 : 1;
|
||||
|
||||
if (scale)
|
||||
cfg->scale = scale;
|
||||
|
||||
cfg->vsync_enabled = vsync_enabled;
|
||||
cfg->cpu_engine = cpu_engine;
|
||||
#ifdef USE_HARDWARE
|
||||
cfg->gpu_backend = gpu_backend;
|
||||
#endif
|
||||
|
||||
@@ -14,6 +14,7 @@ typedef struct {
|
||||
int log_level;
|
||||
int quiet;
|
||||
int console_source;
|
||||
int cpu_engine;
|
||||
int scale;
|
||||
int vsync_enabled;
|
||||
#ifdef USE_HARDWARE
|
||||
|
||||
+18
-551
File diff suppressed because it is too large
Load Diff
+228
-119
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "bus.h"
|
||||
#include "bus_init.h"
|
||||
@@ -7,17 +8,30 @@
|
||||
|
||||
#define RANGE(v, s, e) ((v >= s) && (v < e))
|
||||
|
||||
const uint32_t g_psx_bus_region_mask_table[] = {
|
||||
const uint32_t g_psx_bus_region_mask_table[] = {
|
||||
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff,
|
||||
0x7fffffff, 0x1fffffff, 0xffffffff, 0xffffffff
|
||||
};
|
||||
|
||||
psx_bus_t* psx_bus_create(void) {
|
||||
return (psx_bus_t*)malloc(sizeof(psx_bus_t));
|
||||
}
|
||||
|
||||
// Does nothing for now
|
||||
void psx_bus_init(psx_bus_t* bus) {}
|
||||
};
|
||||
|
||||
uint32_t psx_bus_physical_address(uint32_t addr) {
|
||||
return addr & g_psx_bus_region_mask_table[addr >> 29];
|
||||
}
|
||||
psx_bus_t* psx_bus_create(void) {
|
||||
return (psx_bus_t*)calloc(1, sizeof(psx_bus_t));
|
||||
}
|
||||
|
||||
void psx_bus_init(psx_bus_t* bus) {
|
||||
if (bus)
|
||||
memset(bus, 0, sizeof(*bus));
|
||||
}
|
||||
|
||||
void psx_bus_set_write_observer(psx_bus_t* bus, psx_bus_write_observer_t observer, void* udata) {
|
||||
if (!bus)
|
||||
return;
|
||||
|
||||
bus->write_observer = observer;
|
||||
bus->write_observer_udata = udata;
|
||||
}
|
||||
|
||||
void psx_bus_destroy(psx_bus_t* bus) {
|
||||
free(bus);
|
||||
@@ -28,17 +42,19 @@ void psx_bus_destroy(psx_bus_t* bus) {
|
||||
bus->access_cycles = bus->dev->bus_delay; \
|
||||
return psx_ ## dev ## _read ## bits (bus->dev, addr - bus->dev->io_base); \
|
||||
}
|
||||
#define HANDLE_WRITE(dev, bits) \
|
||||
if (RANGE(addr, bus->dev->io_base, (bus->dev->io_base + bus->dev->io_size))) { \
|
||||
bus->access_cycles = bus->dev->bus_delay; \
|
||||
psx_ ## dev ## _write ## bits (bus->dev, addr - bus->dev->io_base, value); \
|
||||
return; \
|
||||
}
|
||||
#define HANDLE_WRITE(dev, bits) \
|
||||
if (RANGE(addr, bus->dev->io_base, (bus->dev->io_base + bus->dev->io_size))) { \
|
||||
bus->access_cycles = bus->dev->bus_delay; \
|
||||
psx_ ## dev ## _write ## bits (bus->dev, addr - bus->dev->io_base, value); \
|
||||
if (bus->write_observer) \
|
||||
bus->write_observer(bus->write_observer_udata, addr, (bits) / 8); \
|
||||
return; \
|
||||
}
|
||||
|
||||
uint32_t psx_bus_read32(psx_bus_t* bus, uint32_t addr) {
|
||||
uint32_t vaddr = addr;
|
||||
|
||||
addr &= g_psx_bus_region_mask_table[addr >> 29];
|
||||
addr = psx_bus_physical_address(addr);
|
||||
|
||||
if (addr & 0x3) {
|
||||
log_fatal("Unaligned 32-bit read from %08x:%08x", vaddr, addr);
|
||||
@@ -75,7 +91,7 @@ uint16_t psx_bus_read16(psx_bus_t* bus, uint32_t addr) {
|
||||
|
||||
uint32_t vaddr = addr;
|
||||
|
||||
addr &= g_psx_bus_region_mask_table[addr >> 29];
|
||||
addr = psx_bus_physical_address(addr);
|
||||
|
||||
if (addr & 0x1) {
|
||||
log_fatal("Unaligned 16-bit read from %08x:%08x", vaddr, addr);
|
||||
@@ -122,7 +138,7 @@ uint8_t psx_bus_read8(psx_bus_t* bus, uint32_t addr) {
|
||||
|
||||
// uint32_t vaddr = addr;
|
||||
|
||||
addr &= g_psx_bus_region_mask_table[addr >> 29];
|
||||
addr = psx_bus_physical_address(addr);
|
||||
|
||||
HANDLE_READ(bios, 8);
|
||||
HANDLE_READ(ram, 8);
|
||||
@@ -153,7 +169,7 @@ void psx_bus_write32(psx_bus_t* bus, uint32_t addr, uint32_t value) {
|
||||
|
||||
uint32_t vaddr = addr;
|
||||
|
||||
addr &= g_psx_bus_region_mask_table[addr >> 29];
|
||||
addr = psx_bus_physical_address(addr);
|
||||
|
||||
if (addr & 0x3) {
|
||||
log_fatal("Unaligned 32-bit write to %08x:%08x (%08x)", vaddr, addr, value);
|
||||
@@ -187,7 +203,7 @@ void psx_bus_write16(psx_bus_t* bus, uint32_t addr, uint32_t value) {
|
||||
|
||||
uint32_t vaddr = addr;
|
||||
|
||||
addr &= g_psx_bus_region_mask_table[addr >> 29];
|
||||
addr = psx_bus_physical_address(addr);
|
||||
|
||||
if (addr & 0x1) {
|
||||
log_fatal("Unaligned 16-bit write to %08x:%08x (%04x)", vaddr, addr, value);
|
||||
@@ -222,7 +238,7 @@ void psx_bus_write8(psx_bus_t* bus, uint32_t addr, uint32_t value) {
|
||||
|
||||
uint32_t vaddr = addr;
|
||||
|
||||
addr &= g_psx_bus_region_mask_table[addr >> 29];
|
||||
addr = psx_bus_physical_address(addr);
|
||||
|
||||
HANDLE_WRITE(bios, 8);
|
||||
HANDLE_WRITE(ram, 8);
|
||||
@@ -319,4 +335,4 @@ uint32_t psx_bus_get_access_cycles(psx_bus_t* bus) {
|
||||
}
|
||||
|
||||
#undef HANDLE_READ
|
||||
#undef HANDLE_WRITE
|
||||
#undef HANDLE_WRITE
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct psx_bus_t;
|
||||
|
||||
typedef struct psx_bus_t psx_bus_t;
|
||||
struct psx_bus_t;
|
||||
|
||||
typedef struct psx_bus_t psx_bus_t;
|
||||
typedef void (*psx_bus_write_observer_t)(void*, uint32_t, uint32_t);
|
||||
|
||||
psx_bus_t* psx_bus_create(void);
|
||||
void psx_bus_init(psx_bus_t*);
|
||||
@@ -13,9 +14,11 @@ uint32_t psx_bus_read32(psx_bus_t*, uint32_t);
|
||||
uint16_t psx_bus_read16(psx_bus_t*, uint32_t);
|
||||
uint8_t psx_bus_read8(psx_bus_t*, uint32_t);
|
||||
void psx_bus_write32(psx_bus_t*, uint32_t, uint32_t);
|
||||
void psx_bus_write16(psx_bus_t*, uint32_t, uint32_t);
|
||||
void psx_bus_write8(psx_bus_t*, uint32_t, uint32_t);
|
||||
uint32_t psx_bus_get_access_cycles(psx_bus_t*);
|
||||
void psx_bus_destroy(psx_bus_t*);
|
||||
void psx_bus_write16(psx_bus_t*, uint32_t, uint32_t);
|
||||
void psx_bus_write8(psx_bus_t*, uint32_t, uint32_t);
|
||||
uint32_t psx_bus_get_access_cycles(psx_bus_t*);
|
||||
uint32_t psx_bus_physical_address(uint32_t);
|
||||
void psx_bus_set_write_observer(psx_bus_t*, psx_bus_write_observer_t, void*);
|
||||
void psx_bus_destroy(psx_bus_t*);
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
+7
-5
@@ -34,10 +34,12 @@ struct psx_bus_t {
|
||||
psx_timer_t* timer;
|
||||
psx_cdrom_t* cdrom;
|
||||
psx_pad_t* pad;
|
||||
psx_mdec_t* mdec;
|
||||
|
||||
uint32_t access_cycles;
|
||||
};
|
||||
psx_mdec_t* mdec;
|
||||
|
||||
uint32_t access_cycles;
|
||||
psx_bus_write_observer_t write_observer;
|
||||
void* write_observer_udata;
|
||||
};
|
||||
|
||||
void psx_bus_init_bios(psx_bus_t*, psx_bios_t*);
|
||||
void psx_bus_init_ram(psx_bus_t*, psx_ram_t*);
|
||||
@@ -56,4 +58,4 @@ void psx_bus_init_cdrom(psx_bus_t*, psx_cdrom_t*);
|
||||
void psx_bus_init_pad(psx_bus_t*, psx_pad_t*);
|
||||
void psx_bus_init_mdec(psx_bus_t*, psx_mdec_t*);
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -9,9 +9,21 @@
|
||||
#define PSX_CPU_CPS 33868800 // 33868800 Clocks/s
|
||||
#define PSX_CPU_FREQ 33.868800f // 33.868800 MHz
|
||||
|
||||
struct psx_cpu_t;
|
||||
|
||||
typedef struct psx_cpu_t psx_cpu_t;
|
||||
struct psx_cpu_t;
|
||||
struct psx_cpu_cache_t;
|
||||
|
||||
typedef struct psx_cpu_t psx_cpu_t;
|
||||
|
||||
typedef enum {
|
||||
PSX_CPU_INTERPRETER = 0,
|
||||
PSX_CPU_CACHED_INTERPRETER = 1
|
||||
} psx_cpu_execution_mode_t;
|
||||
|
||||
typedef struct {
|
||||
uint64_t hits;
|
||||
uint64_t misses;
|
||||
uint64_t invalidations;
|
||||
} psx_cpu_cache_stats_t;
|
||||
|
||||
typedef void (*psx_cpu_kcall_hook_t)(psx_cpu_t*);
|
||||
|
||||
@@ -151,9 +163,11 @@ struct __attribute__((__packed__)) psx_cpu_t {
|
||||
|
||||
psx_bus_t* bus;
|
||||
|
||||
psx_cpu_kcall_hook_t a_function_hook;
|
||||
psx_cpu_kcall_hook_t b_function_hook;
|
||||
};
|
||||
psx_cpu_kcall_hook_t a_function_hook;
|
||||
psx_cpu_kcall_hook_t b_function_hook;
|
||||
psx_cpu_execution_mode_t execution_mode;
|
||||
struct psx_cpu_cache_t* cache;
|
||||
};
|
||||
|
||||
/*
|
||||
0 IEc Current Interrupt Enable (0=Disable, 1=Enable) ;rfe pops IUp here
|
||||
@@ -231,9 +245,14 @@ void psx_cpu_set_irq_pending(psx_cpu_t*);
|
||||
void psx_cpu_load_state(psx_cpu_t*, FILE*);
|
||||
void psx_cpu_save_state(psx_cpu_t*, FILE*);
|
||||
void psx_cpu_fetch(psx_cpu_t*);
|
||||
void psx_cpu_set_a_kcall_hook(psx_cpu_t*, psx_cpu_kcall_hook_t);
|
||||
void psx_cpu_set_b_kcall_hook(psx_cpu_t*, psx_cpu_kcall_hook_t);
|
||||
int psx_cpu_execute(psx_cpu_t*);
|
||||
void psx_cpu_set_a_kcall_hook(psx_cpu_t*, psx_cpu_kcall_hook_t);
|
||||
void psx_cpu_set_b_kcall_hook(psx_cpu_t*, psx_cpu_kcall_hook_t);
|
||||
void psx_cpu_set_execution_mode(psx_cpu_t*, psx_cpu_execution_mode_t);
|
||||
psx_cpu_execution_mode_t psx_cpu_get_execution_mode(const psx_cpu_t*);
|
||||
void psx_cpu_invalidate_cache(psx_cpu_t*);
|
||||
void psx_cpu_invalidate_range(psx_cpu_t*, uint32_t, uint32_t);
|
||||
psx_cpu_cache_stats_t psx_cpu_get_cache_stats(const psx_cpu_t*);
|
||||
int psx_cpu_execute(psx_cpu_t*);
|
||||
|
||||
/*
|
||||
00h INT Interrupt
|
||||
@@ -310,4 +329,4 @@ int psx_cpu_execute(psx_cpu_t*);
|
||||
#define GTEF_M1POVF 0x40000000
|
||||
#define GTEF_ERRORF 0x80000000
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
+90
-3
@@ -13,20 +13,28 @@
|
||||
|
||||
#define CHD_CD_FRAME_SIZE CD_FRAME_SIZE
|
||||
#define CHD_CD_TRACK_ALIGN 4u
|
||||
#define CHD_DEFAULT_PREGAP_FRAMES (2u * 75u)
|
||||
#define CHD_TRACK1_PREGAP_FRAMES (2u * 75u)
|
||||
|
||||
typedef enum {
|
||||
CHD_TRACK_MODE_AUDIO = 0,
|
||||
CHD_TRACK_MODE_DATA = 1
|
||||
} chd_track_mode_t;
|
||||
|
||||
typedef enum {
|
||||
CHD_SUBCHANNEL_NONE = 0,
|
||||
CHD_SUBCHANNEL_COOKED,
|
||||
CHD_SUBCHANNEL_RAW
|
||||
} chd_subchannel_mode_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t track_number;
|
||||
uint32_t start_lba;
|
||||
uint64_t file_lba;
|
||||
uint32_t pregap_frames;
|
||||
uint32_t data_frames;
|
||||
uint32_t postgap_frames;
|
||||
chd_track_mode_t mode;
|
||||
chd_subchannel_mode_t subchannel_mode;
|
||||
} chd_track_t;
|
||||
|
||||
struct chd_s {
|
||||
@@ -119,6 +127,25 @@ static int chd_parse_track_mode(const char* type_str, chd_track_mode_t* mode_out
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int chd_parse_subchannel_mode(const char* subtype, chd_subchannel_mode_t* mode_out) {
|
||||
if (!subtype || !mode_out)
|
||||
return 0;
|
||||
|
||||
if (!strcmp(subtype, "NONE")) {
|
||||
*mode_out = CHD_SUBCHANNEL_NONE;
|
||||
return 1;
|
||||
}
|
||||
if (!strcmp(subtype, "RW")) {
|
||||
*mode_out = CHD_SUBCHANNEL_COOKED;
|
||||
return 1;
|
||||
}
|
||||
if (!strcmp(subtype, "RW_RAW")) {
|
||||
*mode_out = CHD_SUBCHANNEL_RAW;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int chd_read_hunk(chd_t* chd, uint32_t hunk_index) {
|
||||
if (!chd || !chd->handle || !chd->hunk_buffer) {
|
||||
return 0;
|
||||
@@ -157,6 +184,7 @@ static int chd_find_track(const chd_t* chd, uint32_t lba, int* is_pregap_out) {
|
||||
? (track->start_lba - track->pregap_frames)
|
||||
: 0u;
|
||||
const uint32_t track_end = track->start_lba + track->data_frames;
|
||||
const uint32_t postgap_end = track_end + track->postgap_frames;
|
||||
|
||||
if (lba >= pregap_start && lba < track->start_lba) {
|
||||
if (is_pregap_out) {
|
||||
@@ -171,6 +199,13 @@ static int chd_find_track(const chd_t* chd, uint32_t lba, int* is_pregap_out) {
|
||||
}
|
||||
return (int)index;
|
||||
}
|
||||
|
||||
if (lba >= track_end && lba < postgap_end) {
|
||||
if (is_pregap_out) {
|
||||
*is_pregap_out = 1;
|
||||
}
|
||||
return (int)index;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_pregap_out) {
|
||||
@@ -252,6 +287,7 @@ int chd_load(chd_t* chd, const char* path) {
|
||||
int postgap_frames = 0;
|
||||
uint32_t metadata_length = 0;
|
||||
chd_track_mode_t track_mode = CHD_TRACK_MODE_DATA;
|
||||
chd_subchannel_mode_t subchannel_mode = CHD_SUBCHANNEL_NONE;
|
||||
int have_metadata = 0;
|
||||
|
||||
memset(metadata, 0, sizeof(metadata));
|
||||
@@ -303,8 +339,14 @@ int chd_load(chd_t* chd, const char* path) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (pregap_frames <= 0 && track_mode != CHD_TRACK_MODE_AUDIO) {
|
||||
pregap_frames = CHD_DEFAULT_PREGAP_FRAMES;
|
||||
if (!chd_parse_subchannel_mode(subtype_str, &subchannel_mode)) {
|
||||
log_error("Unsupported CHD subchannel mode '%s' in %s", subtype_str, path);
|
||||
chd_reset(chd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (pregap_frames <= 0 && track_num == 1) {
|
||||
pregap_frames = CHD_TRACK1_PREGAP_FRAMES;
|
||||
}
|
||||
|
||||
const int pregap_in_file = (pregap_frames > 0 && toupper((unsigned char)pgtype_str[0]) == 'V');
|
||||
@@ -339,7 +381,9 @@ int chd_load(chd_t* chd, const char* path) {
|
||||
.file_lba = track_file_lba,
|
||||
.pregap_frames = (uint32_t)pregap_frames,
|
||||
.data_frames = data_frames,
|
||||
.postgap_frames = (uint32_t)(postgap_frames > 0 ? postgap_frames : 0),
|
||||
.mode = track_mode,
|
||||
.subchannel_mode = subchannel_mode,
|
||||
};
|
||||
|
||||
if (!chd_append_track(chd, &track)) {
|
||||
@@ -349,6 +393,9 @@ int chd_load(chd_t* chd, const char* path) {
|
||||
}
|
||||
|
||||
disc_lba += data_frames;
|
||||
if (postgap_frames > 0) {
|
||||
disc_lba += (uint32_t)postgap_frames;
|
||||
}
|
||||
file_lba += data_frames;
|
||||
file_lba = align_up_u64(file_lba, CHD_CD_TRACK_ALIGN);
|
||||
}
|
||||
@@ -466,6 +513,46 @@ int chd_get_track_lba(chd_t* chd, int track) {
|
||||
return (int)chd->tracks[(size_t)track - 1u].start_lba;
|
||||
}
|
||||
|
||||
static void chd_deinterleave_subchannel(const uint8_t raw[96], uint8_t cooked[96]) {
|
||||
memset(cooked, 0, 96);
|
||||
for (unsigned symbol = 0; symbol < 96; ++symbol) {
|
||||
for (unsigned channel = 0; channel < 8; ++channel) {
|
||||
const uint8_t bit = (uint8_t)((raw[symbol] >> (7u - channel)) & 1u);
|
||||
cooked[channel * 12u + symbol / 8u] |= (uint8_t)(bit << (7u - (symbol & 7u)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int chd_read_subchannel_q(chd_t* chd, uint32_t lba, uint8_t q[12]) {
|
||||
if (!chd || !q || !chd->handle || !chd->hunk_buffer)
|
||||
return 0;
|
||||
|
||||
int is_gap = 0;
|
||||
const int track_index = chd_find_track(chd, lba, &is_gap);
|
||||
if (track_index < 0 || is_gap)
|
||||
return 0;
|
||||
|
||||
const chd_track_t* track = &chd->tracks[track_index];
|
||||
if (track->subchannel_mode == CHD_SUBCHANNEL_NONE)
|
||||
return 0;
|
||||
|
||||
const uint64_t file_frame = track->file_lba + (uint64_t)(lba - track->start_lba);
|
||||
const uint32_t hunk_index = (uint32_t)(file_frame / chd->sectors_per_hunk);
|
||||
const uint32_t hunk_offset = (uint32_t)((file_frame % chd->sectors_per_hunk) * CHD_CD_FRAME_SIZE);
|
||||
if ((hunk_offset + CHD_CD_FRAME_SIZE) > chd->hunk_size || !chd_read_hunk(chd, hunk_index))
|
||||
return 0;
|
||||
|
||||
const uint8_t* subchannel = &chd->hunk_buffer[hunk_offset + CD_SECTOR_SIZE];
|
||||
if (track->subchannel_mode == CHD_SUBCHANNEL_COOKED) {
|
||||
memcpy(q, subchannel + 12, 12);
|
||||
} else {
|
||||
uint8_t cooked[96];
|
||||
chd_deinterleave_subchannel(subchannel, cooked);
|
||||
memcpy(q, cooked + 12, 12);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void chd_destroy(chd_t* chd) {
|
||||
if (!chd) {
|
||||
return;
|
||||
|
||||
@@ -17,6 +17,7 @@ int chd_query(chd_t* cue, uint32_t lba);
|
||||
int chd_get_track_number(chd_t* cue, uint32_t lba);
|
||||
int chd_get_track_count(chd_t* cue);
|
||||
int chd_get_track_lba(chd_t* cue, int track);
|
||||
int chd_read_subchannel_q(chd_t* cue, uint32_t lba, uint8_t q[12]);
|
||||
void chd_destroy(chd_t* cue);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -203,6 +203,7 @@ int psx_disc_open_as(psx_disc_t* disc, const char* path, int type) {
|
||||
disc->get_track_number = (get_track_number_func)chd_get_track_number;
|
||||
disc->get_track_count = (get_track_count_func)chd_get_track_count;
|
||||
disc->get_track_lba = (get_track_lba_func)chd_get_track_lba;
|
||||
disc->read_subchannel_q = (read_subchannel_q_func)chd_read_subchannel_q;
|
||||
disc->destroy = (destroy_func)chd_destroy;
|
||||
} break;
|
||||
#endif
|
||||
@@ -242,6 +243,13 @@ int psx_disc_get_track_lba(psx_disc_t* disc, int track) {
|
||||
return disc->get_track_lba(disc->udata, track);
|
||||
}
|
||||
|
||||
int psx_disc_read_subchannel_q(psx_disc_t* disc, uint32_t lba, uint8_t q[12]) {
|
||||
if (!disc || !disc->read_subchannel_q || !q)
|
||||
return 0;
|
||||
|
||||
return disc->read_subchannel_q(disc->udata, lba, q);
|
||||
}
|
||||
|
||||
void psx_disc_destroy(psx_disc_t* disc) {
|
||||
disc->destroy(disc->udata);
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ typedef int (*query_sector_func)(void*, uint32_t);
|
||||
typedef int (*get_track_number_func)(void*, uint32_t);
|
||||
typedef int (*get_track_count_func)(void*);
|
||||
typedef uint32_t (*get_track_lba_func)(void*, int);
|
||||
typedef int (*read_subchannel_q_func)(void*, uint32_t, uint8_t*);
|
||||
typedef void (*destroy_func)(void*);
|
||||
|
||||
typedef struct {
|
||||
@@ -58,6 +59,7 @@ typedef struct {
|
||||
get_track_number_func get_track_number;
|
||||
get_track_count_func get_track_count;
|
||||
get_track_lba_func get_track_lba;
|
||||
read_subchannel_q_func read_subchannel_q;
|
||||
destroy_func destroy;
|
||||
} psx_disc_t;
|
||||
|
||||
@@ -69,6 +71,7 @@ int psx_disc_query(psx_disc_t* disc, uint32_t lba);
|
||||
int psx_disc_get_track_number(psx_disc_t* disc, uint32_t lba);
|
||||
int psx_disc_get_track_count(psx_disc_t* disc);
|
||||
int psx_disc_get_track_lba(psx_disc_t* disc, int track);
|
||||
int psx_disc_read_subchannel_q(psx_disc_t* disc, uint32_t lba, uint8_t q[12]);
|
||||
void psx_disc_close(psx_disc_t* disc);
|
||||
void psx_disc_destroy(psx_disc_t* disc);
|
||||
|
||||
|
||||
+16
-2
@@ -417,13 +417,27 @@ void cdrom_cmd_getlocl(psx_cdrom_t* cdrom) {
|
||||
|
||||
void cdrom_cmd_getlocp(psx_cdrom_t* cdrom) {
|
||||
int lba = cdrom->lba;
|
||||
|
||||
int track = psx_disc_get_track_number(cdrom->disc, lba);
|
||||
int track_lba = psx_disc_get_track_lba(cdrom->disc, track);
|
||||
|
||||
if (!cdrom->seek_precision)
|
||||
lba -= 25;
|
||||
|
||||
uint8_t subq[12];
|
||||
if (lba >= 0 && psx_disc_read_subchannel_q(cdrom->disc, (uint32_t)lba, subq)) {
|
||||
cdrom_set_int(cdrom, 3);
|
||||
queue_push(cdrom->response, subq[1]);
|
||||
queue_push(cdrom->response, subq[2]);
|
||||
queue_push(cdrom->response, subq[3]);
|
||||
queue_push(cdrom->response, subq[4]);
|
||||
queue_push(cdrom->response, subq[5]);
|
||||
queue_push(cdrom->response, subq[7]);
|
||||
queue_push(cdrom->response, subq[8]);
|
||||
queue_push(cdrom->response, subq[9]);
|
||||
cdrom_restore_state(cdrom);
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t diff = lba - track_lba;
|
||||
|
||||
if (diff < 0)
|
||||
@@ -732,4 +746,4 @@ void cdrom_cmd_readtoc(psx_cdrom_t* cdrom) {
|
||||
|
||||
void cdrom_cmd_videocd(psx_cdrom_t* cdrom) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,11 @@ int psx_exe_load(psx_cpu_t* cpu, const char* path) {
|
||||
// Read to RAM directly
|
||||
uint32_t offset = hdr.ramdest & 0x7fffffff;
|
||||
|
||||
if (!fread(cpu->bus->ram->buf + offset, 1, hdr.filesz, file))
|
||||
return 3;
|
||||
|
||||
if (!fread(cpu->bus->ram->buf + offset, 1, hdr.filesz, file))
|
||||
return 3;
|
||||
|
||||
psx_cpu_invalidate_range(cpu, offset, hdr.filesz);
|
||||
|
||||
// Load initial register values
|
||||
cpu->pc = hdr.ipc;
|
||||
cpu->next_pc = cpu->pc + 4;
|
||||
@@ -43,4 +45,4 @@ int psx_exe_load(psx_cpu_t* cpu, const char* path) {
|
||||
fclose(file);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user