C++ games and interactive media framework that makes creating cross-platform applications easier and faster https://dank.game
Go to file
Cocktail Frank b3d78289f0 Use absolute links for tags in README 2024-12-05 14:30:27 -05:00
demo wip 2024-09-09 16:36:20 -04:00
icon box demo android support, android file to string for reading files inside APK 2022-11-08 17:17:20 -05:00
lib Update test program with special conditions for Emscripten builds 2024-11-16 18:53:27 -05:00
src Add 0.1.1 release highlights to README 2024-12-04 21:04:29 -05:00
.gitignore Update test program with special conditions for Emscripten builds 2024-11-16 18:53:27 -05:00
BPmono.ttf make android and wasm config file paths configurable 2023-12-26 20:26:31 -05:00
LICENSE.txt box demo android support, android file to string for reading files inside APK 2022-11-08 17:17:20 -05:00
LICENSE_BPmono.txt added licensing info 2021-06-26 20:25:03 -04:00
README.md Use absolute links for tags in README 2024-12-05 14:30:27 -05:00

README.md

logo

🌊 S P A C E 🪐 🅱 O X 💫

       /\         +-------------------------------------------------------+
  ____/  \____   /| Open source game framework licensed to freely use,    |
  \          /  / | copy, and modify - created for dank.game              |
+--\ ^__^   /--+  |                                                       |
| ~/        \~ |  | Download at https://open.shampoo.ooo/shampoo/spacebox |
| ~~~~~~~~~~~~ |  +-------------------------------------------------------+
| SPACE ~~~~~  | /
|  ~~~~~~~ BOX |/
+--------------+ 

SPACE🪐BOX is a framework that makes creating cross-platform games and other interactive applications easier and faster by providing an added layer of abstraction between SDL + OpenGL and the project.

It was created for 💫dank.game💫. The games Cakefoot, B.U.D.D.I., Gunkiss, and Pepy use SPACE🪐BOX.

Requirements

External

  • SDL2 (currently tested against 2.26.3)
  • SDL2-image
  • SDL2-ttf
  • SDL2-mixer
  • OpenGL or OpenGL ES (currently tested against OpenGL 3.2 and OpenGL ES 2.0)
  • C++ compiler that supports C++17
  • Emscripten SDK (optional - necessary only for web browser builds)

SDL2, SDL2-image, SDL2-ttf, SDL2-mixer

Use a package manager like apt if possible.

apt install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev

Otherwise, visit the repositories for each and follow instructions for installation.

OpenGL

Install OpenGL or OpenGL ES according to your platform and link to it during compilation. In most cases, it is already installed.

Emscripten

The Emscripten SDK is necessary for creating a web browser build. Detailed installation instructions are available on Emscripten's website. Alternatively, Emscripten may be available in your system's package manager.

sudo apt install emscripten

Once installed, see the test program's Makefile for an example of a Emscripten build.

Internal

The rest of the required libraries are included in the repository.

  • GLM
  • GLEW
  • nlohmann::json
  • SDL2-gfx
  • GIF-H
  • SuperXBR
  • CLI11
  • Catch2

Building a project

🚧🚧🚧

SPACE🪐BOX is expected to be built along with the project implementing it since currently there are no library builds available.

The details of the build process are still mostly undocumented aside from the notes below, which are under construction and not guaranteed to be kept up to date with the latest version.

There is a test program with a Makefile that demonstrates how to build a project on Linux, Windows, MacOS, and the web. The test is kept up-to-date, but it currently only covers a small portion of the framework.

There is also the Cakefoot project which has an up-to-date Makefile for Linux, Windows, MacOS, and the web.

Linux

A simple up-to-date Makefile is available at src/test/Makefile, and there is also a more complex one in the Cakefoot repository.

Copy the Makefile from Cakefoot, which demonstrates how to compile SPACE🪐BOX and link to required libraries, adapt it to fit a new project, and build a Linux executable with GNU make.

Packaging a distributable Linux build

There are few techniques that can be used to increase compatibility among Linux versions so that the build can be distributed.

➡ See also this related Stack Overflow thread

Checking dependencies

Use readelf to see which libraries a binary uses and what the extra search path is

$ readelf -d build/x86_64/Cakefoot-linux.x86_64
Dynamic section at offset 0x1a2c10 contains 36 entries:
Tag        Type                         Name/Value
0x0000000000000001 (NEEDED)             Shared library: [libSDL2-2.0.so.0]
0x0000000000000001 (NEEDED)             Shared library: [libGL.so.1]
0x0000000000000001 (NEEDED)             Shared library: [libGLESv2.so.2]
0x0000000000000001 (NEEDED)             Shared library: [libSDL2_image-2.0.so.0]
0x0000000000000001 (NEEDED)             Shared library: [libSDL2_ttf-2.0.so.0]
0x0000000000000001 (NEEDED)             Shared library: [libSDL2_mixer-2.0.so.0]
0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
0x0000000000000001 (NEEDED)             Shared library: [libm.so.6]
0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
...

Use objdump to see which symbols a binary uses. In this case ios_base is needed from GLIBCXX_3.4.32 (an older version of GLIBCXX couldn't be used for the ios_base_library_init symbol during compilation).

$ objdump -x build/x64/Cakefoot-linux.x64 | grep GLIBCXX_3.4.32 | c++filt
0x0297f842 0x00 23 GLIBCXX_3.4.32
0000000000000000       F *UND*  0000000000000000              std::ios_base_library_init()@GLIBCXX_3.4.32
Static flags

Adding -static-libgcc and -static-libstdc++ to the linker flags will remove the dependencies on libstdc++ and libgcc_s.

Older compiler

Using an older version of GCC to compile can lower the version of some symbols.

Packaging shared libraries

The rpath property can be used to add a local library search path to the linker, and any libraries to package with the distributable can be added to the local path. For example, this is how SDL is packaged with Cakefoot.

This flag is used to add a relative library lookup path

-Wl,-rpath $(X64_BUILD_LIB_DIR)

This block is used to copy the SDL libraries into the local library folder

cp $$(ldd $(X64_BUILD_DIR)/$@ | grep libSDL2-2.0 | cut -d" " -f3) \
    $$(ldd $(X64_BUILD_DIR)/$@ | grep libSDL2_image | cut -d" " -f3) \
    $$(ldd $(X64_BUILD_DIR)/$@ | grep libSDL2_ttf | cut -d" " -f3) \
    $$(ldd $(X64_BUILD_DIR)/$@ | grep libSDL2_mixer | cut -d" " -f3) ${basename $@}/$(X64_BUILD_LIB_DIR)
Virtualization using Docker

Docker can be used to compile for a target platform with a desired baseline compatibility profile, for example, Ubuntu 16.04.

apt install docker.io

Make sure your user has sudo permissions to use /usr/bin/docker by editing either /etc/sudoers/ or a file in /etc/sudoers.d/. Then pull the Ubuntu 16.04 image.

sudo docker pull ubuntu:16.04

To be continued (for now, see the test program Makefile)... 🚧

Emscripten builds

Creating a browser build with Emscripten and its built-in version of SDL has worked well on a few projects. The general process is to create a separate make target that uses Emscripten's C++ to WebAssembly compiler. See the test program for an example.

emscripten_set_main_loop

Emscripten has two different ways of running a main loop: emscripten_set_main_loop and asyncify. Asyncify is similar to a traditional loop, where the program sleeps for a small amount of time between loops using emscripten_sleep. This fits well with the framework because other platform builds use SDL_Delay to sleep for a configured amount of milliseconds per loop. However, asyncify is too slow to run smoothly, so Emscripten builds by default use emscripten_set_main_loop.

With emscripten_set_main_loop, the program chooses between either throwing an exception and continuing execution in a infinite loop, which Emscripten describes as a simulation of an infinite loop, or asynchronously running the loop and allowing the rest of the program to run until completion.

SPACE🪐BOX uses the first option to implement Game::run. This means any code after Game::run will not run in Emscripten builds. In general, the program is expected to run in a loop for as long as the web page is open, and the code after Game::run is just for quitting, so that option works well.

However, that option does not allow for running Game::run more than once, and it doesn't allow for Game to run within a larger program, like a launcher or a test suite. For SPACE🪐BOX tests, there is support for asyncify built-in, and it can be used by other programs by adding -sASYNCIFY to the Emscripten linker flags and -D__ASYNCIFY__ to the compiler flags.

Expected warnings

The browser console will report an error Uncaught unwind. This is expected and is related to how Emscripten launches the main loop (unless using -sASYNCIFY, which is not recommended, see above).

If exception support is enabled using the -fexception flag, the browser console will report an error.

emscripten_set_main_loop_timing: Cannot set timing mode for main loop since a main loop does not exist!
Call emscripten_set_main_loop first to set one up.

This warning can be safely ignored. See the Github issue for more information.

Notes

Using fs::file_time_type causes a compiler error when running a check of the value in Catch2.

fs::file_time_type last_write_time = fs::last_write_time(*tmp_dir);
...
CHECK(fs::last_write_time(*tmp_dir) == last_write_time); /* Causes error with Emscripten compiler */

Raspberry Pi

Raspberry Pi builds, like Android and Emscripten, require using OpenGL ES. The build process is similar to standard desktop Linux.

GLEW is not available for OpenGL ES, so GLES headers are included directly from the expected system directories.

The flag -DGLEW_NO_GLU may also be necessary since there may not be a glu.h file on Raspberry Pi installations. This flag can also just be used by default because GLU is deprecated.

SDL

SDL should be built with --enable-video-kmsdrm. This will require the installation of external packages. OpenGL ES will also need to be installed.

sudo apt install libgbm-dev libdrm-dev libegl-dev libgles2-mesa-dev

Otherwise, SDL can be installed in the same way described above.

KMS

If using the Raspberry Pi without X-windows, SDL will run in KMS mode. The config for the latest versions of the Raspberry Pi OS Lite should be setup correctly by default, and the installation of SDL from source should enable KMS by default if the necessary packages above have been installed. However, it is good to verify that /boot/firmware/config.txt is setup correctly and that SDL is built with KMS video output enabled.

To verify /boot/firmware/config.txt, make sure the line dtoverlay=vc4-kms-v3d is included in the file. This activates KMS mode for the console instead of either Fake KMS or the older framebuffer modes.

Build the testgles2.c SDL test

In the SDL source downloaded in the previous step, there is a test/testgles2.c program that draws a rotating cube using GLES. This can be used to verify that the SDL installation is working on the Pi.

cd [SDL_source]/test/
gcc $(sdl-config --cflags --libs) -LSDL_test -o testgles
./testgles

Raspberry Pi Model 3B+

This model only supports up to Open GL ES 2.0.

Raspberry Pi Model 4

This model supports up to Open GL ES 3.2.

External demos

Try these non-SPACEBOX demos for verifying the Raspberry Pi OpenGL ES setup with KMS.

Android

The fill_screen demo has a working example of how to build for Android. It may be worthwhile to read the SDL wiki Android page and SDL docs Android README and compile an SDL example for Linux before doing a SPACEBOX Android build. The source distributions for SDL, SDL image, SDL ttf, and SDL mixer, and the Android SDK are required.

After building the demo, see the following for further information on using SDL on Android.

Building an SDL example for Linux

  • Install Java packages

      apt install openjdk-17-jdk ant
    
  • Make a folder for Android to store the SDK, NDK, emulator, tools, etc.

      mkdir -p ~/local/Android/
    
  • Download Android command line tools to that folder, extract them, and install an NDK (the gradle tool included with SDL defaults to NDK 21.4.7075529). Note that the gradle tool may download Android tools, but this step seems to be necessary for signing the license, which gets created in licenses

      $ cd ~/local/Android/
      $ unzip commandlinetools-linux-8512546_latest.zip
      $ cmdline-tools/bin/sdkmanager --sdk_root=$HOME/local/Android/ "ndk;21.4.7075529"
    
  • Download and extract SDL source

      $ wget "https://github.com/libsdl-org/SDL/releases/download/release-2.24.0/SDL2-2.24.0.tar.gz"
      $ tar -xf SDL2-2.24.0.tar.gz
      $ cd SDL2-2.24.0/
    
  • Copy android-project/ to another folder, symlink the SDL source,

      $ cp -r android-project org.my.testgles
      $ cd org.my.testgles/app/jni
      $ ln -s ../../.. SDL
    
  • Edit the line with YourSourcehere.c in org.my.testgles/app/jni/src/Android.mk to point to an SDL example

      $ cd src/ && sed -i s#YourSourceHere.c#../SDL/test/testgles.c# Android.mk
    
  • Build with gradlew in the root of the project, specifying where to find the Android folder

      $ ANDROID_SDK_ROOT=$HOME/local/Android ./gradlew build
    
  • The APK should be output to app/build/outputs/apk/debug/app-debug.apk. It can be uploaded to the phone for testing or run on an emulator. To create an emulator, use the Android SDK's tools. For example, to create an Android emulator for API level 31 (Android 12.0) with ABI x86_64.

      # Install a version of command line tools with support for later versions of Java
      $ cmdline-tools/bin/sdkmanager --sdk_root=$HOME/local/Android "cmdline-tools;latest"
    
      # Install a system image
      $ cmdline-tools/bin/sdkmanager --sdk_root=$HOME/local/Android "system-images;android-31;default;x86_64"
    
      # Create emulator
      $ cmdline-tools/latest/bin/avdmanager create avd -n android_31_x86_64 -k "system-images;android-31;default;x86_64"
    
      # Launch in the background
      $ ~/local/Android/tools/emulator -avd android_31_x86_64 &
    
      # Install the APK to the running emulator
      $ ~/local/Android/platform-tools/adb -e install -r app/build/outputs/apk/debug/app-debug.apk
    
      # Start the log viewer
      $ ~/local/Android/platform-tools/adb [-s device_name] logcat
    

fill_screen demo

The fill_screen demo has a Makefile that should work for building for Android if the paths in the file are adjusted to match the project. Edit the Makefile and run make build/android/[org.my.app]. If that isn't working, see below for notes on how the build was originally done manually.

Custom assets

Assets can be copied to app/src/main/assets. The box demo has an example of how to include custom assets like config JSON and shaders.

Creating the fill_screen Android build

These steps were taken to build the fill_screen demo for Android. The Android SDK is assumed to be installed as explained above in the SDL test example. The instructions are based on SDL 2.24.0. There is also a Makefile target that scripts this process in [demo/fill_screen/Makefile][].

  • Copy the included Android project in the SDL source into the root of the fill_screen project folder.

      $ cp -r path/to/SDL2-2.24.0/android-project [fill_screen root]/ooo.shampoo.fill_screen
      $ cd [fill_screen root]/ooo.shampoo.fill_screen
    
  • Edit the application ID

      $ sed -i s/org.libsdl.app/ooo.shampoo.fill_screen/ app/build.gradle app/src/main/AndroidManifest.xml
    
  • Enable C++ STL

      $ sed -i "s/^#.*\(APP_STL\)/\1/" app/jni/Application.mk
    
  • Enable C++ 17 features and exceptions

      $ echo "APP_CPPFLAGS := -std=c++17 -fexceptions -frtti" >> app/jni/Application.mk
    
  • Modify rules to allow OpenGL ES 3.0 (necessary for example for using GL_RGBA8)

      $ sed -i -e 's/^LOCAL_LDLIBS.*/& -lGLESv3/' app/jni/src/Android.mk
      $ sed -i 's/0x0002/0x0003/' app/src/main/AndroidManifest.xml
      $ sed -i 's/\(minSdkVersion\).*16/\1 18/' app/build.gradle
      $ sed -i 's/\(android\)-16/\1-18/' app/build.gradle app/jni/Application.mk
    
  • std::filesystem is only available in NDK 22+, so install NDK version 22 and set the project to use it

      $ ~/local/Android/cmdline-tools/bin/sdkmanager --sdk_root=$HOME/local/Android --install "ndk;22.1.7171670"
      $ sed -i '11i\    ndkVersion "22.1.7171670"' app/build.gradle
    
  • Link to SDL source packages (versions other than the listed ones may work)

      $ ln -s path/to/SDL2-2.24.0 app/jni/SDL
      $ ln -s path/to/SDL2_image-2.6.2 app/jni/SDL2_image
      $ ln -s path/to/SDL2_mixer-2.6.2 app/jni/SDL2_mixer
      $ ln -s path/to/SDL2_ttf-2.20.1 app/jni/SDL2_ttf
    
  • Add SDL packages as libraries in the Makefile

      $ sed -i 's/^LOCAL_SHARED_LIBRARIES.*/& SDL2_image SDL2_mixer SDL2_ttf/' app/jni/src/Android.mk
    
  • Add SPACEBOX lib/ and src/ to include search path. In this command, the paths are relative to the path of Android.mk and based on the location of fill_screen as included in the SPACEBOX repository, but they can be edited to other paths if necessary.

      $ sed -i 's#^LOCAL_C_INCLUDES.*#& $(LOCAL_PATH)/../../../../../../lib $(LOCAL_PATH)/../../../../../../src#' \
          app/jni/src/Android.mk
    
  • Add SPACEBOX source files from lib/ src/ and source files for fill_screen

      $ sed -i 's#YourSourceHere.c#$(LOCAL_PATH)/../../../../fill_screen.cpp#' app/jni/src/Android.mk
      $ sed -i 's#^LOCAL_SRC_FILES.*#& $(wildcard $(LOCAL_PATH)/../../../../../../src/*.cpp)#' app/jni/src/Android.mk
      $ sed -i 's#^LOCAL_SRC_FILES.*#& $(wildcard $(LOCAL_PATH)/../../../../../../lib/sdl2-gfx/*.c)#' app/jni/src/Android.mk
    
  • Create a file at app/src/main/java/ooo/shampoo/fill_screen/FillScreen.java with the following contents

      package ooo.shampoo.fill_screen;
    
      import org.libsdl.app.SDLActivity;
    
      public class FillScreen extends SDLActivity {
          protected String getMainFunction() {
              return "main";
          }
      }
    
  • Edit the manifest to point to that class

      $ sed -i 's/\(name=\)"SDLActivity"/\1"FillScreen"/' app/src/main/AndroidManifest.xml
    
  • Run gradle

      $ ANDROID_SDK_ROOT=$HOME/local/Android ./gradlew build
    

Screen rotation

Note that SDL_WINDOW_RESIZABLE is required for screen rotation to work

Cross compilation considerations

  • Even though SDL_RWops is able to directly read a file stored in an Android APK, std::filesystem::exists will not detect the file's existence while it is embedded in the APK. Files can be first copied out of an APK using sb::copy_file.

Windows

Windows builds are made with MinGW. The box demo contains a make target for building a Windows 32-bit executable from a Linux system. To build from Windows itself, the make target will have to be modified. The SDL MinGW releases and MinGW itself must be downloaded separately.

Download SDL's MinGW libraries from the SDL releases page. Download the libraries for SDL image, SDL mixer, and SDL ttf as well.

mkdir SDL2-mingw
cd SDL2-mingw
wget "https://github.com/libsdl-org/SDL/releases/download/release-2.24.2/SDL2-devel-2.24.2-mingw.zip"
unzip SDL2-devel-2.24.2-mingw.zip
wget "https://github.com/libsdl-org/SDL_image/releases/download/prerelease-2.5.2/SDL2_image-devel-2.5.2-mingw.tar.gz"
tar -xf SDL2_image-devel-2.5.2-mingw.tar.gz
wget "https://github.com/libsdl-org/SDL_ttf/releases/download/release-2.0.15/SDL2_ttf-devel-2.0.15-mingw.tar.gz"
tar -xf SDL2_ttf-devel-2.0.15-mingw.tar.gz
wget "https://github.com/libsdl-org/SDL_mixer/releases/download/prerelease-2.5.2/SDL2_mixer-devel-2.5.2-mingw.tar.gz"
tar -xf SDL2_mixer-devel-2.5.2-mingw.tar.gz

Install MinGW. The simplest way to install is through the package manager. On Debian the command is

apt install mingw-w64

Add -DGLEW_STATIC to compile the GLEW library into a [SPACE BOX] project using MinGW.

Add -static-libgcc and -static-libstdc++ to the linker flags to compile GCC and C++ STD runtime DLLs into the executable instead of distributing the DLL file with the executable.

Add -mwindows to the linker flags to prevent Windows from opening a console window in the background when launching the game.

See the Windows section of demo/box/Makefile for a complete list of necessary flags and build process.

Cross compilation considerations

  • std::filesystem::path::c_str returns a const wchar_t* (as opposed to const char* in Linux). To make a platform generic call, convert the path to a string, then use std::filesystem::path::string::c_str instead.

  • In order to use std::thread with MinGW, you must use the POSIX version of the compiler, for example x86_64-w64-mingw32-g++-posix and add -lpthread to the linker flags.

MacOS

SDL Wiki - macOS

To fully support macOS, both Intel x86_64-macos and ARM aarch64-macos architecture versions must be compiled.

Make sure the version of macOS on the build host system is supported by the version of SDL being used. There is information in the documentation in the SDL source at SDL2-2.X.X/docs/README-macos.md. If necessary, upgrade the macOS operating system, using the official Apple support links, or downgrade SDL by downloading a lower version.

To begin compiling for macOS, clone the cross compilation toolkit osxcross tool

git clone https://github.com/tpoechtrager/osxcross

Install necessary packages

apt install clang make libssl-dev lzma-dev libxml2-dev xz-utils bzip2 cpio zlib1g-dev

Download the Xcode XIP archive to the root of the repository and use the toolkit to generate the SDK as a TAR archive

./tools/gen_sdk_package_pbzx.sh <xcode>.xip

Move the SDK TAR archive into tarballs/ and run the build script at the root of the repository

./build.sh

Add the bin/ directory to PATH

PATH=$PATH:/media/gdrive/osxcross/target/bin/

Get the target value from osxcross-conf. In this case it's darwin20.4

$ target/bin/osxcross-conf
export OSXCROSS_VERSION=1.5
export OSXCROSS_OSX_VERSION_MIN=10.9
export OSXCROSS_TARGET=darwin20.4
...

Update references to OpenGL for macOS if necessary. See this thread about alternatives to OpenGL on Mac for future concerns about OpenGL on macOS. This is currently handled in SPACE🪐BOX with the following pre-processor directive.

#if defined(__APPLE__)
#define GL_SILENCE_DEPRECATION
#include <OpenGL/gl3.h>
#endif

Get the SDL Framework DMG releases so SDL can be included in the project. The following is a build target for creating a signed and notarized app bundle and DMG of Cakefoot for macOS. There are some important parts to be expanded on in a future update, including linking to frameworks, using an rpath, compiling with a deployment target and universal architecture, and creating an app bundle along with its Info.plist. The signing and notarization process is explained in the next section.

In case the library path is lost when moving the MACOS_CROSS_ROOT, it's good to add it to LD_LIBRARY_PATH before running like in the linker command below. This can prevent error loading library errors like error while loading shared libraries: libxar.so.1: cannot open shared object file: No such file or directory.

MACOS_CROSS_ROOT = /media/gdrive/osxcross
MACOS_CROSS_FW = $(MACOS_CROSS_ROOT)/local/Frameworks
MACOS_CFLAGS = -Wall -Wextra -O3 -c -I$(SB_LIB_DIR) -I$(SB_SRC_DIR) -DGLEW_STATIC -DGLEW_NO_GLU -F$(MACOS_CROSS_FW) \
    -I$(MACOS_CROSS_FW)/SDL2.framework/Headers -I$(MACOS_CROSS_FW)/SDL2_image.framework/Headers \
    -I$(MACOS_CROSS_FW)/SDL2_ttf.framework/Headers -I$(MACOS_CROSS_FW)/SDL2_mixer.framework/Headers
MACOS_CXXFLAGS = $(CFLAGS) --std=c++17
MACOS_LFLAGS =  -Wl,-rpath,@executable_path/../Frameworks -pthread -F$(MACOS_CROSS_FW) -framework SDL2 \
    -framework SDL2_image -framework SDL2_ttf -framework SDL2_mixer -framework OpenGL
MACOS_OBJ = $(addprefix $(MACOS_BUILD_DIR)/, glew.o SDL2_rotozoom.o SDL2_gfxPrimitives.o $(SB_O_FILES) $(SRC_O_FILES))
MACOS_BUNDLE = $(MACOS_BUILD_DIR)/dmg/$@
MACOS_BUNDLE_CONTENTS = $(MACOS_BUILD_DIR)/dmg/$@/Contents
MACOS_CROSS_BIN = $(MACOS_CROSS_ROOT)/target/bin
MACOS_CERTIFICATE = local/Cakefoot_MacOS_DeveloperID_Application.pem
MACOS_RCODESIGN = ~/ext/software/apple-codesign-0.27.0/rcodesign

Cakefoot.app : CC = MACOSX_DEPLOYMENT_TARGET=11.3 PATH=$(PATH):$(MACOS_CROSS_BIN) o64-clang -arch arm64 -arch x86_64
Cakefoot.app : CXX = MACOSX_DEPLOYMENT_TARGET=11.3 PATH=$(PATH):$(MACOS_CROSS_BIN) o64-clang++ -arch arm64 -arch x86_64
Cakefoot.app : CFLAGS = $(MACOS_CFLAGS)
Cakefoot.app : CXXFLAGS = $(MACOS_CXXFLAGS)
Cakefoot.app : LFLAGS = $(MACOS_LFLAGS)
Cakefoot.app : $(MACOS_OBJ)
    mkdir -p $(MACOS_BUNDLE_CONTENTS)/MacOS $(MACOS_BUNDLE_CONTENTS)/Frameworks $(MACOS_BUNDLE_CONTENTS)/Resources
    cp src/Info.plist $(MACOS_BUNDLE_CONTENTS)
    cp -r $(MACOS_CROSS_FW)/SDL2*.framework $(MACOS_BUNDLE_CONTENTS)/Frameworks
    LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):$(MACOS_CROSS_ROOT)/target/lib $(CXX) $^ $(LFLAGS) -o \
        $(MACOS_BUNDLE_CONTENTS)/MacOS/$(basename $@)
    rsync -arRL resource/ src/shaders/ config.json $(MACOS_BUNDLE_CONTENTS)/Resources
    $(MACOS_RCODESIGN) sign --for-notarization --code-signature-flags runtime --pem-file $(MACOS_CERTIFICATE) \
        $(MACOS_BUNDLE)

notarize :
    $(MACOS_RCODESIGN) notary-submit --api-key-file local/Cakefoot_App_Store_Connect_API_key.json --staple \
        $(MACOS_BUILD_DIR)/dmg/Cakefoot.app
    cd $(MACOS_BUILD_DIR) && genisoimage -V Cakefoot -D -R -apple -no-pad -hide-rr-moved -o Cakefoot.dmg dmg && cd -

Signing a DMG

Download Apple Codesign Tools

wget https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign/0.27.0/apple-codesign-0.27.0-x86_64-unknown-linux-musl.tar.gz

Generate a Certificate Signing Request

openssl genrsa -out local/Cakefoot_CSR_key.pem 2048
rcodesign generate-certificate-signing-request --pem-file local/Cakefoot_CSR_key.pem --csr-pem-file \
    local/Cakefoot_Certificate_Signing_Request.pem

Or generate certificates using Xcode on MacOS

Exchange the Certificate Signing Request (CSR) for a Code Signing Certificate (CSC)

  1. Go to https://developer.apple.com/account/resources/certificates/add (you must be logged into Apples website)
  2. Select the certificate flavor you want to issue.
  3. Click Continue to advance to the next form.
  4. Select the G2 Sub-CA (Xcode 11.4.1 or later) Profile Type (we support it).
  5. Choose the file containing your CSR.
  6. Click Continue.
  7. If all goes according to plan, you should see a page saying Download Your Certificate.
  8. Click the Download button.
  9. Save the certificate somewhere. (Saved below as Cakefoot_MacOS_DeveloperID_Application.crt)

View the certificate

openssl x509 -in local/Cakefoot_MacOS_DeveloperID_Application.cer -text -noout

Combine the certificate with the private key into a single PEM file

openssl x509 -inform der -in Cakefoot_MacOS_DeveloperID_Application.cer -out Cakefoot_MacOS_DeveloperID_Application.pem
cat local/Cakefoot_CSR_key.pm >> Cakefoot_MacOS_DeveloperID_Application.pem

Sign the Application Bundle (in-place)

rcodesign sign --for-notarization --code-signature-flags runtime \
    --pem-file local/Cakefoot_MacOS_DeveloperID_Application.pem Cakefoot.app

To view the signature info of a signed app on MacOS

codesign --display --verbose=4 /Volumes/Cakefoot/Cakefoot.app

To notarize, generate a team API key with Developer access role. Download it to a path, for example Cakefoot_App_Store_Connect_API_key_33UBLVSW4Y.p8. Use the Issuer ID and KEY ID from the Apple Developer site combined with the downloaded file to create a JSON document. Submit a request for notarization, wait for the response, and automatically staple. See Review Guidelines for details on what Apple checks for during notarization.

rcodesign notary-submit --api-key-file local/Cakefoot_App_Store_Connect_API_key.json --staple build/macos/dmg/Cakefoot.app

Check the app bundle on MacOS

codesign --verify --verbose Cakefoot.app
spctl -a -t exec -vv Cakefoot.app

Debugging notes

  • Make sure there are no absolute paths other than system libraries in the binary by verifying with otool -l /path/to/binary. In the case of Cakefoot, an unnecessary absolute rpath was erroneously being added in a previous version of the build script. See this thread for more info.

  • Make sure the ZIP archive of the app bundle is zipped with --symlinks to prevent creating extra copies of the contents of the linked directories in the framework folders. In addition to taking up more space, removing the symlinks will cause the app to fail against the spctl test and Gatekeeper.

  • Gatekeeper seems to require that Info.plist has the CFBundlePackageType key set to APPL.

  • An application downloaded from the internet can be flagged as quarantined regardless of whether it is certified and notarized. The quarantine flag is controlled by the application which downloads the file, and it seems like web browsers on MacOS are configured to quarantine files. When an application is quarantined, it is moved into the /private/var/ directory. When Cakefoot is directly downloaded from the web in DMG format, it is flagged to be quarantined, and an unknown reason related to quarantining cause the game to crash. The current less than ideal solution is to move Cakefoot out of the DMG into another folder, for example /System/Applications or /Users/[user]/Desktop.

  • Possibly useful entitlements that would be added to a plist file Cakefoot.entitlements

      com.apple.security.app-sandbox (required for App Store but not compatible with Steam API)
      com.apple.security.cs.allow-unsigned-executable-memory
      com.apple.security.cs.disable-executable-page-protection
      com.apple.security.cs.disable-library-validation (needed by Steam API)
      com.apple.security.cs.allow-dyld-environment-variables (needed by Steam API)
    

Creating a fancy DMG UI

See create-dmg and this thread for how to make a DMG that shows the Applications folder and an arrow directing the user to drag the app bundle into Applications.

Testing

The Catch2 framework is used for testing SPACE🪐BOX. It comes included with the repository. Catch2 can be used to write custom tests for SPACE🪐BOX projects as well.

The tests and test data are kept in the src/test/ folder. There is a Makefile that builds test programs for Linux, Windows, MacOS, and the web. Each platform will need the necessary prerequisites to be installed as described above. For example, if the SDL and OpenGL prerequisites are installed, and the target is Linux, the build can be created by running the following in the test folder.

make SPACEBOX-linux_test.x64

The test program will be output to src/test/build/x64/SPACEBOX-linux_text.x64. Run the program, and it should exit with all tests passing.

$ build/x64/SPACEBOX-linux_test.x64
Randomness seeded to: 68735375
===============================================================================
All tests passed (1 assertion in 1 test case)

A text file with the results will be created in the working directory. This can be useful for reading the results without running in a terminal, for example on Windows or MacOS. A JSON file with the test results will also be created automatically in the working directory. This can be useful for loading the results into an external program like a CI system.

To disable the results files, pass --no-default-reporters when launching the tests.

Emscripten tests

Because the Emscripten tests require that Catch2 is able to finish running after Game::run exits, they must use asyncify to handle the main loop. See emscripten_set_main_loop for details on asyncify. This makes the Emscripten test environment significantly different from a standard Emscripten build's environment.

Steam API tests

There are optional tests available for the Steam API. They require an internet connection, an app ID (stored in a file name steam_appid.txt, and the Steam SDK. See Steam API for details on installing the SDK. By default, the tests are not compiled with the test program. They can be enabled by adding STEAM=yes to the make command.

make STEAM=yes SPACEBOX-linux_test.x64

The tests also require the app ID to have defined stats named _BANANAS and _PIES with no max value and not set to increment only. These stats are only used for testing and will be set to a random value and read back to check that setting stats is working. The app must also have at least one achievement defined because the achievement at index 0 will be read, toggled either on or off, checked, and then toggled back to its original state at the end of the test.

Custom tests

A project can use the included Catch2 library for creating its own test program.

If using more than one file to define tests, define the following macro in only one of the files before including catch_amalgamated.hpp.

#define CATCH_CONFIG_MAIN

To use a custom main function, which is what the SPACE🪐BOX tests do, use the -DCATCH_AMALGAMATED_CUSTOM_MAIN preprocessor flag when building the tests. Then it becomes possible to add command line configuration directly to the test code.

Catch::Session sess;
const char *args[11] = {"program", "-s", "-d", "yes", "--use-colour", "no", "-r",
                        "compact", "-o", "test_result1", "Test1"};
int res = sess.run(11, args);

Catch2 provides listener classes which can listen for events, for example test run begin and end, and run custom code

#include <catch2/catch_amalgamated.hpp>

class testRunListener : public Catch::EventListenerBase {
public:
    using Catch::EventListenerBase::EventListenerBase;

    void testRunStarting(Catch::TestRunInfo const&) override {
        lib_foo_init();
    }
};

Demos

As the project matures, these demos are intended to be transitioned into an automated testing suite, but they are not currently guaranteed to be up to date with the latest version of SPACE🪐BOX — in fact, they don't even indicate which version of SPACE🪐BOX they're compatible with, as versioning was only added recently.

The demo/ folder contains programs that demonstrate and test the capabilities of the framework. In order to compile each, you should edit the definitions in the Makefile.

Fill screen

This is intended to be a bare minimum test of the framework which loads the framework and fills the screen with a new color each frame. It currently builds to Linux and Android.

Box

Test OpenGL context by drawing a textured, rotating cube. It currently builds to Linux, Android, and web browsers.

Browser webcam

An example for using a C++ program to display a webcam stream in the browser using Emscripten to translate the code from C++ to WebAssembly. Get the frame pixel data from a canvas element, read it into a SPACEBOX object, write the pixel data to an OpenGL texture, and use Emscripten to display the video.

Squircle

Map an image from a rectangle to a circle or from a circle to a rectangle using a shader program. Based on a blog post about elliptical grid mapping equations.

OpenCV camera

Get camera input using the OpenCV library on Linux or Android.

Extensions

The following external libraries can be used with SPACE🪐BOX projects but are installed separately.

In the case of the Steam API, there is code within SPACE🪐BOX that can be enabled if the library is installed.

Steam API

The Steam API is not open source, so it is not distributed with SPACE🪐BOX and is not required for building a project. By default, SPACE🪐BOX will compile with Steamworks disabled. There are, however, some Steam API features built into the framework that can be used if the Steamworks SDK is enabled, its headers are available, and the SDK's redistributable library is linked to during compilation.

The SPACE🪐BOX test program can be used as an example of how to enable the Steam API.

cd src/test

Download the Steamworks SDK from Valve and save it to the folder. Unzip and install the headers and libraries to the locations where the test program's Makefile expects them.

mkdir -p lib/steam/include lib/steam/lib
unzip steamworks_sdk_160.zip
cp -r sdk/public/steam lib/steam/include
cp -r sdk/redistributable_bin lib/steam/

The installation steps can also be run automatically using the Steam API install script after the SDK is downloaded.

Then compile the program with the STEAM parameter enabled. The Makefile will define the __STEAM__ macro when STEAM is set to yes, enabling the Steamworks specific calls to be compiled into the project.

make STEAM=yes SPACEBOX_test-linux.x64

Steam API with MinGW

The DLL provided by Valve in the Steamworks SDK is compiled with MSVC. Although it is possible to use that DLL with MinGW to compile a Windows build, certain functions cannot be used because there are incompatibilities with Valve's DLL which will cause a segmentation fault. In order to support MinGW for SPACE🪐BOX projects, the framework uses the flat interface in steam_api_flat.h as much as possible because they do not have the same C++ function name-mangling issues that cause incompatibility between the DLL and MinGW.

For example, because SteamUser()->GetSteamID() causes a segmentation fault when compiled with MinGW, SteamAPI_ISteamUser_GetSteamID(SteamUser()) is used instead. The Steam API's standard C++ style of callback handling also cannot be used and requires a particular workaround which Valve calls manual dispatch. Callbacks are launched with the flat API and periodically checked manually by running a loop and dispatching received callbacks and results according the IDs and data identified by the loop.

The code that does this is in src/cloud.cpp. The manual dispatch loop is in sb::cloud::steam::update, and an example callback is launched in sb::cloud::steam::count_players.

Since this is a less than ideal, and somewhat risky workaround, another reasonable option for using the Steam API is to leave this extension out, and instead follow Valve's documentation for using the library, and compile the project using MSVC. In fact, Godot's Steam extension currently recommends against using MinGW.

Some related links:

Steam API with MacOS

The library can be linked with -Lpath/to/steam/lib/osx/ -lsteam_api. The library file libsteam_api.dylib load path is set to @loader_path/ so if it is copied into the app bundle to the same location as the executable, the app bundle will be able to load it. If testing the Steam API, steam_appid.txt should be in resources, which is the directory SDL uses as the base path.

cp path/to/steam/lib/osx/libsteam_api.dylib path/to/bundle/MacOS/
cp steam_appid.txt path/to/bundle/Resources

OpenCV

Download a source package and follow the specific instructions for each platform.

Linux

Create a build directory, then configure and make. This example uses a custom installation path ~/local/opencv:

$ mkdir build_linux/ && cd build_linux/
$ cmake -DCMAKE_INSTALL_PREFIX=$HOME/local/opencv ..
$ make && make install

The core, imgproc, videoio, and highgui modules can then be linked to by adding the following to the linker flags:

-L$(HOME)/local/opencv/lib -Wl,-rpath,$(HOME)/local/opencv/lib -lopencv_videoio -lopencv_core -lopencv_highgui \
    -lopencv_imgproc

The specific modules needed may vary depending on the project. See detailed instructions for building OpenCV on Linux for more advanced installation. There are also contributed modules available, which can be downloaded separately and compiled with the core modules. For example, this command builds all the modules in ../../opencv_contrib-4.x/modules, except python3, along with the core modules.

$ cmake -D CMAKE_INSTALL_PREFIX=$HOME/local/opencv -D OPENCV_EXTRA_MODULES_PATH="../../opencv_contrib-4.x/modules" -D \ 
    BUILD_opencv_python3=OFF ..

Emscripten

To build the WASM libraries necessary to include OpenCV in an Emscripten build of a SPACEBOX project, set up the Emscripten environment and run OpenCV's build_js.py script with the WASM output option. Note that OpenCV's cv::VideoCapture object will not be available in the libraries built this way because OpenCV emulates that object in its JS implementation.

$ source [EMSDK_PATH]/emsdk_env.sh
$ python3 [OPENCV_PATH]/platforms/js/build_js.py --emscripten_dir [EMSCRIPTEN_PATH] build_wasm --build_wasm

There is a detailed explanation of this process at [https://docs.opencv.org/4.6.0/d4/da1/tutorial_js_setup.html]. And useful information for getting around the absence of cv::VideoCapture at [https://docs.opencv.org/4.6.0/dd/d00/tutorial_js_video_display.html]. Check out a minimal example of using OpenCV with C++ and Emscripten at [https://github.com/mpizenberg/emscripten-opencv]. There is also information on how to build the contributed modules as WASM libraries in the first link.

At the time of writing this, the contributed module for barcode scanning needs -DBUILD_PROTOBUF=ON added to the build_js.py script and needs js added to its list of wrapper languages in its CMakeLists.txt file to compile successfully.

To link to the WASM libraries, add the *.a files from the build directory to Emscripten's linker flags. Add both lib/*.a and 3rdparty/lib/*.a. For GNU Make, this might look like the following.

$(wildcard $(addprefix $(WASM_BUILD_DIR)/lib/,*.a)) $(wildcard $(addprefix $(WASM_BUILD_DIR)/3rdparty/lib/,*.a))

Android

Follow the steps at Building an SDL example for Linux up to the NDK installation steps, using 24.0.8215888 as the NDK version. This version or higher is necessary for using cv::VideoCapture.

$ ~/local/Android/cmdline-tools/bin/sdkmanager --sdk_root=$HOME/local/Android --install "ndk;24.0.8215888"

To build the OpenCV Android SDK, including shared object files, one for each Android ABI, run the build_sdk.py script packaged with the OpenCV source.

$ cd [opencv_source]
$ python3 platforms/android/build_sdk.py --sdk_path ~/local/Android/ --ndk_path ~/local/Android/ndk/24.0.8215888/ \ 
    --extra_modules_path ../opencv_contrib-4.7.0-subset/ --shared --no_samples_build \
    --config ndk-18-api-level-24.config.py build_android/
$ ls -R build_android/OpenCV-android-sdk/sdk/native/libs/
build_android/OpenCV-android-sdk/sdk/native/libs/:
arm64-v8a  armeabi-v7a  x86  x86_64

build_android/OpenCV-android-sdk/sdk/native/libs/arm64-v8a:
libopencv_world.so  libtbb.so

build_android/OpenCV-android-sdk/sdk/native/libs/armeabi-v7a:
libopencv_world.so  libtbb.so

build_android/OpenCV-android-sdk/sdk/native/libs/x86:
libopencv_world.so  libtbb.so

build_android/OpenCV-android-sdk/sdk/native/libs/x86_64:
libopencv_world.so  libtbb.so

This SDK can be included in an Android NDK project. See the camera demo for an example of how to add it to a SPACEBOX project build.

Full example

This builds the local, WASM, and Android libraries by downloading OpenCV 4.7.0 and the contributed modules source packages. See Gunkiss for an example of using these libraries in a project with multiple target platforms.

$ wget https://github.com/opencv/opencv/archive/4.7.0.zip
$ unzip 4.7.0.zip
$ tar -xf opencv_contrib-4.7.0.tar.gz
$ mkdir opencv_contrib-4.7.0-subset
$ cp -r opencv_contrib-4.7.0/modules/barcode/ opencv_contrib-4.7.0-subset/
$ rm -rf ~/local/opencv/
$ cd opencv-4.7.0/
$ mkdir build_linux
$ cd build_linux/
$ cmake -D CMAKE_INSTALL_PREFIX=$HOME/local/opencv -D OPENCV_EXTRA_MODULES_PATH="../../opencv_contrib-4.7.0-subset/" ..
...
--   OpenCV modules:
--     To be built: barcode calib3d core dnn features2d flann gapi highgui imgcodecs imgproc java ml objdetect
                    photo python3 stitching ts video videoio
...
$ make -j3 && make install
$ cd ..
$ source ~/ext/software/emsdk/emsdk_env.sh
$ python3 platforms/js/build_js.py --cmake_option="-DOPENCV_EXTRA_MODULES_PATH=../../opencv_contrib-4.7.0-subset/" \
    --emscripten_dir ~/ext/software/emsdk/upstream/emscripten build_wasm_contrib --build_wasm
$ python3 platforms/android/build_sdk.py --sdk_path ~/local/Android/ --ndk_path ~/local/Android/ndk/22.1.7171670/ \ 
    --extra_modules_path ../opencv_contrib-4.7.0-subset/ --shared --no_samples_build build_android/

curl

Linux

Install from the package manager

sudo apt install libcurl4-openssl-dev

Emscripten

Use Emscripten's Fetch API instead of curl

Android

There are instructions on how to build for Android in the curl documentation. There is also a project libcurl-android that facilitates the build process.

$ git clone --recursive https://github.com/ibaoger/libcurl-android

Add x86 to the APP_ABI parameter in build_for_android.sh. Then run the build script.

$ NDK_ROOT=/path/to/NDK ./build_for_android.sh

The libraries should be written the jni/build folder. The folder libs/x86-64 should actually be named libs/x86_64, so rename it.

$ mv jni/build/zlib/x86-64 jni/build/zlib/x86_64
$ mv jni/build/curl/x86-64 jni/build/curl/x86_64
$ mv jni/build/openssl/x86-64/ jni/build/openssl/x86_64

See how the OpenCV libraries are included in an Android NDK project in the camera demo for an example of how to use pre-built shared libraries.

ZBar

Linux

Download from http://zbar.sourceforge.net/download.html and configure to only use image processing features (requires the imagemagickwand library, available from, for example apt get libmagickwand-dev) and choose your installation directory:

./configure --without-gtk --without-python --without-qt --without-xshm --without-xv --without-jpeg \
    --disable-video --prefix=$HOME/local/zbar

make

make && make install

Emscripten

To build a WASM library that can be used to build an Emscripten version of a SPACEBOX project, set up the Emscripten environment and configure using emconfigure with the same disable flags as above.

$ source emsdk_env.sh
$ emconfigure ./configure --without-gtk --without-python --without-qt --without-xshm --without-xv --without-jpeg \
    --disable-video
$ emmake make
$ find . -iname *.a
./zbar/.libs/libzbar.a

There is a detailed tutorial on using Zbar with WebAssembly at https://barkeywolf.consulting/posts/barcode-scanner-webassembly/

External Resources

Font

When initializing a Game object, the framework will attempt to load the font file BPmono.ttf from the project root (where the compiled executable is located). If this file isn't found, the program can still run successfully, but the repository contains BPmono.ttf if you want to create a symlink to the file in the project root.

Releases

Release versions are available to checkout using tags. This project uses semantic versioning and is currently in version zero, the initial development phase, which means there are no guarantees for backward compatibility between releases.

0.1.1

  • Add function for panning sound by channel

0.1.0

  • Support for achievements and stats and for syncing them with Steam.
  • Added the sb::progress namespace for tracking persistent data - save files, user preferences, stats, achievements, etc.
  • Draw and update loops are now passed directly to Game::run and can optionally be lambda functions.
  • Added mock Game object and mock game runs to the test suite.
  • Log messages can be written with the stream operator <<.
  • Added a CLI, which supports passing configuration JSON overrides from the console at game launch.
  • Added the Catch2 unit testing framework and created a test program with support for multiple platforms.

0.0.1

  • Disable logging by default
  • Configure and enable/disable stdout and file logs separately
  • Prevent GL error checking by default and add functions for allowing/disallowing

0.0.0

  • Started tracking versioning using Git tags and a version string generated by Git

CI

SPACE🪐BOX doesn't have any CI features other than the tests, which are not automated. In the future, Gitea runners and/or git hooks may be used to implement merge protection.

License

SPACE🪐BOX is released under the zlib license. It is free to use, copy, and modify. Commercial use is permitted. See LICENSE.txt for details.

Included libraries are included under various permissive licenses compatible with the zlib license:

Library License
BPmono.ttf LICENSE_BPmono.txt
gif-h lib/gif-h/LICENSE
GLEW lib/glew/LICENSE.txt
GLM lib/glm/LICENSE
nlohmann::json lib/json/LICENSE.MIT
SDL2-gfx lib/sdl2-gfx/LICENSE
superxbr.cpp lib/superxbr.cpp
CLI11 lib/cli11/LICENSE
Catch2 lib/catch2/LICENSE.txt

Contact

Method Contact information
E-mail cocktail.frank at dank.game
Web https://dank.game
X https://x.com/diskmem
PayPal https://paypal.me/ohsqueezy