Skip to content
Merged
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
39 changes: 39 additions & 0 deletions .github/workflows/build-console.yml
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,42 @@ jobs:
with:
name: butterscotch-vita
path: build/Butterscotch.vpk

build-switch:
name: Build (Nintendo Switch)
runs-on: ubuntu-24.04
container:
image: devkitpro/devkita64:latest

steps:
- uses: actions/checkout@v7

- name: Set up ccache
uses: actions/cache@v6
with:
path: ~/.cache/ccache
key: switch-ccache-${{ github.sha }}
restore-keys: |
switch-ccache-

- name: Install ccache
run: |
apt-get update && apt-get install -y --no-install-recommends ccache

- name: Configure and build
run: |
cmake -B build -G Ninja \
-DWERROR=ON \
-DPLATFORM=switch \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache

cmake --build build

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: butterscotch-switch
path: build/butterscotch.nro
40 changes: 35 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ endif()

set(BACKEND "sdl2")

elseif(PLATFORM STREQUAL "switch")
if(NOT DEFINED CMAKE_TOOLCHAIN_FILE)
if(NOT DEFINED ENV{DEVKITPRO})
message(FATAL_ERROR "Please define DEVKITPRO to point to your SDK path!")
endif()
set(CMAKE_TOOLCHAIN_FILE "$ENV{DEVKITPRO}/cmake/Switch.cmake" CACHE PATH "toolchain file")
endif()

set(BACKEND "sdl2")

endif()

project(butterscotch C)
Expand Down Expand Up @@ -79,7 +89,7 @@ if(AUDIO_BACKEND STREQUAL "")
set(AUDIO_BACKEND "web")
elseif(PLATFORM STREQUAL "web-meta")
set(AUDIO_BACKEND "none")
elseif(PLATFORM STREQUAL "ps3" OR PLATFORM STREQUAL "vita")
elseif(PLATFORM STREQUAL "ps3" OR PLATFORM STREQUAL "vita" OR PLATFORM STREQUAL "switch")
set(AUDIO_BACKEND "openal")
elseif(PLATFORM STREQUAL "ps2")
set(AUDIO_BACKEND "ps2")
Expand Down Expand Up @@ -111,6 +121,9 @@ option(ENABLE_WAD14 "Enable support for WAD Version 14 and below" ON)
option(ENABLE_WAD16 "Enable support for WAD Version 15/16" ON)
option(ENABLE_WAD17 "Enable support for WAD Version 17" ON)
option(ENABLE_LEGACY_GL "Enable the legacy OpenGL renderer" ON)
if(PLATFORM STREQUAL "switch")
set(ENABLE_LEGACY_GL OFF CACHE BOOL "Enable the legacy OpenGL renderer" FORCE)
endif()
option(ENABLE_MODERN_GL "Enable the modern OpenGL renderer" ON)
if(NOT ENABLE_WAD14 AND NOT ENABLE_WAD16 AND NOT ENABLE_WAD17)
message(FATAL_ERROR "You need to build Butterscotch with at least one WAD version enabled!")
Expand Down Expand Up @@ -164,7 +177,7 @@ if(AUDIO_BACKEND STREQUAL "openal")
add_compile_definitions(USE_OPENAL)
if(APPLE)
set(AUDIO_LIBRARIES "-framework OpenAL")
elseif(PLATFORM STREQUAL "vita")
elseif(PLATFORM STREQUAL "vita" OR PLATFORM STREQUAL "switch")
Comment thread
cobaltgit marked this conversation as resolved.
set(AUDIO_LIBRARIES openal pthread)
elseif(NOT PLATFORM STREQUAL "ps3")
find_package(OpenAL REQUIRED)
Expand Down Expand Up @@ -233,14 +246,25 @@ if(ENABLE_VM_EXCEPTIONS_LOGS)
add_compile_definitions(ENABLE_VM_EXCEPTIONS_LOGS)
endif()

if(PLATFORM STREQUAL "cli" OR PLATFORM STREQUAL "vita")
if(PLATFORM STREQUAL "cli" OR PLATFORM STREQUAL "vita" OR PLATFORM STREQUAL "switch")
if(PLATFORM STREQUAL "vita")
# Normally, __VITA__ should already be defined, but for some reason,
# in my case, this didn't really work... so just in case, when we're
# building for the Vita, just force-feed these...
add_compile_definitions(__VITA__)
add_compile_definitions(PLATFORM_VITA)

set(VM_GML_PROFILER_DEFAULT OFF)
set(VM_TRACING_DEFAULT OFF)
set(VM_OPCODE_PROFILER_DEFAULT OFF)
set(VM_STUB_LOGS_DEFAULT OFF)
elseif(PLATFORM STREQUAL "switch")
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
add_compile_options(-ffast-math)
endif()

add_compile_definitions(PLATFORM_SWITCH)

set(VM_GML_PROFILER_DEFAULT OFF)
set(VM_TRACING_DEFAULT OFF)
set(VM_OPCODE_PROFILER_DEFAULT OFF)
Expand Down Expand Up @@ -303,7 +327,7 @@ if(PLATFORM STREQUAL "cli" OR PLATFORM STREQUAL "vita")
target_include_directories(butterscotch PUBLIC vendor/stb/image)

set(PLATFORM_LIBRARIES bzip2 stb_ds sha1 stb_vorbis)
if(NOT PLATFORM STREQUAL "vita")
if(NOT PLATFORM STREQUAL "vita" AND NOT PLATFORM STREQUAL "switch")
# GLAD
add_library(glad STATIC vendor/glad/src/glad.c)
target_include_directories(glad PUBLIC vendor/glad/include)
Expand All @@ -315,7 +339,7 @@ if(PLATFORM STREQUAL "cli" OR PLATFORM STREQUAL "vita")
endif()
if(WIN32)
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} opengl32 gdi32 winmm)
elseif(NOT PLATFORM STREQUAL "vita")
elseif(NOT PLATFORM STREQUAL "vita" AND NOT PLATFORM STREQUAL "switch")
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} ${CMAKE_DL_LIBS})
endif()

Expand Down Expand Up @@ -395,6 +419,12 @@ if(PLATFORM STREQUAL "cli" OR PLATFORM STREQUAL "vita")
)
else()
target_link_libraries(butterscotch PRIVATE ${BACKEND_LIBRARIES} ${AUDIO_LIBRARIES} ${PLATFORM_LIBRARIES})

if (PLATFORM STREQUAL "switch")
target_link_libraries(butterscotch PRIVATE GLESv2 stdc++)
nx_generate_nacp(OUTPUT Butterscotch.nacp NAME "Butterscotch" AUTHOR "Butterscotch" VERSION "1.0.0")
nx_create_nro(butterscotch NACP Butterscotch.nacp)
endif()
endif()
if(BACKEND STREQUAL "appkit")
target_sources(butterscotch PRIVATE src/backends/${BACKEND}.m)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Of course, there are exceptions that break game compatibility altogether:
* PlayStation 2
* PlayStation 3
* PlayStation Vita
* Nintendo Switch
* ...and maybe more in the future!

Additionally, any platform with reasonably complete C and POSIX conformance should work, the following have been tested.
Expand Down
18 changes: 17 additions & 1 deletion src/backends/sdl2.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
#include "platformdefs.h"
#include "gettime.h"
#include "runner_mouse.h"
#ifdef PLATFORM_SWITCH
#include <switch.h>
#include "switch_input.h"
#endif

static Runner *g_runner;
static SDL_Surface* scr;
Expand Down Expand Up @@ -139,6 +143,12 @@ static float platformGetWindowScale(void) {
void platformSetWindowSize(int32_t width, int32_t height) {
if (width <= 0 || height <= 0) return;

#ifdef PLATFORM_SWITCH
u32 operationMode = appletGetOperationMode();
width = (operationMode == AppletOperationMode_Console) ? 1920 : 1280;
height = (operationMode == AppletOperationMode_Console) ? 1080 : 720;
#endif

float scale = platformGetWindowScale();
SDL_SetWindowSize(window, (int)(width / scale), (int)(height / scale));

Expand Down Expand Up @@ -218,7 +228,7 @@ bool platformInit(int reqW, int reqH, const char *title, bool headless) {
// If we don't do this, the window will be larger than it should be on HiDPI displays.
platformSetWindowSize(reqW, reqH);

#ifndef PLATFORM_VITA
#if !defined(PLATFORM_VITA) && !defined(PLATFORM_SWITCH)
// init gamepad mappings
const char* dbPath = "gamecontrollerdb.txt";
if (SDL_GameControllerAddMappingsFromFile(dbPath) >= 0) {
Expand Down Expand Up @@ -323,6 +333,7 @@ void *platformGetProcAddress(const char *name) {

#endif

#ifndef PLATFORM_SWITCH
static int32_t SDLKeyToGml(int sdlkey) {
// Letters and numbers are the same as GML
if (sdlkey >= 'a' && sdlkey <= 'z') return toupper(sdlkey);
Expand Down Expand Up @@ -449,8 +460,12 @@ static void mapSdl2ToGml(SDL_GameController* gc, GamepadSlot* slot) {
slot->buttonValue[i] = slot->buttonDown[i] ? 1.0f : 0.0f;
}
}
#endif

bool platformHandleEvents(void) {
#ifdef PLATFORM_SWITCH
return SwitchInput_handleEvents(g_runner);
#else
SDL_Event e;
while (SDL_PollEvent(&e)) {
switch (e.type) {
Expand Down Expand Up @@ -574,6 +589,7 @@ bool platformHandleEvents(void) {
}

return false;
#endif
}

void platformSleepUntil(uint64_t time) {
Expand Down
8 changes: 4 additions & 4 deletions src/gl/gl_renderer.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#include "matrix_math.h"
#include "text_utils.h"

#if defined(__EMSCRIPTEN__) || defined(__ANDROID__)
#if defined(__EMSCRIPTEN__) || defined(__ANDROID__) || defined(__SWITCH__)
#include <GLES3/gl3.h>
#elif PLATFORM_VITA
#include <vitaGL.h>
Expand Down Expand Up @@ -56,15 +56,15 @@ static const char* baseFragmentShader =
// ===[ Runtime OpenGL extension checks ]===

static bool hasFBO() {
#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(__VITA__)
#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(__VITA__) && !defined(__SWITCH__)
return glGenFramebuffers;
#else
return true;
#endif
}

static bool hasVAO() {
#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(__VITA__)
#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(__VITA__) && !defined(__SWITCH__)
return glGenVertexArrays;
#else
return true;
Expand Down Expand Up @@ -439,7 +439,7 @@ static void glInit(Renderer* renderer, DataWin* dataWin) {
gl->isGL3 = (ver.major >= 3);
gl->isGLES = ver.isGLES;

#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(PLATFORM_VITA)
#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(PLATFORM_VITA) && !defined(__SWITCH__)
gl_init_wrappers();
#endif

Expand Down
2 changes: 1 addition & 1 deletion src/gl/gl_renderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include "common.h"
#include "renderer.h"
#include "runner.h"
#if defined(__EMSCRIPTEN__) || defined(__ANDROID__)
#if defined(__EMSCRIPTEN__) || defined(__ANDROID__) || defined(__SWITCH__)
#include <GLES3/gl3.h>
#elif PLATFORM_VITA
#include <vitaGL.h>
Expand Down
2 changes: 1 addition & 1 deletion src/gl_common/gl_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include "common.h"
#include <stdint.h>

#if defined(__EMSCRIPTEN__) || defined(__ANDROID__)
#if defined(__EMSCRIPTEN__) || defined(__ANDROID__) || defined(__SWITCH__)
#include <GLES3/gl3.h>
#elif PLATFORM_PS3
#include "ps3gl.h"
Expand Down
2 changes: 1 addition & 1 deletion src/gl_common/gl_wrappers.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#if !defined(_BS_GL_WRAPPERS_H_) && !defined(__EMSCRIPTEN__) && !defined(PLATFORM_PS3) && !defined(PLATFORM_VITA) && !defined(__ANDROID__)
#if !defined(_BS_GL_WRAPPERS_H_) && !defined(__EMSCRIPTEN__) && !defined(PLATFORM_PS3) && !defined(PLATFORM_VITA) && !defined(__ANDROID__) && !defined(__SWITCH__)
#define _BS_GL_WRAPPERS_H_

static inline void gl_init_wrappers(void) {
Expand Down
6 changes: 3 additions & 3 deletions src/loop.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
#include "input_recording.h"
#include "debug_overlay.h"
#if (defined(ENABLE_LEGACY_GL) || defined(ENABLE_MODERN_GL) || ((defined(USE_GLFW3) || defined(USE_GLFW2)) && defined(ENABLE_SW_RENDERER))) && \
!defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(PLATFORM_PS3) && !defined(PLATFORM_VITA)
!defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(PLATFORM_PS3) && !defined(PLATFORM_VITA) && !defined(__SWITCH__)
#define USE_GLAD
#include <glad/glad.h>
#endif
Expand Down Expand Up @@ -404,7 +404,7 @@ void saveInputRecording() {
}
}

#if !defined(_WIN32) && !defined(PLATFORM_VITA)
#if !defined(_WIN32) && !defined(PLATFORM_VITA) && !defined(__SWITCH__)
#define USE_CRASH_SIGNAL_HANDLER
typedef struct { int key; struct sigaction value; } PreviousSignalActionEntry;
static PreviousSignalActionEntry* previousSignalActions = nullptr;
Expand Down Expand Up @@ -466,7 +466,7 @@ int loop(CommandLineArgs args, const char *argv0) {
arrput(currentGameArgs, args.gameArgs[i]);
}
// The first argument will ALWAYS be the argv[0]
arrins(currentGameArgs, 0, safeStrdup(argv0));
arrins(currentGameArgs, 0, safeStrdup(argv0 != nullptr ? argv0 : ""));

bool platformInitialized = false;
int32_t inputFrameCount = 0;
Expand Down
40 changes: 40 additions & 0 deletions src/switch/log.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#include <stdio.h>
#include <string.h>
#include <switch.h>

#include "log.h"

#define LOG_BUFFER_SIZE 1024

void platformLog(const logType type, const char *format, va_list va) {
const char* colourPrefix = ANSI_COLOUR_CODE_RESET;
const char* textPrefix = "";
char buffer[LOG_BUFFER_SIZE];

switch (type) {
case LOG_TYPE_NORMAL:
break;
case LOG_TYPE_WARNING:
colourPrefix = ANSI_COLOUR_CODE_BOLD_YELLOW;
textPrefix = "Warning: ";
break;
case LOG_TYPE_ERROR:
colourPrefix = ANSI_COLOUR_CODE_BOLD_RED;
textPrefix = "Error: ";
break;
case LOG_TYPE_DEBUG:
colourPrefix = ANSI_COLOUR_CODE_BOLD_PURPLE;
textPrefix = "Debug: ";
break;
}
int written = snprintf(buffer, sizeof(buffer), "%s", textPrefix);

if (written >= 0 && written < (int)sizeof(buffer)) {
vsnprintf(buffer + written, (int)sizeof(buffer) - written, format, va);
}
buffer[sizeof(buffer) - 1] = '\0';

printf("%s%s%s", colourPrefix, buffer, ANSI_COLOUR_CODE_RESET);

svcOutputDebugString(buffer, strlen(buffer));
}
44 changes: 44 additions & 0 deletions src/switch/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include <loop.h>
#include <switch.h>
#include <sys/stat.h>
#include <GLES3/gl3.h>

/* For SDL_main */
#if defined(USE_SDL1)
#include <SDL/SDL_main.h>
#elif defined(USE_SDL2)
#include <SDL2/SDL_main.h>
#elif defined(USE_SDL3)
#include <SDL3/SDL_main.h>
#endif

int main(int argc, char* argv[]) {
(void)argc;
setbuf(stdout, NULL);
setbuf(stderr, NULL);

fsdevMountSdmc();

CommandLineArgs args = {0};

args.exitAtFrame = -1;
#ifdef ENABLE_VM_TRACING
args.traceBytecodeAfterFrame = 0;
#endif
args.speedMultiplier = 1.0;
args.fastForwardSpeed = 0.0;
args.osType = OS_WINDOWS;
args.profilerFramesBetween = 0;
args.loadType = DATAWINLOADTYPE_LOAD_IN_MEMORY_AHEAD_OF_TIME;
#if defined(ENABLE_MODERN_GL)
args.renderer = MODERN_GL;
#else
args.renderer = SOFTWARE;
#endif
args.dataWinPath = "sdmc:/switch/butterscotch/data.win";
args.saveFolder = "sdmc:/switch/butterscotch";

int ret = loop(args, argv[0]);
freeCommandLineArgs(&args);
return ret;
}
5 changes: 5 additions & 0 deletions src/switch/stb_impl.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
Loading
Loading