Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 120 additions & 9 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
cmake_minimum_required(VERSION 3.16)
project(MegaManXSNESRecomp C)
project(MegaManXSNESRecomp C CXX)

if(APPLE)
enable_language(OBJCXX)
endif()

# Cross-platform (macOS/Linux) build for MegaManXSNESRecomp.
#
Expand All @@ -22,6 +26,8 @@ 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(SNESRECOMP_ROOT ${CMAKE_SOURCE_DIR}/snesrecomp)

Expand All @@ -33,10 +39,61 @@ if(BUILD_TESTING)
endif()

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. This repo 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 platform backends vendored here.
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(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 @@ -66,20 +123,32 @@ function(add_mmx_variant TARGET GEN_DIR CFG_INCLUDE_DIR)
# game-specific runtime (shared across both variants)
src/config.c
src/mmx_display.c
src/glsl_shader.c
$<$<NOT:$<PLATFORM_ID:Darwin>>:src/glsl_shader.c>
src/main.c
src/mmx_wide_preview.c
$<$<PLATFORM_ID:Linux>:src/linux_ui.cpp>
# 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>
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>
$<$<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 @@ -94,6 +163,23 @@ 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)
target_compile_definitions(${TARGET} PRIVATE CRT_SYSTEM=3)
endif()

# 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()

target_compile_definitions(${TARGET} PRIVATE
SMW_DISABLE_LM=1
SYSTEM_VOLUME_MIXER_AVAILABLE=0 # Win32 WASAPI volume control excluded
Expand All @@ -110,10 +196,22 @@ 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
Expand All @@ -128,8 +226,21 @@ function(add_mmx_variant TARGET GEN_DIR CFG_INCLUDE_DIR)

# 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
Binary file added assets/MegaManX.icns
Binary file not shown.
28 changes: 28 additions & 0 deletions resources/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>com.mstan.megamanxrecomp</string>
<key>CFBundleName</key>
<string>Mega Man X Recomp</string>
<key>CFBundleExecutable</key>
<string>MegaManXSNESRecomp</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<key>CFBundleDisplayName</key>
<string>Mega Man X Recomp</string>
<key>CFBundleIconFile</key>
<string>MegaManX.icns</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>LSMinimumSystemVersion</key>
<string>12.0</string>
</dict>
</plist>
11 changes: 10 additions & 1 deletion src/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,11 @@ static bool HandleIniConfig(int section, const char *key, char *value) {
return true;
} else if (StringEqualsNoCase(key, "OutputMethod")) {
g_config.output_method = StringEqualsNoCase(value, "SDL-Software") ? kOutputMethod_SDLSoftware :
StringEqualsNoCase(value, "OpenGL") ? kOutputMethod_OpenGL : kOutputMethod_SDL;
StringEqualsNoCase(value, "OpenGL") ? kOutputMethod_OpenGL :
#if defined(__APPLE__)
StringEqualsNoCase(value, "Metal") ? kOutputMethod_Metal :
#endif
kOutputMethod_SDL;
return true;
} else if (StringEqualsNoCase(key, "LinearFiltering")) {
return ParseBool(value, &g_config.linear_filtering);
Expand Down Expand Up @@ -424,6 +428,11 @@ static bool ParseOneConfigFile(const char *filename, int depth) {

void ParseConfigFile(const char *filename) {
g_config.enable_audio = true;
#if defined(__APPLE__)
g_config.output_method = kOutputMethod_Metal;
#else
g_config.output_method = kOutputMethod_OpenGL;
#endif
/* Audio defaults match the values shipped in config.ini's [Sound]
* section. Without these a release with no config.ini next to the
* exe leaves audio_freq/audio_channels/audio_samples at 0, which
Expand Down
3 changes: 3 additions & 0 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ enum {
kOutputMethod_SDL,
kOutputMethod_SDLSoftware,
kOutputMethod_OpenGL,
#if defined(__APPLE__)
kOutputMethod_Metal,
#endif
};

typedef struct Config {
Expand Down
139 changes: 139 additions & 0 deletions src/linux_ui.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#include "linux_ui.h"

#include "mmx_display.h"
extern "C" {
#include "mmx_rtl.h"
}

#include "imgui.h"
#include "backends/imgui_impl_opengl3.h"
#include "backends/imgui_impl_sdl2.h"
#include "backends/imgui_impl_sdlrenderer2.h"

#include <SDL.h>
#include <stdio.h>

static bool g_ui_open;
static bool g_use_opengl;
static char g_state_message[64];

static void RenderMenu(void) {
ImGui::SetNextWindowSize(ImVec2(430.0f, 600.0f), ImGuiCond_FirstUseEver);
ImGui::Begin("Mega Man X", &g_ui_open);
ImGui::TextUnformatted("Display settings");
ImGui::Separator();

bool widescreen = MmxDisplay_IsWidescreenEnabled();
if (ImGui::Checkbox("True widescreen renderer", &widescreen))
MmxDisplay_SetWidescreenEnabled(widescreen);
ImGui::Text("Status: %s, %s (%dx224)", widescreen ? "enabled" : "disabled",
MmxDisplay_IsWidescreenActive() ? "active" : "inactive",
MmxDisplay_GetCurrentFrameWidth());
ImGui::TextWrapped("Adds host-only PPU scene columns; gameplay stays unchanged.");

ImGui::Separator();
ImGui::TextUnformatted("Save states");
for (int slot = 0; slot < 10; ++slot) {
ImGui::PushID(slot);
char save_label[16];
char load_label[16];
snprintf(save_label, sizeof(save_label), "Save %d", slot + 1);
snprintf(load_label, sizeof(load_label), "Load %d", slot + 1);
if (ImGui::Button(save_label)) {
RtlSaveLoad(kSaveLoad_Save, slot);
snprintf(g_state_message, sizeof(g_state_message), "Saved state %d", slot + 1);
}
ImGui::SameLine();
if (ImGui::Button(load_label)) {
RtlSaveLoad(kSaveLoad_Load, slot);
snprintf(g_state_message, sizeof(g_state_message), "Loaded state %d", slot + 1);
}
ImGui::PopID();
}
if (g_state_message[0]) ImGui::TextUnformatted(g_state_message);

ImGui::Separator();
ImGui::TextUnformatted("F1 toggles this menu");
ImGui::Text("Renderer: %s", g_use_opengl ? "OpenGL" : "SDL");
ImGui::TextUnformatted("Controller: SDL GameController");
ImGui::TextUnformatted("Audio: SDL audio");
ImGui::End();
}

static void BeginFrame(void) {
if (g_use_opengl)
ImGui_ImplOpenGL3_NewFrame();
else
ImGui_ImplSDLRenderer2_NewFrame();
ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame();
RenderMenu();
ImGui::Render();
}

extern "C" bool LinuxUi_Init(SDL_Window *window, SDL_Renderer *renderer,
bool use_opengl) {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO &io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
ImGui::StyleColorsDark();

g_use_opengl = use_opengl;
bool platform_ok;
bool renderer_ok;
if (use_opengl) {
platform_ok = ImGui_ImplSDL2_InitForOpenGL(window, SDL_GL_GetCurrentContext());
renderer_ok = ImGui_ImplOpenGL3_Init("#version 330 core");
} else {
platform_ok = ImGui_ImplSDL2_InitForSDLRenderer(window, renderer);
renderer_ok = ImGui_ImplSDLRenderer2_Init(renderer);
}
if (!platform_ok || !renderer_ok) {
LinuxUi_Shutdown();
return false;
}
g_ui_open = false;
g_state_message[0] = '\0';
return true;
}

extern "C" void LinuxUi_Shutdown(void) {
if (!ImGui::GetCurrentContext()) return;
if (g_use_opengl)
ImGui_ImplOpenGL3_Shutdown();
else
ImGui_ImplSDLRenderer2_Shutdown();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
g_ui_open = false;
}

extern "C" void LinuxUi_ProcessEvent(const void *event) {
if (ImGui::GetCurrentContext())
ImGui_ImplSDL2_ProcessEvent((const SDL_Event *)event);
}

extern "C" void LinuxUi_Toggle(void) { g_ui_open = !g_ui_open; }
extern "C" bool LinuxUi_IsOpen(void) { return g_ui_open; }
extern "C" bool LinuxUi_CaptureInput(void) { return g_ui_open; }

extern "C" void LinuxUi_RenderSdl(SDL_Renderer *renderer) {
if (!g_ui_open || !ImGui::GetCurrentContext() || g_use_opengl) return;

/* The game uses an emulated-pixel logical size. ImGui uses window pixels,
* so render it with logical scaling disabled and restore the game mapping. */
int logical_width, logical_height;
SDL_RenderGetLogicalSize(renderer, &logical_width, &logical_height);
SDL_RenderSetLogicalSize(renderer, 0, 0);
BeginFrame();
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), renderer);
SDL_RenderSetLogicalSize(renderer, logical_width, logical_height);
}

extern "C" void LinuxUi_RenderOpenGL(void) {
if (!g_ui_open || !ImGui::GetCurrentContext() || !g_use_opengl) return;
BeginFrame();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}
Loading