shithub: candycrisis

Download patch

ref: ceb09f7ffd02027f1f1be6242b9190f15398bcc2
parent: dbf642749d4890bced0a237d5c512ff0f8083b51
author: Iliyas Jorio <iliyas@jor.io>
date: Wed Feb 2 17:21:35 EST 2022

Cross-platform CI

diff: cannot open b/.github/workflows//null: file does not exist: 'b/.github/workflows//null' diff: cannot open b/.github//null: file does not exist: 'b/.github//null' diff: cannot open b/cmake//null: file does not exist: 'b/cmake//null'
--- /dev/null
+++ b/.github/workflows/CompileCheck.yml
@@ -1,0 +1,32 @@
+name: Full Compile Check
+
+on: [workflow_dispatch, push]
+
+jobs:
+  full-compile-check:
+    name: ${{ matrix.platform.name }}
+    runs-on: ${{ matrix.platform.os }}
+
+    strategy:
+      fail-fast: false
+      matrix:
+        platform:
+          - { name: Linux/GCC (ancient), os: ubuntu-18.04, extraflags: "--system-sdl" }
+          - { name: Linux/GCC, os: ubuntu-latest, extraflags: "--system-sdl" }
+          - { name: Linux/Clang, os: ubuntu-latest, extraflags: "--system-sdl" }
+          - { name: Windows/VS2022, os: windows-2022, extraflags: "-G 'Visual Studio 17 2022'" }
+          - { name: Windows/VS2019, os: windows-2019, extraflags: "-G 'Visual Studio 16 2019'" }
+          - { name: Mac, os: macos-11 }
+
+    steps:
+      - name: Install Linux build dependencies
+        if: runner.os == 'Linux'
+        run: |
+          sudo apt update
+          sudo apt install libsdl2-dev
+
+      - uses: actions/checkout@v2
+        with:
+          submodules: 'recursive'
+          
+      - run: python3 build.py --dependencies --configure --build ${{ matrix.platform.extraflags }}
--- /dev/null
+++ b/.github/workflows/ReleaseBuilds.yml
@@ -1,0 +1,80 @@
+name: Make Release Builds
+
+on: [workflow_dispatch]
+
+jobs:
+  build-linux-appimage:
+    runs-on: ubuntu-18.04
+    steps:
+      - uses: actions/checkout@v2  # Checks out repository under $GITHUB_WORKSPACE so the job can access it
+        with:
+          submodules: 'recursive'
+      - name: Get artifact name
+        run: |
+          echo "GAME_ARTIFACT=$(python3 build.py --print-artifact-name)" >> $GITHUB_ENV
+          echo $GAME_ARTIFACT
+      - name: Get build dependencies for SDL from APT  # cf. https://github.com/libsdl-org/SDL/blob/main/docs/README-linux.md
+        run: |
+          sudo apt update
+          sudo apt install libasound2-dev libpulse-dev libaudio-dev libjack-dev libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxi-dev libxinerama-dev libxxf86vm-dev libxss-dev libgl1-mesa-dev libdbus-1-dev libudev-dev libgles2-mesa-dev libegl1-mesa-dev libibus-1.0-dev fcitx-libs-dev libsamplerate0-dev libsndio-dev libwayland-dev libxkbcommon-dev libdrm-dev libgbm-dev
+      - run: python3 build.py --dependencies
+      - run: python3 build.py --configure
+      - run: python3 build.py --build
+      - run: python3 build.py --package
+      - uses: actions/upload-artifact@v2
+        with:
+          name: ${{ env.GAME_ARTIFACT }}
+          path: dist/${{ env.GAME_ARTIFACT }}
+
+  build-windows:
+    runs-on: windows-2022
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          submodules: 'recursive'
+      - name: Get artifact name
+        run: |
+          echo ("GAME_ARTIFACT=" + (python3 build.py --print-artifact-name)) >> $env:GITHUB_ENV
+          echo $env:GAME_ARTIFACT
+      - run: python3 build.py --dependencies
+      - run: python3 build.py --configure -G 'Visual Studio 17 2022'
+      - run: python3 build.py --build
+      - run: python3 build.py --package
+      - uses: actions/upload-artifact@v2
+        with:
+          name: ${{ env.GAME_ARTIFACT }}
+          path: dist/${{ env.GAME_ARTIFACT }}
+
+  build-macos:
+    runs-on: macos-11
+    env:
+      CODE_SIGN_IDENTITY: ${{ secrets.APPLE_CODE_SIGN_IDENTITY }}
+    steps:
+      - uses: apple-actions/import-codesign-certs@v1
+        with:
+          p12-file-base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }}
+          p12-password: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }}
+      - name: Install gon for notarization
+        run: |
+          brew tap mitchellh/gon
+          brew install mitchellh/gon/gon
+      - uses: actions/checkout@v2
+        with:
+          submodules: 'recursive'
+      - name: Get artifact name
+        run: |
+          echo "GAME_ARTIFACT=$(python3 build.py --print-artifact-name)" >> $GITHUB_ENV
+          echo $GAME_ARTIFACT
+      - run: python3 build.py --dependencies
+      - run: python3 build.py --configure
+      - run: python3 build.py --build
+      - run: python3 build.py --package
+      - name: Notarize
+        run: gon packaging/gon-config.json
+        env:
+          AC_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
+          AC_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
+      - uses: actions/upload-artifact@v2
+        with:
+          name: ${{ env.GAME_ARTIFACT }}
+          path: dist/${{ env.GAME_ARTIFACT }}
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,5 @@
-cmake-build-*
-
+build-*/
+cache/
+cmake-build-*/
+dist/
+extern/
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,13 +1,235 @@
-cmake_minimum_required(VERSION 3.16)
-project(CandyCrisis)
+cmake_minimum_required(VERSION 3.13)
 
-set(CMAKE_CXX_STANDARD 17)
+set(GAME_TARGET				"CandyCrisis")
+set(GAME_VERSION			"3.0.0")
+set(GAME_MAC_ICNS			"packaging/${GAME_TARGET}.icns")
+set(GAME_MAC_COPYRIGHT		"https://github.com/jorio/candycrisis")
+set(GAME_MAC_BUNDLE_ID		"io.jor.candycrisis")
 
-find_package(SDL2 REQUIRED)
-include_directories(${SDL2_INCLUDE_DIRS})
+set(CMAKE_MODULE_PATH		"${CMAKE_SOURCE_DIR}/cmake")
+set(CMAKE_C_STANDARD		17)
+set(CMAKE_CXX_STANDARD		17)
 
-file(GLOB_RECURSE GAME_SOURCES CONFIGURE_DEPENDS src/*.cpp src/*.c src/*.h)
+set(CMAKE_OSX_DEPLOYMENT_TARGET "10.11" CACHE STRING "Minimum OS X deployment version")
+set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "Target macOS architectures")
 
-add_executable(CandyCrisis ${GAME_SOURCES})
+project(${GAME_TARGET}
+	VERSION ${GAME_VERSION}
+	LANGUAGES C CXX
+)
 
-target_link_libraries(CandyCrisis ${SDL2_LIBRARIES} ${SDL2_IMAGE_LIBRARIES} SDL2)
+#------------------------------------------------------------------------------
+# GLOBAL OPTIONS (BEFORE ADDING SUBDIRECTORIES)
+#------------------------------------------------------------------------------
+
+if(MSVC)
+	add_compile_definitions(UNICODE _UNICODE)
+
+	set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${GAME_TARGET})
+endif()
+
+#------------------------------------------------------------------------------
+# DEPENDENCIES
+#------------------------------------------------------------------------------
+
+find_package(SDL2 REQUIRED COMPONENTS main)
+
+#------------------------------------------------------------------------------
+# GAME SOURCES
+#------------------------------------------------------------------------------
+
+set(GAME_SRCDIR ${CMAKE_CURRENT_SOURCE_DIR}/src)
+
+# Write header file containing version info
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.h.in ${GAME_SRCDIR}/version.h)
+
+file(GLOB_RECURSE GAME_SOURCES CONFIGURE_DEPENDS ${GAME_SRCDIR}/*.c ${GAME_SRCDIR}/*.cpp)
+
+file(GLOB_RECURSE GAME_HEADERS CONFIGURE_DEPENDS ${GAME_SRCDIR}/*.h)
+
+set(GAME_ALL_SOURCES
+	${GAME_SOURCES}
+	${GAME_HEADERS}
+)
+
+#------------------------------------------------------------------------------
+# SOURCE GROUPS
+#------------------------------------------------------------------------------
+
+source_group(TREE ${GAME_SRCDIR} PREFIX "" FILES ${GAME_ALL_SOURCES})
+
+#------------------------------------------------------------------------------
+# EXECUTABLE TARGET
+#------------------------------------------------------------------------------
+
+set(GAME_LIBRARIES ${SDL2_LIBRARIES})
+
+if(WIN32)
+	# "WIN32" here is equivalent to /SUBSYSTEM:WINDOWS for MSVC
+	add_executable(${GAME_TARGET} WIN32
+		${GAME_ALL_SOURCES}
+		"${CMAKE_CURRENT_SOURCE_DIR}/packaging/${GAME_TARGET}.exe.rc"
+	)
+elseif(APPLE)
+	add_executable(${GAME_TARGET} MACOSX_BUNDLE
+		${GAME_ALL_SOURCES}
+		${GAME_MAC_ICNS}
+		${SDL2_LIBRARIES}
+	)
+else()
+	# Math lib, explicitly required on some Linux systems
+	list(APPEND GAME_LIBRARIES m)
+
+	add_executable(${GAME_TARGET} ${GAME_ALL_SOURCES})
+endif()
+
+target_include_directories(${GAME_TARGET} PRIVATE
+	${SDL2_INCLUDE_DIRS}
+	${GAME_SRCDIR}
+)
+
+target_link_libraries(${GAME_TARGET} ${GAME_LIBRARIES})
+
+#------------------------------------------------------------------------------
+# DEFINES
+#------------------------------------------------------------------------------
+
+add_compile_definitions(
+	"$<$<CONFIG:DEBUG>:_DEBUG>"
+)
+
+if(WIN32)
+	target_compile_definitions(${GAME_TARGET} PRIVATE
+		WIN32_LEAN_AND_MEAN
+		_CRT_SECURE_NO_WARNINGS  # quit whining about snprintf_s
+	)
+endif()
+
+#------------------------------------------------------------------------------
+# COMPILER OPTIONS
+#------------------------------------------------------------------------------
+
+if(NOT MSVC)
+	target_compile_options(${GAME_TARGET} PRIVATE
+		-Wall
+		#-Wextra  # TODO
+		#-Wshadow  # TODO
+		-Wno-sign-compare  # TODO
+		-Wno-multichar
+		-Wno-unknown-pragmas
+		-Werror=return-type
+	)
+else()
+	# By default, MSVC may add /EHsc to CMAKE_CXX_FLAGS, which we don't want (we use /EHs below)
+	string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
+
+	# By default, MSVC may add /W3 to CMAKE_CXX_FLAGS, which we don't want (we use /W4 below)
+	# Note that this is not required with "cmake_minimum_required(VERSION 3.15)" or later
+	string(REPLACE "/W3" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
+	string(REPLACE "/W3" "" CMAKE_C_FLAGS ${CMAKE_C_FLAGS})
+
+	target_compile_options(${GAME_TARGET} PRIVATE
+		/EHs	# synchronous exceptions; also, extern "C" functions may throw exceptions
+		/W4
+		/wd4068 # ignore unrecognized pragmas
+		/wd4100 # unreferenced formal parameters
+		/wd4201 # nonstandard extension (nameless struct)
+		/wd4244 # conversion from double to float
+		/wd4305 # truncation from double to float
+		/wd5105 # see https://developercommunity.visualstudio.com/t/1249671
+	)
+endif()
+
+#------------------------------------------------------------------------------
+# PLATFORM-SPECIFIC PACKAGING
+#------------------------------------------------------------------------------
+
+set_target_properties(${GAME_TARGET} PROPERTIES
+	#--------------------------------------------------------------------------
+	# MSVC/WIN32
+	#--------------------------------------------------------------------------
+
+	VS_DEBUGGER_WORKING_DIRECTORY		"${CMAKE_SOURCE_DIR}"
+	VS_DPI_AWARE						"PerMonitor"
+
+	#--------------------------------------------------------------------------
+	# APPLE
+	#--------------------------------------------------------------------------
+
+	# Set framework search path to (App bundle)/Contents/Frameworks so the game can use its embedded SDL2.framework
+	XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../Frameworks"
+
+	# Explicitly turn off code signing, otherwise downloaded app will be quarantined forever
+	XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY ""
+
+	# Set up Info.plist values
+	MACOSX_BUNDLE_ICON_FILE				"${GAME_TARGET}.icns"	# CFBundleIconFile
+	MACOSX_BUNDLE_EXECUTABLE_NAME		${GAME_TARGET}			# CFBundleExecutable - executable name inside the bundle
+	MACOSX_BUNDLE_SHORT_VERSION_STRING	${PROJECT_VERSION}		# CFBundleShortVersionString
+	MACOSX_BUNDLE_COPYRIGHT				${GAME_MAC_COPYRIGHT}	# NSHumanReadableCopyright (supersedes CFBundleGetInfoString (MACOSX_BUNDLE_INFO_STRING))
+	MACOSX_BUNDLE_BUNDLE_NAME			${GAME_TARGET}			# CFBundleName - user-visible (where??) short name for the bundle, up to 15 characters
+	MACOSX_BUNDLE_GUI_IDENTIFIER		${GAME_MAC_BUNDLE_ID}	# CFBundleIdentifier - unique bundle ID in reverse-DNS format
+
+	# Bundle ID required for code signing - must match CFBundleIdentifier otherwise xcode will complain
+	XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER ${GAME_MAC_BUNDLE_ID}
+
+	# Don't bother with universal builds when we're working on the debug version
+	XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH[variant=Debug] "YES"
+
+	XCODE_EMBED_FRAMEWORKS							"${SDL2_LIBRARIES}"
+	XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY		"YES"		# frameworks must be signed by the same developer as the binary
+	XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY	"YES"		# not strictly necessary, but that's cleaner
+	XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME			"YES"		# required for notarization to pass
+)
+
+if(APPLE)
+	# If we have a code signing identity (CODE_SIGN_IDENTITY environment variable),
+	# set up the release build for proper code signing
+	if(NOT "$ENV{CODE_SIGN_IDENTITY}" STREQUAL "")
+		set_target_properties(${GAME_TARGET} PROPERTIES
+			XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Release] "$ENV{CODE_SIGN_IDENTITY}"
+			XCODE_ATTRIBUTE_DEVELOPMENT_TEAM[variant=Release] "$ENV{CODE_SIGN_IDENTITY}"
+
+			# The following is to pass notarization requirements
+			XCODE_ATTRIBUTE_CODE_SIGN_INJECT_BASE_ENTITLEMENTS[variant=Release] "NO"
+			XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS[variant=Release] "--options=runtime --timestamp"
+		)
+		message("Release build will be code signed!")
+	endif()
+
+	# Copy stuff to app bundle contents
+	set_source_files_properties(${GAME_MAC_ICNS} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
+	#set_source_files_properties(${SDL2_LIBRARIES} PROPERTIES MACOSX_PACKAGE_LOCATION "Frameworks")
+
+	set(BUNDLE_CONTENTS_DIR "$<TARGET_FILE_DIR:${PROJECT_NAME}>/..")
+	set(APP_PARENT_DIR "${BUNDLE_CONTENTS_DIR}/../..")
+
+	add_custom_command(TARGET ${GAME_TARGET} POST_BUILD
+		# Copy assets to app bundle
+		COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/CandyCrisisResources ${BUNDLE_CONTENTS_DIR}/Resources
+
+		# High-DPI support in Info.plist
+#		plutil -replace NSHighResolutionCapable -bool true ${BUNDLE_CONTENTS_DIR}/Info.plist
+	)
+else()
+	set(APP_PARENT_DIR "$<TARGET_FILE_DIR:${GAME_TARGET}>")
+
+	# Copy assets besides executable
+	add_custom_command(TARGET ${GAME_TARGET} POST_BUILD
+		COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/CandyCrisisResources ${APP_PARENT_DIR}/CandyCrisisResources)
+endif()
+
+# Windows-specific libraries
+if(WIN32)
+	# Copy SDL2 DLLs to output folder on Windows for convenience
+	foreach(DLL ${SDL2_DLLS})
+		add_custom_command(TARGET ${GAME_TARGET} POST_BUILD
+			COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DLL} ${APP_PARENT_DIR})
+	endforeach()
+
+	# When installing (cmake --install), copy Visual Studio redistributable DLLs to install location
+	include(InstallRequiredSystemLibraries)
+endif()
+
+# Copy documentation to output folder
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/packaging/ReadMe.txt.in ${CMAKE_CURRENT_BINARY_DIR}/ReadMe.txt)
--- /dev/null
+++ b/build.py
@@ -1,0 +1,454 @@
+#!/usr/bin/env python3
+
+import argparse
+import contextlib
+import hashlib
+import glob
+import multiprocessing
+import os
+import os.path
+import platform
+import shutil
+import stat
+import subprocess
+import sys
+import tempfile
+import urllib.request
+import zipfile
+from pathlib import Path
+
+#----------------------------------------------------------------
+
+libs_dir = os.path.abspath("extern")
+cache_dir = os.path.abspath("cache")
+dist_dir = os.path.abspath("dist")
+
+game_name           = "CandyCrisis"  # no spaces
+game_name_human     = "Candy Crisis"  # spaces and other special characters allowed
+game_ver            = "3.0.0"
+
+sdl_ver             = "2.0.20"
+appimagetool_ver    = "13"
+
+lib_hashes = {  # sha-256
+    "SDL2-2.0.20.tar.gz":           "c56aba1d7b5b0e7e999e4a7698c70b63a3394ff9704b5f6e1c57e0c16f04dd06",
+    "SDL2-2.0.20.dmg":              "e46a3694f5008c4c5ffd33e1dfdffbee64179ad15088781f2f70806dd0102d4d",
+    "SDL2-devel-2.0.20-VC.zip":     "5b1512ca6c9d2427bd2147da01e5e954241f8231df12f54a7074dccde416df18",
+    "appimagetool-x86_64.AppImage": "df3baf5ca5facbecfc2f3fa6713c29ab9cefa8fd8c1eac5d283b79cab33e4acb", # appimagetool v13
+}
+
+NPROC = multiprocessing.cpu_count()
+SYSTEM = platform.system()
+
+if SYSTEM == "Windows":
+    os.system("")  # hack to get ANSI color escapes to work
+
+#----------------------------------------------------------------
+
+parser = argparse.ArgumentParser(description=F"Configure, build, and package {game_name_human}")
+
+if SYSTEM == "Darwin":
+    default_generator = "Xcode"
+    default_architecture = None
+    help_configure = "generate Xcode project"
+    help_build = "build app from Xcode project"
+    help_package = "package up the game into a DMG"
+elif SYSTEM == "Windows":
+    default_generator = "Visual Studio 17 2022"
+    default_architecture = "x64"
+    help_configure = F"generate {default_generator} solution"
+    help_build = F"build exe from {default_generator} solution"
+    help_package = "package up the game into a ZIP"
+else:
+    default_generator = None
+    default_architecture = None
+    help_configure = "generate project"
+    help_build = "build binary"
+    help_package = "package up the game into an AppImage"
+
+parser.add_argument("--dependencies", default=False, action="store_true", help="fetch and set up dependencies (SDL)")
+parser.add_argument("--configure", default=False, action="store_true", help=help_configure)
+parser.add_argument("--build", default=False, action="store_true", help=help_build)
+parser.add_argument("--package", default=False, action="store_true", help=help_package)
+
+parser.add_argument("-G", metavar="<generator>", default=default_generator,
+        help=F"custom project generator for the CMake configure step (default: {default_generator})")
+
+parser.add_argument("-A", metavar="<arch>", default=default_architecture,
+        help=F"custom platform name for the CMake configure step (default: {default_architecture})")
+
+parser.add_argument("--print-artifact-name", default=False, action="store_true",
+        help="print artifact name and exit")
+
+if SYSTEM == "Linux":
+    parser.add_argument("--system-sdl", default=False, action="store_true",
+        help="use system SDL instead of building SDL from scratch")
+
+args = parser.parse_args()
+
+#----------------------------------------------------------------
+
+class Project:
+    def __init__(self, dir_name, gen_args=[], gen_env={}, build_configs=[], build_args=[]):
+        self.dir_name = dir_name
+        self.gen_args = gen_args
+        self.gen_env = gen_env
+        self.build_configs = build_configs
+        self.build_args = build_args
+
+#----------------------------------------------------------------
+
+@contextlib.contextmanager
+def chdir(path):
+    origin = os.getcwd()
+    try:
+        os.chdir(path)
+        yield
+    finally:
+        os.chdir(origin)
+
+def die(message):
+    print(F"\x1b[1;31m{message}\x1b[0m", file=sys.stderr)
+    sys.exit(1)
+
+def log(message):
+    print(message, file=sys.stderr)
+
+def fatlog(message):
+    starbar = len(message) * '*'
+    print(F"\n{starbar}\n{message}\n{starbar}", file=sys.stderr)
+
+def hash_file(path):
+    hasher = hashlib.sha256()
+    with open(path, 'rb') as f:
+        while True:
+            chunk = f.read(64*1024)
+            if not chunk:
+                break
+            hasher.update(chunk)
+    return hasher.hexdigest()
+
+def get_package(url):
+    name = url[url.rfind('/')+1:]
+
+    if name in lib_hashes:
+        reference_hash = lib_hashes[name]
+    else:
+        die(F"Build script lacks reference checksum for {name}")
+
+    path = os.path.normpath(F"{cache_dir}/{name}")
+    if os.path.exists(path):
+        log(F"Not redownloading: {path}")
+    else:
+        log(F"Downloading: {url}")
+        os.makedirs(cache_dir, exist_ok=True)
+        urllib.request.urlretrieve(url, path)
+
+    actual_hash = hash_file(path)
+    if reference_hash != actual_hash:
+        die(F"Bad checksum for {name}: expected {reference_hash}, got {actual_hash}")
+
+    return path
+
+def call(cmd, **kwargs):
+    cmdstr = ""
+    for token in cmd:
+        cmdstr += " "
+        if " " in token:
+            cmdstr += F"\"{token}\""
+        else:
+            cmdstr += token
+
+    log(F">{cmdstr}")
+    try:
+        return subprocess.run(cmd, check=True, **kwargs)
+    except subprocess.CalledProcessError as e:
+        die(F"Aborting setup because: {e}")
+
+def rmtree_if_exists(path):
+    if os.path.exists(path):
+        shutil.rmtree(path)
+
+def zipdir(zipname, topleveldir, arc_topleveldir):
+    with zipfile.ZipFile(zipname, 'w', zipfile.ZIP_DEFLATED, compresslevel=9) as zipf:
+        for root, dirs, files in os.walk(topleveldir):
+            for file in files:
+                filepath = os.path.join(root, file)
+                arcpath = os.path.join(arc_topleveldir, filepath[len(topleveldir)+1:])
+                log(F"Zipping: {filepath} --> {arcpath}")
+                zipf.write(filepath, arcpath)
+
+#----------------------------------------------------------------
+
+def prepare_dependencies_windows():
+    rmtree_if_exists(F"{libs_dir}/SDL2-{sdl_ver}")
+
+    sdl_zip_path = get_package(F"http://libsdl.org/release/SDL2-devel-{sdl_ver}-VC.zip")
+    shutil.unpack_archive(sdl_zip_path, libs_dir)
+
+def prepare_dependencies_macos():
+    sdl2_framework = "SDL2.framework"
+    sdl2_framework_target_path = F"{libs_dir}/{sdl2_framework}"
+
+    rmtree_if_exists(sdl2_framework_target_path)
+
+    sdl_dmg_path = get_package(F"http://libsdl.org/release/SDL2-{sdl_ver}.dmg")
+
+    # Mount the DMG and copy SDL2.framework to extern/
+    with tempfile.TemporaryDirectory() as mount_point:
+        call(["hdiutil", "attach", sdl_dmg_path, "-mountpoint", mount_point, "-quiet"])
+        shutil.copytree(F"{mount_point}/{sdl2_framework}", sdl2_framework_target_path, symlinks=True)
+        call(["hdiutil", "detach", mount_point, "-quiet"])
+
+    if "CODE_SIGN_IDENTITY" in os.environ:
+        call(["codesign", "--force", "--timestamp", "--sign", os.environ["CODE_SIGN_IDENTITY"], sdl2_framework_target_path])
+    else:
+        print("SDL will not be codesigned. Set the CODE_SIGN_IDENTITY environment variable if you want to sign it.")
+
+def prepare_dependencies_linux():
+    if not args.system_sdl:
+        sdl_source_dir = F"{libs_dir}/SDL2-{sdl_ver}"
+        sdl_build_dir = F"{sdl_source_dir}/build"
+        rmtree_if_exists(sdl_source_dir)
+
+        sdl_zip_path = get_package(F"http://libsdl.org/release/SDL2-{sdl_ver}.tar.gz")
+        shutil.unpack_archive(sdl_zip_path, libs_dir)
+
+        with chdir(sdl_source_dir):
+            call([F"{sdl_source_dir}/configure", F"--prefix={sdl_build_dir}", "--quiet"])
+            call(["make", "-j", str(NPROC)], stdout=subprocess.DEVNULL)
+            call(["make", "install", "--silent"])  # install to configured prefix (sdl_build_dir)
+
+#----------------------------------------------------------------
+
+def get_artifact_name():
+    if SYSTEM == "Windows":
+        return F"{game_name}-{game_ver}-windows-x64.zip"
+    elif SYSTEM == "Darwin":
+        return F"{game_name}-{game_ver}-mac.dmg"
+    elif SYSTEM == "Linux":
+        return F"{game_name}-{game_ver}-linux-x86_64.AppImage"
+    else:
+        die("Unknown system for print_artifact_name")
+
+def copy_documentation(proj, appdir, full=True):
+    #shutil.copy(F"{proj.dir_name}/ReadMe.txt", F"{appdir}")
+    #shutil.copy(F"LICENSE.md", F"{appdir}/License.txt")
+
+    if full:
+        pass
+        #shutil.copytree("docs", F"{appdir}/Documentation")
+        # os.remove(F"{appdir}/Documentation/logo.png")
+        #os.remove(F"{appdir}/Documentation/screenshot.png")
+        #os.remove(F"{appdir}/Documentation/screenshot2.png")
+        #for docfile in ["CHANGELOG.md"]:
+        #    shutil.copy(docfile, F"{appdir}/Documentation")
+
+def package_windows(proj):
+    windows_dlls = ["SDL2.dll", "msvcp140.dll", "vcruntime140.dll", "vcruntime140_1.dll"]  # C++
+
+    # Prep DLLs with cmake (copied to {cache_dir}/install/bin)
+    call(["cmake", "--install", proj.dir_name, "--prefix", F"{cache_dir}/install"])
+
+    appdir = F"{cache_dir}/{game_name}-{game_ver}"
+    rmtree_if_exists(appdir)
+    os.makedirs(F"{appdir}", exist_ok=True)
+
+    # Copy executable, libs and assets
+    for dll in windows_dlls:
+        shutil.copy(F"{cache_dir}/install/bin/{dll}", appdir)
+    shutil.copy(F"{proj.dir_name}/Release/{game_name}.exe", appdir)
+    shutil.copytree("CandyCrisisResources", F"{appdir}/CandyCrisisResources")
+
+    copy_documentation(proj, appdir)
+
+    zipdir(F"{dist_dir}/{get_artifact_name()}", appdir, F"{game_name}-{game_ver}")
+
+def package_macos(proj):
+    appdir = F"{proj.dir_name}/Release"
+
+    # Human-friendly name for .app
+    os.rename(F"{appdir}/{game_name}.app", F"{appdir}/{game_name_human}.app")
+
+    copy_documentation(proj, appdir)
+
+    #shutil.copy("packaging/dmg_DS_Store", F"{appdir}/.DS_Store")
+
+    call(["hdiutil", "create",
+        "-fs", "HFS+",
+        "-srcfolder", appdir,
+        "-volname", F"{game_name_human} {game_ver}",
+        F"{dist_dir}/{get_artifact_name()}"])
+
+def package_linux(proj):
+    appimagetool_path = get_package(F"https://github.com/AppImage/AppImageKit/releases/download/{appimagetool_ver}/appimagetool-x86_64.AppImage")
+    os.chmod(appimagetool_path, 0o755)
+
+    appdir = F"{cache_dir}/{game_name}-{game_ver}.AppDir"
+    rmtree_if_exists(appdir)
+
+    os.makedirs(F"{appdir}", exist_ok=True)
+    os.makedirs(F"{appdir}/usr/bin", exist_ok=True)
+    os.makedirs(F"{appdir}/usr/lib", exist_ok=True)
+
+    # Copy executable and assets
+    shutil.copy(F"{proj.dir_name}/{game_name}", F"{appdir}/usr/bin")  # executable
+    shutil.copytree("CandyCrisisResources", F"{appdir}/CandyCrisisResources")
+    copy_documentation(proj, appdir, full=False)
+
+    # Copy XDG stuff
+    shutil.copy(F"packaging/{game_name.lower()}.desktop", appdir)
+    shutil.copy(F"packaging/{game_name.lower()}-desktopicon.png", appdir)
+
+    # Copy AppImage kicker script
+    shutil.copy(F"packaging/AppRun", appdir)
+    os.chmod(F"{appdir}/AppRun", 0o755)
+
+    # Copy SDL (if not using system SDL)
+    if not args.system_sdl:
+        for file in glob.glob(F"{libs_dir}/SDL2-{sdl_ver}/build/lib/libSDL2*.so*"):
+            shutil.copy(file, F"{appdir}/usr/lib", follow_symlinks=False)
+
+    # Invoke appimagetool
+    call([appimagetool_path, "--no-appstream", appdir, F"{dist_dir}/{get_artifact_name()}"])
+
+#----------------------------------------------------------------
+
+if args.print_artifact_name:
+    print(get_artifact_name())
+    sys.exit(0)
+
+fatlog(F"{game_name} {game_ver} build script")
+
+if not (args.dependencies or args.configure or args.build or args.package):
+    log("No build steps specified, running all of them.")
+    args.dependencies = True
+    args.configure = True
+    args.build = True
+    args.package = True
+
+# Make sure we're running from the correct directory...
+if not os.path.exists("src/graymonitor.cpp"):  # some file that's likely to be from the game's source tree
+    die(F"STOP - Please run this script from the root of the {game_name} source repo")
+
+#----------------------------------------------------------------
+# Set up project metadata
+
+projects = []
+
+common_gen_args = []
+if args.G:
+    common_gen_args += ["-G", args.G]
+if args.A:
+    common_gen_args += ["-A", args.A]
+
+if SYSTEM == "Windows":
+
+    projects = [Project(
+        dir_name="build-msvc",
+        gen_args=common_gen_args,
+        build_configs=["Release", "Debug"],
+        build_args=["-m"]  # multiprocessor compilation
+    )]
+
+elif SYSTEM == "Darwin":
+    projects = [Project(
+        dir_name="build-xcode",
+        gen_args=common_gen_args,
+        build_configs=["Release"],
+        build_args=["-j", str(NPROC)]
+    )]
+
+elif SYSTEM == "Linux":
+    gen_env = {}
+    if not args.system_sdl:
+        gen_env["SDL2DIR"] = F"{libs_dir}/SDL2-{sdl_ver}/build"
+
+    projects.append(Project(
+        dir_name="build-relwithdebinfo",
+        gen_args=common_gen_args + ["-DCMAKE_BUILD_TYPE=RelWithDebInfo"],
+        gen_env=gen_env,
+        build_args=["-j", str(NPROC)]
+    ))
+
+    projects.append(Project(
+        dir_name="build-debug",
+        gen_args=common_gen_args + ["-DCMAKE_BUILD_TYPE=Debug"],
+        gen_env=gen_env,
+        build_args=["-j", str(NPROC)]
+    ))
+else:
+    die(F"Unsupported system for configure step: {SYSTEM}")
+
+
+#----------------------------------------------------------------
+# Prepare dependencies
+
+if args.dependencies:
+    fatlog("Setting up dependencies")
+
+    # Check that our submodules are here
+    #if not os.path.exists("extern/Pomme/CMakeLists.txt"):
+    #    die("Submodules appear to be missing.\n"
+    #        + "Did you clone the submodules recursively? Try this:    git submodule update --init --recursive")
+
+    if SYSTEM == "Windows":
+        prepare_dependencies_windows()
+    elif SYSTEM == "Darwin":
+        prepare_dependencies_macos()
+    elif SYSTEM == "Linux":
+        prepare_dependencies_linux()
+    else:
+        die(F"Unsupported system for dependencies step: {SYSTEM}")
+
+#----------------------------------------------------------------
+# Configure projects
+
+if args.configure:
+    for proj in projects:
+        fatlog(F"Configuring {proj.dir_name}")
+
+        rmtree_if_exists(proj.dir_name)
+
+        env = None
+        if proj.gen_env:
+            env = os.environ.copy()
+            env.update(proj.gen_env)
+
+        call(["cmake", "-S", ".", "-B", proj.dir_name] + proj.gen_args, env=env)
+
+#----------------------------------------------------------------
+# Build the game
+
+proj = projects[0]
+
+if args.build:
+    fatlog(F"Building the game: {proj.dir_name}")
+
+    build_command = ["cmake", "--build", proj.dir_name]
+
+    if proj.build_configs:
+        build_command += ["--config", proj.build_configs[0]]
+
+    if proj.build_args:
+        build_command += ["--"] + proj.build_args
+
+    call(build_command)
+
+#----------------------------------------------------------------
+# Package the game
+
+if args.package:
+    fatlog(F"Packaging the game")
+
+    rmtree_if_exists(dist_dir)
+    os.makedirs(dist_dir, exist_ok=True)
+
+    if SYSTEM == "Darwin":
+        package_macos(proj)
+    elif SYSTEM == "Windows":
+        package_windows(proj)
+    elif SYSTEM == "Linux":
+        package_linux(proj)
+    else:
+        die(F"Unsupported system for package step: {SYSTEM}")
--- /dev/null
+++ b/cmake/FindSDL2.cmake
@@ -1,0 +1,74 @@
+#------------------------------------------------------------------------------
+# Usage: find_package(SDL2 [REQUIRED] [COMPONENTS main])
+#
+# Sets variables:
+#     SDL2_INCLUDE_DIRS
+#     SDL2_LIBRARIES
+#     SDL2_DLLS (Windows only)
+#------------------------------------------------------------------------------
+
+include(FindPackageHandleStandardArgs)
+
+set(SDL2_VERSION 2.0.20)
+
+# Check if "main" was specified as a component
+set(_SDL2_use_main FALSE)
+foreach(_SDL2_component ${SDL2_FIND_COMPONENTS})
+    if(_SDL2_component STREQUAL "main")
+        set(_SDL2_use_main TRUE)
+    else()
+        message(WARNING "Unrecognized component \"${_SDL2_component}\"")
+    endif()
+endforeach()
+
+if(WIN32)
+    find_path(SDL2_ROOT "include/SDL.h"
+        PATHS "${CMAKE_SOURCE_DIR}/extern/SDL2-${SDL2_VERSION}"
+        NO_DEFAULT_PATH
+    )
+    
+    if(SDL2_ROOT)
+        set(SDL2_INCLUDE_DIRS "${SDL2_ROOT}/include")
+        set(_SDL2_ARCH "x64")
+        set(SDL2_LIBRARIES "${SDL2_ROOT}/lib/${_SDL2_ARCH}/SDL2.lib")
+        set(SDL2_DLLS "${SDL2_ROOT}/lib/${_SDL2_ARCH}/SDL2.dll")
+        if(_SDL2_use_main)
+            list(APPEND SDL2_LIBRARIES "${SDL2_ROOT}/lib/${_SDL2_ARCH}/SDL2main.lib")
+        endif()
+
+        # When installing, copy DLLs to install location
+        install(FILES ${SDL2_DLLS} DESTINATION bin)
+    endif()
+
+    mark_as_advanced(SDL2_ROOT)
+    find_package_handle_standard_args(SDL2 DEFAULT_MSG SDL2_INCLUDE_DIRS SDL2_LIBRARIES SDL2_DLLS)
+
+elseif(APPLE)
+    find_path(SDL2_INCLUDE_DIRS "SDL.h"
+        PATHS "${CMAKE_SOURCE_DIR}/extern/SDL2.framework/Versions/Current"
+        PATH_SUFFIXES "Headers"
+        REQUIRED
+        NO_DEFAULT_PATH
+    )
+
+    set(SDL2_LIBRARIES "${CMAKE_SOURCE_DIR}/extern/SDL2.framework")
+
+    find_package_handle_standard_args(SDL2 DEFAULT_MSG SDL2_INCLUDE_DIRS SDL2_LIBRARIES)
+
+else()
+    find_path(SDL2_INCLUDE_DIRS "SDL.h"
+        HINTS $ENV{SDL2DIR}
+        PATH_SUFFIXES "include/SDL2" "include"
+        REQUIRED
+    )
+
+    find_library(SDL2_LIBRARIES
+        NAMES "SDL2"
+        HINTS $ENV{SDL2DIR}
+        PATH_SUFFIXES lib64 lib
+        REQUIRED
+    )
+
+    find_package_handle_standard_args(SDL2 DEFAULT_MSG SDL2_INCLUDE_DIRS SDL2_LIBRARIES)
+
+endif()
--- /dev/null
+++ b/cmake/version.h.in
@@ -1,0 +1,7 @@
+#pragma once
+
+#define PROJECT_VERSION "@PROJECT_VERSION@"
+#define PROJECT_VERSION_MAJOR "@PROJECT_VERSION_MAJOR@"
+#define PROJECT_VERSION_MINOR "@PROJECT_VERSION_MINOR@"
+#define PROJECT_VERSION_PATCH "@PROJECT_VERSION_PATCH@"
+
--- /dev/null
+++ b/packaging/AppRun
@@ -1,0 +1,13 @@
+#!/bin/sh
+SELF=$(readlink -f "$0")
+HERE=${SELF%/*}
+export PATH="${HERE}/usr/bin/:${HERE}/usr/sbin/:${HERE}/usr/games/:${HERE}/bin/:${HERE}/sbin/${PATH:+:$PATH}"
+export LD_LIBRARY_PATH="${HERE}/usr/lib/:${HERE}/usr/lib/i386-linux-gnu/:${HERE}/usr/lib/x86_64-linux-gnu/:${HERE}/usr/lib32/:${HERE}/usr/lib64/:${HERE}/lib/:${HERE}/lib/i386-linux-gnu/:${HERE}/lib/x86_64-linux-gnu/:${HERE}/lib32/:${HERE}/lib64/${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
+export PYTHONPATH="${HERE}/usr/share/pyshared/${PYTHONPATH:+:$PYTHONPATH}"
+export XDG_DATA_DIRS="${HERE}/usr/share/${XDG_DATA_DIRS:+:$XDG_DATA_DIRS}"
+export PERLLIB="${HERE}/usr/share/perl5/:${HERE}/usr/lib/perl5/${PERLLIB:+:$PERLLIB}"
+export GSETTINGS_SCHEMA_DIR="${HERE}/usr/share/glib-2.0/schemas/${GSETTINGS_SCHEMA_DIR:+:$GSETTINGS_SCHEMA_DIR}"
+export QT_PLUGIN_PATH="${HERE}/usr/lib/qt4/plugins/:${HERE}/usr/lib/i386-linux-gnu/qt4/plugins/:${HERE}/usr/lib/x86_64-linux-gnu/qt4/plugins/:${HERE}/usr/lib32/qt4/plugins/:${HERE}/usr/lib64/qt4/plugins/:${HERE}/usr/lib/qt5/plugins/:${HERE}/usr/lib/i386-linux-gnu/qt5/plugins/:${HERE}/usr/lib/x86_64-linux-gnu/qt5/plugins/:${HERE}/usr/lib32/qt5/plugins/:${HERE}/usr/lib64/qt5/plugins/${QT_PLUGIN_PATH:+:$QT_PLUGIN_PATH}"
+EXEC=$(grep -e '^Exec=.*' "${HERE}"/*.desktop | head -n 1 | cut -d "=" -f 2 | cut -d " " -f 1)
+cd $HERE
+exec "${EXEC}" "$@"
--- /dev/null
+++ b/packaging/CandyCrisis.exe.rc
@@ -1,0 +1,1 @@
+IDI_ICON1 ICON DISCARDABLE "CandyCrisis.ico"
--- /dev/null
+++ b/packaging/ReadMe.txt.in
@@ -1,0 +1,2 @@
+Candy Crisis source port @PROJECT_VERSION@
+https://github.com/jorio/candycrisis
binary files /dev/null b/packaging/candycrisis-desktopicon.png differ
--- /dev/null
+++ b/packaging/candycrisis.desktop
@@ -1,0 +1,9 @@
+[Desktop Entry]
+Type=Application
+Version=1.0
+Name=Candy Crisis
+Comment=Candy Crisis
+Icon=candycrisis-desktopicon
+Exec=CandyCrisis
+Terminal=false
+Categories=Game;
--- /dev/null
+++ b/packaging/gon-config.json
@@ -1,0 +1,7 @@
+{
+    "notarize": [{
+        "path": "./dist/CandyCrisis-3.0.0-mac.dmg",
+        "bundle_id": "io.jor.candycrisis",
+        "staple": true
+    }]
+}
--- a/src/stdafx.h
+++ b/src/stdafx.h
@@ -4,15 +4,13 @@
 
 #pragma once
 
-#include <SDL2/SDL.h>
-#include <SDL2/SDL_endian.h>
+#include <SDL.h>
+#include <SDL_endian.h>
 
 #if _WIN32
 
 #define _CRT_SECURE_NO_WARNINGS 1
 
-#include "targetver.h"
-
 #define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
 // Windows Header Files:
 #include <windows.h>
@@ -22,8 +20,5 @@
 #include <malloc.h>
 #include <memory.h>
 #include <tchar.h>
-
-#include "SDL.h"
-#include "SDL_endian.h"
 
 #endif
--- /dev/null
+++ b/src/version.h
@@ -1,0 +1,7 @@
+#pragma once
+
+#define PROJECT_VERSION "3.0.0"
+#define PROJECT_VERSION_MAJOR "3"
+#define PROJECT_VERSION_MINOR "0"
+#define PROJECT_VERSION_PATCH "0"
+