Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c26b9ab
Integrate native macOS backend into runtime
navjack Jul 15, 2026
467b1e8
Build native Metal Core Audio and GameController backends
navjack Jul 15, 2026
4d95358
Document and package native macOS build
navjack Jul 15, 2026
56916e5
Fix Metal pipeline and portable app bundling
navjack Jul 15, 2026
89181db
Finalize native macOS controller discovery and build notes
navjack Jul 15, 2026
7d8b631
Fix native audio callback pitch and rate pacing
navjack Jul 15, 2026
bb80538
Use negotiated Core Audio format for pacing
navjack Jul 15, 2026
bbe8c92
Vendor NTSC CRT filter and Dear ImGui
navjack Jul 15, 2026
8434ede
Add optional NTSC CRT display and F1 ImGui menu
navjack Jul 15, 2026
ca72ddf
Fix 4:3 scaling and expose save states
navjack Jul 15, 2026
2f936f9
Refine NTSC sampling and package app icon
navjack Jul 15, 2026
51460c1
Include icon in packaged macOS app
navjack Jul 15, 2026
1b6fdb0
Sync upstream and integrate macOS Linux widescreen work
navjack Jul 16, 2026
0bae7f8
Ignore local sync build directory
navjack Jul 16, 2026
5b7f744
Remove private Steam Deck archive
navjack Jul 16, 2026
c3f45a2
Reconcile pinned dependency and CMake builds
navjack Jul 16, 2026
ae90fcc
Document Steam Deck widescreen validation
navjack Jul 16, 2026
64092d0
Finalize cross-platform widescreen and contributor setup
navjack Jul 16, 2026
340793d
Skip private artifact jobs without ROM secret
navjack Jul 16, 2026
78edcb4
Remove GitHub Actions workflow
navjack Jul 16, 2026
fd02cb7
Refresh macOS launcher UI
navjack Jul 16, 2026
f7ad1c4
Add ROM-free cross-platform release packaging
navjack Jul 16, 2026
0c4c3cc
Keep Linux release payloads clean
navjack Jul 16, 2026
6ca9e5b
Merge pull request #1 from navjack/codex/sync-macos-linux-wide
navjack Jul 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
16 changes: 5 additions & 11 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
# Build artifacts
build/
build-mac/
# macOS/Linux runtime ROM-path cache + sibling-repo symlink
# Build artifacts (all root-level CMake build trees)
/build*/
# macOS/Linux runtime ROM-path cache
rom.cfg
/snesrecomp
*.obj
*.pdb
*.ilk
Expand Down Expand Up @@ -38,9 +36,6 @@ boot_trace*.txt
Thumbs.db
.DS_Store

# External repos accessed via junction/symlink (tracked in their own repos)
snesrecomp/

# Oracle disassembly repo (independent git clone)
SMWDisX/

Expand Down Expand Up @@ -81,12 +76,11 @@ packages/

# Release artifacts (built locally, uploaded via gh release upload)
MegaManXSNESRecomp-windows-*.zip
MegaManXSNESRecomp-steamdeck-*.7z
MegamanXRecomp-windows-*.zip
mmx-v*.zip
release-stage/
release-macos*/
*.fig
# per-variant generated C (copyrighted, regenerated at build time)
variants/*/gen/
build-jp/
build-jp-dbg/
build-release-jp/
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "snesrecomp"]
path = snesrecomp
url = https://github.com/navjack/snesrecomp.git
234 changes: 221 additions & 13 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
cmake_minimum_required(VERSION 3.16)
project(MegaManXSNESRecomp C)
project(MegaManXSNESRecomp C CXX)

include(CTest)

if(APPLE)
enable_language(OBJCXX)
endif()

# Cross-platform (macOS/Linux) build for MegaManXSNESRecomp.
#
Expand All @@ -22,13 +28,150 @@ project(MegaManXSNESRecomp C)
# ROM staged); SDL2 (e.g. `brew install sdl2`).

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(_MMX_SNESRECOMP_SUBMODULE "${CMAKE_SOURCE_DIR}/snesrecomp")
set(SNESRECOMP_ROOT "${_MMX_SNESRECOMP_SUBMODULE}" CACHE PATH
"Path to the snesrecomp dependency checkout")
option(MMX_ALLOW_UNPINNED_SNESRECOMP
"Allow building against a different or dirty snesrecomp checkout" OFF)

if(NOT EXISTS "${SNESRECOMP_ROOT}/runner/runner.cmake")
message(FATAL_ERROR
"snesrecomp is not initialized at ${SNESRECOMP_ROOT}. "
"Run `bash tools/bootstrap.sh` from the repository root, then configure again.")
endif()

if(NOT MMX_ALLOW_UNPINNED_SNESRECOMP)
get_filename_component(_MMX_SNESRECOMP_ROOT_REAL "${SNESRECOMP_ROOT}" REALPATH)
get_filename_component(_MMX_SNESRECOMP_SUBMODULE_REAL
"${_MMX_SNESRECOMP_SUBMODULE}" REALPATH)
if(NOT _MMX_SNESRECOMP_ROOT_REAL STREQUAL _MMX_SNESRECOMP_SUBMODULE_REAL)
message(FATAL_ERROR
"SNESRECOMP_ROOT points outside the pinned submodule. "
"Use -DMMX_ALLOW_UNPINNED_SNESRECOMP=ON when intentionally developing "
"against another checkout.")
endif()

find_package(Git QUIET)
if(NOT GIT_EXECUTABLE)
message(FATAL_ERROR
"Git is required to verify the pinned snesrecomp submodule. "
"Install Git or explicitly configure with "
"-DMMX_ALLOW_UNPINNED_SNESRECOMP=ON for a source archive.")
endif()

execute_process(
COMMAND "${GIT_EXECUTABLE}" -C "${CMAKE_SOURCE_DIR}"
ls-files --stage -- snesrecomp
OUTPUT_VARIABLE _MMX_SNESRECOMP_GITLINK
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE _MMX_SNESRECOMP_GITLINK_RC
ERROR_QUIET)
if(NOT _MMX_SNESRECOMP_GITLINK_RC EQUAL 0 OR
NOT _MMX_SNESRECOMP_GITLINK MATCHES
"^160000 +([0-9a-fA-F]+) +0")
message(FATAL_ERROR
"Could not read the pinned snesrecomp gitlink. "
"Use a Git clone of this repository or explicitly configure with "
"-DMMX_ALLOW_UNPINNED_SNESRECOMP=ON for a source archive.")
endif()
set(_MMX_PIN_SHA "${CMAKE_MATCH_1}")

execute_process(COMMAND "${GIT_EXECUTABLE}" -C "${SNESRECOMP_ROOT}"
rev-parse HEAD
OUTPUT_VARIABLE _MMX_RUNNER_SHA
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE _MMX_RUNNER_SHA_RC)
execute_process(
COMMAND "${GIT_EXECUTABLE}" -C "${SNESRECOMP_ROOT}"
status --porcelain=v1 --untracked-files=no --ignore-submodules=none
OUTPUT_VARIABLE _MMX_RUNNER_STATUS
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE _MMX_RUNNER_STATUS_RC)
if(NOT _MMX_RUNNER_SHA_RC EQUAL 0 OR
NOT _MMX_RUNNER_STATUS_RC EQUAL 0 OR
NOT _MMX_RUNNER_SHA STREQUAL _MMX_PIN_SHA OR
NOT _MMX_RUNNER_STATUS STREQUAL "")
message(FATAL_ERROR
"snesrecomp is not the clean pinned revision ${_MMX_PIN_SHA}. "
"Run `git submodule update --init --recursive` to restore it, or "
"explicitly configure with -DMMX_ALLOW_UNPINNED_SNESRECOMP=ON while "
"developing framework changes.")
endif()
endif()

if(BUILD_TESTING)
add_executable(mmx_display_test tests/mmx_display_test.c src/mmx_display.c)
target_include_directories(mmx_display_test PRIVATE src ${SNESRECOMP_ROOT}/runner/src)
add_test(NAME mmx_display_geometry COMMAND mmx_display_test)
endif()

set(SNESRECOMP_ROOT ${CMAKE_SOURCE_DIR}/snesrecomp)
include(${SNESRECOMP_ROOT}/runner/runner.cmake)
include(${SNESRECOMP_ROOT}/runner/src/launcher_ng/launcher_ng.cmake)

# The runner launcher supplies the game-agnostic C pieces. The fork owns the
# ImGui copy used by both the launcher and native platform UI; using the
# launcher's older bundled ImGui would create duplicate symbols and an API
# mismatch with the Metal backend.
function(mmx_target_launcher_ng TGT)
cmake_parse_arguments(LNG "" "BOXART" "" ${ARGN})
set(LNG_ROOT ${SNESRECOMP_ROOT}/runner/src/launcher_ng)
set(LNG_IMGUI ${CMAKE_SOURCE_DIR}/third_party/imgui)
target_sources(${TGT} PRIVATE
${LNG_ROOT}/launcher_model.c
${LNG_ROOT}/launcher_platform_sdl2.c
${LNG_ROOT}/launcher_gl.c
${LNG_ROOT}/launcher_input.c
${LNG_ROOT}/launcher_files.c
${LNG_ROOT}/launcher_debug.c
${LNG_ROOT}/launcher_binds.c
${LNG_ROOT}/launcher_ng_capi.c
${LNG_ROOT}/third_party/tinyfiledialogs.c
${LNG_ROOT}/backends/imgui/launcher_imgui.cpp
${LNG_IMGUI}/imgui.cpp
${LNG_IMGUI}/imgui_draw.cpp
${LNG_IMGUI}/imgui_tables.cpp
${LNG_IMGUI}/imgui_widgets.cpp
${LNG_IMGUI}/backends/imgui_impl_sdl2.cpp
${LNG_IMGUI}/backends/imgui_impl_opengl3.cpp)
target_include_directories(${TGT} PRIVATE
${LNG_ROOT}/../launcher ${LNG_ROOT}
${LNG_ROOT}/third_party ${LNG_IMGUI}
${LNG_IMGUI}/backends)
target_compile_definitions(${TGT} PRIVATE SNES_LAUNCHER SDL_MAIN_HANDLED)
set_target_properties(${TGT} PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON)
add_custom_command(TARGET ${TGT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_FILE_DIR:${TGT}>/assets/fonts
COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_FILE_DIR:${TGT}>/assets/img
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${LNG_ROOT}/../launcher/assets/fonts/LatoLatin-Regular.ttf
${LNG_ROOT}/../launcher/assets/fonts/LatoLatin-Bold.ttf
$<TARGET_FILE_DIR:${TGT}>/assets/fonts/
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${LNG_ROOT}/../launcher/assets/img/brand_mark.tga
${LNG_ROOT}/../launcher/assets/img/snes_pad.tga
$<TARGET_FILE_DIR:${TGT}>/assets/img/
VERBATIM)
if(LNG_BOXART AND EXISTS ${LNG_BOXART})
add_custom_command(TARGET ${TGT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${LNG_BOXART}
$<TARGET_FILE_DIR:${TGT}>/assets/img/boxart.tga VERBATIM)
endif()
endfunction()

find_package(SDL2 REQUIRED)
find_package(OpenGL REQUIRED)
if(WIN32 AND NOT MSVC)
# The Windows cross-build may use MSYS2, Homebrew MinGW, or another
# toolchain. Do not bake one developer's absolute MSYS2 path into the
# target; callers can provide the import library explicitly, while the
# SDL2 package remains the default when it exposes a usable target.
set(MMX_MINGW_SDL2_LIBRARY "" CACHE FILEPATH
"SDL2 import library for the non-MSVC Windows build")
endif()
if(NOT APPLE)
find_package(OpenGL REQUIRED)
endif()

# prod-vs-debug via the single SNESRECOMP_ENABLE_TRACE switch (runner.cmake,
# default OFF). Prod: no debug_server.c / TCP server / rings. Debug:
Expand Down Expand Up @@ -57,19 +200,33 @@ function(add_mmx_variant TARGET GEN_DIR CFG_INCLUDE_DIR)
${SNESRECOMP_RUNNER_SOURCES}
# game-specific runtime (shared across both variants)
src/config.c
src/glsl_shader.c
src/mmx_display.c
$<$<NOT:$<PLATFORM_ID:Darwin>>:src/glsl_shader.c>
src/main.c
src/mmx_wide_preview.c
$<$<PLATFORM_ID:Linux>:src/linux_ui.cpp>
src/post_mortem.c
src/opengl.c
$<$<NOT:$<PLATFORM_ID:Darwin>>:src/opengl.c>
src/mmx_00.c
src/gen_stubs.c
src/mmx_cpu_infra.c
src/mmx_rtl.c
src/mmx_spc_player.c
src/fiber_compat.c # Win32 Fiber API shim (ucontext) for non-Windows

$<$<PLATFORM_ID:Darwin>:src/macos_backend.m>

# Native macOS UI and optional NTSC CRT display filter.
$<$<PLATFORM_ID:Darwin>:src/macos_ui.mm>
# launcher_ng supplies the single shared ImGui core, SDL2 backend, and
# OpenGL3 backend. Keep only the game UI's platform renderer here.
$<$<PLATFORM_ID:Linux>:third_party/imgui/backends/imgui_impl_sdlrenderer2.cpp>
$<$<PLATFORM_ID:Darwin>:third_party/imgui/backends/imgui_impl_metal.mm>
$<$<PLATFORM_ID:Darwin>:third_party/ntsc-crt/crt_core.c>
$<$<PLATFORM_ID:Darwin>:third_party/ntsc-crt/crt_snes.c>

# OpenGL function loader
third_party/gl_core/gl_core_3_1.c
$<$<NOT:$<PLATFORM_ID:Darwin>>:third_party/gl_core/gl_core_3_1.c>
# recompiled banks (generated, per-variant)
${_MMX_GEN}
)
Expand All @@ -84,6 +241,16 @@ function(add_mmx_variant TARGET GEN_DIR CFG_INCLUDE_DIR)
${SDL2_INCLUDE_DIRS}
)

if(APPLE OR CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_include_directories(${TARGET} PRIVATE
${CMAKE_SOURCE_DIR}/third_party/imgui
${CMAKE_SOURCE_DIR}/third_party/imgui/backends)
endif()
if(APPLE)
target_include_directories(${TARGET} PRIVATE
${CMAKE_SOURCE_DIR}/third_party/ntsc-crt)
endif()

target_compile_definitions(${TARGET} PRIVATE
SMW_DISABLE_LM=1
SYSTEM_VOLUME_MIXER_AVAILABLE=0 # Win32 WASAPI volume control excluded
Expand All @@ -92,6 +259,16 @@ function(add_mmx_variant TARGET GEN_DIR CFG_INCLUDE_DIR)
${ARGN}
)

# host_report.c uses dl_phdr_info for Linux module diagnostics; glibc hides
# that declaration unless this feature-test macro is set before headers.
if(UNIX AND NOT APPLE)
target_compile_definitions(${TARGET} PRIVATE _GNU_SOURCE=1)
endif()

if(APPLE)
target_compile_definitions(${TARGET} PRIVATE CRT_SYSTEM=3)
endif()

# The recompiled C is machine-generated; quiet its warnings and tolerate the
# K&R-style implicit declarations clang rejects by default.
if(NOT MSVC)
Expand All @@ -100,26 +277,57 @@ function(add_mmx_variant TARGET GEN_DIR CFG_INCLUDE_DIR)
endif()

target_link_libraries(${TARGET} PRIVATE
OpenGL::GL
m # libm: glsl_shader.c uses pow() (implicit on macOS)
m # libm: runtime math
${SNESRECOMP_RUNNER_LIBRARIES} # ws2_32 when trace/cosim builds add the TCP servers
)
if(APPLE)
target_link_libraries(${TARGET} PRIVATE
"-framework Metal"
"-framework QuartzCore"
"-framework GameController"
"-framework Foundation"
"-framework CoreAudio"
"-framework AudioToolbox"
"-framework OpenGL")
target_compile_definitions(${TARGET} PRIVATE MMX_MACOS_NATIVE=1)
else()
target_link_libraries(${TARGET} PRIVATE OpenGL::GL)
endif()
if(WIN32 AND NOT MSVC)
# mingw/msys2: this SDL2 does not auto-rename main()->SDL_main, so main.c's
# real main() is the entry point — link SDL2 WITHOUT SDL2main (SDL2main's
# WinMain would demand an SDL_main that doesn't exist). Plus DbgHelp for
# post_mortem.c's StackWalk64 backtrace.
target_link_libraries(${TARGET} PRIVATE
C:/msys64/mingw64/lib/libSDL2.dll.a
dbghelp)
if(MMX_MINGW_SDL2_LIBRARY)
target_link_libraries(${TARGET} PRIVATE
${MMX_MINGW_SDL2_LIBRARY} dbghelp)
elseif(TARGET SDL2::SDL2)
target_link_libraries(${TARGET} PRIVATE SDL2::SDL2 dbghelp)
else()
message(FATAL_ERROR
"Non-MSVC Windows builds require MMX_MINGW_SDL2_LIBRARY or SDL2::SDL2")
endif()
else()
target_link_libraries(${TARGET} PRIVATE ${SDL2_LIBRARIES})
endif()

# Dear ImGui pre-boot launcher (GUI). One call wires sources/includes/
# SNES_LAUNCHER/CXX/asset-staging; see launcher_ng/launcher_ng.cmake.
snesrecomp_target_launcher_ng(${TARGET}
mmx_target_launcher_ng(${TARGET}
BOXART ${CMAKE_SOURCE_DIR}/recomp/launcher/boxart.tga)
if(APPLE)
set(_MMX_ICON ${CMAKE_SOURCE_DIR}/assets/MegaManX.icns)
target_sources(${TARGET} PRIVATE ${_MMX_ICON})
set_source_files_properties(${_MMX_ICON} PROPERTIES
MACOSX_PACKAGE_LOCATION Resources)
set_target_properties(${TARGET} PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/resources/Info.plist
MACOSX_BUNDLE_GUI_IDENTIFIER "com.mstan.megamanxrecomp"
MACOSX_BUNDLE_BUNDLE_NAME "Mega Man X Recomp"
MACOSX_BUNDLE_SHORT_VERSION_STRING "0.1.0"
MACOSX_BUNDLE_BUNDLE_VERSION "0.1.0")
endif()
endfunction()

# ── USA: Mega Man X (v1.1) — faithful LLE scheduler by default ──
Expand Down
Loading