diff --git a/CMakeLists.txt b/CMakeLists.txt index 27279b799f8..2c1422498fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,15 @@ endif() PROJECT(FS2_Open LANGUAGES ${FSO_LANGUAGES}) +if (ANDROID) + message(STATUS "Building for Android (NDK)") + add_definitions(-DPLATFORM_ANDROID) + set(FSO_BUILD_INCLUDED_LIBS ON FORCE) + set(USING_PREBUILT_LIBS ON FORCE) + set(SDL2_USE_PRECOMPILED ON FORCE) + set(OPENAL_USE_PRECOMPILED ON FORCE) +endif() + # Check if the external modules exists IF(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/cmake/external/rpavlik-cmake-modules/launcher-templates") message(FATAL_ERROR "External submodules could not be found. Please make sure you have updated your submodules.") @@ -96,8 +105,8 @@ OPTION(FSO_DEVELOPMENT_MODE "Generate binaries in development mode, only use if OPTION(FSO_BUILD_QTFRED "Build qtFRED2 binary" OFF) -IF(WIN32 OR APPLE) - # On windows and mac the default should be to always build the included libraries +IF(WIN32 OR APPLE OR ANDROID) + # On windows, mac and android the default should be to always build the included libraries SET(FSO_BUILD_INCLUDED_LIBS_DEFAULT ON) ELSE() SET(FSO_BUILD_INCLUDED_LIBS_DEFAULT OFF) diff --git a/code/cfile/cfile.cpp b/code/cfile/cfile.cpp index c344f1f8e07..c828c093f07 100644 --- a/code/cfile/cfile.cpp +++ b/code/cfile/cfile.cpp @@ -185,7 +185,12 @@ int cfile_init(const char *exe_dir, const char *cdrom_dir) char buf[CFILE_ROOT_DIRECTORY_LEN]; - strncpy(buf, exe_dir, CFILE_ROOT_DIRECTORY_LEN - 1); + #ifndef __ANDROID__ + strncpy(buf, exe_dir, CFILE_ROOT_DIRECTORY_LEN - 1); + #else + (void)exe_dir; + strncpy(buf, os_get_working_folder_path().c_str(), CFILE_ROOT_DIRECTORY_LEN - 1); + #endif buf[CFILE_ROOT_DIRECTORY_LEN - 1] = '\0'; diff --git a/code/ddsutils/ddsutils.cpp b/code/ddsutils/ddsutils.cpp index a843a874495..d8b467af78f 100644 --- a/code/ddsutils/ddsutils.cpp +++ b/code/ddsutils/ddsutils.cpp @@ -74,6 +74,28 @@ static bool conversion_needed(const DDS_HEADER &dds_header) return false; } +#ifdef __ANDROID__ +// Auto-determine max texture size based on avail device ram for software decompression +// 128 for <4GB, 256 for < 8GB, 512 for < 12GB and finally 1024 +static size_t calculate_resize_max_size() +{ + size_t size = 1024; + size_t ram_mib = SDL_GetSystemRAM(); + + if (ram_mib > 0) + { + if (ram_mib < 4 * 1024) // < 4GB + return 128; + if (ram_mib < 8 * 1024) // < 8GB + return 256; + if (ram_mib < 12 * 1024) // < 12GB + return 512; + } + + return size; +} +#endif + // Memory usage for uncompressed textures is quite high. Some MVP assets can // require well over a GB of VRAM for a single ship after conversion. To help // alleviate this we need to resize those textures where possible. At 1024x1024 @@ -85,7 +107,11 @@ static bool conversion_needed(const DDS_HEADER &dds_header) // returns: number of mipmap levels to skip static uint conversion_resize(DDS_HEADER &dds_header) { +#ifdef __ANDROID__ + const size_t MAX_SIZE = calculate_resize_max_size(); +#else const size_t MAX_SIZE = 1024; +#endif uint width, height, depth, offset = 0; if (dds_header.dwMipMapCount <= 1) { diff --git a/code/external_dll/externalcode.h b/code/external_dll/externalcode.h index eebdee4cae2..1b82f340199 100644 --- a/code/external_dll/externalcode.h +++ b/code/external_dll/externalcode.h @@ -29,6 +29,23 @@ class SCP_ExternalCode return FALSE; m_library = SDL_LoadObject(externlib); + +#ifdef __ANDROID__ + if (m_library == NULL) { + auto android_path = SDL_AndroidGetInternalStoragePath(); + if (android_path != nullptr) { + SCP_string android_full_path = android_path; + if (android_full_path.back() != DIR_SEPARATOR_CHAR) { + android_full_path += DIR_SEPARATOR_CHAR; + } + android_full_path += SCP_string("natives") + DIR_SEPARATOR_CHAR + externlib; + #ifndef NDEBUG + mprintf(("Calling SDL_LoadObject with the following path: '%s'\n", android_full_path.c_str())); + #endif + m_library = SDL_LoadObject(android_full_path.c_str()); + } + } +#endif #ifndef NDEBUG if (m_library == NULL) diff --git a/code/graphics/2d.cpp b/code/graphics/2d.cpp index 189227e8912..bda42a19e56 100644 --- a/code/graphics/2d.cpp +++ b/code/graphics/2d.cpp @@ -220,7 +220,11 @@ const auto LightingOption __UNUSED = options::OptionBuilder("Graphics.Light .parser(parse_lighting_func) .finish(); +#ifndef __ANDROID__ os::ViewportState Gr_configured_window_state = os::ViewportState::Fullscreen; +#else +os::ViewportState Gr_configured_window_state = os::ViewportState::Borderless; +#endif static bool mode_change_func(os::ViewportState state, bool initial) { diff --git a/code/graphics/opengl/es_compatibility.h b/code/graphics/opengl/es_compatibility.h index 6b78398cf0a..9ade09b44fb 100644 --- a/code/graphics/opengl/es_compatibility.h +++ b/code/graphics/opengl/es_compatibility.h @@ -353,6 +353,7 @@ static inline void glQueryCounter(GLuint id, GLenum target) #else (void)id; #endif + (void)target; } // glGetCompressedTexImage not present on GLES and no equivalent diff --git a/code/graphics/opengl/gropengl.cpp b/code/graphics/opengl/gropengl.cpp index 79252e7eec3..04191047a1c 100644 --- a/code/graphics/opengl/gropengl.cpp +++ b/code/graphics/opengl/gropengl.cpp @@ -6,7 +6,7 @@ #include #endif -#if !defined __APPLE_CC__ && defined SCP_UNIX +#if !defined __APPLE_CC__ && !defined __ANDROID__ && defined SCP_UNIX #include //Required because X defines none and always, which is used later #undef None @@ -1339,7 +1339,7 @@ bool gr_opengl_init(std::unique_ptr&& graphicsOps) Error(LOCATION, "Failed to load OpenGL!"); } -#if !defined __APPLE_CC__ && defined SCP_UNIX +#if !defined __APPLE_CC__ && !defined __ANDROID__ && defined SCP_UNIX if (!gladLoadGLXLoader(GL_context->getLoaderFunction(), nullptr, 0)) { Error(LOCATION, "Failed to load GLX!"); } diff --git a/code/osapi/osapi.cpp b/code/osapi/osapi.cpp index c80016a3385..65ac2b3489a 100644 --- a/code/osapi/osapi.cpp +++ b/code/osapi/osapi.cpp @@ -28,6 +28,13 @@ #include #endif +#ifdef __ANDROID__ +#include +#include +#include +#include "options/Option.h" +#endif + namespace { const char* ORGANIZATION_NAME = "HardLightProductions"; @@ -840,3 +847,126 @@ SCP_string os_get_config_path(const SCP_string& subpath) return ss.str(); } +/* + Special functions for Android +*/ + +#ifdef __ANDROID__ +// Helper to get a static method from a java class and clear the exception +// if the method is not found. This is needed to avoid crashing on the next JNI request. +static jmethodID android_get_static_method(JNIEnv* e, jclass cls, const char* name, const char* sig) +{ + jmethodID m = e->GetStaticMethodID(cls, name, sig); + if (e->ExceptionCheck()) { + e->ExceptionClear(); + m = nullptr; + mprintf(("OSAPI : Method: %s. Not found on GameActivity! Signature: %s. \n", name, sig)); + } + return m; +} + +SCP_string os_get_working_folder_path() +{ + SCP_string wfp{}; + + //Get the JNI Environment pointer and current Activity instance via SDL + JNIEnv* env = (JNIEnv*)SDL_AndroidGetJNIEnv(); + jobject activity = (jobject)SDL_AndroidGetActivity(); + + if (env && activity) { + // Locate the Java class (GameActivity on KnossosNET) + jclass ga = env->GetObjectClass(activity); + if(ga) { + // Get the methodID (activity, methodName, signature); + // "()Ljava/lang/String;" means it takes no paramenters and returns a string + jmethodID methodId = android_get_static_method (env, ga, "getWorkingFolder", "()Ljava/lang/String;"); + if (methodId) { + jstring jString = (jstring)env->CallStaticObjectMethod(ga, methodId); + if (jString) { + const char* workingFolder = env->GetStringUTFChars(jString, 0); + wfp = SCP_string(workingFolder); + env->ReleaseStringUTFChars(jString, workingFolder); + env->DeleteLocalRef(jString); + } else { + mprintf(("os_get_working_folder_path: Couldn't get the jString.\n")); + } + } else { + mprintf(("os_get_working_folder_path: Couldn't get the methodID.\n")); + } + env->DeleteLocalRef(ga); + } else { + mprintf(("os_get_working_folder_path: Couldn't get java class.\n")); + } + } else { + mprintf(("os_get_working_folder_path: Couldn't get JNI enviroment or activity.\n")); + } + + if (wfp.empty()) { + mprintf(("Couldn't get working folder path from Java class, reverting to SDL default.\n")); + // Fallback to app space on internal storage + const char* fallbackPath = SDL_AndroidGetExternalStoragePath(); + if (fallbackPath) { + wfp = SCP_string(fallbackPath); + wfp += "/files/"; + } + } + + // Ensure path ends with a dir separator + if (!wfp.empty() && (wfp.back() != DIR_SEPARATOR_CHAR)) { + wfp += DIR_SEPARATOR_CHAR; + } + mprintf(("Using working folder: %s\n", wfp.c_str())); + return wfp; +} + +void os_touch_overlay_toggle(bool status) +{ + //Get the JNI Environment pointer and current Activity instance via SDL + JNIEnv* env = (JNIEnv*)SDL_AndroidGetJNIEnv(); + jobject activity = (jobject)SDL_AndroidGetActivity(); + + if (env && activity) { + // Locate the Java class (GameActivity on KnossosNET) + jclass ga = env->GetObjectClass(activity); + if(ga) { + jmethodID methodId = android_get_static_method (env, ga, status ? "enableOverlay" : "disableOverlay", "()V"); + if (methodId) { + env->CallStaticVoidMethod(ga, methodId); + } else { + mprintf(("os_touch_overlay_toggle: Couldn't get the methodID.\n")); + } + env->DeleteLocalRef(ga); + } else { + mprintf(("os_touch_overlay_toggle: Couldn't get java class.\n")); + } + } else { + mprintf(("os_touch_overlay_toggle: Couldn't get JNI enviroment or activity.\n")); + } +} + +static bool touch_ui_change(bool new_val, bool initial) +{ + if (initial) { + return false; + } + os_touch_overlay_toggle(new_val); + return true; +} + +static auto TouchOverlayOption = options::OptionBuilder("Input.TouchOverlay", + std::pair{"Touch Overlay", -1}, + std::pair{"Enable or disable the Touch Overlay", -1}) + .category(std::make_pair("Input", 1827)) + .level(options::ExpertLevel::Beginner) + .change_listener(touch_ui_change) + .default_val(true) + .importance(0) + .finish(); + +void os_touch_overlay_init() +{ + os_touch_overlay_toggle(TouchOverlayOption->getValue()); +} + +#endif + diff --git a/code/osapi/osapi.h b/code/osapi/osapi.h index 0d23b002ed3..41d29e4ada0 100644 --- a/code/osapi/osapi.h +++ b/code/osapi/osapi.h @@ -91,6 +91,20 @@ bool os_is_legacy_mode(); */ SCP_string os_get_config_path(const SCP_string& subpath = ""); +/* + Special functions for Android +*/ +#ifdef __ANDROID__ +// Get working folder absolute path from Android Java Class +SCP_string os_get_working_folder_path(); + +// Calls to display the touch overlay depending on its last state +void os_touch_overlay_init(); + +// Enable or disable the touch UI overlay +void os_touch_overlay_toggle(bool status); +#endif + namespace os { /** diff --git a/code/sound/openal.h b/code/sound/openal.h index cd77a1bb290..ddf32509e68 100644 --- a/code/sound/openal.h +++ b/code/sound/openal.h @@ -3,7 +3,7 @@ #define _AL_H -#if defined(__APPLE__) +#if defined(__APPLE__) || defined(__ANDROID__) #include "al.h" #include "alc.h" #else diff --git a/code/windows_stub/stubs.cpp b/code/windows_stub/stubs.cpp index 1f45959c7e1..df4ae39b619 100644 --- a/code/windows_stub/stubs.cpp +++ b/code/windows_stub/stubs.cpp @@ -49,6 +49,7 @@ int filelength(int fd) SCP_string dump_stacktrace() { +#ifndef __ANDROID__ #ifdef SCP_HAVE_EXECINFO_H // The following is adapted from here: https://panthema.net/2008/0901-stacktrace-demangled/ const int ADDR_SIZE = 64; @@ -139,6 +140,9 @@ SCP_string dump_stacktrace() #else return "No stacktrace available"; #endif +#else +return "No stacktrace available"; +#endif } // retrieve the current working directory diff --git a/freespace2/CMakeLists.txt b/freespace2/CMakeLists.txt index aeb6afd9eae..580a8e0c708 100644 --- a/freespace2/CMakeLists.txt +++ b/freespace2/CMakeLists.txt @@ -19,7 +19,18 @@ IF(MSVC60) link_directories(${STLPORT_INCLUDE_LIB_DIRS}) ENDIF(MSVC60) -ADD_EXECUTABLE(Freespace2 ${EXE_GUI_TYPE} ${FREESPACE_SRC}) +# For android, build a library not a executable +if (ANDROID) + add_library(Freespace2 SHARED ${FREESPACE_SRC}) + target_compile_definitions(Freespace2 PRIVATE PLATFORM_ANDROID) + + # NDK base libs + find_library(log-lib log) + find_library(android-lib android) + target_link_libraries(Freespace2 PRIVATE ${log-lib} ${android-lib}) +else() + add_executable(Freespace2 ${EXE_GUI_TYPE} ${FREESPACE_SRC}) +endif() target_compile_features(Freespace2 PUBLIC cxx_std_17) @@ -27,9 +38,20 @@ include(resources.cmake) SET_TARGET_PROPERTIES(Freespace2 PROPERTIES OUTPUT_NAME "fs2_open_${FSO_BINARY_SUFFIX}") -TARGET_LINK_LIBRARIES(Freespace2 code) -TARGET_LINK_LIBRARIES(Freespace2 platform) -TARGET_LINK_LIBRARIES(Freespace2 compiler) +if (ANDROID) + # one call + target_link_libraries(Freespace2 + PRIVATE + code + platform + compiler + ${ANDROID_LIBS} + ) +else() + TARGET_LINK_LIBRARIES(Freespace2 code) + TARGET_LINK_LIBRARIES(Freespace2 platform) + TARGET_LINK_LIBRARIES(Freespace2 compiler) +endif() IF(WIN32) # Link sdl main only here as it interferes with fred @@ -51,6 +73,8 @@ IF(USE_STLPORT) INCLUDE_DIRECTORIES(BEFORE SYSTEM ${STLPORT_INCLUDE_DIRS}) ENDIF(USE_STLPORT) +# Do not install when building android +if (NOT ANDROID) # Add install script to copy the executable to the FreeSpace path if possible and wanted INSTALL( TARGETS Freespace2 @@ -64,6 +88,7 @@ if (FSO_INSTALL_DEBUG_FILES) OPTIONAL) endif() endif() +endif(NOT ANDROID) if(FSO_BUILD_APPIMAGE) configure_file("${CMAKE_CURRENT_LIST_DIR}/cmake/AppRun.in" "${CMAKE_CURRENT_BINARY_DIR}/AppRun.gen" @ONLY) @@ -89,6 +114,8 @@ enable_clang_tidy(Freespace2) INCLUDE(util) COPY_FILES_TO_TARGET(Freespace2) +# Do not launch when building android +if (NOT ANDROID) include(CreateLaunchers) create_target_launcher(Freespace2 WORKING_DIRECTORY ${FSO_FREESPACE_PATH} @@ -101,6 +128,7 @@ add_custom_target(launch_fso WORKING_DIRECTORY "${FSO_FREESPACE_PATH}" COMMENT "Launching FreeSpace" VERBATIM) +endif() # Ensure that Xcode generates debug symbols on macOS for Debug builds set_target_properties(Freespace2 PROPERTIES XCODE_ATTRIBUTE_GCC_GENERATE_DEBUGGING_SYMBOLS[variant=Debug] "YES") diff --git a/freespace2/SDLGraphicsOperations.cpp b/freespace2/SDLGraphicsOperations.cpp index 6993b285fa2..4d52111a5cd 100644 --- a/freespace2/SDLGraphicsOperations.cpp +++ b/freespace2/SDLGraphicsOperations.cpp @@ -201,6 +201,11 @@ std::unique_ptr SDLGraphicsOperations::createViewport(const os::Vi windowflags |= SDL_WINDOW_INPUT_GRABBED; } + #ifdef __ANDROID__ + SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight"); + os_touch_overlay_init(); + #endif + SDL_Rect bounds; if (SDL_GetDisplayBounds(props.display, &bounds) != 0) { mprintf(("Failed to get display bounds: %s\n", SDL_GetError())); diff --git a/freespace2/freespace.cpp b/freespace2/freespace.cpp index cf9d354e695..7361ca1498f 100644 --- a/freespace2/freespace.cpp +++ b/freespace2/freespace.cpp @@ -8169,3 +8169,10 @@ int main(int argc, char *argv[]) return result; } + +#ifdef __ANDROID__ +extern "C" int android_main(int argc, char* argv[]) +{ + return main(argc, argv); +} +#endif \ No newline at end of file diff --git a/lib/FFmpeg.cmake b/lib/FFmpeg.cmake index 740ab4c1c1d..cfbbb367d92 100644 --- a/lib/FFmpeg.cmake +++ b/lib/FFmpeg.cmake @@ -48,7 +48,7 @@ IF(PLATFORM_WINDOWS) target_link_libraries(ffmpeg INTERFACE swscale) target_link_libraries(ffmpeg INTERFACE swresample) ELSE(WIN32) - if (PLATFORM_MAC) + if (PLATFORM_MAC OR ANDROID) option(FFMPEG_USE_PRECOMPILED "Use precompiled version of FFmpeg. If disabled the system libraries will be used." ON) else() option(FFMPEG_USE_PRECOMPILED "Use precompiled version of FFmpeg. If disabled the system libraries will be used." OFF) @@ -97,8 +97,8 @@ ELSE(WIN32) # Use our libraries find_library(${name}_LOCATION ${name} PATHS "${FFMPEG_PATH}/lib" - NO_DEFAULT_PATH) - + NO_DEFAULT_PATH + NO_CMAKE_FIND_ROOT_PATH) file(GLOB ${name}_LIBS "${FFMPEG_PATH}/lib/lib${name}*") get_filename_component(FULL_LIB_PATH "${${name}_LOCATION}" REALPATH) diff --git a/lib/OpenAL.cmake b/lib/OpenAL.cmake index 67adfd8c3a1..ac845fc441c 100644 --- a/lib/OpenAL.cmake +++ b/lib/OpenAL.cmake @@ -58,7 +58,7 @@ ELSE(WIN32) message(STATUS "Using pre-built OpenAL library.") unset(OpenAL_LOCATION CACHE) - find_library(OpenAL_LOCATION openal PATHS "${OpenAL_ROOT_DIR}/lib" NO_DEFAULT_PATH) + find_library(OpenAL_LOCATION openal PATHS "${OpenAL_ROOT_DIR}/lib" NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH) get_filename_component(FULL_LIB_PATH "${OpenAL_LOCATION}" REALPATH) ADD_IMPORTED_LIB(openal "${OpenAL_ROOT_DIR}/include" "${FULL_LIB_PATH}") diff --git a/lib/SDL2.cmake b/lib/SDL2.cmake index d6d99fb06f4..2ca0c5a053f 100644 --- a/lib/SDL2.cmake +++ b/lib/SDL2.cmake @@ -70,12 +70,12 @@ else() message(STATUS "Using pre-built SDL2 library.") unset(SDL2_LOCATION CACHE) - find_library(SDL2_LOCATION NAMES SDL2 SDL2-2.0 PATHS "${SDL2_ROOT_DIR}/lib" NO_DEFAULT_PATH) + find_library(SDL2_LOCATION NAMES SDL2 SDL2-2.0 PATHS "${SDL2_ROOT_DIR}/lib" NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH) get_filename_component(FULL_LIB_PATH "${SDL2_LOCATION}" REALPATH) ADD_IMPORTED_LIB(sdl2 "${SDL2_ROOT_DIR}/include" "${FULL_LIB_PATH}") - file(GLOB SDL2_LIBS "${SDL2_ROOT_DIR}/lib/libSDL2-2*") + file(GLOB SDL2_LIBS "${SDL2_ROOT_DIR}/lib/libSDL2*") add_target_copy_files("${SDL2_LIBS}") endif() endif() diff --git a/lib/freetype.cmake b/lib/freetype.cmake index cda855d3f5c..a64e5f85c87 100644 --- a/lib/freetype.cmake +++ b/lib/freetype.cmake @@ -1,5 +1,5 @@ -if(PLATFORM_WINDOWS OR PLATFORM_MAC OR CMAKE_CROSSCOMPILING) +if(PLATFORM_WINDOWS OR PLATFORM_MAC OR CMAKE_CROSSCOMPILING OR ANDROID) add_library(freetype INTERFACE) # We use prebuilt binaries for windows and mac @@ -10,7 +10,7 @@ if(PLATFORM_WINDOWS OR PLATFORM_MAC OR CMAKE_CROSSCOMPILING) set(SEARCH_PATH "${FREETYPE_ROOT_DIR}/lib") - if(PLATFORM_WINDOWS OR CMAKE_CROSSCOMPILING) + if((PLATFORM_WINDOWS OR CMAKE_CROSSCOMPILING) AND NOT ANDROID) set(LIBNAME "freetype*.dll") else() set(LIBNAME "libfreetype*") @@ -21,7 +21,8 @@ if(PLATFORM_WINDOWS OR PLATFORM_MAC OR CMAKE_CROSSCOMPILING) find_library(freetype_LOCATION NAMES freetype freetype281 PATHS "${SEARCH_PATH}" - NO_DEFAULT_PATH) + NO_DEFAULT_PATH + NO_CMAKE_FIND_ROOT_PATH) if (${freetype_LOCATION} STREQUAL "freetype_LOCATION-NOTFOUND" AND CMAKE_CROSSCOMPILING) #Just try the default here and see if we can make it work. diff --git a/lib/prebuilt.cmake b/lib/prebuilt.cmake index c97d3ef95be..2059839905b 100644 --- a/lib/prebuilt.cmake +++ b/lib/prebuilt.cmake @@ -31,6 +31,16 @@ function(get_prebuilt_path OUT_VAR) endif() elseif(PLATFORM_MAC) set(FILENAME "bin-mac.tar.gz") + elseif(ANDROID) + if(ANDROID_ABI STREQUAL "arm64-v8a") + set(FILENAME "bin-android-arm64.tar.gz") + elseif(ANDROID_ABI STREQUAL "x86_64") + set(FILENAME "bin-android-x64.tar.gz") + elseif(ANDROID_ABI STREQUAL "armeabi-v7a") + set(FILENAME "bin-android-arm32.tar.gz") + elseif(ANDROID_ABI STREQUAL "x86") + set(FILENAME "bin-android-x86.tar.gz") + endif() else() # Use Linux binaries... if (IS_ARM64) @@ -39,7 +49,11 @@ function(get_prebuilt_path OUT_VAR) set(FILENAME "bin-linux.tar.gz") endif() endif() + if(ANDROID) # TODO: REMOVE THIS ONCE ANDROID PREBUILTS ARE IN THE SCP-PREBUILT REPO + set(DOWNLOAD_URL "https://github.com/Shivansps/Fso-Android-Prebuilts/releases/download/prebuilts-cmake/${FILENAME}") + else() set(DOWNLOAD_URL "https://github.com/scp-fs2open/scp-prebuilt/releases/download/${TAG_NAME}/${FILENAME}") + endif() set(DOWNLOAD_FILE "${CURRENT_ROOT}/${FILENAME}") set(MAX_RETRIES 5)