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 <string.h>
|
||||
|
||||
#include "bus.h"
|
||||
#include "bus_init.h"
|
||||
@@ -12,12 +13,25 @@ const uint32_t g_psx_bus_region_mask_table[] = {
|
||||
0x7fffffff, 0x1fffffff, 0xffffffff, 0xffffffff
|
||||
};
|
||||
|
||||
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*)malloc(sizeof(psx_bus_t));
|
||||
return (psx_bus_t*)calloc(1, sizeof(psx_bus_t));
|
||||
}
|
||||
|
||||
// Does nothing for now
|
||||
void psx_bus_init(psx_bus_t* bus) {}
|
||||
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);
|
||||
@@ -32,13 +46,15 @@ void psx_bus_destroy(psx_bus_t* bus) {
|
||||
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);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
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*);
|
||||
@@ -16,6 +17,8 @@ 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*);
|
||||
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
|
||||
@@ -37,6 +37,8 @@ struct psx_bus_t {
|
||||
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*);
|
||||
|
||||
@@ -2,11 +2,38 @@
|
||||
#include "bus.h"
|
||||
#include "log.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cpu_debug.h"
|
||||
|
||||
#define PSX_CPU_CACHE_ENTRY_COUNT 16384u
|
||||
#define PSX_CPU_CACHE_ENTRY_MASK (PSX_CPU_CACHE_ENTRY_COUNT - 1u)
|
||||
|
||||
typedef int (*psx_cpu_cached_handler_t)(psx_cpu_t*);
|
||||
|
||||
typedef struct {
|
||||
uint32_t address;
|
||||
uint32_t opcode;
|
||||
psx_cpu_cached_handler_t handler;
|
||||
uint8_t valid;
|
||||
} psx_cpu_cache_entry_t;
|
||||
|
||||
struct psx_cpu_cache_t {
|
||||
psx_cpu_cache_entry_t entries[PSX_CPU_CACHE_ENTRY_COUNT];
|
||||
psx_cpu_cache_stats_t stats;
|
||||
};
|
||||
|
||||
static int psx_cpu_execute_cached(psx_cpu_t*);
|
||||
|
||||
static uint32_t psx_cpu_cache_index(uint32_t address) {
|
||||
return (address >> 2) & PSX_CPU_CACHE_ENTRY_MASK;
|
||||
}
|
||||
|
||||
static void psx_cpu_bus_write_observer(void* udata, uint32_t address, uint32_t size) {
|
||||
psx_cpu_invalidate_range((psx_cpu_t*)udata, address, size);
|
||||
}
|
||||
|
||||
static const uint32_t g_psx_cpu_cop0_write_mask_table[] = {
|
||||
0x00000000, // cop0r0 - N/A
|
||||
0x00000000, // cop0r1 - N/A
|
||||
@@ -155,13 +182,29 @@ void cpu_b_kcall_hook(psx_cpu_t* cpu) {
|
||||
}
|
||||
|
||||
psx_cpu_t* psx_cpu_create(void) {
|
||||
return (psx_cpu_t*)malloc(sizeof(psx_cpu_t));
|
||||
psx_cpu_t* cpu = (psx_cpu_t*)calloc(1, sizeof(*cpu));
|
||||
|
||||
if (!cpu)
|
||||
return NULL;
|
||||
|
||||
cpu->cache = (struct psx_cpu_cache_t*)calloc(1, sizeof(*cpu->cache));
|
||||
if (!cpu->cache) {
|
||||
free(cpu);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cpu->execution_mode = PSX_CPU_CACHED_INTERPRETER;
|
||||
return cpu;
|
||||
}
|
||||
|
||||
void cpu_a_kcall_hook(psx_cpu_t*);
|
||||
void cpu_b_kcall_hook(psx_cpu_t*);
|
||||
|
||||
void psx_cpu_destroy(psx_cpu_t* cpu) {
|
||||
if (!cpu)
|
||||
return;
|
||||
|
||||
free(cpu->cache);
|
||||
free(cpu);
|
||||
}
|
||||
|
||||
@@ -174,24 +217,47 @@ void psx_cpu_set_b_kcall_hook(psx_cpu_t* cpu, psx_cpu_kcall_hook_t hook) {
|
||||
}
|
||||
|
||||
void psx_cpu_save_state(psx_cpu_t* cpu, FILE* file) {
|
||||
fwrite((char*)cpu, sizeof(*cpu) - sizeof(psx_bus_t*), 1, file);
|
||||
fwrite((char*)cpu, offsetof(psx_cpu_t, bus), 1, file);
|
||||
}
|
||||
|
||||
void psx_cpu_load_state(psx_cpu_t* cpu, FILE* file) {
|
||||
if (!fread((char*)cpu, sizeof(*cpu) - sizeof(psx_bus_t*), 1, file)) {
|
||||
psx_bus_t* bus = cpu->bus;
|
||||
psx_cpu_kcall_hook_t a_hook = cpu->a_function_hook;
|
||||
psx_cpu_kcall_hook_t b_hook = cpu->b_function_hook;
|
||||
psx_cpu_execution_mode_t execution_mode = cpu->execution_mode;
|
||||
struct psx_cpu_cache_t* cache = cpu->cache;
|
||||
|
||||
if (!fread((char*)cpu, offsetof(psx_cpu_t, bus), 1, file)) {
|
||||
perror("Error reading CPU state");
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
cpu->bus = bus;
|
||||
cpu->a_function_hook = a_hook;
|
||||
cpu->b_function_hook = b_hook;
|
||||
cpu->execution_mode = execution_mode;
|
||||
cpu->cache = cache;
|
||||
psx_cpu_invalidate_cache(cpu);
|
||||
}
|
||||
|
||||
void psx_cpu_init(psx_cpu_t* cpu, psx_bus_t* bus) {
|
||||
struct psx_cpu_cache_t* cache = cpu->cache;
|
||||
psx_cpu_execution_mode_t execution_mode = cpu->execution_mode;
|
||||
|
||||
memset(cpu, 0, sizeof(psx_cpu_t));
|
||||
|
||||
cpu->cache = cache;
|
||||
cpu->execution_mode = execution_mode == PSX_CPU_INTERPRETER
|
||||
? PSX_CPU_INTERPRETER
|
||||
: PSX_CPU_CACHED_INTERPRETER;
|
||||
|
||||
psx_cpu_set_a_kcall_hook(cpu, cpu_a_kcall_hook);
|
||||
psx_cpu_set_b_kcall_hook(cpu, cpu_b_kcall_hook);
|
||||
|
||||
cpu->bus = bus;
|
||||
psx_bus_set_write_observer(bus, psx_cpu_bus_write_observer, cpu);
|
||||
psx_cpu_invalidate_cache(cpu);
|
||||
cpu->pc = 0xbfc00000;
|
||||
cpu->next_pc = cpu->pc + 4;
|
||||
|
||||
@@ -199,6 +265,56 @@ void psx_cpu_init(psx_cpu_t* cpu, psx_bus_t* bus) {
|
||||
cpu->cop0_r[COP0_PRID] = 0x00000002;
|
||||
}
|
||||
|
||||
void psx_cpu_set_execution_mode(psx_cpu_t* cpu, psx_cpu_execution_mode_t mode) {
|
||||
if (!cpu)
|
||||
return;
|
||||
|
||||
cpu->execution_mode = mode == PSX_CPU_INTERPRETER
|
||||
? PSX_CPU_INTERPRETER
|
||||
: PSX_CPU_CACHED_INTERPRETER;
|
||||
}
|
||||
|
||||
psx_cpu_execution_mode_t psx_cpu_get_execution_mode(const psx_cpu_t* cpu) {
|
||||
return cpu ? cpu->execution_mode : PSX_CPU_CACHED_INTERPRETER;
|
||||
}
|
||||
|
||||
void psx_cpu_invalidate_cache(psx_cpu_t* cpu) {
|
||||
if (!cpu || !cpu->cache)
|
||||
return;
|
||||
|
||||
memset(cpu->cache->entries, 0, sizeof(cpu->cache->entries));
|
||||
cpu->cache->stats.invalidations++;
|
||||
}
|
||||
|
||||
void psx_cpu_invalidate_range(psx_cpu_t* cpu, uint32_t address, uint32_t size) {
|
||||
if (!cpu || !cpu->cache || !size)
|
||||
return;
|
||||
|
||||
uint32_t first = psx_bus_physical_address(address) & ~3u;
|
||||
uint32_t last = psx_bus_physical_address(address + size - 1u) & ~3u;
|
||||
|
||||
if (last < first) {
|
||||
psx_cpu_invalidate_cache(cpu);
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint32_t current = first;; current += 4u) {
|
||||
psx_cpu_cache_entry_t* entry = &cpu->cache->entries[psx_cpu_cache_index(current)];
|
||||
if (entry->valid && entry->address == current) {
|
||||
entry->valid = 0;
|
||||
cpu->cache->stats.invalidations++;
|
||||
}
|
||||
|
||||
if (current == last)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
psx_cpu_cache_stats_t psx_cpu_get_cache_stats(const psx_cpu_t* cpu) {
|
||||
psx_cpu_cache_stats_t empty = {0, 0, 0};
|
||||
return (cpu && cpu->cache) ? cpu->cache->stats : empty;
|
||||
}
|
||||
|
||||
static inline int psx_cpu_check_irq(psx_cpu_t* cpu) {
|
||||
return (cpu->cop0_r[COP0_SR] & SR_IEC) &&
|
||||
(cpu->cop0_r[COP0_SR] & cpu->cop0_r[COP0_CAUSE] & 0x00000700);
|
||||
@@ -295,7 +411,9 @@ void psx_cpu_cycle(psx_cpu_t* cpu) {
|
||||
return;
|
||||
}
|
||||
|
||||
int cyc = psx_cpu_execute(cpu);
|
||||
int cyc = cpu->execution_mode == PSX_CPU_INTERPRETER
|
||||
? psx_cpu_execute(cpu)
|
||||
: psx_cpu_execute_cached(cpu);
|
||||
|
||||
if (!cyc) {
|
||||
printf("psxe: Illegal instruction %08x at %08x (next=%08x, saved=%08x)\n", cpu->opcode, cpu->pc, cpu->next_pc, cpu->saved_pc);
|
||||
@@ -2169,6 +2287,278 @@ static inline void psx_gte_i_ncct(psx_cpu_t* cpu) {
|
||||
NCCS(2);
|
||||
}
|
||||
|
||||
#define PSX_CPU_CACHED_WRAPPER(name, cycles) \
|
||||
static int psx_cpu_cached_##name(psx_cpu_t* cpu) { \
|
||||
psx_cpu_i_##name(cpu); \
|
||||
return (cycles); \
|
||||
}
|
||||
|
||||
PSX_CPU_CACHED_WRAPPER(sll, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(srl, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(sra, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(sllv, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(srlv, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(srav, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(jr, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(jalr, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(syscall, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(break, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(mfhi, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(mthi, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(mflo, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(mtlo, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(mult, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(multu, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(div, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(divu, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(add, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(addu, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(sub, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(subu, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(and, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(or, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(xor, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(nor, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(slt, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(sltu, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(j, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(jal, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(beq, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(bne, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(blez, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(bgtz, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(addi, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(addiu, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(slti, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(sltiu, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(andi, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(ori, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(xori, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(lui, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(mfc0, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(mtc0, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(rfe, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(mfc2, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(cfc2, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(mtc2, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(ctc2, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(lb, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(lh, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(lwl, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(lw, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(lbu, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(lhu, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(lwr, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(sb, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(sh, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(swl, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(sw, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(swr, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(lwc0, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(lwc1, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(lwc2, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(lwc3, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(swc0, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(swc1, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(swc2, 2)
|
||||
PSX_CPU_CACHED_WRAPPER(swc3, 2)
|
||||
|
||||
#define PSX_CPU_CACHED_BXX_WRAPPER(name) \
|
||||
static int psx_cpu_cached_##name(psx_cpu_t* cpu) { \
|
||||
cpu->branch = 1; \
|
||||
cpu->branch_taken = 0; \
|
||||
psx_cpu_i_##name(cpu); \
|
||||
return 2; \
|
||||
}
|
||||
|
||||
PSX_CPU_CACHED_BXX_WRAPPER(bltz)
|
||||
PSX_CPU_CACHED_BXX_WRAPPER(bgez)
|
||||
PSX_CPU_CACHED_BXX_WRAPPER(bltzal)
|
||||
PSX_CPU_CACHED_BXX_WRAPPER(bgezal)
|
||||
|
||||
#define PSX_CPU_CACHED_GTE_WRAPPER(name, cycles) \
|
||||
static int psx_cpu_cached_gte_##name(psx_cpu_t* cpu) { \
|
||||
DO_PENDING_LOAD; \
|
||||
cpu->gte_sf = ((cpu->opcode & 0x80000) != 0) * 12; \
|
||||
cpu->gte_lm = (cpu->opcode & 0x400) != 0; \
|
||||
cpu->gte_cv = (cpu->opcode >> 13) & 3; \
|
||||
cpu->gte_v = (cpu->opcode >> 15) & 3; \
|
||||
cpu->gte_mx = (cpu->opcode >> 17) & 3; \
|
||||
psx_gte_i_##name(cpu); \
|
||||
return (cycles); \
|
||||
}
|
||||
|
||||
PSX_CPU_CACHED_GTE_WRAPPER(rtps, 15)
|
||||
PSX_CPU_CACHED_GTE_WRAPPER(nclip, 8)
|
||||
PSX_CPU_CACHED_GTE_WRAPPER(op, 6)
|
||||
PSX_CPU_CACHED_GTE_WRAPPER(dpcs, 8)
|
||||
PSX_CPU_CACHED_GTE_WRAPPER(intpl, 8)
|
||||
PSX_CPU_CACHED_GTE_WRAPPER(mvmva, 8)
|
||||
PSX_CPU_CACHED_GTE_WRAPPER(ncds, 19)
|
||||
PSX_CPU_CACHED_GTE_WRAPPER(cdp, 13)
|
||||
PSX_CPU_CACHED_GTE_WRAPPER(ncdt, 44)
|
||||
PSX_CPU_CACHED_GTE_WRAPPER(nccs, 17)
|
||||
PSX_CPU_CACHED_GTE_WRAPPER(cc, 11)
|
||||
PSX_CPU_CACHED_GTE_WRAPPER(ncs, 14)
|
||||
PSX_CPU_CACHED_GTE_WRAPPER(nct, 30)
|
||||
PSX_CPU_CACHED_GTE_WRAPPER(sqr, 5)
|
||||
PSX_CPU_CACHED_GTE_WRAPPER(dcpl, 8)
|
||||
PSX_CPU_CACHED_GTE_WRAPPER(dpct, 17)
|
||||
PSX_CPU_CACHED_GTE_WRAPPER(avsz3, 5)
|
||||
PSX_CPU_CACHED_GTE_WRAPPER(avsz4, 6)
|
||||
PSX_CPU_CACHED_GTE_WRAPPER(rtpt, 23)
|
||||
PSX_CPU_CACHED_GTE_WRAPPER(gpf, 5)
|
||||
PSX_CPU_CACHED_GTE_WRAPPER(gpl, 5)
|
||||
PSX_CPU_CACHED_GTE_WRAPPER(ncct, 39)
|
||||
|
||||
static int psx_cpu_cached_gte_invalid(psx_cpu_t* cpu) {
|
||||
DO_PENDING_LOAD;
|
||||
psx_gte_i_invalid(cpu);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static psx_cpu_cached_handler_t psx_cpu_decode(uint32_t opcode) {
|
||||
switch (opcode >> 26) {
|
||||
case 0x00:
|
||||
switch (opcode & 0x3f) {
|
||||
case 0x00: return psx_cpu_cached_sll;
|
||||
case 0x02: return psx_cpu_cached_srl;
|
||||
case 0x03: return psx_cpu_cached_sra;
|
||||
case 0x04: return psx_cpu_cached_sllv;
|
||||
case 0x06: return psx_cpu_cached_srlv;
|
||||
case 0x07: return psx_cpu_cached_srav;
|
||||
case 0x08: return psx_cpu_cached_jr;
|
||||
case 0x09: return psx_cpu_cached_jalr;
|
||||
case 0x0c: return psx_cpu_cached_syscall;
|
||||
case 0x0d: return psx_cpu_cached_break;
|
||||
case 0x10: return psx_cpu_cached_mfhi;
|
||||
case 0x11: return psx_cpu_cached_mthi;
|
||||
case 0x12: return psx_cpu_cached_mflo;
|
||||
case 0x13: return psx_cpu_cached_mtlo;
|
||||
case 0x18: return psx_cpu_cached_mult;
|
||||
case 0x19: return psx_cpu_cached_multu;
|
||||
case 0x1a: return psx_cpu_cached_div;
|
||||
case 0x1b: return psx_cpu_cached_divu;
|
||||
case 0x20: return psx_cpu_cached_add;
|
||||
case 0x21: return psx_cpu_cached_addu;
|
||||
case 0x22: return psx_cpu_cached_sub;
|
||||
case 0x23: return psx_cpu_cached_subu;
|
||||
case 0x24: return psx_cpu_cached_and;
|
||||
case 0x25: return psx_cpu_cached_or;
|
||||
case 0x26: return psx_cpu_cached_xor;
|
||||
case 0x27: return psx_cpu_cached_nor;
|
||||
case 0x2a: return psx_cpu_cached_slt;
|
||||
case 0x2b: return psx_cpu_cached_sltu;
|
||||
default: return NULL;
|
||||
}
|
||||
case 0x01:
|
||||
switch ((opcode >> 16) & 0x1f) {
|
||||
case 0x00: return psx_cpu_cached_bltz;
|
||||
case 0x01: return psx_cpu_cached_bgez;
|
||||
case 0x10: return psx_cpu_cached_bltzal;
|
||||
case 0x11: return psx_cpu_cached_bgezal;
|
||||
default: return (opcode & 0x00010000)
|
||||
? psx_cpu_cached_bgez
|
||||
: psx_cpu_cached_bltz;
|
||||
}
|
||||
case 0x02: return psx_cpu_cached_j;
|
||||
case 0x03: return psx_cpu_cached_jal;
|
||||
case 0x04: return psx_cpu_cached_beq;
|
||||
case 0x05: return psx_cpu_cached_bne;
|
||||
case 0x06: return psx_cpu_cached_blez;
|
||||
case 0x07: return psx_cpu_cached_bgtz;
|
||||
case 0x08: return psx_cpu_cached_addi;
|
||||
case 0x09: return psx_cpu_cached_addiu;
|
||||
case 0x0a: return psx_cpu_cached_slti;
|
||||
case 0x0b: return psx_cpu_cached_sltiu;
|
||||
case 0x0c: return psx_cpu_cached_andi;
|
||||
case 0x0d: return psx_cpu_cached_ori;
|
||||
case 0x0e: return psx_cpu_cached_xori;
|
||||
case 0x0f: return psx_cpu_cached_lui;
|
||||
case 0x10:
|
||||
switch ((opcode >> 21) & 0x1f) {
|
||||
case 0x00: return psx_cpu_cached_mfc0;
|
||||
case 0x04: return psx_cpu_cached_mtc0;
|
||||
case 0x10: return psx_cpu_cached_rfe;
|
||||
default: return NULL;
|
||||
}
|
||||
case 0x12:
|
||||
switch ((opcode >> 21) & 0x1f) {
|
||||
case 0x00: return psx_cpu_cached_mfc2;
|
||||
case 0x02: return psx_cpu_cached_cfc2;
|
||||
case 0x04: return psx_cpu_cached_mtc2;
|
||||
case 0x06: return psx_cpu_cached_ctc2;
|
||||
default:
|
||||
switch (opcode & 0x3f) {
|
||||
case 0x01: return psx_cpu_cached_gte_rtps;
|
||||
case 0x06: return psx_cpu_cached_gte_nclip;
|
||||
case 0x0c: return psx_cpu_cached_gte_op;
|
||||
case 0x10: return psx_cpu_cached_gte_dpcs;
|
||||
case 0x11: return psx_cpu_cached_gte_intpl;
|
||||
case 0x12: return psx_cpu_cached_gte_mvmva;
|
||||
case 0x13: return psx_cpu_cached_gte_ncds;
|
||||
case 0x14: return psx_cpu_cached_gte_cdp;
|
||||
case 0x16: return psx_cpu_cached_gte_ncdt;
|
||||
case 0x1b: return psx_cpu_cached_gte_nccs;
|
||||
case 0x1c: return psx_cpu_cached_gte_cc;
|
||||
case 0x1e: return psx_cpu_cached_gte_ncs;
|
||||
case 0x20: return psx_cpu_cached_gte_nct;
|
||||
case 0x28: return psx_cpu_cached_gte_sqr;
|
||||
case 0x29: return psx_cpu_cached_gte_dcpl;
|
||||
case 0x2a: return psx_cpu_cached_gte_dpct;
|
||||
case 0x2d: return psx_cpu_cached_gte_avsz3;
|
||||
case 0x2e: return psx_cpu_cached_gte_avsz4;
|
||||
case 0x30: return psx_cpu_cached_gte_rtpt;
|
||||
case 0x3d: return psx_cpu_cached_gte_gpf;
|
||||
case 0x3e: return psx_cpu_cached_gte_gpl;
|
||||
case 0x3f: return psx_cpu_cached_gte_ncct;
|
||||
default: return psx_cpu_cached_gte_invalid;
|
||||
}
|
||||
}
|
||||
case 0x20: return psx_cpu_cached_lb;
|
||||
case 0x21: return psx_cpu_cached_lh;
|
||||
case 0x22: return psx_cpu_cached_lwl;
|
||||
case 0x23: return psx_cpu_cached_lw;
|
||||
case 0x24: return psx_cpu_cached_lbu;
|
||||
case 0x25: return psx_cpu_cached_lhu;
|
||||
case 0x26: return psx_cpu_cached_lwr;
|
||||
case 0x28: return psx_cpu_cached_sb;
|
||||
case 0x29: return psx_cpu_cached_sh;
|
||||
case 0x2a: return psx_cpu_cached_swl;
|
||||
case 0x2b: return psx_cpu_cached_sw;
|
||||
case 0x2e: return psx_cpu_cached_swr;
|
||||
case 0x30: return psx_cpu_cached_lwc0;
|
||||
case 0x31: return psx_cpu_cached_lwc1;
|
||||
case 0x32: return psx_cpu_cached_lwc2;
|
||||
case 0x33: return psx_cpu_cached_lwc3;
|
||||
case 0x38: return psx_cpu_cached_swc0;
|
||||
case 0x39: return psx_cpu_cached_swc1;
|
||||
case 0x3a: return psx_cpu_cached_swc2;
|
||||
case 0x3b: return psx_cpu_cached_swc3;
|
||||
default: return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int psx_cpu_execute_cached(psx_cpu_t* cpu) {
|
||||
if (!cpu->cache)
|
||||
return psx_cpu_execute(cpu);
|
||||
|
||||
const uint32_t address = psx_bus_physical_address(cpu->saved_pc);
|
||||
psx_cpu_cache_entry_t* entry = &cpu->cache->entries[psx_cpu_cache_index(address)];
|
||||
|
||||
if (entry->valid && entry->address == address && entry->opcode == cpu->opcode) {
|
||||
cpu->cache->stats.hits++;
|
||||
} else {
|
||||
cpu->cache->stats.misses++;
|
||||
entry->address = address;
|
||||
entry->opcode = cpu->opcode;
|
||||
entry->handler = psx_cpu_decode(cpu->opcode);
|
||||
entry->valid = 1;
|
||||
}
|
||||
|
||||
return entry->handler ? entry->handler(cpu) : 0;
|
||||
}
|
||||
|
||||
int psx_cpu_execute(psx_cpu_t* cpu) {
|
||||
switch ((cpu->opcode & 0xfc000000) >> 26) {
|
||||
case 0x00000000 >> 26: {
|
||||
|
||||
@@ -10,9 +10,21 @@
|
||||
#define PSX_CPU_FREQ 33.868800f // 33.868800 MHz
|
||||
|
||||
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*);
|
||||
|
||||
/*
|
||||
@@ -153,6 +165,8 @@ struct __attribute__((__packed__)) psx_cpu_t {
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -233,6 +247,11 @@ 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);
|
||||
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*);
|
||||
|
||||
/*
|
||||
|
||||
+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);
|
||||
|
||||
|
||||
+15
-1
@@ -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)
|
||||
|
||||
@@ -27,6 +27,8 @@ int psx_exe_load(psx_cpu_t* cpu, const char* path) {
|
||||
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;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user