Repository: Wohlstand/TheXTech Branch: main Commit: f98b95e99525 Files: 899 Total size: 10.9 MB Directory structure: gitextract_aj92w71a/ ├── .appveyor.yml ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.yml │ │ └── compile-fail-report.yml │ ├── ci-helper/ │ │ ├── .gitignore │ │ ├── create-dmg.sh │ │ ├── pack-game-macos.sh │ │ ├── pack-game.sh │ │ ├── support/ │ │ │ ├── dmg-license.py │ │ │ ├── dmg-license3.py │ │ │ └── template.applescript │ │ ├── translate_patcher.py │ │ ├── translate_sync_assets.sh │ │ ├── translate_sync_to_stable.sh │ │ └── translate_update.sh │ └── workflows/ │ ├── 16m-ci.yml │ ├── 3ds-ci.yml │ ├── android-ci.yml │ ├── android-fdroid-ci.yml │ ├── blocksds-ci.yml │ ├── emscripten.yml │ ├── flatpak.yml │ ├── homebrew-assets-ci.yml │ ├── macos-ci.yml │ ├── portmaster-ci.yml │ ├── regression-testing-ci.yaml │ ├── switch-ci.yml │ ├── sync-langs.yml │ ├── ubuntu-deb-ci.yml │ ├── ubuntu-tar-ci.yml │ ├── vita-ci.yml │ ├── wii-ci.yml │ ├── wiiu-ci.yml │ └── windows-ci.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE ├── PORTING.md ├── README.ESP.md ├── README.RUS.md ├── README.md ├── android-project/ │ ├── .gitignore │ ├── build.gradle │ ├── build_init.sh │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle │ └── thextech/ │ ├── build.gradle │ ├── icon/ │ │ ├── debug/ │ │ │ ├── mipmap-anydpi-v26/ │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ └── values/ │ │ │ └── ic_launcher_background.xml │ │ ├── devel/ │ │ │ ├── mipmap-anydpi-v26/ │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ └── values/ │ │ │ └── ic_launcher_background.xml │ │ └── main/ │ │ ├── mipmap-anydpi-v26/ │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ └── values/ │ │ └── ic_launcher_background.xml │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── assets/ │ │ └── buttons/ │ │ └── Credits.txt │ ├── generate_patternPath.sh │ ├── java/ │ │ ├── javautil/ │ │ │ ├── FileUtils.java │ │ │ └── README.md │ │ ├── org/ │ │ │ └── libsdl/ │ │ │ └── app/ │ │ │ ├── HIDDevice.java │ │ │ ├── HIDDeviceBLESteamController.java │ │ │ ├── HIDDeviceManager.java │ │ │ ├── HIDDeviceUSB.java │ │ │ ├── SDL.java │ │ │ ├── SDLActivity.java │ │ │ ├── SDLAudioManager.java │ │ │ ├── SDLControllerManager.java │ │ │ └── SDLSurface.java │ │ └── ru/ │ │ └── wohlsoft/ │ │ └── thextech/ │ │ ├── GameSettings.java │ │ ├── IniFile.java │ │ ├── Launcher.java │ │ ├── OpenFileDialog.java │ │ ├── UIUpdater.java │ │ └── thextechActivity.java │ └── res/ │ ├── layout/ │ │ ├── activity_launcher.xml │ │ └── settings_activity.xml │ ├── values/ │ │ ├── arrays.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── values-bg/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-de/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-es-rES/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-fr/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-hu/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-it/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-ja/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-ko/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-nb-rNO/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-pl/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-pt/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-pt-rBR/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-ru-rRU/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-ta/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-tr/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-uk/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-zh-rCN/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-zh-rTW/ │ │ ├── arrays.xml │ │ └── strings.xml │ └── xml/ │ └── root_preferences.xml ├── changelog.txt ├── cmake/ │ ├── TargetArch.cmake │ ├── build_props.cmake │ ├── ci_linux_gcc_toolchain_arm64.cmake │ ├── ci_linux_gcc_toolchain_armhf.cmake │ ├── ci_linux_gcc_toolchain_x32.cmake │ ├── ci_windows_mingw_toolchain_x32.cmake │ ├── ci_windows_mingw_toolchain_x64.cmake │ ├── ci_windows_msvc_toolchain_arm64.cmake │ ├── deploy.cmake │ ├── git_info.cmake │ ├── git_version.cmake │ ├── git_version_update.cmake │ ├── icon.desktop.in │ ├── library_FreeImage.cmake │ ├── library_FreeType.cmake │ ├── library_HarfBuzz.cmake │ ├── library_SDLMixerX.cmake │ ├── library_discord_rpc.cmake │ ├── library_glew.cmake │ ├── library_luabind.cmake │ ├── library_luajit.cmake │ ├── library_syslibs.cmake │ ├── library_zlib.cmake │ ├── ndk-stl-config.cmake │ ├── overlays-16m.ld │ ├── package_switch.cmake │ ├── package_vita.cmake │ └── vita_buildprops.cmake ├── debian/ │ ├── changelog │ ├── control │ ├── copyright │ └── rules ├── docs/ │ ├── README_3DS.md │ ├── README_DSI.md │ ├── README_SWITCH.md │ ├── README_VITA.ESP.md │ ├── README_VITA.RUS.md │ ├── README_VITA.md │ ├── README_WII.md │ ├── README_WIIU.md │ └── editor.ini ├── fastlane/ │ └── metadata/ │ └── android/ │ ├── en-GB/ │ │ ├── changelogs/ │ │ │ ├── 1030700.txt │ │ │ └── 1030701.txt │ │ ├── full_description.txt │ │ ├── short_description.txt │ │ └── title.txt │ └── ru/ │ ├── full_description.txt │ └── short_description.txt ├── lib/ │ ├── Allocator/ │ │ ├── Allocator.h │ │ ├── LICENSE │ │ ├── LinearAllocator.cpp │ │ ├── LinearAllocator.h │ │ ├── PoolAllocator.cpp │ │ ├── PoolAllocator.h │ │ ├── README.md │ │ ├── StackLinkedList.h │ │ ├── StackLinkedListImpl.h │ │ ├── Utils.h │ │ ├── linear-allocator.cmake │ │ └── pool-allocator.cmake │ ├── AppPath/ │ │ ├── app_path.cmake │ │ ├── app_path.h │ │ └── private/ │ │ ├── app_path.cpp │ │ ├── app_path_16m.cpp │ │ ├── app_path_3ds.cpp │ │ ├── app_path_android.cpp │ │ ├── app_path_emscripten.cpp │ │ ├── app_path_macos.cpp │ │ ├── app_path_macos_dirs.h │ │ ├── app_path_macos_dirs.m │ │ ├── app_path_old.cpp │ │ ├── app_path_private.h │ │ ├── app_path_skeleton.cpp │ │ ├── app_path_switch.cpp │ │ ├── app_path_unix.cpp │ │ ├── app_path_vita.cpp │ │ ├── app_path_wii.cpp │ │ ├── app_path_wiiu.cpp │ │ └── app_path_win32.cpp │ ├── Archives/ │ │ ├── archives.h │ │ ├── archives_dir.cpp │ │ ├── archives_mount.cpp │ │ ├── archives_priv.h │ │ └── archives_rwops.cpp │ ├── CrashHandler/ │ │ ├── StackWalker/ │ │ │ ├── StackWalker.cmake │ │ │ ├── StackWalker.cpp │ │ │ └── StackWalker.h │ │ ├── backtrace.h.in │ │ ├── crash_handler.cpp │ │ └── crash_handler.h │ ├── FixPointCS/ │ │ ├── Fixed64.h │ │ └── FixedUtil.h │ ├── Graphics/ │ │ ├── PGEString.h │ │ ├── bitmask2rgba.c │ │ ├── bitmask2rgba.cmake │ │ ├── bitmask2rgba.h │ │ ├── graphics_funcs.cpp │ │ ├── graphics_funcs.h │ │ ├── image_size.cpp │ │ ├── image_size.h │ │ ├── size.cpp │ │ ├── size.h │ │ └── xt_qoi.cpp │ ├── Integrator/ │ │ ├── int_discorcrpc.cpp │ │ ├── int_discorcrpc.h │ │ ├── integrator.cpp │ │ └── integrator.h │ ├── InterProcess/ │ │ ├── editor_pipe.cpp │ │ ├── editor_pipe.h │ │ ├── intproc.cpp │ │ └── intproc.h │ ├── Logger/ │ │ ├── logger.cmake │ │ ├── logger.h │ │ ├── logger_level.h │ │ └── private/ │ │ ├── logger.cpp │ │ ├── logger_android.cpp │ │ ├── logger_desktop.cpp │ │ ├── logger_dummy.cpp │ │ ├── logger_emscripten.cpp │ │ ├── logger_min.cpp │ │ ├── logger_private.h │ │ ├── logger_sets.h │ │ ├── logger_vita.cpp │ │ └── logger_wiiu.cpp │ ├── Utf8Main/ │ │ ├── utf8main.cmake │ │ ├── utf8main.h │ │ └── utf8main_win32.cpp │ ├── Utils/ │ │ ├── Utils.cmake │ │ ├── UtilsSDL.cmake │ │ ├── dir_list_ci.cpp │ │ ├── dir_list_ci.h │ │ ├── elapsed_timer.cpp │ │ ├── elapsed_timer.h │ │ ├── files.cpp │ │ ├── files.h │ │ ├── files_ini.h │ │ ├── openUrl.cmake │ │ ├── open_url.cpp │ │ ├── open_url.h │ │ ├── strings.cpp │ │ ├── strings.h │ │ └── vptrlist.h │ ├── fixed_point.cpp │ ├── fixed_point.h │ ├── floating_point.cpp │ ├── floating_point.h │ ├── fmt/ │ │ ├── CMakeLists.txt │ │ ├── CONTRIBUTING.rst │ │ ├── LICENSE.rst │ │ ├── README.rst │ │ ├── README.txt │ │ ├── fmt.cmake │ │ ├── fmt_container.h │ │ ├── fmt_format.cpp │ │ ├── fmt_format.h │ │ ├── fmt_ostream.cpp │ │ ├── fmt_ostream.h │ │ ├── fmt_posix.cpp │ │ ├── fmt_posix.h │ │ ├── fmt_printf.cpp │ │ ├── fmt_printf.h │ │ ├── fmt_qformat.h │ │ ├── fmt_string.h │ │ ├── fmt_time.h │ │ ├── orig/ │ │ │ ├── CMakeLists.txt │ │ │ ├── container.h │ │ │ ├── format.cc │ │ │ ├── format.h │ │ │ ├── ostream.cc │ │ │ ├── ostream.h │ │ │ ├── posix.cc │ │ │ ├── posix.h │ │ │ ├── printf.cc │ │ │ ├── printf.h │ │ │ ├── string.h │ │ │ └── time.h │ │ └── update.sh │ ├── fmt_format_ne.h │ ├── fmt_impl.cpp │ ├── fmt_time_ne.h │ ├── forced_int.h │ ├── gif.h │ ├── gif_writer.h │ ├── json/ │ │ ├── LICENSE.MIT │ │ ├── README.md │ │ ├── json.hpp │ │ └── json_rwops_input.hpp │ ├── md5/ │ │ ├── LICENSE │ │ ├── README │ │ ├── md5.cmake │ │ ├── md5.cpp │ │ ├── md5.h │ │ ├── md5_loc.h │ │ ├── md5tools.cpp │ │ └── md5tools.hpp │ ├── numeric_types.h │ ├── pcg/ │ │ ├── LICENSE.txt │ │ ├── README.md │ │ ├── pcg_extras.hpp │ │ ├── pcg_random.hpp │ │ └── pcg_uint128.hpp │ ├── pge_cpu_arch.h │ ├── pge_delay.h │ ├── pge_tonearest.h │ ├── pge_video_rec/ │ │ ├── pge-video-rec.cmake │ │ ├── pge_record_gif.cpp │ │ ├── pge_record_vp8.cpp │ │ ├── pge_video_rec.h │ │ └── pge_video_sink.cpp │ ├── sdl_proxy/ │ │ ├── 16m/ │ │ │ └── std_16m.cpp │ │ ├── 3ds/ │ │ │ ├── 3ds-audio-lib.cpp │ │ │ ├── 3ds-audio-lib.h │ │ │ ├── mixer_3ds.cpp │ │ │ └── std_3ds.cpp │ │ ├── _trash/ │ │ │ ├── mixer_mixerx.cpp │ │ │ ├── sdl_assert.h │ │ │ ├── sdl_atomic.h │ │ │ ├── sdl_head.h │ │ │ ├── sdl_stdinc.h │ │ │ └── sdl_timer.h │ │ ├── base/ │ │ │ └── std_base.cpp │ │ ├── mixer.h │ │ ├── null/ │ │ │ ├── mixer_null.cpp │ │ │ ├── sdl_null.h │ │ │ └── std_null.cpp │ │ ├── sdl_assert.h │ │ ├── sdl_atomic.h │ │ ├── sdl_audio.h │ │ ├── sdl_common.h │ │ ├── sdl_filesystem.h │ │ ├── sdl_head.h │ │ ├── sdl_proxy.cmake │ │ ├── sdl_stdinc.h │ │ ├── sdl_timer.h │ │ ├── sdl_types.h │ │ └── wii/ │ │ └── std_wii.cpp │ ├── sdl_proxy_rwops/ │ │ ├── CMakeLists.txt │ │ ├── include/ │ │ │ └── SDL2/ │ │ │ ├── SDL_rwops.h │ │ │ └── _priv_rwops_funcs.h │ │ ├── sdl_proxy_rwops.c │ │ └── sdl_proxy_rwops_file.c │ ├── sorting/ │ │ ├── pdqsort.h │ │ └── tinysort.h │ ├── tclap/ │ │ ├── Arg.h │ │ ├── ArgContainer.h │ │ ├── ArgException.h │ │ ├── ArgGroup.h │ │ ├── ArgTraits.h │ │ ├── CmdLine.h │ │ ├── CmdLineInterface.h │ │ ├── CmdLineOutput.h │ │ ├── Constraint.h │ │ ├── DeferDelete.h │ │ ├── DocBookOutput.h │ │ ├── HelpVisitor.h │ │ ├── IgnoreRestVisitor.h │ │ ├── MultiArg.h │ │ ├── MultiSwitchArg.h │ │ ├── OptionalUnlabeledTracker.h │ │ ├── README │ │ ├── StandardTraits.h │ │ ├── StdOutput.h │ │ ├── SwitchArg.h │ │ ├── UnlabeledMultiArg.h │ │ ├── UnlabeledValueArg.h │ │ ├── ValueArg.h │ │ ├── ValuesConstraint.h │ │ ├── VersionVisitor.h │ │ ├── Visitor.h │ │ ├── sstream.h │ │ └── tclap.cmake │ ├── util.cpp │ └── util.h ├── pge_version.h ├── repo.sh ├── resources/ │ ├── PkgInfo │ ├── _concepts/ │ │ ├── tv-banner-label.xcf │ │ └── tv-banner.xcf │ ├── _dev_scripts/ │ │ ├── snd_id_2_enum.sh │ │ ├── snd_id_sed.sh │ │ └── xserver.py │ ├── emscripten/ │ │ ├── manifest.json.in │ │ ├── shell_minimal.html │ │ └── sw.js.in │ ├── file_icons/ │ │ ├── file_lvl.icns │ │ ├── file_lvlx.icns │ │ ├── file_wld.icns │ │ └── file_wldx.icns │ ├── flatpak/ │ │ ├── appdata.xml.in │ │ └── build.yml │ ├── haiku/ │ │ ├── PackageInfo.in │ │ ├── icon.hvif │ │ └── icon.rdef │ ├── icon/ │ │ ├── make_icns.sh │ │ └── make_icns_linux.sh │ ├── icon_dev_android/ │ │ └── thextech_512.xcf │ ├── languages/ │ │ ├── assets_bg.json │ │ ├── assets_de.json │ │ ├── assets_en.json │ │ ├── assets_es.json │ │ ├── assets_fr.json │ │ ├── assets_hu.json │ │ ├── assets_it.json │ │ ├── assets_ja.json │ │ ├── assets_ko.json │ │ ├── assets_nb-no.json │ │ ├── assets_pl.json │ │ ├── assets_pt-br.json │ │ ├── assets_pt.json │ │ ├── assets_ru.json │ │ ├── assets_ta.json │ │ ├── assets_tr.json │ │ ├── assets_uk.json │ │ ├── assets_zh-cn.json │ │ ├── assets_zh-tw.json │ │ ├── thextech_bg.json │ │ ├── thextech_de.json │ │ ├── thextech_en.json │ │ ├── thextech_es.json │ │ ├── thextech_fr.json │ │ ├── thextech_hu.json │ │ ├── thextech_it.json │ │ ├── thextech_ja.json │ │ ├── thextech_ko.json │ │ ├── thextech_nb-no.json │ │ ├── thextech_pl.json │ │ ├── thextech_pt-br.json │ │ ├── thextech_pt.json │ │ ├── thextech_ru.json │ │ ├── thextech_ta.json │ │ ├── thextech_tr.json │ │ ├── thextech_uk.json │ │ ├── thextech_zh-cn.json │ │ └── thextech_zh-tw.json │ ├── switch/ │ │ ├── Important.txt │ │ └── thextech-logo.xcf │ ├── thextech.icns │ ├── thextech.plist.in │ ├── thextech.plist.tiger.in │ ├── thextech.rc │ ├── tiger/ │ │ ├── TheXTechRun.in │ │ └── thextech.icns │ ├── vita/ │ │ ├── frag.cgf │ │ ├── sce_sys/ │ │ │ └── livearea/ │ │ │ └── contents/ │ │ │ └── template.xml.in │ │ └── vert.cgv │ ├── wii/ │ │ ├── icon.xcf │ │ └── meta.xml.in │ └── wiiu/ │ ├── icon.xcf │ └── meta.xml.in ├── script/ │ ├── CMakeLists.txt │ ├── include/ │ │ └── xtech_lua_main.h │ └── src/ │ └── xtech_lua_main.cpp ├── src/ │ ├── blk_id.h │ ├── blocks.cpp │ ├── blocks.h │ ├── capabilities.cpp │ ├── capabilities.h │ ├── change_res.cpp │ ├── change_res.h │ ├── cmd_line_setup.h │ ├── collision.cpp │ ├── collision.h │ ├── compat.cpp │ ├── compat.h │ ├── config/ │ │ ├── config_base.cpp │ │ ├── config_base.hpp │ │ ├── config_hooks.cpp │ │ ├── config_hooks.h │ │ ├── config_impl.hpp │ │ ├── config_legacy_compat.cpp │ │ └── config_main.cpp │ ├── config.h │ ├── control/ │ │ ├── con_control.h │ │ ├── controls.cpp │ │ ├── controls_methods.h │ │ ├── controls_strings.h │ │ ├── duplicate.cpp │ │ ├── duplicate.h │ │ ├── input_16m.cpp │ │ ├── input_16m.h │ │ ├── input_3ds.cpp │ │ ├── input_3ds.h │ │ ├── input_wii.cpp │ │ ├── input_wii.h │ │ ├── input_wii_gc.cpp │ │ ├── input_wii_gc.h │ │ ├── joystick.cpp │ │ ├── joystick.h │ │ ├── keyboard.cpp │ │ ├── keyboard.h │ │ ├── touchscreen.cpp │ │ ├── touchscreen.h │ │ └── touchscreen.txt │ ├── control_types.h │ ├── controls.h │ ├── custom.cpp │ ├── custom.h │ ├── draw_planes.h │ ├── editor/ │ │ ├── editor.cpp │ │ ├── editor_custom.cpp │ │ ├── editor_custom.h │ │ ├── editor_strings.cpp │ │ ├── editor_strings.h │ │ ├── magic_block.cpp │ │ ├── magic_block.h │ │ ├── new_editor.cpp │ │ ├── new_editor.h │ │ ├── write_common.cpp │ │ ├── write_common.h │ │ ├── write_level.cpp │ │ ├── write_level.h │ │ ├── write_world.cpp │ │ └── write_world.h │ ├── editor.h │ ├── eff_id.h │ ├── effect.cpp │ ├── effect.h │ ├── fontman/ │ │ ├── crop_info.h │ │ ├── font_engine_base.h │ │ ├── font_manager.cpp │ │ ├── font_manager.h │ │ ├── font_manager_private.cpp │ │ ├── font_manager_private.h │ │ ├── hardcoded_font.cpp │ │ ├── hardcoded_font.h │ │ ├── legacy_font.cpp │ │ ├── legacy_font.h │ │ ├── raster_font.cpp │ │ ├── raster_font.h │ │ ├── ttf_font.cpp │ │ ├── ttf_font.h │ │ └── utf8_helpers.cpp │ ├── frame_timer.cpp │ ├── frame_timer.h │ ├── frm_main.cpp │ ├── frm_main.h │ ├── game_main.cpp │ ├── game_main.h │ ├── gfx.cpp │ ├── gfx.h │ ├── global_constants.h │ ├── global_dirs.cpp │ ├── global_dirs.h │ ├── global_strings.cpp │ ├── global_strings.h │ ├── globals.cpp │ ├── globals.h │ ├── graphics/ │ │ ├── gfx_background.cpp │ │ ├── gfx_camera.cpp │ │ ├── gfx_camera.h │ │ ├── gfx_credits.cpp │ │ ├── gfx_draw_player.cpp │ │ ├── gfx_editor.cpp │ │ ├── gfx_enter_screen.cpp │ │ ├── gfx_frame.cpp │ │ ├── gfx_frame.h │ │ ├── gfx_hud.cpp │ │ ├── gfx_keyhole.cpp │ │ ├── gfx_keyhole.h │ │ ├── gfx_marquee.cpp │ │ ├── gfx_marquee.h │ │ ├── gfx_message.cpp │ │ ├── gfx_print.cpp │ │ ├── gfx_screen.cpp │ │ ├── gfx_special_frames.cpp │ │ ├── gfx_special_frames.h │ │ ├── gfx_update.cpp │ │ ├── gfx_update.h │ │ ├── gfx_update2.cpp │ │ ├── gfx_world.cpp │ │ └── gfx_world.h │ ├── graphics.cpp │ ├── graphics.h │ ├── layers.cpp │ ├── layers.h │ ├── load_gfx.cpp │ ├── load_gfx.h │ ├── location.h │ ├── logic/ │ │ ├── object_graph.cpp │ │ └── object_graph.h │ ├── main/ │ │ ├── asset_pack.cpp │ │ ├── asset_pack.h │ │ ├── block_table.cpp │ │ ├── block_table.h │ │ ├── block_table.hpp │ │ ├── cheat_code.cpp │ │ ├── cheat_code.h │ │ ├── client.cpp │ │ ├── client.h │ │ ├── client_methods.cpp │ │ ├── client_methods.h │ │ ├── game_globals.h │ │ ├── game_info.cpp │ │ ├── game_info.h │ │ ├── game_loop.cpp │ │ ├── game_loop_interrupt.h │ │ ├── game_save.cpp │ │ ├── game_strings.cpp │ │ ├── game_strings.h │ │ ├── gameplay_timer.cpp │ │ ├── gameplay_timer.h │ │ ├── hints.cpp │ │ ├── hints.h │ │ ├── level_file.cpp │ │ ├── level_file.h │ │ ├── level_medals.cpp │ │ ├── level_medals.h │ │ ├── level_save_info.cpp │ │ ├── level_save_info.h │ │ ├── main_config.cpp │ │ ├── menu_controls.cpp │ │ ├── menu_controls.h │ │ ├── menu_loop.cpp │ │ ├── menu_main.cpp │ │ ├── menu_main.h │ │ ├── outro_loop.cpp │ │ ├── outro_loop.h │ │ ├── player_frames.cpp │ │ ├── record.cpp │ │ ├── record.h │ │ ├── screen_asset_pack.cpp │ │ ├── screen_asset_pack.h │ │ ├── screen_connect.cpp │ │ ├── screen_connect.h │ │ ├── screen_content.cpp │ │ ├── screen_content.h │ │ ├── screen_options.cpp │ │ ├── screen_options.h │ │ ├── screen_pause.cpp │ │ ├── screen_pause.h │ │ ├── screen_progress.cpp │ │ ├── screen_progress.h │ │ ├── screen_prompt.cpp │ │ ├── screen_prompt.h │ │ ├── screen_quickreconnect.cpp │ │ ├── screen_quickreconnect.h │ │ ├── screen_textentry.cpp │ │ ├── screen_textentry.h │ │ ├── setup_physics.cpp │ │ ├── setup_vars.cpp │ │ ├── speedrunner.cpp │ │ ├── speedrunner.h │ │ ├── translate/ │ │ │ ├── tr_level.h │ │ │ ├── tr_script.h │ │ │ ├── tr_title.h │ │ │ └── tr_world.h │ │ ├── translate.cpp │ │ ├── translate.h │ │ ├── translate_episode.cpp │ │ ├── translate_episode.h │ │ ├── trees.cpp │ │ ├── trees.h │ │ ├── world_file.cpp │ │ ├── world_file.h │ │ ├── world_globals.h │ │ └── world_loop.cpp │ ├── main.cpp │ ├── message.cpp │ ├── message.h │ ├── npc/ │ │ ├── npc_activation.cpp │ │ ├── npc_activation.h │ │ ├── npc_bonus.cpp │ │ ├── npc_cockpit_bits.h │ │ ├── npc_frames.cpp │ │ ├── npc_hit.cpp │ │ ├── npc_kill.cpp │ │ ├── npc_queues.cpp │ │ ├── npc_queues.h │ │ ├── npc_update/ │ │ │ ├── npc_block_logic.cpp │ │ │ ├── npc_collide.cpp │ │ │ ├── npc_effects.cpp │ │ │ ├── npc_generator.cpp │ │ │ ├── npc_movement_logic.cpp │ │ │ ├── npc_special_maybe_held.cpp │ │ │ ├── npc_update_priv.h │ │ │ └── npc_walking_logic.cpp │ │ ├── npc_update.cpp │ │ ├── safe_set.hpp │ │ ├── section_overlap.cpp │ │ └── section_overlap.h │ ├── npc.cpp │ ├── npc.h │ ├── npc_constant_traits.h │ ├── npc_effect.h │ ├── npc_id.h │ ├── npc_special_data.h │ ├── npc_traits.h │ ├── phys_env.cpp │ ├── phys_env.h │ ├── phys_id.h │ ├── pinched_info.h │ ├── player/ │ │ ├── player_action_logic.cpp │ │ ├── player_block_logic.cpp │ │ ├── player_char5_logic.cpp │ │ ├── player_death_logic.cpp │ │ ├── player_effect.h │ │ ├── player_fairy_logic.cpp │ │ ├── player_movement_logic.cpp │ │ ├── player_npc_logic.cpp │ │ ├── player_pinched_logic.cpp │ │ ├── player_screen_logic.cpp │ │ ├── player_update.cpp │ │ ├── player_update_priv.h │ │ ├── player_vehicle_logic.cpp │ │ ├── player_vine_logic.cpp │ │ └── player_warp_logic.cpp │ ├── player.cpp │ ├── player.h │ ├── pseudo_vb.h │ ├── rand.cpp │ ├── rand.h │ ├── range_arr.hpp │ ├── ref_type.h │ ├── saved_layers.cpp │ ├── saved_layers.h │ ├── screen.cpp │ ├── screen.h │ ├── screen_fader.cpp │ ├── screen_fader.h │ ├── script/ │ │ ├── luau/ │ │ │ ├── test.cpp │ │ │ └── test.h │ │ ├── luna/ │ │ │ ├── autocode.cpp │ │ │ ├── autocode.h │ │ │ ├── autocode_manager.cpp │ │ │ ├── autocode_manager.h │ │ │ ├── csprite.cpp │ │ │ ├── csprite.h │ │ │ ├── hitbox.cpp │ │ │ ├── hitbox.h │ │ │ ├── levels/ │ │ │ │ ├── Docopoper-AbstractAssault.cpp │ │ │ │ ├── Docopoper-AbstractAssault.h │ │ │ │ ├── Docopoper-Calleoca.cpp │ │ │ │ ├── Docopoper-Calleoca.h │ │ │ │ ├── Docopoper-TheFloorisLava.cpp │ │ │ │ ├── Docopoper-TheFloorisLava.h │ │ │ │ ├── KilArmoryCode.cpp │ │ │ │ ├── KilArmoryCode.h │ │ │ │ ├── README.txt │ │ │ │ ├── SAJewers-QraestoliaCaverns.cpp │ │ │ │ ├── SAJewers-QraestoliaCaverns.h │ │ │ │ ├── SAJewers-Snowboardin.cpp │ │ │ │ ├── SAJewers-Snowboardin.h │ │ │ │ ├── Talkhaus-Science_Final_Battle.cpp │ │ │ │ └── Talkhaus-Science_Final_Battle.h │ │ │ ├── luna.cpp │ │ │ ├── luna.h │ │ │ ├── lunablock.cpp │ │ │ ├── lunablock.h │ │ │ ├── lunacounter.cpp │ │ │ ├── lunacounter.h │ │ │ ├── lunacounter_record.cpp │ │ │ ├── lunacounter_record.h │ │ │ ├── lunacounter_util.h │ │ │ ├── lunadefs.h │ │ │ ├── lunaglobals.cpp │ │ │ ├── lunaimgbox.cpp │ │ │ ├── lunaimgbox.h │ │ │ ├── lunainput.cpp │ │ │ ├── lunainput.h │ │ │ ├── lunalayer.cpp │ │ │ ├── lunalayer.h │ │ │ ├── lunalevel.cpp │ │ │ ├── lunalevel.h │ │ │ ├── lunalevels.cpp │ │ │ ├── lunalevels.h │ │ │ ├── lunamisc.cpp │ │ │ ├── lunamisc.h │ │ │ ├── lunanpc.cpp │ │ │ ├── lunanpc.h │ │ │ ├── lunaplayer.cpp │ │ │ ├── lunaplayer.h │ │ │ ├── lunarender.cpp │ │ │ ├── lunarender.h │ │ │ ├── lunaspriteman.cpp │ │ │ ├── lunaspriteman.h │ │ │ ├── lunavarbank.cpp │ │ │ ├── lunavarbank.h │ │ │ ├── mememu.cpp │ │ │ ├── mememu.h │ │ │ ├── renderop.h │ │ │ ├── renderop_bitmap.cpp │ │ │ ├── renderop_bitmap.h │ │ │ ├── renderop_effect.cpp │ │ │ ├── renderop_effect.h │ │ │ ├── renderop_rect.cpp │ │ │ ├── renderop_rect.h │ │ │ ├── renderop_string.cpp │ │ │ ├── renderop_string.h │ │ │ ├── sprite_component.cpp │ │ │ ├── sprite_component.h │ │ │ ├── sprite_funcs.cpp │ │ │ └── sprite_funcs.h │ │ ├── msg_macros.cpp │ │ ├── msg_macros.h │ │ ├── msg_preprocessor.cpp │ │ └── msg_preprocessor.h │ ├── sorting.cpp │ ├── sorting.h │ ├── sound/ │ │ ├── fx/ │ │ │ ├── fx_common.hpp │ │ │ ├── fx_format.h │ │ │ ├── reverb.cpp │ │ │ ├── reverb.h │ │ │ ├── spc_echo.cpp │ │ │ └── spc_echo.h │ │ ├── snd-src/ │ │ │ ├── message.mid │ │ │ └── sm-glass.mid │ │ ├── sound_msgsnd.cpp │ │ └── sound_msgsnd.h │ ├── sound.cpp │ ├── sound.h │ ├── sound_spatial.cpp │ ├── sound_thread.cpp │ ├── sound_thread.h │ ├── std_picture.cpp │ ├── std_picture.h │ ├── video.h │ └── xt_color.h ├── test/ │ ├── CI-tests.py │ ├── CMakeLists.txt │ ├── common/ │ │ ├── catch_amalgamated.cpp │ │ └── catch_amalgamated.hpp │ ├── levels/ │ │ ├── Beech kick 1.lvl │ │ ├── Beech kick 2.lvl │ │ ├── Conveyor hell.lvl │ │ ├── Exits test.lvl │ │ ├── Fall test.lvl │ │ ├── Fence climb Move.lvl │ │ ├── Fence climb.lvl │ │ ├── Player clip - min.lvl │ │ ├── Player clip.lvl │ │ ├── Player through block.lvl │ │ ├── Pockey test.lvl │ │ ├── Raft ride.lvl │ │ ├── Shelf surf.lvl │ │ ├── Shell on spring.lvl │ │ ├── Skull raft 2.lvl │ │ ├── Slope test.lvl │ │ ├── Veggies shoot.lvl │ │ ├── compat.ini │ │ └── doors test.lvl │ ├── test_msg_macro/ │ │ ├── CMakeLists.txt │ │ └── test_msg_macro.cpp │ └── worlds/ │ └── speedrun-unit-test/ │ ├── unit-test.lvlx │ └── unit-test.wldx ├── utils/ │ ├── convertkit/ │ │ ├── GIFs2PNG/ │ │ │ ├── CMakeLists.txt │ │ │ ├── _resources/ │ │ │ │ ├── cat_gif2png/ │ │ │ │ │ └── make_icns.sh │ │ │ │ ├── cat_gif2png.icns │ │ │ │ ├── gifs2png.qrc │ │ │ │ └── gifs2png.rc │ │ │ ├── common_features/ │ │ │ │ ├── config_manager.cpp │ │ │ │ └── config_manager.h │ │ │ ├── gifs2png.cpp │ │ │ ├── version.cmake │ │ │ └── version.h │ │ ├── assets-convert-homebrew.py │ │ ├── audio_convert_16m.py │ │ ├── convert_plr.py │ │ ├── dist/ │ │ │ ├── gameinfo.ini │ │ │ ├── gfx-convert-lin.sh │ │ │ ├── gfx-convert-win.cmd │ │ │ ├── music.ini │ │ │ ├── sounds.ini │ │ │ └── thextech.ini │ │ ├── exe2ui/ │ │ │ ├── exe2ui.c │ │ │ └── exe2ui_build.sh │ │ ├── gfx-convert-16m.py │ │ ├── gfx-convert-3ds.py │ │ └── gfx-convert-wii.py │ ├── submodule-update.sh │ └── update-copyright.sh ├── version.cmake └── version.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .appveyor.yml ================================================ version: build-{build} environment: global: PLATFORMTOOLSET: "v140" CMAKEPREFIXPATH: "." SEVENZIP: "C:\\Program Files\\7-Zip\\7z.exe" WGET_BIN: "C:\\msys64\\usr\\bin\\wget.exe" APPVEYOR_BUILD_WORKER_IMAGE: "Visual Studio 2015" matrix: - BUILD_TYPE: MinSizeRel COMPILER: MinGW-w32 COMPILER_FAMILY: MinGW GENERATOR: "MinGW Makefiles" PLATFORM: Win32 CMAKEPREFIXPATH: "C:/Qt/Tools/mingw730_32" TOOLCHAIN_BIN: "C:\\Qt\\Tools\\mingw730_32\\bin" - BUILD_TYPE: MinSizeRel COMPILER: MinGW-w64 COMPILER_FAMILY: MinGW GENERATOR: "MinGW Makefiles" PLATFORM: x64 CMAKEPREFIXPATH: "C:/Qt/Tools/mingw730_64" TOOLCHAIN_BIN: "C:\\Qt\\Tools\\mingw730_64\\bin" build_script: - git submodule init - git submodule update - md b - cd b - if NOT [%TOOLCHAIN_BIN%]==[] set PATH=%TOOLCHAIN_BIN%;%PATH:C:\Program Files\Git\usr\bin;=% - cmake -G "%GENERATOR%" -DCPACK_PACKAGE_FILE_NAME=thextech-%APPVEYOR_REPO_BRANCH%-%COMPILER%-%BUILD_TYPE%-%PLATFORM% -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DCMAKE_PREFIX_PATH=%CMAKEPREFIXPATH% .. - cmake --build . --config %BUILD_TYPE% -- -j 2 - cpack .. - appveyor PushArtifact "thextech-%APPVEYOR_REPO_BRANCH%-%COMPILER%-%BUILD_TYPE%-%PLATFORM%.7z" deploy: - provider: Environment name: WohlnetFTP #on_finish: # - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) ================================================ FILE: .gitattributes ================================================ changelog.txt text eol=crlf LICENSE text eol=crlf README.md text eol=crlf *.lvl text eol=crlf *.wld text eol=crlf *.lvlx text eol=lf *.wldx text eol=lf ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.yml ================================================ name: Bug Report description: Create a report of the issue ocurred with the game during its use labels: ["NEW-BUG"] projects: ["TheXTech/1"] body: - type: textarea id: description attributes: label: Describe the issue description: A clear and concise description of what the bug. validations: required: true - type: dropdown id: bug_type attributes: label: A type of bug description: | How you would classify this bug by yourself? * **Native bug** - The own bug of TheXTech itself that was never happen in the original SMBX. * **Vanilla bug** - The bug of original SMBX that got being inherited by TheXTech during its initial creation. * **Compatibility bug** - The deviation of behaviour from the original even with the strict compatibility mode (run the game with `--compat-level smbx13` command-line argument). **Note:** The development team can classify this issue in a different way depending on the result of the analysis. options: - [Not specified] - Native bug - Vanilla bug - Compatibility bug validations: required: true - type: dropdown id: frequency attributes: label: Does this issue happens always or randomly? description: Is it possible to reproduce this issue easily, or it happens very rare? options: - Always happens - Happens randomly, often - Happens randomly, rare - Happens very rare validations: required: true - type: input id: version attributes: label: Version description: What version of TheXtech you are using? You can see it in the title of the window. validations: required: true - type: input id: version_hash attributes: label: Version Hash description: If you run a **DEVEL** version, please also tell the hash tag (like `#1A2BC3E`) that can be found in the window title or in the main menu at the right-bottom of the screen. validations: required: false - type: dropdown id: os attributes: label: Platform description: What the platform where you run the game? options: - [Not specified] - Linux - Windows - macOS - Android - Haiku - xBSD - Web-browser (Emscripten) - 3DS - Wii - Wii U - Switch - PS Vita - Other (tell in the bug description) validations: required: true - type: dropdown id: cpu attributes: label: Processor architecture description: | Which architecture your processor where you ran the game have? * If you run the **Windows**, you can easily check your architecture (x86 32-bit, 64-bit, or "64-bit processor ARM"), you can open the properties of the "This computer" / "My computer". * On **Linux** / **xBSD**, you can open the terminal and run the `uname -m` command to get the exact processor architecture. * On **macOS**, just can open the "About this Mac" to see the architecture in the description. If you see in description such as 32-bit "Power PC" (PPC G3, PPC G4) or PPC G5 which is 64-bit Power PC, **Intel Core Solo, Core Duo (not 2 Duo!)** which are 32-bit i386, **Intel Core 2 Duo, Quad-Core, i3/i5/i7** which are 64-bit x86_64, and the **Apple M1/M2/M3/etc.** which is an ARM64). * On **Android** you might want to use program such as AIDA64 where you can open the "CPU" list, and find the "Instructions set" paragraph where you can see exact processor architecture that your device runs. **Note:** Some platforms do have one single architecture only, for example: * **PSP** has the MIPS32. * **3DS** and **Vita** has the ARM32/ARMv7. * **Wii** and **Wii U** has the PPC32. * **Switch** has the ARM64/AARCH64. options: - [Not specified / I don't know] - x86_64 / x64 (64-bit x86) - i386 / x86_32 (32-bit x86) - ARM64 / AARCH64 (64-bit ARM) - ARM32 / ARMv7 (32-bit ARM) - PPC64LE (64-bit Power PC with Little-Endian) - PPC64 (64-bit Power PC with Big-Endian) - PPC32 (32-bit Power PC with Big-Endian) - MIPS64 (32-bit MIPS) - MIPS32 (64-bit MIPS) - RISC-V - WebAssembly (Emscripten) - Other (tell in the bug description) validations: required: false - type: textarea id: log_file attributes: label: Log file (if presented) description: | Please upload the log file that have the `TheXTech_log_YYYY_MM_DD_HH_mm_ss.txt` name which can be found in the `logs` sub-directory in the game's user directory. validations: required: false - type: textarea id: example_case attributes: label: Example Case description: | Post a link to the level where the issue occurs: *levelname.lvl* from `[Episode name](https://link.to.episode/)` Even better: If you make a dedicated test level and attach it to your GitHub Post (pack it as `.zip` first, and then drag it and drop into this text area). validations: required: false - type: textarea id: recording attributes: label: Recording description: Embed or add a link to a Recording here. If it's an in-game issue, you can use TheXTech's built-in GIF Recorder (Press F11 or F10 on macOS). If it's a crash, use a Screen Capture program such as OBS or vokoscreen. validations: required: false - type: textarea id: vanilla_recording attributes: label: Vanilla Recording description: Not required, but if you can run SMBX 1.3 (or [use our Research Version](https://github.com/Wohlstand/smbx-experiments/releases) that will work better on Linux under Wine and has the built-in GIF recorder using the F11 key), it would be appreciated if you made a recording of whether the bug occurs here as well. validations: required: false - type: textarea id: misc_info attributes: label: Additional context description: Add any other context that could be useful for solving the problem here. If your problem is tied to your Operating System, add it at the beginning of the Title ("[Windows] Windows Issue"). validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/compile-fail-report.yml ================================================ name: Build/Compilation failure report description: Create a report of the problems to compile the game from the source code labels: ["NEW-BUG"] projects: ["TheXTech/1"] body: - type: textarea id: description attributes: label: Describe the issue description: A clear and concise description of what the bug. validations: required: true - type: input id: version attributes: label: Version description: What version of TheXtech you are using? Do you trying to build the stable version or the latest state of the GIT? validations: required: true - type: input id: version_hash attributes: label: Version Hash description: If you build the game from the source source code clonned from the GIT, retrieve the hash of the commit where your current state is. validations: required: false - type: dropdown id: os_host attributes: label: Build platform description: What the platform where you build the game? options: - [Not specified] - Linux - Windows - macOS - Android - Haiku - xBSD - Other (tell in the bug description) validations: required: true - type: dropdown id: os_target attributes: label: Target platform description: For which platform you trying to build the game? options: - [Not specified] - Linux - Windows - macOS - Android - Haiku - xBSD - Web-browser (Emscripten) - 3DS - Wii - Wii U - Switch - PS Vita - Other (tell in the bug description) validations: required: true - type: dropdown id: cpu attributes: label: Target processor architecture description: Which target architecture for which you attempted to build the game? options: - [Not specified] - x86_64 / x64 (64-bit x86) - i386 / x86_32 (32-bit x86) - ARM64 / AARCH64 (64-bit ARM) - ARM32 / ARMv7 (32-bit ARM) - PPC64LE (64-bit Power PC with Little-Endian) - PPC64 (64-bit Power PC with Big-Endian) - PPC32 (32-bit Power PC with Big-Endian) - MIPS64 (32-bit MIPS) - MIPS32 (64-bit MIPS) - RISC-V - Other (tell in the bug description) validations: required: true - type: dropdown id: compiler attributes: label: Compiler name description: What the compiler you used to build the game? options: - [Not specified] - GCC - Clang - MSVC - Intel-CC - ARM-CC - Other (tell in the bug description) validations: required: true - type: textarea id: build_log attributes: label: Build log description: | Please save your full build output log into a text file and upload it here. validations: required: false - type: textarea id: build_commands attributes: label: What commands you typed in the terminal or what you did in your IDE? description: | Explain step-by-step what you did before you got an error. validations: required: false - type: textarea id: misc_info attributes: label: Additional context description: Add any other context that could be useful for solving the problem here. If your problem is tied to your Operating System, add it at the beginning of the Title ("[Windows] Windows Issue"). validations: required: false ================================================ FILE: .github/ci-helper/.gitignore ================================================ .idea/ ================================================ FILE: .github/ci-helper/create-dmg.sh ================================================ #!/bin/bash # Create a read-only disk image of the conkmtents of a folder set -e function pure_version() { echo '1.0.1.0' } function version() { echo "create-dmg $(pure_version)" } function usage() { version echo "Creates a fancy DMG file." echo "Usage: $(basename $0) options... image.dmg source_folder" echo "All contents of source_folder will be copied into the disk image." echo "Options:" echo " --volname name" echo " set volume name (displayed in the Finder sidebar and window title)" echo " --volicon icon.icns" echo " set volume icon" echo " --background pic.png" echo " set folder background image (provide png, gif, jpg)" echo " --window-pos x y" echo " set position the folder window" echo " --window-size width height" echo " set size of the folder window" echo " --icon-size icon_size" echo " set window icons size (up to 128)" echo " --icon file_name x y" echo " set position of the file's icon" echo " --hide-extension file_name" echo " hide the extension of file" echo " --custom-icon file_name custom_icon_or_sample_file x y" echo " set position and custom icon" echo " --app-drop-link x y" echo " make a drop link to Applications, at location x,y" echo " --eula eula_file" echo " attach a license file to the dmg" echo " --no-internet-enable" echo " disable automatic mount©" echo " --subfolder" echo " put all content of source folder into sub-folder" echo " --version show tool version number" echo " -h, --help display this help" exit 0 } WINX=10 WINY=60 WINW=500 WINH=350 ICON_SIZE=128 while test "${1:0:1}" = "-"; do case $1 in --volname) VOLUME_NAME="$2" shift shift ;; --volicon) VOLUME_ICON_FILE="$2" shift shift ;; --background) BACKGROUND_FILE="$2" BACKGROUND_FILE_NAME="$(basename $BACKGROUND_FILE)" BACKGROUND_CLAUSE="set background picture of opts to file \".background:$BACKGROUND_FILE_NAME\"" shift shift ;; --icon-size) ICON_SIZE="$2" shift shift ;; --window-pos) WINX=$2 WINY=$3 shift shift shift ;; --window-size) WINW=$2 WINH=$3 shift shift shift ;; --icon) POSITION_CLAUSE="${POSITION_CLAUSE}set position of item \"$2\" to {$3, $4} " shift shift shift shift ;; --hide-extension) HIDING_CLAUSE="${HIDING_CLAUSE}set the extension hidden of item \"$2\" to true" shift shift ;; --custom-icon) shift shift shift shift shift ;; -h | --help) usage ;; --version) version exit 0 ;; --pure-version) pure_version exit 0 ;; --app-drop-link) APPLICATION_LINK=$2 APPLICATION_CLAUSE="set position of item \"Applications\" to {$2, $3} " shift shift shift ;; --eula) EULA_RSRC=$2 shift shift ;; --no-internet-enable) NOINTERNET=1 shift ;; --subfolder) DOSUBFOLDER=1 shift ;; -*) echo "Unknown option $1. Run with --help for help." exit 1 ;; esac done test -z "$2" && { echo "Not enough arguments. Invoke with --help for help." exit 1 } DMG_PATH="$1" DMG_DIRNAME="$(dirname "$DMG_PATH")" DMG_DIR="$( cd $DMG_DIRNAME >/dev/null pwd )" DMG_NAME="$(basename "$DMG_PATH")" DMG_TEMP_NAME="$DMG_DIR/rw.${DMG_NAME}" SRC_FOLDER="$( cd "$2" >/dev/null pwd )" test -z "$VOLUME_NAME" && VOLUME_NAME="$(basename "$DMG_PATH" .dmg)" AUX_PATH="$(dirname $0)/support" test -d "$AUX_PATH" || { echo "Cannot find support directory: $AUX_PATH" exit 1 } if [[ $DOSUBFOLDER == 1 ]]; then SRC_FOLDER_TEMP="${SRC_FOLDER}_tmp/" SRC_FOLDER_SF="${SRC_FOLDER}_tmp/${SRC_FOLDER##*/}" echo "Generating subfolder $SRC_FOLDER_SF..." mkdir -p "$SRC_FOLDER_SF" cp -a "$SRC_FOLDER" "$SRC_FOLDER_SF" SRC_FOLDER="$SRC_FOLDER_SF" fi if [ -f "$SRC_FOLDER/.DS_Store" ]; then echo "Deleting any .DS_Store in source folder" rm "$SRC_FOLDER/.DS_Store" fi # Create the image echo "Creating disk image..." test -f "${DMG_TEMP_NAME}" && rm -f "${DMG_TEMP_NAME}" ACTUAL_SIZE=$(du -sm "$SRC_FOLDER" | sed -e 's/ .*//g') DISK_IMAGE_SIZE=$(expr $ACTUAL_SIZE + 20) hdiutil create -srcfolder "$SRC_FOLDER" -volname "${VOLUME_NAME}" -fs HFS+ -fsargs "-c c=64,a=16,e=16" -format UDRW -size ${DISK_IMAGE_SIZE}m "${DMG_TEMP_NAME}" # mount it echo "Mounting disk image..." MOUNT_DIR="/Volumes/${VOLUME_NAME}" # try unmount dmg if it was mounted previously (e.g. developer mounted dmg, installed app and forgot to unmount it) echo "Unmounting disk image..." DEV_NAME=$(hdiutil info | egrep '^/dev/' | sed 1q | awk '{print $1}') test -d "${MOUNT_DIR}" && hdiutil detach "${DEV_NAME}" echo "Mount directory: $MOUNT_DIR" DEV_NAME=$(hdiutil attach -readwrite -noverify -noautoopen "${DMG_TEMP_NAME}" | egrep '^/dev/' | sed 1q | awk '{print $1}') echo "Device name: $DEV_NAME" if ! test -z "$BACKGROUND_FILE"; then echo "Copying background file..." test -d "$MOUNT_DIR/.background" || mkdir "$MOUNT_DIR/.background" cp "$BACKGROUND_FILE" "$MOUNT_DIR/.background/$BACKGROUND_FILE_NAME" fi if ! test -z "$APPLICATION_LINK"; then echo "making link to Applications dir" echo $MOUNT_DIR ln -s /Applications "$MOUNT_DIR/Applications" fi if ! test -z "$VOLUME_ICON_FILE"; then echo "Copying volume icon file '$VOLUME_ICON_FILE'..." cp "$VOLUME_ICON_FILE" "$MOUNT_DIR/.VolumeIcon.icns" SetFile -c icnC "$MOUNT_DIR/.VolumeIcon.icns" fi # run applescript APPLESCRIPT=$(mktemp -t createdmg.XXXXXXX) cat "$AUX_PATH/template.applescript" | sed -e "s/WINX/$WINX/g" -e "s/WINY/$WINY/g" -e "s/WINW/$WINW/g" -e "s/WINH/$WINH/g" -e "s/BACKGROUND_CLAUSE/$BACKGROUND_CLAUSE/g" -e "s/ICON_SIZE/$ICON_SIZE/g" | perl -pe "s/POSITION_CLAUSE/$POSITION_CLAUSE/g" | perl -pe "s/APPLICATION_CLAUSE/$APPLICATION_CLAUSE/g" | perl -pe "s/HIDING_CLAUSE/$HIDING_CLAUSE/" >"$APPLESCRIPT" echo "Running Applescript: /usr/bin/osascript \"${APPLESCRIPT}\" \"${VOLUME_NAME}\"" "/usr/bin/osascript" "${APPLESCRIPT}" "${VOLUME_NAME}" || true echo "Done running the applescript..." sleep 4 rm "$APPLESCRIPT" # make sure it's not world writeable echo "Fixing permissions..." chmod -Rf go-w "${MOUNT_DIR}" &>/dev/null || true echo "Done fixing permissions." # make the top window open itself on mount: echo "Blessing started" bless --folder "${MOUNT_DIR}" --openfolder "${MOUNT_DIR}" echo "Blessing finished" if ! test -z "$VOLUME_ICON_FILE"; then # tell the volume that it has a special file attribute SetFile -a C "$MOUNT_DIR" fi # unmount echo "Unmounting disk image..." hdiutil detach "${DEV_NAME}" # compress image echo "Compressing disk image..." hdiutil convert "${DMG_TEMP_NAME}" -format UDBZ -o "${DMG_DIR}/${DMG_NAME}" rm -f "${DMG_TEMP_NAME}" # adding EULA resources if [ ! -z "${EULA_RSRC}" -a "${EULA_RSRC}" != "-null-" ]; then echo "adding EULA resources" "${AUX_PATH}/dmg-license3.py" "${DMG_DIR}/${DMG_NAME}" "${EULA_RSRC}" fi if [ ! -z "${NOINTERNET}" -a "${NOINTERNET}" == 1 ]; then echo "not setting 'internet-enable' on the dmg" else hdiutil internet-enable -yes "${DMG_DIR}/${DMG_NAME}" fi # Removing temporary folder if [[ $DOSUBFOLDER == 1 ]]; then rm -Rf ${SRC_FOLDER_TEMP} fi echo "Disk image done" exit 0 ================================================ FILE: .github/ci-helper/pack-game-macos.sh ================================================ #!/bin/bash # $1 assets; $2 icon $3 bundle name ASSETS_NAME=$1 ICON_FILE=$2 BUNDLE_NAME=$3 ARCHIVE_NAME=$4 if [[ "${ASSETS_NAME}" != "none" ]]; then echo "Preparing the application..." cp -R TheXTech.app "tmpapp" cp -R $ASSETS_NAME/* "tmpapp/Contents/Resources/assets/" find tmpapp -name ".DS_Store" -delete plutil -replace CFBundleName -string "$BUNDLE_NAME" "tmpapp/Contents/Info.plist" plutil -replace CFBundleIconFile -string "$ICON_FILE" "tmpapp/Contents/Info.plist" mv tmpapp "$BUNDLE_NAME.app" fi mkdir dmg-root cp LICENSE "dmg-root/License.TheXTech.txt" cp README.md "dmg-root/ReadMe.txt" cp README.RUS.md "dmg-root/ReadMe.RUS.txt" cp README.ESP.md "dmg-root/ReadMe.ESP.txt" echo "== mv \"$BUNDLE_NAME.app\" dmg-root/" mv "$BUNDLE_NAME.app" dmg-root/ if [[ "${ASSETS_NAME}" == "none" ]]; then echo "Creating ZIP..." cd dmg-root zip -9 -r ../${ARCHIVE_NAME} * cd .. else echo "Creating DMG..." ./.github/ci-helper/create-dmg.sh \ --volname "$BUNDLE_NAME" \ --window-size 800 600 \ --app-drop-link 450 320 \ --no-internet-enable \ "$ARCHIVE_NAME" \ "dmg-root/" fi echo "Cleaning up..." rm -Rf dmg-root printf "\n----------------------------------------------------------------\n\n" ================================================ FILE: .github/ci-helper/pack-game.sh ================================================ #!/bin/bash RUNNER_OS=$1 SUBDIR_NAME=$2 EXECUTABLE_NAME=$3 ARCHIVE_NAME="$4" ASSETS_NAME=$5 cd build mkdir -p "package/${SUBDIR_NAME}" cp ../changelog.txt "package/${SUBDIR_NAME}/" cp ../LICENSE "package/${SUBDIR_NAME}/License.TheXTech.txt" cp ../README.md "package/${SUBDIR_NAME}/ReadMe.txt" cp ../README.RUS.md "package/${SUBDIR_NAME}/ReadMe.RUS.txt" cp ../README.ESP.md "package/${SUBDIR_NAME}/ReadMe.ESP.txt" if [[ "${RUNNER_OS}" == "Windows" ]]; then cp output/bin/thextech.exe "package/${SUBDIR_NAME}/${EXECUTABLE_NAME}.exe" cp output/bin/*.dll "package/${SUBDIR_NAME}/" elif [[ "${RUNNER_OS}" == "Linux" ]]; then cp output/bin/thextech "package/${SUBDIR_NAME}/${EXECUTABLE_NAME}" fi if [[ "${ASSETS_NAME}" != "none" ]]; then cp -r ../${ASSETS_NAME}/* "package/${SUBDIR_NAME}/" fi cd package if [[ "${RUNNER_OS}" == "Windows" ]]; then 7z a "${ARCHIVE_NAME}.7z" "${SUBDIR_NAME}" else tar -cvzf "${ARCHIVE_NAME}.tar.gz" "${SUBDIR_NAME}" fi rm -Rf "${SUBDIR_NAME}" cd ../.. ================================================ FILE: .github/ci-helper/support/dmg-license.py ================================================ #! /usr/bin/env python """ This script adds a license file to a DMG. Requires Xcode and a plain ascii text license file. Obviously only runs on a Mac. Copyright (C) 2011 Jared Hobbs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ import os import sys import tempfile import optparse class Path(str): def __enter__(self): return self def __exit__(self, in_type, value, traceback): os.unlink(self) def mktemp(dir=None, suffix=''): (fd, filename) = tempfile.mkstemp(dir=dir, suffix=suffix) os.close(fd) return Path(filename) def main(in_options, in_args): dmg_file, license_file = in_args with mktemp('.') as tmpFile: with open(tmpFile, 'w') as f: f.write("""data 'LPic' (5000) { $"0002 0011 0003 0001 0000 0000 0002 0000" $"0000 000E 0006 0001 0005 0007 0000 0007" $"0008 0000 0047 0009 0000 0034 000A 0001" $"0035 000B 0001 0020 000C 0000 0011 000D" $"0000 005B 0004 0000 0033 000F 0001 000C" $"0010 0000 000B 000E 0000" };\n\n""") with open(license_file, 'r') as l: f.write('data \'TEXT\' (5002, "English") {\n') for line in l: if len(line) < 1000: f.write(' "' + line.strip().replace('"', '\\"') + '\\n"\n') else: for liner in line.split('.'): f.write(' "' + liner.strip().replace('"', '\\"') + '. \\n"\n') f.write('};\n\n') f.write("""resource 'STR#' (5002, "English") { { "English", "Agree", "Disagree", "Print", "Save...", "IMPORTANT - By clicking on the \\"Agree\\" button, you agree " "to be bound by the terms of the License Agreement.", "Software License Agreement", "This text cannot be saved. This disk may be full or locked, or the " "file may be locked.", "Unable to print. Make sure you have selected a printer." } };""") os.system('/usr/bin/hdiutil unflatten -quiet "%s"' % dmg_file) os.system('%s "%s/"*.r %s -a -o "%s"' % (in_options.rez, in_options.flat_carbon, tmpFile, dmg_file)) os.system('/usr/bin/hdiutil flatten -quiet "%s"' % dmg_file) if in_options.compression is not None: os.system('cp %s %s.temp.dmg' % (dmg_file, dmg_file)) os.remove(dmg_file) if in_options.compression == "bz2": os.system('hdiutil convert %s.temp.dmg -format UDBZ -o %s' % (dmg_file, dmg_file)) elif in_options.compression == "gz": os.system('hdiutil convert %s.temp.dmg -format ' % dmg_file + 'UDZO -imagekey zlib-devel=9 -o %s' % dmg_file) os.remove('%s.temp.dmg' % dmg_file) print "Successfully added license to '%s'" % dmg_file if __name__ == '__main__': parser = optparse.OptionParser() parser.set_usage("""%prog [OPTIONS] This program adds a software license agreement to a DMG file. It requires Xcode and a plain ascii text . See --help for more details.""") parser.add_option( '--rez', '-r', action='store', default='/Applications/Xcode.app/Contents/Developer/Tools/Rez', help='The path to the Rez tool. Defaults to %default' ) parser.add_option( '--flat-carbon', '-f', action='store', default='/Applications/Xcode.app/Contents/Developer/Platforms' '/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk' '/Developer/Headers/FlatCarbon', help='The path to the FlatCarbon headers. Defaults to %default' ) parser.add_option( '--compression', '-c', action='store', choices=['bz2', 'gz'], default=None, help='Optionally compress dmg using specified compression type. ' 'Choices are bz2 and gz.' ) options, args = parser.parse_args() cond = len(args) != 2 or not os.path.exists(options.rez) \ or not os.path.exists(options.flat_carbon) if cond: parser.print_usage() sys.exit(1) main(options, args) ================================================ FILE: .github/ci-helper/support/dmg-license3.py ================================================ #! /usr/bin/env python3 """ This script adds a license file to a DMG. Requires Xcode and a plain ascii text license file. Obviously only runs on a Mac. Copyright (C) 2011 Jared Hobbs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ import os import sys import tempfile import optparse class Path(str): def __enter__(self): return self def __exit__(self, in_type, value, traceback): os.unlink(self) def mktemp(dirname=None, suffix=''): (fd, filename) = tempfile.mkstemp(dir=dirname, suffix=suffix) os.close(fd) return Path(filename) def main(in_options, in_args): dmg_file, license_file = in_args with mktemp('.') as tmpFile: with open(tmpFile, 'w') as f: f.write("""data 'LPic' (5000) { $"0002 0011 0003 0001 0000 0000 0002 0000" $"0000 000E 0006 0001 0005 0007 0000 0007" $"0008 0000 0047 0009 0000 0034 000A 0001" $"0035 000B 0001 0020 000C 0000 0011 000D" $"0000 005B 0004 0000 0033 000F 0001 000C" $"0010 0000 000B 000E 0000" };\n\n""") with open(license_file, 'r') as l: f.write('data \'TEXT\' (5002, "English") {\n') for line in l: if len(line) < 1000: f.write(' "' + line.strip().replace('"', '\\"') + '\\n"\n') else: for liner in line.split('.'): f.write(' "' + liner.strip().replace('"', '\\"') + '. \\n"\n') f.write('};\n\n') f.write("""resource 'STR#' (5002, "English") { { "English", "Agree", "Disagree", "Print", "Save...", "IMPORTANT - By clicking on the \\"Agree\\" button, you agree " "to be bound by the terms of the License Agreement.", "Software License Agreement", "This text cannot be saved. This disk may be full or locked, or the " "file may be locked.", "Unable to print. Make sure you have selected a printer." } };""") os.system('/usr/bin/hdiutil unflatten -quiet "%s"' % dmg_file) os.system('%s "%s/"*.r %s -a -o "%s"' % (in_options.rez, in_options.flat_carbon, tmpFile, dmg_file)) os.system('/usr/bin/hdiutil flatten -quiet "%s"' % dmg_file) if in_options.compression is not None: os.system('cp %s %s.temp.dmg' % (dmg_file, dmg_file)) os.remove(dmg_file) if in_options.compression == "bz2": os.system('hdiutil convert %s.temp.dmg -format UDBZ -o %s' % (dmg_file, dmg_file)) elif in_options.compression == "gz": os.system('hdiutil convert %s.temp.dmg -format ' % dmg_file + 'UDZO -imagekey zlib-devel=9 -o %s' % dmg_file) os.remove('%s.temp.dmg' % dmg_file) print("Successfully added license to '%s'" % dmg_file) if __name__ == '__main__': parser = optparse.OptionParser() parser.set_usage("""%prog [OPTIONS] This program adds a software license agreement to a DMG file. It requires Xcode and a plain ascii text . See --help for more details.""") parser.add_option( '--rez', '-r', action='store', default='/Applications/Xcode.app/Contents/Developer/Tools/Rez', help='The path to the Rez tool. Defaults to %default' ) parser.add_option( '--flat-carbon', '-f', action='store', default='/Applications/Xcode.app/Contents/Developer/Platforms' '/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk' '/Developer/Headers/FlatCarbon', help='The path to the FlatCarbon headers. Defaults to %default' ) parser.add_option( '--compression', '-c', action='store', choices=['bz2', 'gz'], default=None, help='Optionally compress dmg using specified compression type. ' 'Choices are bz2 and gz.' ) options, args = parser.parse_args() cond = len(args) != 2 or not os.path.exists(options.rez) \ or not os.path.exists(options.flat_carbon) if cond: parser.print_usage() sys.exit(1) main(options, args) ================================================ FILE: .github/ci-helper/support/template.applescript ================================================ on run (volumeName) tell application "Finder" tell disk (volumeName as string) open set theXOrigin to WINX set theYOrigin to WINY set theWidth to WINW set theHeight to WINH set theBottomRightX to (theXOrigin + theWidth) set theBottomRightY to (theYOrigin + theHeight) set dsStore to "\"" & "/Volumes/" & volumeName & "/" & ".DS_STORE\"" tell container window set current view to icon view set toolbar visible to false set statusbar visible to false set the bounds to {theXOrigin, theYOrigin, theBottomRightX, theBottomRightY} set statusbar visible to false end tell set opts to the icon view options of container window tell opts set icon size to ICON_SIZE set arrangement to not arranged end tell BACKGROUND_CLAUSE -- Positioning POSITION_CLAUSE -- Hiding HIDING_CLAUSE -- Application Link Clause APPLICATION_CLAUSE close open update without registering applications -- Force saving of the size delay 1 tell container window set statusbar visible to false set the bounds to {theXOrigin, theYOrigin, theBottomRightX - 10, theBottomRightY - 10} end tell update without registering applications end tell delay 1 tell disk (volumeName as string) tell container window set statusbar visible to false set the bounds to {theXOrigin, theYOrigin, theBottomRightX, theBottomRightY} end tell update without registering applications end tell --give the finder some time to write the .DS_Store file delay 3 set waitTime to 0 set ejectMe to false repeat while ejectMe is false delay 1 set waitTime to waitTime + 1 if (do shell script "[ -f " & dsStore & " ]; echo $?") = "0" then set ejectMe to true end repeat log "waited " & waitTime & " seconds for .DS_STORE to be created." end tell end run ================================================ FILE: .github/ci-helper/translate_patcher.py ================================================ #!/usr/bin/python3 import json import sys import optparse def merge_jsons(my_dict, output): for k, v in my_dict.items(): if isinstance(v, dict): if k not in output: output[k] = dict() merge_jsons(v, output[k]) continue output[k] = v def main(in_args): in_file = in_args[0] patch_file = in_args[1] out_file = in_args[2] with open(in_file, encoding='utf-8') as first_file: json_out = json.load(first_file) with open(patch_file, encoding='utf-8') as second_file: json_in = json.load(second_file) print("-- Patching translation file: %s\n" "-- By the patch file: %s\n" "-- Writing result into the %s" % (in_file, patch_file, out_file)) merge_jsons(json_in, json_out) with open(out_file, 'w', encoding='utf-8') as f: json.dump(json_out, f, ensure_ascii=False, indent=4) print("Completed!") if __name__ == '__main__': parser = optparse.OptionParser() parser.set_usage("""%prog . See --help for more details.""") options, args = parser.parse_args() cond = len(args) != 3 if cond: parser.print_usage() sys.exit(1) main(args) ================================================ FILE: .github/ci-helper/translate_sync_assets.sh ================================================ #!/bin/bash OLD=$PWD GIT_ROOT=$PWD/build-git WORKDIR="$PWD/.github/ci-helper" mkdir -p build-git function update_repo() { q=$1 branch=$2 if [[ "$branch" != "" ]]; then branch_command="-b $branch" branch_display="[Branch $branch] " fi echo "=============================================================" echo "================ $q $branch_display================" echo "=============================================================" cd "${GIT_ROOT}" git clone "$q" --depth 1 $branch_command repo cd "${WORKDIR}" bash translate_update.sh "${GIT_ROOT}/repo" cd "${GIT_ROOT}/repo" if [[ ! -z $(git status -s) ]]; then echo "-- Found updated languages, commiting..." git add --all git commit --author="${GIT_AUTHOR}" -m "Synchronized translations" git push else echo "-- No updates found, skipping..." fi cd .. rm -Rf repo printf "=============================================================\n\n\n" } # FIXME: Replace this hardcoded list with a server-side one to don't re-commit this list # every time for q in \ "git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-smbx.git" \ "git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-adventures-of-demo.git" \ "git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-convert-kit.git" \ "git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-a2xt-analog-funk.git" \ "git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-a2xt-prelude-to-the-stupid.git" \ "git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-lowser-s-conquest-beta.git" \ "git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-sarasaland-adventure-v1-2.git" \ "git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-sarasaland-adventure-2.git" \ "git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-sarasaland-adventure-v4-0.git" \ "git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-smbx-nes.git" \ "git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-smbx-nostalgic.git" \ "git@gitea.wohlsoft.ru:Games-for-TheXTech/thextech-super-talking-time-bros-1n2-v1-5.git" \ ; do update_repo "$q" done # Nostalgic Paradise needs another branch update_repo git@gitea.wohlsoft.ru:Games-for-TheXTech/nostalgic-paradise.git development cd "${OLD}" ================================================ FILE: .github/ci-helper/translate_sync_to_stable.sh ================================================ #!/bin/bash OLD=$PWD GIT_ROOT=$PWD/build-git WORKDIR="$PWD/.github/ci-helper" STABLE_BRANCH=stable-1.3.7.x if [[ -z "$GITHUB_TOKEN" ]]; then echo 'Missing input "github_token: $GITHUB_TOKEN".'; exit 1; fi git clone https://github.com/TheXTech/TheXTech.git --depth 1 -b ${STABLE_BRANCH} build-git # General engine translations cp -av resources/languages/* build-git/resources/languages/ # Android launcher translations cd android-project/thextech/src/main/res for q in values values-*; do if [[ -f "$q/strings.xml" ]]; then cp -v "$q/strings.xml" "${GIT_ROOT}/android-project/thextech/src/main/res/${q}/strings.xml" fi if [[ -f "$q/arrays.xml" ]]; then cp -v "$q/arrays.xml" "${GIT_ROOT}/android-project/thextech/src/main/res/${q}/arrays.xml" fi done cd "$OLD" # Commit all changes that was done cd build-git if [[ ! -z $(git status -s) ]]; then echo "-- Found updated languages, commiting..." git add --all git commit --author="${GIT_AUTHOR}" -m "Synchronized translations with mainstream branch" remote_repo="https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/TheXTech/TheXTech.git" git push "${remote_repo}" ${STABLE_BRANCH} else echo "-- No updates found, skipping..." fi cd .. rm -Rf build-git cd "${OLD}" exit 0 ================================================ FILE: .github/ci-helper/translate_update.sh ================================================ #!/bin/bash OLD_DIR=~+ ASSETS_ROOT=$1 LANGS_IN="$PWD/../../resources/languages" LANGS_PATCH=$1/languages/patches LANGS_OUT=$1/languages # Update only existing files at the target (Don't copy new files, they should be placed manually to add them for the autosync) cd "${LANGS_OUT}" function update_tr_file() { q="$1" if [[ ! -f "${LANGS_PATCH}/$q" ]]; then # If no patch file exists, just copy printf "==== COPY: %s ====\n" $q cp -v "${LANGS_IN}/$q" "${LANGS_OUT}/$q" else # Otherwise, apply the patch printf "==== PATCH: %s ====\n" $q echo python3 "${OLD_DIR}/translate_patcher.py" "${LANGS_IN}/$q" "${LANGS_PATCH}/$q" "${LANGS_OUT}/$q" python3 "${OLD_DIR}/translate_patcher.py" "${LANGS_IN}/$q" "${LANGS_PATCH}/$q" "${LANGS_OUT}/$q" fi printf "\n" } echo "---------------------------------------------" for q in thextech_*.json; do update_tr_file "$q" done for q in assets_*.json; do update_tr_file "$q" done echo "---------------------------------------------" cd "$OLD_DIR" ================================================ FILE: .github/workflows/16m-ci.yml ================================================ name: DSi CI on: push: branches: - main - versus-ci-homebrew pull_request: branches: - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: name: "${{ matrix.config.name }} | ${{ matrix.config.build_type }}" runs-on: ubuntu-latest container: ghcr.io/thextech/wohlnet-ci-ubuntu2404-dkp-vita:latest env: DEVKITPRO: /opt/devkitpro DEVKITARM: /opt/devkitpro/devkitARM strategy: fail-fast: false matrix: config: - { name: "DSi build", extra_options: "-DCMAKE_TOOLCHAIN_FILE=$DEVKITPRO/cmake/NDS.cmake -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON", deps_cmdline: "echo 'DevkitPro SDK is already pre-installed'", generator: "Ninja", build_type: "RelWithDebInfo", executable_name: "thextech", subdir_name: "thextech-dsi", upload_directory: "www/dsi/" } steps: - name: Check for the upload support id: upload-check shell: bash run: | if [[ "${{ secrets.builds_login }}" != '' && \ "${{ secrets.builds_password }}" != '' && \ "${{ secrets.builds_host }}" != '' ]]; then echo "available=true" >> $GITHUB_OUTPUT; else echo "available=false" >> $GITHUB_OUTPUT; fi - name: Install Dependencies shell: bash run: | if [[ ! -z "${{ matrix.config.deps_cmdline }}" ]]; then eval ${{ matrix.config.deps_cmdline }} fi cmake --version - uses: TheXTech/checkout@v0.1 - uses: TheXTech/branch-name@v0.1 - name: Pull submodules shell: bash run: | git submodule update --init --recursive - name: Configure shell: bash run: | cmake -B build -G "${{ matrix.config.generator }}" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} . - name: Build shell: bash run: | cmake --build build --target all -j4 - name: Check codesize and RAM usage if: success() && runner.os == 'Linux' shell: bash run: | size build/output/bin/thextech.elf - name: Create Package if: success() shell: bash run: | cd build mkdir package mkdir "package/${{ matrix.config.subdir_name }}" cp ../changelog.txt "package/${{ matrix.config.subdir_name }}/" cp ../LICENSE "package/${{ matrix.config.subdir_name }}/License.TheXTech.txt" cat ../docs/README_DSI.md ../README.md >> "package/${{ matrix.config.subdir_name }}/README.md" cp thextech.nds "package/${{ matrix.config.subdir_name }}/" cd package zip -9 -r "thextech-calico-${BRANCH_NAME}.zip" "${{ matrix.config.subdir_name }}" rm -Rf "${{ matrix.config.subdir_name }}" cd ../.. - name: Upload artifact if: success() uses: actions/upload-artifact@v4 continue-on-error: true with: path: build/package/*.zip name: ${{ matrix.config.name }} ${{ matrix.config.build_type }} - name: Deploy to builds.wohlsoft.ru if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true' continue-on-error: true shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi UPLOAD_LIST="set ssl:verify-certificate no;" for q in ./build/package/*.zip; do UPLOAD_LIST="${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;" done lftp -e "${UPLOAD_LIST} exit" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }} - name: List Build Directory if: always() shell: bash run: | git status ls -lR build ================================================ FILE: .github/workflows/3ds-ci.yml ================================================ name: 3DS CI on: push: branches: - main - stable* - versus-ci-homebrew pull_request: branches: - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: name: "${{ matrix.config.name }} | ${{ matrix.config.build_type }}" runs-on: ubuntu-latest container: ghcr.io/thextech/wohlnet-ci-ubuntu2404-dkp-vita:latest env: DEVKITPRO: /opt/devkitpro DEVKITARM: /opt/devkitpro/devkitARM strategy: fail-fast: false matrix: config: - { name: "3DS build", extra_options: "-DCMAKE_TOOLCHAIN_FILE=$DEVKITPRO/cmake/3DS.cmake -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON", deps_cmdline: "echo 'DevkitPro SDK is already pre-installed'", generator: "Ninja", build_type: "Release", executable_name: "thextech", subdir_name: "thextech-3ds", upload_directory: "www/3ds/" } steps: - name: Check for the upload support id: upload-check shell: bash run: | if [[ "${{ secrets.builds_login }}" != '' && \ "${{ secrets.builds_password }}" != '' && \ "${{ secrets.builds_host }}" != '' ]]; then echo "available=true" >> $GITHUB_OUTPUT; else echo "available=false" >> $GITHUB_OUTPUT; fi - name: Install Dependencies shell: bash run: | if [[ ! -z "${{ matrix.config.deps_cmdline }}" ]]; then eval ${{ matrix.config.deps_cmdline }} fi cmake --version - uses: TheXTech/checkout@v0.1 - uses: TheXTech/branch-name@v0.1 - name: Pull submodules shell: bash run: | git submodule update --init --recursive - name: Configure shell: bash run: | cmake -B build -G "${{ matrix.config.generator }}" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} . - name: Build shell: bash run: | cmake --build build --target all -j4 - name: Check codesize and RAM usage if: success() && runner.os == 'Linux' shell: bash run: | size build/output/bin/thextech.elf - name: Create Package if: success() shell: bash run: | cd build mkdir package mkdir "package/${{ matrix.config.subdir_name }}" cp ../changelog.txt "package/${{ matrix.config.subdir_name }}/" cp ../LICENSE "package/${{ matrix.config.subdir_name }}/License.TheXTech.txt" cat ../docs/README_3DS.md ../README.md >> "package/${{ matrix.config.subdir_name }}/README.md" cp thextech.3dsx "package/${{ matrix.config.subdir_name }}/" cd package zip -9 -r "thextech-3ds-${BRANCH_NAME}.zip" "${{ matrix.config.subdir_name }}" rm -Rf "${{ matrix.config.subdir_name }}" cd ../.. - name: Upload artifact if: success() uses: actions/upload-artifact@v4 continue-on-error: true with: path: build/package/*.zip name: ${{ matrix.config.name }} ${{ matrix.config.build_type }} - name: Deploy to builds.wohlsoft.ru if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true' continue-on-error: true shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi UPLOAD_LIST="set ssl:verify-certificate no;" for q in ./build/package/*.zip; do UPLOAD_LIST="${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;" done lftp -e "${UPLOAD_LIST} exit" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }} - name: List Build Directory if: always() shell: bash run: | git status ls -lR build ================================================ FILE: .github/workflows/android-ci.yml ================================================ name: Android CI on: push: branches: - main - stable* - versus-ci-android pull_request: branches: - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true defaults: run: working-directory: android-project jobs: build: runs-on: ubuntu-latest name: Build release-apk steps: - uses: TheXTech/checkout@v0.1 - uses: TheXTech/branch-name@v0.1 - name: Pull submodules shell: bash run: | git submodule update --init --recursive - name: Check for the upload support id: upload-check shell: bash run: | if [[ "${{ secrets.builds_login }}" != '' && \ "${{ secrets.builds_password }}" != '' && \ "${{ secrets.builds_host }}" != '' ]]; then echo "available=true" >> $GITHUB_OUTPUT; else echo "available=false" >> $GITHUB_OUTPUT; fi - name: Check for the upload support id: signing-check shell: bash run: | if [[ "${{ secrets.ANDROID_KEYSTORE }}" != '' && \ "${{ secrets.RELEASE_STORE_PASSWORD }}" != '' && \ "${{ secrets.RELEASE_KEY_PASSWORD }}" != '' && \ "${{ secrets.RELEASE_KEY_ALIAS }}" != '' ]]; then echo "available=true" >> $GITHUB_OUTPUT; else echo "available=false" >> $GITHUB_OUTPUT; fi - name: Install Dependencies shell: bash run: | sudo apt-get update -qq sudo apt-get install -qq lftp - name: Set up JDK 21 uses: actions/setup-java@v3.11.0 with: java-version: 21 distribution: 'temurin' - uses: nttld/setup-ndk@v1 with: ndk-version: r23c # IMPORTANT NOTE: The SDK r23b is REQUIRED to support Android 4.1, 4.2, and 4.3, and to support non-Neon hardware - name: Setup Android SDK uses: android-actions/setup-android@v2 # Without NDK not compile and not normal error message. NDK is required #- name: Install NDK # run: echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;23.2.8568313" --sdk_root=${ANDROID_SDK_ROOT} # Some times is have problems with permissions for ./gradle file. Then uncommit it code # - name: Make gradlew executable # run: chmod +x ./gradlew - name: Output version code run: echo VERSION_CODE=${{ github.run_number }} > ./version.properties - name: Import the signing if: ${{ steps.signing-check.outputs.available == 'true' }} run: echo "${{ secrets.ANDROID_KEYSTORE }}" | base64 -d > release-key.jks - name: Build with Gradle if: ${{ steps.signing-check.outputs.available == 'true' }} run: ./gradlew assembleApkReleaseci env: RELEASE_STORE_PASSWORD: ${{ secrets.RELEASE_STORE_PASSWORD }} RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }} RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }} PIN_ALIAS: ${{ secrets.PIN_ALIAS }} DB_PASS_ALIAS: ${{ secrets.DB_PASS_ALIAS }} - name: Build with Gradle (unsigned) if: ${{ steps.signing-check.outputs.available != 'true' }} run: ./gradlew assembleApkRelease env: RELEASE_STORE_PASSWORD: ${{ secrets.RELEASE_STORE_PASSWORD }} RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }} RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }} PIN_ALIAS: ${{ secrets.PIN_ALIAS }} DB_PASS_ALIAS: ${{ secrets.DB_PASS_ALIAS }} - name: Rename APK if: ${{ steps.signing-check.outputs.available == 'true' }} shell: bash run: | mv thextech/build/outputs/apk/apk/releaseci/thextech-apk-releaseci.apk thextech-android-${BRANCH_NAME}.apk - name: Rename APK (unsigned) if: ${{ steps.signing-check.outputs.available != 'true' }} shell: bash run: | mv thextech/build/outputs/apk/apk/release/thextech-apk-release-unsigned.apk thextech-android-${BRANCH_NAME}-unsigned.apk - name: Upload APK if: success() && ${{ steps.signing-check.outputs.available == 'true' }} uses: actions/upload-artifact@v4 with: name: thextech-android-${BRANCH_NAME} path: android-project/thextech-android-*.apk - name: Upload APK (unsigned) if: success() && ${{ steps.signing-check.outputs.available != 'true' }} uses: actions/upload-artifact@v4 continue-on-error: true with: name: thextech-android-${BRANCH_NAME}-unsigned path: android-project/thextech-android-*.apk - name: Deploy to builds.wohlsoft.ru if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true' continue-on-error: true shell: bash run: | UPLOAD_LIST="set ssl:verify-certificate no;" if [[ "${{ steps.signing-check.outputs.available }}" == 'true' ]]; then UPLOAD_LIST="${UPLOAD_LIST} put -O "www/android/" ./thextech-android-${BRANCH_NAME}.apk;" else UPLOAD_LIST="${UPLOAD_LIST} put -O "www/android/" ./thextech-android-${BRANCH_NAME}-unsigned.apk;" fi lftp -e "${UPLOAD_LIST} exit" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }} - name: List Build Directory if: always() shell: bash run: | git status ls -lR . ================================================ FILE: .github/workflows/android-fdroid-ci.yml ================================================ name: Android (F-Droid) CI on: workflow_dispatch: push: branches: - release-1.3.7-fdroid concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true defaults: run: working-directory: android-project jobs: build: runs-on: ubuntu-latest name: Build release-apk steps: - uses: TheXTech/checkout@v0.1 - uses: TheXTech/branch-name@v0.1 - name: Pull submodules shell: bash run: | git submodule update --init --recursive - name: Check for the upload support id: upload-check shell: bash run: | if [[ "${{ secrets.builds_login }}" != '' && \ "${{ secrets.builds_password }}" != '' && \ "${{ secrets.builds_host }}" != '' ]]; then echo "available=true" >> $GITHUB_OUTPUT; else echo "available=false" >> $GITHUB_OUTPUT; fi - name: Check for the upload support id: signing-check shell: bash run: | if [[ "${{ secrets.ANDROID_KEYSTORE }}" != '' && \ "${{ secrets.RELEASE_STORE_PASSWORD }}" != '' && \ "${{ secrets.RELEASE_KEY_PASSWORD }}" != '' && \ "${{ secrets.RELEASE_KEY_ALIAS }}" != '' ]]; then echo "available=true" >> $GITHUB_OUTPUT; else echo "available=false" >> $GITHUB_OUTPUT; fi - name: Extracting version name id: extract-version shell: bash run: | ver_name=`perl -ne 'm/THEXTECH_ANDROID_VERSION_NAME\s+"([\d\w.\-]+)"/ && print "$1"' ../version.cmake` echo "Version name in version.cmake: ${ver_name}" echo "ver_name=${ver_name}" >> $GITHUB_OUTPUT; - name: Install Dependencies shell: bash run: | sudo apt-get update -qq sudo apt-get install -qq lftp apksigner - name: Set up JDK 21 uses: actions/setup-java@v3.11.0 with: java-version: 21 distribution: 'temurin' - uses: nttld/setup-ndk@v1 with: ndk-version: r23c # IMPORTANT NOTE: The SDK r23b is REQUIRED to support Android 4.1, 4.2, and 4.3, and to support non-Neon hardware - name: Setup Android SDK uses: android-actions/setup-android@v2 # Without NDK not compile and not normal error message. NDK is required #- name: Install NDK # run: echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;23.2.8568313" --sdk_root=${ANDROID_SDK_ROOT} # Some times is have problems with permissions for ./gradle file. Then uncommit it code # - name: Make gradlew executable # run: chmod +x ./gradlew - name: Output version code run: echo VERSION_CODE=${{ github.run_number }} > ./version.properties - name: Import the signing if: ${{ steps.signing-check.outputs.available == 'true' }} run: echo "${{ secrets.ANDROID_KEYSTORE }}" | base64 -d > release-key.jks - name: Build unsigned with Gradle run: ./gradlew assembleFdroidRelease - name: Sign and rename APK if: ${{ steps.signing-check.outputs.available == 'true' }} shell: bash run: | apksigner sign --ks 'release-key.jks' \ --ks-pass env:RELEASE_STORE_PASSWORD \ --ks-key-alias ${{ secrets.RELEASE_KEY_ALIAS }} \ --min-sdk-version 16 \ --v1-signing-enabled \ --v2-signing-enabled \ --out thextech-android-fdroid-${{ steps.extract-version.outputs.ver_name }}.apk \ thextech/build/outputs/apk/fdroid/release/thextech-fdroid-release-unsigned.apk env: RELEASE_STORE_PASSWORD: ${{ secrets.RELEASE_STORE_PASSWORD }} RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }} RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }} PIN_ALIAS: ${{ secrets.PIN_ALIAS }} DB_PASS_ALIAS: ${{ secrets.DB_PASS_ALIAS }} - name: Rename APK (unsigned) if: ${{ steps.signing-check.outputs.available != 'true' }} shell: bash run: | mv thextech/build/outputs/apk/fdroid/release/thextech-fdroid-release-unsigned.apk thextech-android-fdroid-${{ steps.extract-version.outputs.ver_name }}-unsigned.apk - name: Upload APK if: success() && ${{ steps.signing-check.outputs.available == 'true' }} uses: actions/upload-artifact@v4 with: name: thextech-android-${{ steps.extract-version.outputs.ver_name }} path: android-project/thextech-android-*.apk - name: Upload APK (unsigned) if: success() && ${{ steps.signing-check.outputs.available != 'true' }} uses: actions/upload-artifact@v4 continue-on-error: true with: name: thextech-android-${{ steps.extract-version.outputs.ver_name }}-unsigned path: android-project/thextech-android-*.apk - name: Deploy to builds.wohlsoft.ru if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true' continue-on-error: true shell: bash run: | UPLOAD_LIST="set ssl:verify-certificate no;" if [[ "${{ steps.signing-check.outputs.available }}" == 'true' ]]; then UPLOAD_LIST="${UPLOAD_LIST} put -O "www/android/fdroid/" ./thextech-android-fdroid-${{ steps.extract-version.outputs.ver_name }}.apk;" else UPLOAD_LIST="${UPLOAD_LIST} put -O "www/android/fdroid/" ./thextech-android-fdroid-${{ steps.extract-version.outputs.ver_name }}-unsigned.apk;" fi lftp -e "${UPLOAD_LIST} exit" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }} - name: List Build Directory if: always() shell: bash run: | git status ls -lR . ================================================ FILE: .github/workflows/blocksds-ci.yml ================================================ name: DSi BlocksDS CI on: push: branches: - main - versus-ci-homebrew pull_request: branches: - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: name: "${{ matrix.config.name }} | ${{ matrix.config.build_type }}" runs-on: ubuntu-latest container: ghcr.io/thextech/wohlnet-ci-ubuntu2404-blocksds:latest env: WONDERFUL_TOOLCHAIN: /opt/wonderful BLOCKSDS: /opt/wonderful/thirdparty/blocksds/core BLOCKSDSEXT: /opt/wonderful/thirdparty/blocksds/external strategy: fail-fast: false matrix: config: - { name: "DSi BlocksDS build", extra_options: "-DCMAKE_TOOLCHAIN_FILE=$BLOCKSDS/cmake/BlocksDSi.cmake -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON", extra_path: "/opt/wonderful/bin", deps_cmdline: "echo 'BlocksDS SDK is already pre-installed'", generator: "Ninja", build_type: "RelWithDebInfo", executable_name: "thextech", subdir_name: "thextech-dsi", upload_directory: "www/dsi/" } steps: - name: Check for the upload support id: upload-check shell: bash run: | if [[ "${{ secrets.builds_login }}" != '' && \ "${{ secrets.builds_password }}" != '' && \ "${{ secrets.builds_host }}" != '' ]]; then echo "available=true" >> $GITHUB_OUTPUT; else echo "available=false" >> $GITHUB_OUTPUT; fi - name: Install Dependencies shell: bash run: | if [[ ! -z "${{ matrix.config.deps_cmdline }}" ]]; then eval ${{ matrix.config.deps_cmdline }} fi cmake --version - uses: TheXTech/checkout@v0.1 - uses: TheXTech/branch-name@v0.1 - name: Pull submodules shell: bash run: | git submodule update --init --recursive - name: Configure shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi cmake -B build -G "${{ matrix.config.generator }}" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} . - name: Build shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi cmake --build build --target all -j4 - name: Check codesize and RAM usage if: success() && runner.os == 'Linux' shell: bash run: | size build/output/bin/thextech.elf - name: Create Package if: success() shell: bash run: | cd build mkdir package mkdir "package/${{ matrix.config.subdir_name }}" cp ../changelog.txt "package/${{ matrix.config.subdir_name }}/" cp ../LICENSE "package/${{ matrix.config.subdir_name }}/License.TheXTech.txt" cat ../docs/README_DSI.md ../README.md >> "package/${{ matrix.config.subdir_name }}/README.md" cp thextech.nds "package/${{ matrix.config.subdir_name }}/" cd package zip -9 -r "thextech-blocksds-${BRANCH_NAME}.zip" "${{ matrix.config.subdir_name }}" rm -Rf "${{ matrix.config.subdir_name }}" cd ../.. - name: Upload artifact if: success() uses: actions/upload-artifact@v4 continue-on-error: true with: path: build/package/*.zip name: ${{ matrix.config.name }} ${{ matrix.config.build_type }} - name: Deploy to builds.wohlsoft.ru if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true' continue-on-error: true shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi UPLOAD_LIST="set ssl:verify-certificate no;" for q in ./build/package/*.zip; do UPLOAD_LIST="${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;" done lftp -e "${UPLOAD_LIST} exit" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }} - name: List Build Directory if: always() shell: bash run: | git status ls -lR build ================================================ FILE: .github/workflows/emscripten.yml ================================================ name: Emscripten CI on: push: branches: - main - stable* - versus-ci-emscripten pull_request: branches: - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: name: "${{ matrix.config.name }} | ${{ matrix.config.build_type }}" runs-on: ${{ matrix.config.os }} strategy: fail-fast: false matrix: config: # ======================================= Adventures of Demo ======================================= # The side game about Demo and siblings from the A2XT universe by raocow and his fan community. - { name: "Adventures of Demo", os: ubuntu-24.04, extra_options: "-DTHEXTECH_EXECUTABLE_NAME=advdemo \ -DTHEXTECH_GAME_NAME_TITLE=\"Adventures of Demo - Web Edition\" \ -DTHEXTECH_CREDITS_URL=\"wohlsoft.ru\" \ -DTHEXTECH_CREDITS_TITLE=\"Adventures of Demo\" \ -DTHEXTECH_MANIFEST_NAME=\"AoD on TheXTech ${THEXTECH_VERSION_STRING}\" \ -DTHEXTECH_MANIFEST_ID=\"wohlsoft-aod-thextech\" \ -DTHEXTECH_MANIFEST_DESC=\"Play AoD on TheXTech ${THEXTECH_VERSION_STRING}\" \ -DTHEXTECH_DEPLOY_URL=\"https://wohlsoft.ru/projects/TheXTech/aod-on-web-debug/\"", deps_cmdline: "sudo apt-get update -qq \ && sudo apt-get install -qq cmake ninja-build cmake ninja-build lftp", generator: "Ninja", build_type: "MinSizeRel", executable_name: "advdemo", assets_url: "https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-adventure-of-demo-assets-full.7z", subdir_name: "thextech-adventures-of-demo", package_filename_game: "adventures-of-demo", upload_directory: "www/webassembly/", deploy_directory: "www/webassembly/debug-deploy/aod-on-web-debug" } # ======================================= Super Mario Bros. X - a fan-game ======================================= # Was made in 2009 by Andrew Spinks "Redigit", and supported up to 2011 by it's original author. - { name: "Super Mario Bros. X", os: ubuntu-24.04, extra_options: "-DTHEXTECH_EXECUTABLE_NAME=smbx \ -DTHEXTECH_GAME_NAME_TITLE=\"Super Mario Bros. X - Web Edition\" \ -DTHEXTECH_CREDITS_URL=\"www.SuperMarioBrosX.org\" \ -DTHEXTECH_CREDITS_TITLE=\"Super Mario Bros. X\" \ -DTHEXTECH_MANIFEST_NAME=\"SMBX on TheXTech ${THEXTECH_VERSION_STRING}\" \ -DTHEXTECH_MANIFEST_ID=\"wohlsoft-smbx-thextech\" \ -DTHEXTECH_MANIFEST_DESC=\"Play SMBX on TheXTech ${THEXTECH_VERSION_STRING}\" \ -DTHEXTECH_DEPLOY_URL=\"https://wohlsoft.ru/projects/TheXTech/smbx-on-web-debug/\"", deps_cmdline: "sudo apt-get update -qq \ && sudo apt-get install -qq cmake ninja-build cmake ninja-build lftp", generator: "Ninja", build_type: "MinSizeRel", executable_name: "smbx", assets_url: "https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-smbx13-assets-full.7z", subdir_name: "thextech-super-mario-bros-x", package_filename_game: "super-mario-bros-x", upload_directory: "www/webassembly/", deploy_directory: "www/webassembly/debug-deploy/smbx-on-web-debug" } steps: - name: Check for the upload support id: upload-check shell: bash run: | if [[ "${{ secrets.builds_login }}" != '' && \ "${{ secrets.builds_password }}" != '' && \ "${{ secrets.builds_host }}" != '' ]]; then echo "available=true" >> $GITHUB_OUTPUT; else echo "available=false" >> $GITHUB_OUTPUT; fi - name: Install Dependencies shell: bash run: | if [[ ! -z "${{ matrix.config.deps_cmdline }}" ]]; then eval ${{ matrix.config.deps_cmdline }} fi cmake --version - uses: TheXTech/checkout@v0.1 - uses: TheXTech/branch-name@v0.1 - name: Pull submodules shell: bash run: | # Pull submodules git submodule update --init --recursive - name: Install emscripten run: | cd .. git clone https://github.com/emscripten-core/emsdk.git cd emsdk ./emsdk install latest ./emsdk activate latest - name: Download assets shell: bash run: wget -d -nv -t 5 -O assets.7z "${{ matrix.config.assets_url }}" - name: Unpack assets shell: bash run: | mkdir -p assets cd assets 7z x ../assets.7z cd .. rm assets.7z - name: Apply update to translations shell: bash run: | ASSETS_ROOT="$PWD/assets" cd .github/ci-helper bash translate_update.sh "${ASSETS_ROOT}" cd ../.. - name: Configure shell: bash run: | source ../emsdk/emsdk_env.sh emcmake cmake -B build -G "${{ matrix.config.generator }}" \ -DPGE_PRELOAD_ENVIRONMENT="`pwd`/assets" \ -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} . - name: Build shell: bash run: | source ../emsdk/emsdk_env.sh emmake cmake --build build --target all - name: Create Package if: success() shell: bash run: | cd build mkdir package mkdir "package/${{ matrix.config.subdir_name }}" cp ../changelog.txt "package/${{ matrix.config.subdir_name }}/" cp ../LICENSE "package/${{ matrix.config.subdir_name }}/License.TheXTech.txt" cp ../README.md "package/${{ matrix.config.subdir_name }}/" cp ../README.RUS.md "package/${{ matrix.config.subdir_name }}/" cp ../README.ESP.md "package/${{ matrix.config.subdir_name }}/" cp output/bin/* "package/${{ matrix.config.subdir_name }}/" cd package tar -cvzf "thextech-${{ matrix.config.package_filename_game }}-webassembly-${BRANCH_NAME}.tar.gz" "${{ matrix.config.subdir_name }}" rm -Rf "${{ matrix.config.subdir_name }}" cd ../.. - name: Upload artifact if: success() uses: actions/upload-artifact@v4 continue-on-error: true with: path: build/package/*.tar.gz name: ${{ matrix.config.name }} ${{ matrix.config.build_type }} - name: Deploy to builds.wohlsoft.ru if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true' continue-on-error: true shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi UPLOAD_LIST="set ssl:verify-certificate no;" for q in ./build/package/*.tar.gz; do UPLOAD_LIST="${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;" done UPLOAD_LIST="${UPLOAD_LIST} rm -rf ${{ matrix.config.deploy_directory }}-next;" UPLOAD_LIST="${UPLOAD_LIST} mkdir ${{ matrix.config.deploy_directory }}-next;" for q in ./build/output/bin/*; do UPLOAD_LIST="${UPLOAD_LIST} put -O ${{ matrix.config.deploy_directory }}-next/ $q;" done UPLOAD_LIST="${UPLOAD_LIST} rm -rf ${{ matrix.config.deploy_directory }}-prev;" UPLOAD_LIST="${UPLOAD_LIST} mv ${{ matrix.config.deploy_directory }} ${{ matrix.config.deploy_directory }}-prev;" UPLOAD_LIST="${UPLOAD_LIST} mv ${{ matrix.config.deploy_directory }}-next ${{ matrix.config.deploy_directory }};" UPLOAD_LIST="${UPLOAD_LIST} rm -rf ${{ matrix.config.deploy_directory }}-prev;" lftp -e "${UPLOAD_LIST} exit" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }} - name: List Build Directory if: always() shell: bash run: | git status ls -lR build ================================================ FILE: .github/workflows/flatpak.yml ================================================ name: Flatpak CI on: push: branches: - main - stable* - versus-ci - versus-ci-flatpak pull_request: branches: - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: name: Flatpak for ${{ matrix.arch.name }} runs-on: ${{ matrix.arch.runs_on }} container: image: ${{ matrix.arch.image }} options: --privileged strategy: fail-fast: true matrix: arch: - { name: "x86-64", id: "x86_64", # sdk_ver: "22.08", runs_on: "ubuntu-latest", image: "ghcr.io/thextech/wohlnet-ci-ubuntu2204:latest" } - { name: "arm64", id: "aarch64", # sdk_ver: "22.08", runs_on: "ubuntu-24.04-arm", image: "ghcr.io/thextech/wohlnet-ci-ubuntu2204-arm64:latest" } steps: - name: Check for the upload support id: upload-check shell: bash run: | if [[ "${{ secrets.builds_login }}" != '' && \ "${{ secrets.builds_password }}" != '' && \ "${{ secrets.builds_host }}" != '' ]]; then echo "available=true" >> $GITHUB_OUTPUT; else echo "available=false" >> $GITHUB_OUTPUT; fi # - name: Install Ubuntu Dependencies # shell: bash # run: | # apt update --fix-missing -y # apt upgrade -y # # apt install -y flatpak flatpak-builder p7zip-full git lftp # # - name: Install flatpak Dependencies # shell: bash # run: | # flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo # flatpak install -y flathub org.freedesktop.Platform/${{ matrix.arch.id }}/${{ matrix.arch.sdk_ver }} org.freedesktop.Sdk/${{ matrix.arch.id }}/${{ matrix.arch.sdk_ver }} - uses: TheXTech/branch-name@v0.1 - uses: TheXTech/checkout@v0.1 - name: Pull submodules shell: bash run: | git config --global --add safe.directory '*' git submodule update --init --recursive - name: Create flatpak manifest YML id: make-yml shell: bash run: | export REPO_PATH="$(pwd)" export REPO_PATH_SED="$(pwd | sed 's_/_\\/_g')" export BRANCH_NAME_SED="$(echo $BRANCH_NAME | sed 's_/_\\/_g')" cd .. cat $REPO_PATH/resources/flatpak/build.yml \ | sed "s/- type: git/- type: dir/" \ | sed "s/url: https:.*/path: $REPO_PATH_SED/" \ | sed "s/OVERRIDE_GIT_BRANCH=/OVERRIDE_GIT_BRANCH=$BRANCH_NAME_SED/" \ | tee ci-build.yml - name: Build flatpak shell: bash run: | cd .. flatpak-builder --arch=${{ matrix.arch.id }} --force-clean --repo=temp_repo temp_build ci-build.yml - name: Create bundle shell: bash run: | mkdir -p package # ru.wohlsoft.TheXTech is the package id, and dev is the branch flatpak build-bundle --arch=${{ matrix.arch.id }} ../temp_repo package/thextech-${{ matrix.arch.name }}.flatpak ru.wohlsoft.TheXTech dev - name: Upload artifact if: success() uses: actions/upload-artifact@v4 continue-on-error: true with: path: package/thextech-${{ matrix.arch.name }}.flatpak name: TheXTech (${{ matrix.arch.name }}) - name: Deploy to builds.wohlsoft.ru if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true' continue-on-error: true shell: bash run: | cd package mv "thextech-${{ matrix.arch.name }}.flatpak" "thextech-${BRANCH_NAME}-${{ matrix.arch.name }}.flatpak" UPLOAD_LIST="set ssl:verify-certificate no;" UPLOAD_LIST="${UPLOAD_LIST} put -O www/flatpak thextech-${BRANCH_NAME}-${{ matrix.arch.name }}.flatpak;" lftp -e "${UPLOAD_LIST} exit" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }} ================================================ FILE: .github/workflows/homebrew-assets-ci.yml ================================================ name: Homebrew Assets CI on: push: branches: - main - stable* paths: - .github/workflows/homebrew-assets-ci.yml - utils/convertkit/assets-convert-homebrew.py pull_request: branches: - main paths: - .github/workflows/homebrew-assets-ci.yml - utils/convertkit/assets-convert-homebrew.py workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: name: ${{ matrix.source.name }} for ${{ matrix.target.name }} runs-on: ubuntu-latest container: ${{ matrix.target.docker_image }} env: DEVKITPRO: /opt/devkitpro strategy: fail-fast: true matrix: target: - { name: "3DS", id: "3ds", docker_image: "devkitpro/devkitarm", upload_directory: "www/3ds/" } - { name: "Wii", id: "wii", docker_image: "devkitpro/devkitppc", upload_directory: "www/wii/" } source: # ======================================= Adventures of Demo ======================================= # The side game about Demo and siblings from the A2XT universe by raocow and his fan community. - { name: "Adventures of Demo", id: "aod", assets_url: "https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-adventure-of-demo-assets-full.7z", } # ======================================= Super Mario Bros. X - a fan-game ======================================= # Was made in 2009 by Andrew Spinks "Redigit", and supported up to 2011 by it's original author. - { name: "Super Mario Bros. X", id: "smbx13", assets_url: "https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-smbx13-assets-full.7z", } steps: - name: Check for the upload support id: upload-check shell: bash run: | if [[ "${{ secrets.builds_login }}" != '' && \ "${{ secrets.builds_password }}" != '' && \ "${{ secrets.builds_host }}" != '' ]]; then echo "available=true" >> $GITHUB_OUTPUT; else echo "available=false" >> $GITHUB_OUTPUT; fi - name: Resolve artifact filename id: res shell: bash run: | echo "filename=assets-${{ matrix.source.id }}-${{ matrix.target.id }}.zip" >> $GITHUB_OUTPUT; echo "The target will be named: [assets-${{ matrix.source.id }}-${{ matrix.target.id }}.${{ matrix.target.package_ext }}]" - name: Install Debian Dependencies shell: bash run: | apt update apt install -y build-essential cmake \ imagemagick ffmpeg \ python3 \ p7zip-full \ zip \ lftp - uses: TheXTech/branch-name@v0.1 - name: Build and install spc2it Dependency shell: bash continue-on-error: true run: | git clone https://github.com/ds-sloth/spc2it ../spc2it cd ../spc2it mkdir build cd build cmake .. make cp spc2it /usr/bin/spc2it - uses: TheXTech/checkout@v0.1 - name: Download assets shell: bash run: wget -d -nv -t 5 -O assets-source.7z "${{ matrix.source.assets_url }}" - name: Unpack assets shell: bash run: | mkdir -p assets/source cd assets/source 7z x ../../assets-source.7z cd ../.. rm assets-source.7z mkdir -p package - name: Apply update to translations shell: bash run: | ASSETS_ROOT="$PWD/assets/source" cd .github/ci-helper bash translate_update.sh "${ASSETS_ROOT}" cd ../.. - name: Convert assets shell: bash run: | cd assets python3 ../utils/convertkit/assets-convert-homebrew.py -t ${{ matrix.target.id }} source target - name: Package for 3DS if: matrix.target.id == '3ds' shell: bash run: | mkromfs3ds assets/target package/${{ steps.res.outputs.filename }}.romfs cd package zip -9 "${{ steps.res.outputs.filename }}" "${{ steps.res.outputs.filename }}.romfs" cd .. - name: Package for Wii if: matrix.target.id == 'wii' shell: bash run: | cd assets/target zip -9 -r "../../package/${{ steps.res.outputs.filename }}" . cd ../.. - name: Upload artifact if: success() uses: actions/upload-artifact@v4 continue-on-error: true with: path: package/${{ steps.res.outputs.filename }} name: ${{ matrix.source.name }} Assets (${{ matrix.target.name }}) - name: Deploy to builds.wohlsoft.ru if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true' continue-on-error: true shell: bash run: | cd package UPLOAD_LIST="set ssl:verify-certificate no;" UPLOAD_LIST="${UPLOAD_LIST} put -O ${{ matrix.target.upload_directory }} ${{ steps.res.outputs.filename }};" lftp -e "${UPLOAD_LIST} exit" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }} - name: List Build Directory if: always() shell: bash run: | cd assets/target pwd ls -lR ================================================ FILE: .github/workflows/macos-ci.yml ================================================ name: macOS CI on: push: branches: - main - stable* - versus-ci-macos pull_request: branches: - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: name: "${{ matrix.config.name }} | ${{ matrix.config.build_type }}" runs-on: ${{ matrix.config.os }} strategy: fail-fast: false matrix: config: - { name: "macOS Universal", os: macos-15-intel, extra_options: "-DTHEXTECH_PRELOAD_ENVIRONMENT_MANUALLY=ON -DTHEXTECH_EXECUTABLE_NAME=\"thextech\"", deps_cmdline: "brew install ninja wget p7zip lftp", generator: "Ninja", build_type: "MinSizeRel", upload_directory: "www/macosx/" } steps: - name: Check for the upload support id: upload-check shell: bash run: | if [[ "${{ secrets.builds_login }}" != '' && \ "${{ secrets.builds_password }}" != '' && \ "${{ secrets.builds_host }}" != '' ]]; then echo "available=true" >> $GITHUB_OUTPUT; else echo "available=false" >> $GITHUB_OUTPUT; fi - name: Install Dependencies shell: bash run: | if [[ ! -z "${{ matrix.config.deps_cmdline }}" ]]; then eval ${{ matrix.config.deps_cmdline }} fi - name: CI Server's network meta-info shell: bash run: | echo "CI server IP address:" wget -q -O - ipinfo.io/ip printf "\n" wget -d ipinfo.io/ip -O/dev/null 2>&1 |grep ^User-Agent - uses: TheXTech/checkout@v0.1 - uses: TheXTech/branch-name@v0.1 - name: Pull submodules shell: bash run: | git submodule update --init --recursive - name: Download SMBX assets shell: bash run: wget -d -nv -t 5 -O smbx13.7z "https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-smbx13-assets-full.7z" - name: Download AoD assets shell: bash run: wget -d -nv -t 5 -O aod.7z "https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-adventure-of-demo-assets-full.7z" - name: Unpack all assets shell: bash run: | mkdir -p smbx13 cd smbx13 7z x ../smbx13.7z cd .. rm smbx13.7z mkdir -p aod cd aod 7z x ../aod.7z cd .. rm aod.7z - name: Apply update to translations shell: bash run: | ASSETS_ROOT1="$PWD/smbx13" ASSETS_ROOT2="$PWD/aod" cd .github/ci-helper bash translate_update.sh "${ASSETS_ROOT1}" bash translate_update.sh "${ASSETS_ROOT2}" cd ../.. - name: Configure (x86_64) shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} echo "PATH environment: ${PATH}" fi if [[ "${{ secrets.DISCORD_APP_ID }}" != '' ]]; then LOCAL_EXTRA_SETUP="-DTHEXTECH_ENABLE_DISCORD_RPC=ON -DTHEXTECH_DISCORD_APPID=\"${{ secrets.DISCORD_APP_ID }}\"" fi cmake -B build-x64 -G "${{ matrix.config.generator }}" \ -DCMAKE_OSX_DEPLOYMENT_TARGET="10.9" \ -DCMAKE_OSX_ARCHITECTURES="x86_64" \ -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} \ ${{ matrix.config.extra_options }} \ ${LOCAL_EXTRA_SETUP} . - name: Build (x86_64) shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi export MAKEFLAGS=--keep-going cmake --build build-x64 --config ${{ matrix.config.build_type }} --parallel 3 - name: Configure (arm64) shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} echo "PATH environment: ${PATH}" fi if [[ "${{ secrets.DISCORD_APP_ID }}" != '' ]]; then LOCAL_EXTRA_SETUP="-DTHEXTECH_ENABLE_DISCORD_RPC=ON -DTHEXTECH_DISCORD_APPID=\"${{ secrets.DISCORD_APP_ID }}\"" fi cmake -B build-arm -G "${{ matrix.config.generator }}" \ -DCMAKE_OSX_DEPLOYMENT_TARGET="11.0" \ -DCMAKE_OSX_ARCHITECTURES="arm64" \ -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} \ ${{ matrix.config.extra_options }} \ ${LOCAL_EXTRA_SETUP} . - name: Build (arm64) shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi export MAKEFLAGS=--keep-going cmake --build build-arm --config ${{ matrix.config.build_type }} --parallel 3 - name: Merging executible shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi cp -R build-arm/output/bin/TheXTech.app TheXTech.app rm TheXTech.app/Contents/MacOS/TheXTech lipo -create -output TheXTech.app/Contents/MacOS/TheXTech build-x64/output/bin/TheXTech.app/Contents/MacOS/TheXTech build-arm/output/bin/TheXTech.app/Contents/MacOS/TheXTech mkdir TheXTech.app/Contents/Resources/assets - name: Executable info shell: bash run: | file TheXTech.app/Contents/MacOS/TheXTech otool -L TheXTech.app/Contents/MacOS/TheXTech - name: Create Package if: success() shell: bash run: | bash .github/ci-helper/pack-game-macos.sh \ aod \ "assets/graphics/ui/icon/advdemo.icns" \ "Adventures of Demo" \ "thextech-adventures-of-demo-macos-uni-${BRANCH_NAME}.dmg" bash .github/ci-helper/pack-game-macos.sh \ smbx13 \ "assets/graphics/ui/icon/smbx.icns" \ "Super Mario Bros. X" \ "thextech-super-mario-bros-x-macos-uni-${BRANCH_NAME}.dmg" bash .github/ci-helper/pack-game-macos.sh \ none \ "thextech.icns" \ "TheXTech" \ "thextech-macos-uni-${BRANCH_NAME}.zip" - name: Upload DMG artifact if: success() uses: actions/upload-artifact@v4 continue-on-error: true with: path: ./*.dmg name: ${{ matrix.config.name }} ${{ matrix.config.build_type }} DMG - name: Upload ZIP artifact if: success() uses: actions/upload-artifact@v4 continue-on-error: true with: path: ./*.zip name: ${{ matrix.config.name }} ${{ matrix.config.build_type }} ZIP - name: Deploy to builds.wohlsoft.ru if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true' continue-on-error: true shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi UPLOAD_LIST="set ssl:verify-certificate no;" if ls *.dmg 1> /dev/null 2>&1; then for q in *.dmg; do UPLOAD_LIST="${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;" done fi if ls *.zip 1> /dev/null 2>&1; then for q in *.zip; do UPLOAD_LIST="${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;" done fi lftp -e "${UPLOAD_LIST} exit" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }} - name: Clean-up if: always() shell: bash run: | rm -Rf TheXTech.app rm -Rf aod rm -Rf smbx13 - name: List Build Directory if: always() shell: bash run: | git status ls -lR build-x64 ls -lR build-arm ================================================ FILE: .github/workflows/portmaster-ci.yml ================================================ name: PortMaster CI on: push: branches: - main - stable* - versus-ci - versus-ci-portmaster pull_request: branches: - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: name: "${{ matrix.config.name }} | ${{ matrix.config.build_type }}" runs-on: ${{ matrix.config.os }} container: ${{ matrix.config.container }} strategy: fail-fast: false matrix: config: - { name: "PortMaster Ubuntu 20.04 aarch64", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu2004-arm64-cross:latest", extra_options: "-DPORTMASTER=ON -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_linux_gcc_toolchain_arm64.cmake", deps_cmdline: "echo 'Ubuntu 20 arm64, cross from x86_64, everything pre-installed'", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "portmaster", upload_directory: "www/portmaster/" } steps: - name: Check for the upload support id: upload-check shell: bash run: | if [[ "${{ secrets.builds_login }}" != '' && \ "${{ secrets.builds_password }}" != '' && \ "${{ secrets.builds_host }}" != '' ]]; then echo "available=true" >> $GITHUB_OUTPUT; else echo "available=false" >> $GITHUB_OUTPUT; fi - name: Install Dependencies shell: bash run: | if [[ ! -z "${{ matrix.config.deps_cmdline }}" ]]; then eval ${{ matrix.config.deps_cmdline }} fi cmake --version - uses: TheXTech/checkout@v0.1 - uses: TheXTech/branch-name@v0.1 - name: Pull submodules shell: bash run: | git submodule update --init --recursive - name: Configure shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} echo "PATH environment: ${PATH}" fi if [[ "${{ secrets.DISCORD_APP_ID }}" != '' ]]; then LOCAL_EXTRA_SETUP="-DTHEXTECH_ENABLE_DISCORD_RPC=ON -DTHEXTECH_DISCORD_APPID=\"${{ secrets.DISCORD_APP_ID }}\"" fi cmake -B build -G "${{ matrix.config.generator }}" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} ${LOCAL_EXTRA_SETUP} . - name: Build shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi export MAKEFLAGS=--keep-going cmake --build build --config ${{ matrix.config.build_type }} --parallel 4 - name: List dependent libraries if: success() && runner.os == 'Linux' shell: bash run: | file build/output/bin/thextech # ldd build/output/bin/thextech # Not for cross compile! - name: Create Package if: success() id: create_package shell: bash run: | bash .github/ci-helper/pack-game.sh \ "${{ runner.os }}" \ "thextech-bin" \ "thextech${{ matrix.config.executable_name_suffix }}" \ "thextech-bin-${{ matrix.config.package_filename_suffix }}-${BRANCH_NAME}" \ "none" - name: Upload artifact if: success() uses: actions/upload-artifact@v4 continue-on-error: true with: path: build/package/*.tar.gz name: TheXTech PortMaster ${{ matrix.config.build_type }} - name: Deploy to builds.wohlsoft.ru if: steps.create_package.outcome == 'success' && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true' continue-on-error: true shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi UPLOAD_LIST="set ssl:verify-certificate no;" if [[ "${{ runner.os }}" == 'Windows' ]]; then for q in ./build/package/*.7z; do UPLOAD_LIST="${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;" done elif [[ "${{ runner.os }}" == 'Linux' ]]; then for q in ./build/package/*.tar.gz; do UPLOAD_LIST="${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;" done fi lftp -e "${UPLOAD_LIST} exit" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }} - name: List Build Directory if: always() shell: bash run: | git status ls -lR build ================================================ FILE: .github/workflows/regression-testing-ci.yaml ================================================ name: Regression Test CI on: push: branches: - main - stable* pull_request: branches: - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: name: "${{ matrix.config.name }} | ${{ matrix.config.build_type }}" runs-on: ${{ matrix.config.os }} container: ${{ matrix.config.container }} strategy: fail-fast: false matrix: config: - { name: "Ubuntu 24.04 x86_64 (no-SDL)", os: ubuntu-24.04, extra_options: "-DTHEXTECH_EXECUTABLE_NAME=thextech \ -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \ -DUSE_STATIC_LIBC=ON \ -DUSE_SYSTEM_LIBS=OFF \ -DTHEXTECH_CLI_BUILD=ON \ -DTHEXTECH_NO_SDL_BUILD=ON", deps_cmdline: "sudo apt-get update -qq \ && sudo apt-get install -qq cmake ninja-build lftp \ build-essential", executable_name: "thextech", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-24-04-amd64", upload_directory: "" } - { name: "Ubuntu 24.04 x86_64 (no-SDL, legacy floats)", os: ubuntu-24.04, extra_options: "-DTHEXTECH_EXECUTABLE_NAME=thextech \ -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \ -DUSE_STATIC_LIBC=ON \ -DUSE_SYSTEM_LIBS=OFF \ -DTHEXTECH_CLI_BUILD=ON \ -DTHEXTECH_NO_SDL_BUILD=ON \ -DTHEXTECH_FIXED_POINT=OFF", deps_cmdline: "sudo apt-get update -qq \ && sudo apt-get install -qq cmake ninja-build lftp \ build-essential", executable_name: "thextech", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-24-04-amd64-legacy-floats", upload_directory: "" } steps: - name: Check for the upload support id: upload-check shell: bash run: | if [[ "${{ secrets.builds_login }}" != '' && \ "${{ secrets.builds_password }}" != '' && \ "${{ secrets.builds_host }}" != '' ]]; then echo "available=true" >> $GITHUB_OUTPUT; else echo "available=false" >> $GITHUB_OUTPUT; fi - name: Install Dependencies shell: bash run: | if [[ ! -z "${{ matrix.config.deps_cmdline }}" ]]; then eval ${{ matrix.config.deps_cmdline }} fi cmake --version - uses: TheXTech/checkout@v0.1 - uses: TheXTech/branch-name@v0.1 - name: Pull submodules shell: bash run: | git submodule update --init --recursive # - name: Download SMBX assets # shell: bash # run: wget -d -nv -t 5 -O smbx13.7z "https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-smbx13-assets-full.7z" # # - name: Download AoD assets # shell: bash # run: wget -d -nv -t 5 -O aod.7z "https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-adventure-of-demo-assets-full.7z" # # - name: Unpack all assets # shell: bash # run: | # mkdir -p smbx13 # cd smbx13 # 7z x ../smbx13.7z # cd .. # rm smbx13.7z # mkdir -p aod # cd aod # 7z x ../aod.7z # cd .. # rm aod.7z # # - name: Apply update to translations # shell: bash # run: | # ASSETS_ROOT1="$PWD/smbx13" # ASSETS_ROOT2="$PWD/aod" # cd .github/ci-helper # bash translate_update.sh "${ASSETS_ROOT1}" # bash translate_update.sh "${ASSETS_ROOT2}" # cd ../.. - name: Configure shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} echo "PATH environment: ${PATH}" fi cmake -B build -G "${{ matrix.config.generator }}" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} ${LOCAL_EXTRA_SETUP} . - name: Build shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi export MAKEFLAGS=--keep-going cmake --build build --config ${{ matrix.config.build_type }} --parallel 3 - name: List dependent libraries if: success() && runner.os == 'Linux' shell: bash run: | file build/output/bin/thextech ldd build/output/bin/thextech - name: Check codesize and RAM usage if: success() && runner.os == 'Linux' shell: bash run: | size build/output/bin/thextech # TODO: some performance and regression testing on a suite. Wait for wip-archives merge so that it is easy to use a test-suite. # - name: Create Package # if: success() # id: create_package # shell: bash # run: | # bash .github/ci-helper/pack-game.sh \ # "${{ runner.os }}" \ # "thextech-bin" \ # "thextech${{ matrix.config.executable_name_suffix }}" \ # "thextech-bin-${{ matrix.config.package_filename_suffix }}-${BRANCH_NAME}" \ # "none" # - name: Upload artifact # if: success() && runner.os == 'Linux' && config.upload_directory != '' # uses: actions/upload-artifact@v3 # continue-on-error: true # with: # path: build/package/*.tar.gz # name: TheXTech ${{ matrix.config.name }} ${{ matrix.config.build_type }} # - name: Upload artifact # if: success() && runner.os == 'Windows' # uses: actions/upload-artifact@v3 # continue-on-error: true # with: # path: build/package/*.7z # name: ${{ matrix.config.name }} ${{ matrix.config.build_type }} # - name: Deploy to builds.wohlsoft.ru # if: steps.create_package.outcome == 'success' && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true' && config.upload_directory != '' # continue-on-error: true # shell: bash # run: | # if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then # export PATH=${{ matrix.config.extra_path }}:${PATH} # fi # UPLOAD_LIST="set ssl:verify-certificate no;" # if [[ "${{ runner.os }}" == 'Windows' ]]; then # for q in ./build/package/*.7z; do # UPLOAD_LIST="${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;" # done # elif [[ "${{ runner.os }}" == 'Linux' ]]; then # for q in ./build/package/*.tar.gz; do # UPLOAD_LIST="${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;" # done # fi # lftp -e "${UPLOAD_LIST} exit" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }} - name: List Build Directory if: always() shell: bash run: | git status ls -lR build ================================================ FILE: .github/workflows/switch-ci.yml ================================================ name: Switch CI on: push: branches: - main - stable* - versus-ci-homebrew pull_request: branches: - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: name: "${{ matrix.config.name }} | ${{ matrix.config.build_type }}" runs-on: ubuntu-latest container: ghcr.io/thextech/wohlnet-ci-ubuntu2404-dkp-vita:latest env: DEVKITPRO: /opt/devkitpro DEVKITA64: /opt/devkitpro/devkitA64 strategy: fail-fast: false matrix: config: - { name: "Switch build", extra_options: "-DTHEXTECH_EXECUTABLE_NAME=thextech \ -DCMAKE_TOOLCHAIN_FILE=$DEVKITPRO/cmake/Switch.cmake \ -DCMAKE_INSTALL_PREFIX=$DEVKITPRO/portlibs/switch", # -DCMAKE_PREFIX_PATH=/opt/devkitpro/portlibs/switch-local deps_cmdline: "echo 'DevkitPro SDK is already pre-installed'", generator: "Ninja", build_type: "MinSizeRel", executable_name: "thextech", assets_url: "https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-adventure-of-demo-assets-full.7z", subdir_name: "thextech-switch", upload_directory: "www/switch/" } steps: - name: Check for the upload support id: upload-check shell: bash run: | if [[ "${{ secrets.builds_login }}" != '' && \ "${{ secrets.builds_password }}" != '' && \ "${{ secrets.builds_host }}" != '' ]]; then echo "available=true" >> $GITHUB_OUTPUT; else echo "available=false" >> $GITHUB_OUTPUT; fi - name: Install Dependencies shell: bash run: | if [[ ! -z "${{ matrix.config.deps_cmdline }}" ]]; then eval ${{ matrix.config.deps_cmdline }} fi cmake --version - uses: TheXTech/checkout@v0.1 - uses: TheXTech/branch-name@v0.1 - name: Pull submodules shell: bash run: | git submodule update --init --recursive # - name: Install Devkit Pro # run: | # cd .. # sudo chmod 755 /opt # if [[ ! -f /usr/local/share/keyring/devkitpro-pub.gpg ]]; then # sudo mkdir -p /usr/local/share/keyring/ # sudo wget -O /usr/local/share/keyring/devkitpro-pub.gpg https://apt.devkitpro.org/devkitpro-pub.gpg # fi # # if [[ ! -f /etc/apt/sources.list.d/devkitpro.list ]]; then # echo "deb [signed-by=/usr/local/share/keyring/devkitpro-pub.gpg] https://apt.devkitpro.org stable main" | sudo tee -a /etc/apt/sources.list.d/devkitpro.list # fi # # sudo apt-get update -qq # sudo apt-get install -qq devkitpro-pacman # # sudo dkp-pacman --noconfirm -Sy # sudo dkp-pacman --noconfirm -Syu # sudo dkp-pacman --noconfirm -Sy switch-dev switch-libpng switch-libjpeg-turbo switch-zlib switch-tools switch-cmake switch-mesa switch-libdrm_nouveau switch-glm switch-glad # - name: Install modified SDL2 # run: | # cd .. # git clone https://github.com/Wohlstand/SDL.git -b switch-sdl2-2.0.14-dev --depth 1 SDL2 # cd SDL2 # mkdir build # cd build # cmake -G Ninja -DCMAKE_TOOLCHAIN_FILE=$DEVKITPRO/cmake/Switch.cmake -DCMAKE_PREFIX_PATH=/opt/devkitpro/portlibs/switch-local -DCMAKE_PREFIX_PATH=/opt/devkitpro/portlibs/switch-local .. # cmake --build . --target all -j4 # sudo cmake --build . --target install - name: Configure shell: bash run: | cmake -B build -G "${{ matrix.config.generator }}" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} . - name: Build shell: bash run: | cmake --build build --target all -j4 - name: Create Package if: success() shell: bash run: | cd build mkdir package mkdir "package/${{ matrix.config.subdir_name }}" cp ../changelog.txt "package/${{ matrix.config.subdir_name }}/" cp ../LICENSE "package/${{ matrix.config.subdir_name }}/License.TheXTech.txt" cat ../docs/README_SWITCH.md ../README.md >> "package/${{ matrix.config.subdir_name }}/README.md" cp output/bin/thextech.nro "package/${{ matrix.config.subdir_name }}/" cd package zip -9 -r "thextech-switch-${BRANCH_NAME}.zip" "${{ matrix.config.subdir_name }}" rm -Rf "${{ matrix.config.subdir_name }}" cd ../.. - name: Upload artifact if: success() uses: actions/upload-artifact@v4 continue-on-error: true with: path: build/package/*.zip name: ${{ matrix.config.name }} ${{ matrix.config.build_type }} - name: Deploy to builds.wohlsoft.ru if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true' continue-on-error: true shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi UPLOAD_LIST="set ssl:verify-certificate no;" for q in ./build/package/*.zip; do UPLOAD_LIST="${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;" done lftp -e "${UPLOAD_LIST} exit" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }} - name: List Build Directory if: always() shell: bash run: | git status ls -lR build ================================================ FILE: .github/workflows/sync-langs.yml ================================================ name: Languages sync CI on: push: branches: - main - versus-ci-langsync paths: - .github/workflows/sync-langs.yml - .github/ci-helper/translate_sync_assets.sh - .github/ci-helper/translate_update.sh - .github/ci-helper/translate_patcher.py - resources/languages/**.json - android-project/thextech/src/main/res/values/strings.xml - android-project/thextech/src/main/res/values/arrays.xml - android-project/thextech/src/main/res/values-**/strings.xml - android-project/thextech/src/main/res/values-**/arrays.xml workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: name: Syncing languages runs-on: ubuntu-latest permissions: contents: write packages: read container: ghcr.io/thextech/wohlnet-ci-ubuntu2404:latest strategy: fail-fast: true steps: - uses: TheXTech/branch-name@v0.1 - uses: TheXTech/checkout@v0.1 - name: Adding Gitea SSH key shell: bash env: SSH_KEY: ${{secrets.GITEA_SYNCBOT_SSH_KEY}} run: | mkdir ~/.ssh chmod 700 ~/.ssh eval `ssh-agent -s` ssh-add - <<< "${SSH_KEY}" ls -la ~/.ssh/ ssh -o StrictHostKeyChecking=no -T git@gitea.wohlsoft.ru echo "SSH_AUTH_SOCK=${SSH_AUTH_SOCK}" >> $GITHUB_ENV git config --global user.email "ci@example.ru" git config --global user.name "Language Sync Bot" - name: Copy updated translations to stable branch shell: bash env: GIT_AUTHOR: "github-actions[bot] " GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_ACTOR: ${{ secrets.GITHUB_ACTOR }} run: | bash .github/ci-helper/translate_sync_to_stable.sh - name: Apply update to all the translations shell: bash env: GIT_AUTHOR: ${{secrets.GITEA_SYNCBOT_AUTHOR}} run: | bash .github/ci-helper/translate_sync_assets.sh ================================================ FILE: .github/workflows/ubuntu-deb-ci.yml ================================================ name: Ubuntu DEB CI on: push: branches: - main - stable* - versus-ci - versus-ci-ubuntu pull_request: branches: - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: name: "${{ matrix.config.name }} | ${{ matrix.config.build_type }}" runs-on: ${{ matrix.config.os }} container: ${{ matrix.config.container }} strategy: fail-fast: false matrix: config: - { name: "DEB Ubuntu 14.04 x86_64", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu1404-64bit:latest", cross: false, extra_options: "-DPGE_SHARED_SDLMIXER=OFF -DUSE_SYSTEM_SDL2=OFF -DUSE_FREEIMAGE_SYSTEM_LIBS=ON \ -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \ -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF \ -DTHEXTECH_PACKAGE_NAME=\"thextech-bin\" \ -DTHEXTECH_INSTALLER_PACKAGE_NAME=\"thextech-bin\" \ -DTHEXTECH_USE_ANGLE_TRANSLATOR=OFF \ -DCMAKE_C_COMPILER=gcc-8 \ -DCMAKE_CXX_COMPILER=g++-8 \ -DCPACK_GENERATOR=DEB \ -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\"https://wohlsoft.ru\" \ -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \ -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=amd64 \ -DCPACK_DEBIAN_PACKAGE_DEPENDS=\"libc6 (>= 2.19), libpng12-0 (>= 1.2.50), libjpeg-turbo8 (>= 1.3.0) | libturbojpeg0 (>= 1.3.0), libasound2 (>= 1.0.27), libpulse0\" \ -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\"TheXTech - the modern C++ port and successor of the SMBX engine\"", deps_cmdline: "echo 'Ubuntu 14 x86_64, everything pre-installed'", executable_name: "thextech", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-14-04-amd64", upload_directory: "www/ubuntu-14-04/" } - { name: "DEB Ubuntu 14.04 32bit", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu1404-32bit:latest", cross: false, extra_options: "-DPGE_SHARED_SDLMIXER=OFF \ -DUSE_STATIC_LIBC=ON \ -DUSE_SYSTEM_LIBS=OFF \ -DUSE_SHARED_FREEIMAGE=OFF \ -DUSE_SYSTEM_SDL2=OFF \ -DUSE_FREEIMAGE_SYSTEM_LIBS=OFF \ -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \ -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF \ -DTHEXTECH_PACKAGE_NAME=\"thextech-bin\" \ -DTHEXTECH_INSTALLER_PACKAGE_NAME=\"thextech-bin\" \ -DTHEXTECH_USE_ANGLE_TRANSLATOR=OFF \ -DCMAKE_C_COMPILER=/usr/bin/i686-linux-gnu-gcc-8 \ -DCMAKE_CXX_COMPILER=/usr/bin/i686-linux-gnu-g++-8 \ -DCPACK_GENERATOR=DEB \ -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\"https://wohlsoft.ru\" \ -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \ -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=i386 \ -DCPACK_DEBIAN_PACKAGE_DEPENDS=\"libc6 (>= 2.19), libpng12-0 (>= 1.2.50), libjpeg-turbo8 (>= 1.3.0) | libturbojpeg0 (>= 1.3.0), libasound2 (>= 1.0.27), libpulse0\" \ -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\"TheXTech - the modern C++ port and successor of the SMBX engine\"", deps_cmdline: "echo 'Ubuntu 14 x86_32, everything pre-installed'; ln -sf /usr/bin/i686-linux-gnu-gcc-8 /usr/bin/gcc; ln -sf /usr/bin/i686-linux-gnu-g++-8 /usr/bin/g++;", executable_name: "thextech", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-14-04-i386", upload_directory: "www/ubuntu-14-04/" } - { name: "DEB Ubuntu 16.04 x86_64", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu1604-64bit:latest", cross: false, extra_options: "-DPGE_SHARED_SDLMIXER=OFF -DUSE_SYSTEM_SDL2=OFF -DUSE_FREEIMAGE_SYSTEM_LIBS=ON \ -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \ -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF \ -DTHEXTECH_PACKAGE_NAME=\"thextech-bin\" \ -DTHEXTECH_INSTALLER_PACKAGE_NAME=\"thextech-bin\" \ -DCPACK_GENERATOR=DEB \ -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\"https://wohlsoft.ru\" \ -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \ -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=amd64 \ -DCPACK_DEBIAN_PACKAGE_DEPENDS=\"libc6 (>= 2.23), libpng12-0 (>= 1.2.54), libjpeg-turbo8 (>= 1.4.2) | libturbojpeg0 (>= 1.4.2), libasound2 (>= 1.1.0), libpulse0\" \ -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\"TheXTech - the modern C++ port and successor of the SMBX engine\"", deps_cmdline: "echo 'Ubuntu 16 x86_64, everything pre-installed'", executable_name: "thextech", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-16-04-amd64", upload_directory: "www/ubuntu-16-04/" } - { name: "DEB Ubuntu 16.04 32bit", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu1604-32bit:latest", cross: false, extra_options: "-DPGE_SHARED_SDLMIXER=OFF \ -DUSE_STATIC_LIBC=ON \ -DUSE_SYSTEM_LIBS=OFF \ -DUSE_SHARED_FREEIMAGE=OFF \ -DUSE_SYSTEM_SDL2=OFF \ -DUSE_FREEIMAGE_SYSTEM_LIBS=OFF \ -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \ -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF \ -DTHEXTECH_PACKAGE_NAME=\"thextech-bin\" \ -DTHEXTECH_INSTALLER_PACKAGE_NAME=\"thextech-bin\" \ -DCPACK_GENERATOR=DEB \ -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\"https://wohlsoft.ru\" \ -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \ -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=i386 \ -DCPACK_DEBIAN_PACKAGE_DEPENDS=\"libc6 (>= 2.23), libpng12-0 (>= 1.2.54), libjpeg-turbo8 (>= 1.4.2) | libturbojpeg0 (>= 1.4.2), libasound2 (>= 1.1.0), libpulse0\" \ -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\"TheXTech - the modern C++ port and successor of the SMBX engine\" \ -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_linux_gcc_toolchain_x32.cmake", deps_cmdline: "echo 'Ubuntu 16 x86_32, everything pre-installed'", executable_name: "thextech", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-16-04-i386", upload_directory: "www/ubuntu-16-04/" } - { name: "DEB Ubuntu 16.04 armhf", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu1604-armhf-cross:latest", cross: true, extra_options: "-DPGE_SHARED_SDLMIXER=OFF \ -DUSE_STATIC_LIBC=ON \ -DUSE_SYSTEM_LIBS=OFF \ -DUSE_SHARED_FREEIMAGE=OFF \ -DUSE_SYSTEM_SDL2=OFF \ -DUSE_FREEIMAGE_SYSTEM_LIBS=OFF \ -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \ -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF \ -DTHEXTECH_PACKAGE_NAME=\"thextech-bin\" \ -DTHEXTECH_INSTALLER_PACKAGE_NAME=\"thextech-bin\" \ -DCPACK_GENERATOR=DEB \ -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\"https://wohlsoft.ru\" \ -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \ -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=armhf \ -DCPACK_DEBIAN_PACKAGE_DEPENDS=\"libc6 (>= 2.23), libpng12-0 (>= 1.2.54), libjpeg-turbo8 (>= 1.4.2) | libturbojpeg0 (>= 1.4.2), libasound2 (>= 1.1.0), libpulse0\" \ -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\"TheXTech - the modern C++ port and successor of the SMBX engine\" \ -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_linux_gcc_toolchain_armhf.cmake", deps_cmdline: "echo 'Ubuntu 16 armhf, cross from x86_64, everything pre-installed'", executable_name: "thextech", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-16-04-armhf", upload_directory: "www/ubuntu-16-04/" } - { name: "DEB Ubuntu 18.04 x86_64", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu1804:latest", cross: false, extra_options: "-DPGE_SHARED_SDLMIXER=OFF -DUSE_SYSTEM_SDL2=ON -DUSE_FREEIMAGE_SYSTEM_LIBS=ON \ -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \ -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF \ -DTHEXTECH_PACKAGE_NAME=\"thextech-bin\" \ -DTHEXTECH_INSTALLER_PACKAGE_NAME=\"thextech-bin\" \ -DCPACK_GENERATOR=DEB \ -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\"https://wohlsoft.ru\" \ -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \ -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=amd64 \ -DCPACK_DEBIAN_PACKAGE_DEPENDS=\"libc6 (>= 2.27), libsdl2-2.0-0 (>= 2.0.8), libpng16-16 (>= 1.6.34), libjpeg-turbo8 (>= 1.5.2) | libturbojpeg0 (>= 1.5.2), libasound2 (>= 1.1.3), libpulse0\" \ -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\"TheXTech - the modern C++ port and successor of the SMBX engine\"", deps_cmdline: "echo 'Ubuntu 18 x86_32, everything pre-installed'", executable_name: "thextech", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-18-04-amd64", upload_directory: "www/ubuntu-18-04/" } - { name: "DEB Ubuntu 20.04 x86_64", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu2004:latest", cross: false, extra_options: "-DPGE_SHARED_SDLMIXER=OFF -DUSE_SYSTEM_SDL2=ON -DUSE_FREEIMAGE_SYSTEM_LIBS=ON \ -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \ -DTHEXTECH_PACKAGE_NAME=\"thextech-bin\" \ -DTHEXTECH_INSTALLER_PACKAGE_NAME=\"thextech-bin\" \ -DCPACK_GENERATOR=DEB \ -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\"https://wohlsoft.ru\" \ -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \ -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=amd64 \ -DCPACK_DEBIAN_PACKAGE_DEPENDS=\"libc6 (>= 2.31), libsdl2-2.0-0 (>= 2.0.10), libpng16-16 (>= 1.6.37), libjpeg-turbo8 (>= 2.0.3) | libturbojpeg0 (>= 2.0.3), libasound2 (>= 1.2.2), libpulse0\" \ -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\"TheXTech - the modern C++ port and successor of the SMBX engine\"", deps_cmdline: "echo 'Ubuntu 20 x86_32, everything pre-installed'", executable_name: "thextech", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-20-04-amd64", upload_directory: "www/ubuntu-20-04/" } - { name: "DEB Ubuntu 20.04 arm64", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu2004-arm64-cross:latest", cross: true, extra_options: "-DPGE_SHARED_SDLMIXER=OFF -DUSE_SYSTEM_SDL2=ON -DUSE_FREEIMAGE_SYSTEM_LIBS=ON \ -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \ -DTHEXTECH_PACKAGE_NAME=\"thextech-bin\" \ -DTHEXTECH_INSTALLER_PACKAGE_NAME=\"thextech-bin\" \ -DCPACK_GENERATOR=DEB \ -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\"https://wohlsoft.ru\" \ -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \ -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=arm64 \ -DCPACK_DEBIAN_PACKAGE_DEPENDS=\"libc6 (>= 2.31), libsdl2-2.0-0 (>= 2.0.10), libpng16-16 (>= 1.6.37), libjpeg-turbo8 (>= 2.0.3) | libturbojpeg0 (>= 2.0.3), libasound2 (>= 1.2.2), libpulse0\" \ -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\"TheXTech - the modern C++ port and successor of the SMBX engine\" \ -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_linux_gcc_toolchain_arm64.cmake", deps_cmdline: "echo 'Ubuntu 20 arm64, cross from x86_64, everything pre-installed'", executable_name: "thextech", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-20-04-aarch64", upload_directory: "www/ubuntu-20-04/" } - { name: "DEB Ubuntu 20.04 armhf", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu2004-armhf-cross:latest", cross: true, extra_options: "-DPGE_SHARED_SDLMIXER=OFF -DUSE_SYSTEM_SDL2=ON -DUSE_FREEIMAGE_SYSTEM_LIBS=ON \ -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \ -DTHEXTECH_PACKAGE_NAME=\"thextech-bin\" \ -DTHEXTECH_INSTALLER_PACKAGE_NAME=\"thextech-bin\" \ -DCPACK_GENERATOR=DEB \ -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\"https://wohlsoft.ru\" \ -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \ -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=armhf \ -DCPACK_DEBIAN_PACKAGE_DEPENDS=\"libc6 (>= 2.31), libsdl2-2.0-0 (>= 2.0.10), libpng16-16 (>= 1.6.37), libjpeg-turbo8 (>= 2.0.3) | libturbojpeg0 (>= 2.0.3), libasound2 (>= 1.2.2), libpulse0\" \ -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\"TheXTech - the modern C++ port and successor of the SMBX engine\" \ -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_linux_gcc_toolchain_armhf.cmake", deps_cmdline: "echo 'Ubuntu 20 armhf, cross from x86_64, everything pre-installed'", executable_name: "thextech", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-20-04-armhf", upload_directory: "www/ubuntu-20-04/" } - { name: "DEB Ubuntu 22.04 x86_64", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu2204:latest", cross: false, extra_options: "-DPGE_SHARED_SDLMIXER=OFF -DUSE_SYSTEM_SDL2=ON -DUSE_FREEIMAGE_SYSTEM_LIBS=ON \ -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \ -DTHEXTECH_PACKAGE_NAME=\"thextech-bin\" \ -DTHEXTECH_INSTALLER_PACKAGE_NAME=\"thextech-bin\" \ -DCPACK_GENERATOR=DEB \ -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\"https://wohlsoft.ru\" \ -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \ -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=amd64 \ -DCPACK_DEBIAN_PACKAGE_DEPENDS=\"libc6 (>= 2.35), libsdl2-2.0-0 (>= 2.0.20), libpng16-16 (>= 1.6.37), libjpeg-turbo8 (>= 2.1.2) | libturbojpeg0 (>= 2.1.2), libasound2 (>= 1.2.6), libpulse0\" \ -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\"TheXTech - the modern C++ port and successor of the SMBX engine\"", deps_cmdline: "echo 'Ubuntu 22 x86_32, everything pre-installed'", executable_name: "thextech", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-22-04-amd64", upload_directory: "www/ubuntu-22-04/" } - { name: "DEB Ubuntu 22.04 aarch64", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu2204-arm64-cross:latest", cross: true, extra_options: "-DPGE_SHARED_SDLMIXER=OFF -DUSE_SYSTEM_SDL2=ON -DUSE_FREEIMAGE_SYSTEM_LIBS=ON \ -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \ -DTHEXTECH_PACKAGE_NAME=\"thextech-bin\" \ -DTHEXTECH_INSTALLER_PACKAGE_NAME=\"thextech-bin\" \ -DCPACK_GENERATOR=DEB \ -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\"https://wohlsoft.ru\" \ -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \ -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=arm64 \ -DCPACK_DEBIAN_PACKAGE_DEPENDS=\"libc6 (>= 2.35), libsdl2-2.0-0 (>= 2.0.20), libpng16-16 (>= 1.6.37), libjpeg-turbo8 (>= 2.1.2) | libturbojpeg0 (>= 2.1.2), libasound2 (>= 1.2.6), libpulse0\" \ -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\"TheXTech - the modern C++ port and successor of the SMBX engine\" \ -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_linux_gcc_toolchain_arm64.cmake", deps_cmdline: "echo 'Ubuntu 22 arm64, cross from x86_64, everything pre-installed'", executable_name: "thextech", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-22-04-aarch64", upload_directory: "www/ubuntu-22-04/" } - { name: "DEB Ubuntu 24.04 x86_64", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu2404:latest", cross: false, extra_options: "-DPGE_SHARED_SDLMIXER=OFF -DUSE_SYSTEM_SDL2=ON -DUSE_FREEIMAGE_SYSTEM_LIBS=ON \ -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \ -DTHEXTECH_PACKAGE_NAME=\"thextech-bin\" \ -DTHEXTECH_INSTALLER_PACKAGE_NAME=\"thextech-bin\" \ -DCPACK_GENERATOR=DEB \ -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\"https://wohlsoft.ru\" \ -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \ -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=amd64 \ -DCPACK_DEBIAN_PACKAGE_DEPENDS=\"libc6 (>= 2.38), libsdl2-2.0-0 (>= 2.30.0), libpng16-16 (>= 1.6.43), libjpeg-turbo8 (>= 2.1.5), libasound2 (>= 1.2.10), libpulse0\" \ -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\"TheXTech - the modern C++ port and successor of the SMBX engine\"", deps_cmdline: "echo 'Ubuntu 24 x86_64, everything pre-installed'", executable_name: "thextech", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-24-04-amd64", upload_directory: "www/ubuntu-24-04/" } - { name: "DEB Debian 13 (Trixie) x86_64", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-debian13:latest", cross: false, extra_options: "-DPGE_SHARED_SDLMIXER=OFF -DUSE_SYSTEM_SDL2=ON -DUSE_FREEIMAGE_SYSTEM_LIBS=ON \ -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=MinSizeRel \ -DTHEXTECH_PACKAGE_NAME=\"thextech-bin\" \ -DTHEXTECH_INSTALLER_PACKAGE_NAME=\"thextech-bin\" \ -DCPACK_GENERATOR=DEB \ -DCPACK_DEBIAN_PACKAGE_HOMEPAGE=\"https://wohlsoft.ru\" \ -DCPACK_DEBIAN_PACKAGE_RELEASE=${GITHUB_RUN_NUMBER} \ -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=amd64 \ -DCPACK_DEBIAN_PACKAGE_DEPENDS=\"libc6 (>= 2.38), libsdl2-2.0-0 (>= 2.30.0), libpng16-16 (>= 1.6.43), libturbojpeg0 (>= 2.1.5), libasound2 (>= 1.2.10), libpulse0\" \ -DCPACK_DEBIAN_PACKAGE_DESCRIPTION=\"TheXTech - the modern C++ port and successor of the SMBX engine\"", deps_cmdline: "echo 'Debian 13 Trixie x86_64, everything pre-installed'", executable_name: "thextech", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "debian-13-trixie-amd64", upload_directory: "www/debian-13/" } steps: - name: Host info shell: bash run: | uname -a cat /proc/cpuinfo - name: Check for the upload support id: upload-check shell: bash run: | if [[ "${{ secrets.builds_login }}" != '' && \ "${{ secrets.builds_password }}" != '' && \ "${{ secrets.builds_host }}" != '' ]]; then echo "available=true" >> $GITHUB_OUTPUT; else echo "available=false" >> $GITHUB_OUTPUT; fi - name: Install Dependencies shell: bash run: | if [[ ! -z "${{ matrix.config.deps_cmdline }}" ]]; then eval ${{ matrix.config.deps_cmdline }} fi cmake --version - uses: TheXTech/checkout@v0.1 - uses: TheXTech/branch-name@v0.1 - name: Pull submodules shell: bash run: | git submodule update --init --recursive - name: Configure shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} echo "PATH environment: ${PATH}" fi if [[ "${{ secrets.DISCORD_APP_ID }}" != '' ]]; then LOCAL_EXTRA_SETUP="-DTHEXTECH_ENABLE_DISCORD_RPC=ON -DTHEXTECH_DISCORD_APPID=\"${{ secrets.DISCORD_APP_ID }}\"" fi cmake -B build -G "${{ matrix.config.generator }}" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} ${LOCAL_EXTRA_SETUP} . - name: Build shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi export MAKEFLAGS=--keep-going cmake --build build --config ${{ matrix.config.build_type }} --parallel 3 - name: List dependent libraries if: success() && runner.os == 'Linux' shell: bash run: | file build/output/bin/${{ matrix.config.executable_name }} if [[ "${{ matrix.config.cross}}" != true ]]; then ldd build/output/bin/${{ matrix.config.executable_name }} fi - name: Create DEB packages shell: bash id: create_package if: success() run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi cd build cpack . mv *.deb thextech-bin-${{ matrix.config.package_filename_suffix }}-${BRANCH_NAME}.deb cd .. - name: Upload artifact if: success() uses: actions/upload-artifact@v4 continue-on-error: true with: path: build/*.deb name: TheXTech ${{ matrix.config.name }} ${{ matrix.config.build_type }} - name: Deploy to builds.wohlsoft.ru if: steps.create_package.outcome == 'success' && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true' continue-on-error: true shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi UPLOAD_LIST="set ssl:verify-certificate no;" for q in ./build/*.deb; do UPLOAD_LIST="${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;" done lftp -e "${UPLOAD_LIST} exit" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }} - name: List Build Directory if: always() shell: bash run: | git status ls -lR build ================================================ FILE: .github/workflows/ubuntu-tar-ci.yml ================================================ name: Ubuntu TAR CI on: push: branches: - main - stable* - versus-ci - versus-ci-ubuntu pull_request: branches: - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: name: "${{ matrix.config.name }} | ${{ matrix.config.build_type }}" runs-on: ${{ matrix.config.os }} container: ${{ matrix.config.container }} strategy: fail-fast: false matrix: config: - { name: "TAR Ubuntu 14.04 x86_64", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu1404-64bit:latest", cross: false, extra_options: "-DTHEXTECH_EXECUTABLE_NAME=thextech \ -DUSE_STATIC_LIBC=ON \ -DUSE_SYSTEM_LIBS=OFF \ -DUSE_SYSTEM_SDL2=OFF \ -DUSE_SHARED_FREEIMAGE=OFF \ -DPGE_SHARED_SDLMIXER=OFF \ -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF \ -DTHEXTECH_USE_ANGLE_TRANSLATOR=OFF \ -DCMAKE_C_COMPILER=gcc-8 \ -DCMAKE_CXX_COMPILER=g++-8", deps_cmdline: "echo 'Ubuntu 14 x86_64, everything pre-installed'", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-14-04-amd64", upload_directory: "www/ubuntu-14-04/" } - { name: "TAR Ubuntu 14.04 x86_64 (legacy floats)", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu1404-64bit:latest", cross: false, extra_options: "-DTHEXTECH_EXECUTABLE_NAME=thextech \ -DUSE_STATIC_LIBC=ON \ -DUSE_SYSTEM_LIBS=OFF \ -DUSE_SYSTEM_SDL2=OFF \ -DUSE_SHARED_FREEIMAGE=OFF \ -DPGE_SHARED_SDLMIXER=OFF \ -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF \ -DTHEXTECH_USE_ANGLE_TRANSLATOR=OFF \ -DTHEXTECH_FIXED_POINT=OFF \ -DCMAKE_C_COMPILER=gcc-8 \ -DCMAKE_CXX_COMPILER=g++-8", deps_cmdline: "echo 'Ubuntu 14 x86_64, everything pre-installed'", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-14-04-amd64-legacy-floats", upload_directory: "www/ubuntu-14-04/" } - { name: "TAR Ubuntu 14.04 32bit", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu1404-32bit:latest", cross: false, extra_options: "-DTHEXTECH_EXECUTABLE_NAME=thextech \ -DUSE_STATIC_LIBC=ON \ -DUSE_SYSTEM_LIBS=OFF \ -DUSE_SYSTEM_SDL2=OFF \ -DUSE_SHARED_FREEIMAGE=OFF \ -DPGE_SHARED_SDLMIXER=OFF \ -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF \ -DTHEXTECH_USE_ANGLE_TRANSLATOR=OFF \ -DCMAKE_C_COMPILER=/usr/bin/i686-linux-gnu-gcc-8 \ -DCMAKE_CXX_COMPILER=/usr/bin/i686-linux-gnu-g++-8", deps_cmdline: "echo 'Ubuntu 14 x86_32, everything pre-installed'; ln -sf /usr/bin/i686-linux-gnu-gcc-8 /usr/bin/gcc; ln -sf /usr/bin/i686-linux-gnu-g++-8 /usr/bin/g++;", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-14-04-i386", upload_directory: "www/ubuntu-14-04/" } - { name: "TAR Ubuntu 16.04 x86_64", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu1604-64bit:latest", cross: false, extra_options: "-DTHEXTECH_EXECUTABLE_NAME=thextech \ -DUSE_STATIC_LIBC=ON \ -DUSE_SYSTEM_LIBS=OFF \ -DUSE_SYSTEM_SDL2=OFF \ -DUSE_SHARED_FREEIMAGE=OFF \ -DPGE_SHARED_SDLMIXER=OFF \ -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF", deps_cmdline: "echo 'Ubuntu 16 x86_64, everything pre-installed'", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-16-04-amd64", upload_directory: "www/ubuntu-16-04/" } - { name: "TAR Ubuntu 16.04 x86_64 (legacy floats)", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu1604-64bit:latest", cross: false, extra_options: "-DTHEXTECH_EXECUTABLE_NAME=thextech \ -DUSE_STATIC_LIBC=ON \ -DUSE_SYSTEM_LIBS=OFF \ -DUSE_SYSTEM_SDL2=OFF \ -DUSE_SHARED_FREEIMAGE=OFF \ -DPGE_SHARED_SDLMIXER=OFF \ -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF \ -DTHEXTECH_FIXED_POINT=OFF", deps_cmdline: "echo 'Ubuntu 16 x86_64, everything pre-installed'", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-16-04-amd64-legacy-floats", upload_directory: "www/ubuntu-16-04/" } - { name: "TAR Ubuntu 16.04 32bit", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu1604-32bit:latest", cross: false, extra_options: "-DTHEXTECH_EXECUTABLE_NAME=thextech \ -DUSE_STATIC_LIBC=ON \ -DUSE_SYSTEM_LIBS=OFF \ -DUSE_SYSTEM_SDL2=OFF \ -DUSE_SHARED_FREEIMAGE=OFF \ -DPGE_SHARED_SDLMIXER=OFF \ -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF \ -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_linux_gcc_toolchain_x32.cmake", deps_cmdline: "echo 'Ubuntu 16 x86_32, everything pre-installed'", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-16-04-i386", upload_directory: "www/ubuntu-16-04/" } - { name: "TAR Ubuntu 16.04 armhf", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu1604-armhf-cross:latest", cross: true, extra_options: "-DTHEXTECH_EXECUTABLE_NAME=thextech \ -DUSE_STATIC_LIBC=ON \ -DUSE_SYSTEM_LIBS=OFF \ -DUSE_SYSTEM_SDL2=OFF \ -DUSE_SHARED_FREEIMAGE=OFF \ -DPGE_SHARED_SDLMIXER=OFF \ -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_linux_gcc_toolchain_armhf.cmake", deps_cmdline: "echo 'Ubuntu 16 armhf, cross from x86_64, everything pre-installed'", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-16-04-armhf", upload_directory: "www/ubuntu-16-04/" } - { name: "TAR Ubuntu 18.04 x86_64", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu1804:latest", cross: false, extra_options: "-DTHEXTECH_EXECUTABLE_NAME=thextech \ -DUSE_STATIC_LIBC=ON \ -DUSE_SYSTEM_LIBS=OFF \ -DUSE_SYSTEM_SDL2=OFF \ -DUSE_SHARED_FREEIMAGE=OFF \ -DPGE_SHARED_SDLMIXER=OFF \ -DTHEXTECH_BUILD_GL_ES_LEGACY=OFF", deps_cmdline: "echo 'Ubuntu 18 x86_32, everything pre-installed'", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-18-04-amd64", upload_directory: "www/ubuntu-18-04/" } - { name: "TAR Ubuntu 20.04 x86_64", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu2004:latest", cross: false, extra_options: "-DTHEXTECH_EXECUTABLE_NAME=thextech \ -DUSE_STATIC_LIBC=ON \ -DUSE_SYSTEM_LIBS=OFF \ -DUSE_SYSTEM_SDL2=OFF \ -DUSE_SHARED_FREEIMAGE=OFF \ -DPGE_SHARED_SDLMIXER=OFF", deps_cmdline: "echo 'Ubuntu 20 x86_32, everything pre-installed'", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-20-04-amd64", upload_directory: "www/ubuntu-20-04/" } - { name: "TAR Ubuntu 20.04 aarch64", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu2004-arm64-cross:latest", cross: true, extra_options: "-DTHEXTECH_EXECUTABLE_NAME=thextech \ -DUSE_STATIC_LIBC=ON \ -DUSE_SYSTEM_LIBS=OFF \ -DUSE_SYSTEM_SDL2=OFF \ -DUSE_SHARED_FREEIMAGE=OFF \ -DPGE_SHARED_SDLMIXER=OFF \ -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_linux_gcc_toolchain_arm64.cmake", deps_cmdline: "echo 'Ubuntu 20 arm64, cross from x86_64, everything pre-installed'", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-20-04-aarch64", upload_directory: "www/ubuntu-20-04/" } - { name: "TAR Ubuntu 20.04 armhf", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu2004-armhf-cross:latest", cross: true, extra_options: "-DTHEXTECH_EXECUTABLE_NAME=thextech \ -DUSE_STATIC_LIBC=ON \ -DUSE_SYSTEM_LIBS=OFF \ -DUSE_SYSTEM_SDL2=OFF \ -DUSE_SHARED_FREEIMAGE=OFF \ -DPGE_SHARED_SDLMIXER=OFF \ -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_linux_gcc_toolchain_armhf.cmake", deps_cmdline: "echo 'Ubuntu 20 armhf, cross from x86_64, everything pre-installed'", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-20-04-armhf", upload_directory: "www/ubuntu-20-04/" } - { name: "TAR Ubuntu 22.04 x86_64", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu2204:latest", cross: false, extra_options: "-DTHEXTECH_EXECUTABLE_NAME=thextech \ -DUSE_STATIC_LIBC=ON \ -DUSE_SYSTEM_LIBS=OFF \ -DUSE_SYSTEM_SDL2=OFF \ -DUSE_SHARED_FREEIMAGE=OFF \ -DPGE_SHARED_SDLMIXER=OFF", deps_cmdline: "echo 'Ubuntu 22 x86_32, everything pre-installed'", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-22-04-amd64", upload_directory: "www/ubuntu-22-04/" } - { name: "TAR Ubuntu 22.04 aarch64", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu2204-arm64-cross:latest", cross: true, extra_options: "-DTHEXTECH_EXECUTABLE_NAME=thextech \ -DUSE_STATIC_LIBC=ON \ -DUSE_SYSTEM_LIBS=OFF \ -DUSE_SYSTEM_SDL2=OFF \ -DUSE_SHARED_FREEIMAGE=OFF \ -DPGE_SHARED_SDLMIXER=OFF \ -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_linux_gcc_toolchain_arm64.cmake", deps_cmdline: "echo 'Ubuntu 22 arm64, cross from x86_64, everything pre-installed'", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-22-04-aarch64", upload_directory: "www/ubuntu-22-04/" } - { name: "TAR Ubuntu 24.04 x86_64", os: ubuntu-latest, container: "ghcr.io/thextech/wohlnet-ci-ubuntu2404:latest", cross: false, extra_options: "-DTHEXTECH_EXECUTABLE_NAME=thextech \ -DUSE_STATIC_LIBC=ON \ -DUSE_SYSTEM_LIBS=OFF \ -DUSE_SYSTEM_SDL2=OFF \ -DUSE_SHARED_FREEIMAGE=OFF \ -DPGE_SHARED_SDLMIXER=OFF", deps_cmdline: "echo 'Ubuntu 24 x86_32, everything pre-installed'", executable_name: "thextech", generator: "Ninja", build_type: "MinSizeRel", executable_name_suffix: "", package_filename_suffix: "ubuntu-24-04-amd64", upload_directory: "www/ubuntu-24-04/" } steps: - name: Host info shell: bash run: | uname -a cat /proc/cpuinfo - name: Check for the upload support id: upload-check shell: bash run: | if [[ "${{ secrets.builds_login }}" != '' && \ "${{ secrets.builds_password }}" != '' && \ "${{ secrets.builds_host }}" != '' ]]; then echo "available=true" >> $GITHUB_OUTPUT; else echo "available=false" >> $GITHUB_OUTPUT; fi - name: Install Dependencies shell: bash run: | if [[ ! -z "${{ matrix.config.deps_cmdline }}" ]]; then eval ${{ matrix.config.deps_cmdline }} fi cmake --version - uses: TheXTech/checkout@v0.1 - uses: TheXTech/branch-name@v0.1 - name: Pull submodules shell: bash run: | git submodule update --init --recursive - name: Download SMBX assets shell: bash run: wget -d -nv -t 5 -O smbx13.7z "https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-smbx13-assets-full.7z" - name: Download AoD assets shell: bash run: wget -d -nv -t 5 -O aod.7z "https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-adventure-of-demo-assets-full.7z" - name: Unpack all assets shell: bash run: | mkdir -p smbx13 cd smbx13 7z x ../smbx13.7z cd .. rm smbx13.7z mkdir -p aod cd aod 7z x ../aod.7z cd .. rm aod.7z - name: Apply update to translations shell: bash run: | ASSETS_ROOT1="$PWD/smbx13" ASSETS_ROOT2="$PWD/aod" cd .github/ci-helper bash translate_update.sh "${ASSETS_ROOT1}" bash translate_update.sh "${ASSETS_ROOT2}" cd ../.. - name: Configure shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} echo "PATH environment: ${PATH}" fi if [[ "${{ secrets.DISCORD_APP_ID }}" != '' ]]; then LOCAL_EXTRA_SETUP="-DTHEXTECH_ENABLE_DISCORD_RPC=ON -DTHEXTECH_DISCORD_APPID=\"${{ secrets.DISCORD_APP_ID }}\"" fi cmake -B build -G "${{ matrix.config.generator }}" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} ${LOCAL_EXTRA_SETUP} . - name: Build shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi export MAKEFLAGS=--keep-going cmake --build build --config ${{ matrix.config.build_type }} --parallel 3 - name: List dependent libraries if: success() && runner.os == 'Linux' shell: bash run: | file build/output/bin/thextech if [[ "${{ matrix.config.cross}}" != true ]]; then ldd build/output/bin/thextech fi - name: Create Package if: success() id: create_package shell: bash # ======================================= Adventures of Demo ======================================= # The side game about Demo and siblings from the A2XT universe by raocow and his fan community. # ======================================= Super Mario Bros. X - a fan-game ======================================= # Was made in 2009 by Andrew Spinks "Redigit", and supported up to 2011 by it's original author. run: | bash .github/ci-helper/pack-game.sh \ "${{ runner.os }}" \ "thextech-adventures-of-demo" \ "advdemo${{ matrix.config.executable_name_suffix }}" \ "thextech-adventures-of-demo-${{ matrix.config.package_filename_suffix }}-${BRANCH_NAME}" \ "aod" bash .github/ci-helper/pack-game.sh \ "${{ runner.os }}" \ "thextech-super-mario-bros-x" \ "smbx${{ matrix.config.executable_name_suffix }}" \ "thextech-super-mario-bros-x-${{ matrix.config.package_filename_suffix }}-${BRANCH_NAME}" \ "smbx13" bash .github/ci-helper/pack-game.sh \ "${{ runner.os }}" \ "thextech-bin" \ "thextech${{ matrix.config.executable_name_suffix }}" \ "thextech-bin-${{ matrix.config.package_filename_suffix }}-${BRANCH_NAME}" \ "none" - name: Upload artifact if: success() uses: actions/upload-artifact@v4 continue-on-error: true with: path: build/package/*.tar.gz name: TheXTech ${{ matrix.config.name }} ${{ matrix.config.build_type }} - name: Deploy to builds.wohlsoft.ru if: steps.create_package.outcome == 'success' && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true' continue-on-error: true shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi UPLOAD_LIST="set ssl:verify-certificate no;" if [[ "${{ runner.os }}" == 'Windows' ]]; then for q in ./build/package/*.7z; do UPLOAD_LIST="${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;" done elif [[ "${{ runner.os }}" == 'Linux' ]]; then for q in ./build/package/*.tar.gz; do UPLOAD_LIST="${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;" done fi lftp -e "${UPLOAD_LIST} exit" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }} - name: List Build Directory if: always() shell: bash run: | git status ls -lR build ================================================ FILE: .github/workflows/vita-ci.yml ================================================ name: Vita CI on: push: branches: - main - stable* - versus-ci-homebrew pull_request: branches: - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: name: "${{ matrix.config.name }} | ${{ matrix.config.build_type }}" runs-on: ubuntu-latest container: ghcr.io/thextech/wohlnet-ci-ubuntu2404-dkp-vita:latest env: VITASDK: /usr/local/vitasdk strategy: fail-fast: true matrix: config: - { name: "Vita build", extra_options: "-DCMAKE_TOOLCHAIN_FILE=$VITASDK/share/vita.toolchain.cmake", deps_cmdline: "echo 'Vita SDK is already pre-installed'", generator: "Unix Makefiles", build_type: "MinSizeRel", executable_name: "thextech", assets_url: "https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-adventure-of-demo-assets-full.7z", subdir_name: "thextech-vita", upload_directory: "www/vita/" } steps: - name: Check for the upload support id: upload-check shell: bash run: | if [[ "${{ secrets.builds_login }}" != '' && \ "${{ secrets.builds_password }}" != '' && \ "${{ secrets.builds_host }}" != '' ]]; then echo "available=true" >> $GITHUB_OUTPUT; else echo "available=false" >> $GITHUB_OUTPUT; fi - name: Install Dependencies shell: bash run: | if [[ ! -z "${{ matrix.config.deps_cmdline }}" ]]; then eval ${{ matrix.config.deps_cmdline }} fi cmake --version - uses: TheXTech/checkout@v0.1 - uses: TheXTech/branch-name@v0.1 - name: Pull submodules shell: bash run: | git submodule update --init --recursive # NOTE: 2023-08-11 now using container including Vita SDK # - name: Install Vita SDK # run: | # cd .. # export VITASDK=/usr/local/vitasdk # export PATH=$VITASDK/bin:$PATH # git clone https://github.com/vitasdk/vdpm # cd vdpm # ./bootstrap-vitasdk.sh # ./install-all.sh || echo A library from vitasdk failed to install. # NOTE: 2/13/2022 Current Vita version does not use cglm or vitaGL renderer. All is handled by SDL2 at the moment. # - name: Install cglm # run: | # cd .. # export PATH=$VITASDK/bin:$PATH # git clone https://github.com/recp/cglm.git # cd cglm # sed -i "s|-Werror||g" CMakeLists.txt # cmake -B build-vita -G Ninja -DCMAKE_INSTALL_PREFIX=$VITASDK/arm-vita-eabi -DCGLM_STATIC=ON -DCGLM_SHARED=OFF -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_TOOLCHAIN_FILE=/usr/local/vitasdk/share/vita.toolchain.cmake . # cmake --build build-vita --target all # cmake --build build-vita --target install # - name: Install Latest vitaGL # run: | # cd .. # export PATH=$VITASDK/bin:$PATH # git clone https://github.com/Rinnegatamante/vitaGL.git # cd vitaGL # NO_DEBUG=1 NO_TEX_COMBINER=1 make # make install # no longer distributing packed Vita builds # - name: Download assets # uses: carlosperate/download-file-action@v2 # with: # file-url: "${{ matrix.config.assets_url }}" # file-name: assets.7z # # - name: Unpack assets # shell: bash # run: | # mkdir -p assets # cd assets # 7z x ../assets.7z # cd .. # rm assets.7z - name: Configure shell: bash run: | export PATH=$VITASDK/bin:$PATH cmake -B build -G "${{ matrix.config.generator }}" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} . - name: Build shell: bash run: | export PATH=$VITASDK/bin:$PATH cmake --build build --target all -j4 - name: Create Package if: success() id: create_package shell: bash run: | cd build mkdir package mkdir "package/${{ matrix.config.subdir_name }}" cp ../changelog.txt "package/${{ matrix.config.subdir_name }}/" cp ../LICENSE "package/${{ matrix.config.subdir_name }}/License.TheXTech.txt" cat ../docs/README_VITA.md ../README.md >> "package/${{ matrix.config.subdir_name }}/README.md" cat ../docs/README_VITA.RUS.md ../README.RUS.md >> "package/${{ matrix.config.subdir_name }}/README.RUS.md" cat ../docs/README_VITA.ESP.md ../README.ESP.md >> "package/${{ matrix.config.subdir_name }}/README.ESP.md" cp thextech.vpk "package/${{ matrix.config.subdir_name }}/" cd package zip -9 -r "thextech-vita-${BRANCH_NAME}.zip" "${{ matrix.config.subdir_name }}" rm -Rf "${{ matrix.config.subdir_name }}" cd ../.. - name: Upload artifact if: success() uses: actions/upload-artifact@v4 continue-on-error: true with: path: build/package/*.zip name: ${{ matrix.config.name }} ${{ matrix.config.build_type }} - name: Deploy to builds.wohlsoft.ru if: steps.create_package.outcome == 'success' && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true' continue-on-error: true shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi UPLOAD_LIST="set ssl:verify-certificate no;" for q in ./build/package/*.zip; do UPLOAD_LIST="${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;" done if [ "${BRANCH_NAME}" = "main" ]; then UPLOAD_LIST="${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} build/thextech.vpk -o thextech-vitadb-nightly.vpk;" fi lftp -e "${UPLOAD_LIST} exit" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }} - name: List Build Directory if: always() shell: bash run: | git status ls -lR build ================================================ FILE: .github/workflows/wii-ci.yml ================================================ name: Wii CI on: push: branches: - main - stable* - versus-ci-homebrew pull_request: branches: - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: name: "${{ matrix.config.name }} | ${{ matrix.config.build_type }}" runs-on: ubuntu-latest container: ghcr.io/thextech/wohlnet-ci-ubuntu2404-dkp-vita:latest env: DEVKITPRO: /opt/devkitpro DEVKITPPC: /opt/devkitpro/devkitPPC strategy: fail-fast: false matrix: config: - { name: "Wii build", extra_options: "-DTHEXTECH_EXECUTABLE_NAME=thextech \ -DCMAKE_TOOLCHAIN_FILE=$DEVKITPRO/cmake/Wii.cmake \ -DCMAKE_PREFIX_PATH=/opt/devkitpro/portlibs/wii-local \ -DCMAKE_INSTALL_PREFIX=$DEVKITPRO/portlibs/wii \ -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON", deps_cmdline: "echo 'DevkitPro SDK is already pre-installed'", generator: "Ninja", build_type: "MinSizeRel", executable_name: "thextech", subdir_name: "thextech-wii", upload_directory: "www/wii/" } steps: - name: Check for the upload support id: upload-check shell: bash run: | if [[ "${{ secrets.builds_login }}" != '' && \ "${{ secrets.builds_password }}" != '' && \ "${{ secrets.builds_host }}" != '' ]]; then echo "available=true" >> $GITHUB_OUTPUT; else echo "available=false" >> $GITHUB_OUTPUT; fi - name: Install Dependencies shell: bash run: | if [[ ! -z "${{ matrix.config.deps_cmdline }}" ]]; then eval ${{ matrix.config.deps_cmdline }} fi cmake --version - uses: TheXTech/checkout@v0.1 - uses: TheXTech/branch-name@v0.1 - name: Pull submodules shell: bash run: | git submodule update --init --recursive - name: Install modified SDL2 run: | cd .. git clone https://github.com/Wohlstand/SDL.git -b wii-support --depth 1 SDL2 cd SDL2 mkdir build cd build cmake -G Ninja \ -DCMAKE_TOOLCHAIN_FILE=$DEVKITPRO/cmake/Wii.cmake \ -DCMAKE_PREFIX_PATH=/opt/devkitpro/portlibs/wii-local \ -DCMAKE_PREFIX_PATH=/opt/devkitpro/portlibs/wii-local \ -DSDL_ALTIVEC=OFF \ -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} \ .. cmake --build . --target all -j4 sudo cmake --build . --target install - name: Configure shell: bash run: | cmake -B build -G "${{ matrix.config.generator }}" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} . - name: Build shell: bash run: | cmake --build build --target all -j4 - name: Check codesize and RAM usage if: success() && runner.os == 'Linux' shell: bash run: | size build/output/bin/thextech.elf - name: Create Package if: success() shell: bash run: | cd build mkdir "package/${{ matrix.config.subdir_name }}" cp ../changelog.txt "package/${{ matrix.config.subdir_name }}/" cp ../LICENSE "package/${{ matrix.config.subdir_name }}/License.TheXTech.txt" mv "package/meta.xml" "package/${{ matrix.config.subdir_name }}/" mv "package/icon.png" "package/${{ matrix.config.subdir_name }}/" mv "package/boot.dol" "package/${{ matrix.config.subdir_name }}/boot.dol" cat ../docs/README_WII.md ../README.md >> "package/${{ matrix.config.subdir_name }}/README.md" cd package zip -9 -r "thextech-wii-${BRANCH_NAME}.zip" "${{ matrix.config.subdir_name }}" rm -Rf "${{ matrix.config.subdir_name }}" cd ../.. - name: Upload artifact if: success() uses: actions/upload-artifact@v4 continue-on-error: true with: path: build/package/*.zip name: ${{ matrix.config.name }} ${{ matrix.config.build_type }} - name: Deploy to builds.wohlsoft.ru if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true' continue-on-error: true shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi UPLOAD_LIST="set ssl:verify-certificate no;" for q in ./build/package/*.zip; do UPLOAD_LIST="${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;" done lftp -e "${UPLOAD_LIST} exit" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }} - name: List Build Directory if: always() shell: bash run: | git status ls -lR build ================================================ FILE: .github/workflows/wiiu-ci.yml ================================================ name: Wii U CI on: push: branches: - main - stable* - versus-ci-homebrew pull_request: branches: - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: name: "${{ matrix.config.name }} | ${{ matrix.config.build_type }}" runs-on: ubuntu-latest container: ghcr.io/thextech/wohlnet-ci-ubuntu2404-dkp-vita:latest env: DEVKITPRO: /opt/devkitpro DEVKITPPC: /opt/devkitpro/devkitPPC strategy: fail-fast: false matrix: config: - { name: "Wii U build", extra_options: "-DTHEXTECH_EXECUTABLE_NAME=thextech \ -DCMAKE_TOOLCHAIN_FILE=$DEVKITPRO/cmake/WiiU.cmake \ -DCMAKE_PREFIX_PATH=/opt/devkitpro/portlibs/wiiu-local \ -DSDL2_DIR=/opt/devkitpro/portlibs/wiiu-local/lib/cmake/SDL2 \ -DCMAKE_INSTALL_PREFIX=$DEVKITPRO/portlibs/wiiu", deps_cmdline: "echo 'DevkitPro SDK is already pre-installed'", generator: "Ninja", build_type: "MinSizeRel", executable_name: "thextech", assets_url: "https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-adventure-of-demo-assets-full.7z", subdir_name: "thextech-wiiu", upload_directory: "www/wiiu/" } steps: - name: Check for the upload support id: upload-check shell: bash run: | if [[ "${{ secrets.builds_login }}" != '' && \ "${{ secrets.builds_password }}" != '' && \ "${{ secrets.builds_host }}" != '' ]]; then echo "available=true" >> $GITHUB_OUTPUT; else echo "available=false" >> $GITHUB_OUTPUT; fi - name: Install Dependencies shell: bash run: | if [[ ! -z "${{ matrix.config.deps_cmdline }}" ]]; then eval ${{ matrix.config.deps_cmdline }} fi cmake --version - uses: TheXTech/checkout@v0.1 - uses: TheXTech/branch-name@v0.1 - name: Pull submodules shell: bash run: | git submodule update --init --recursive - name: Install modified SDL2 run: | cd .. git clone https://github.com/Wohlstand/SDL.git -b wiiu-fixes-2.28--06-2025 --depth 1 SDL2 cd SDL2 mkdir build cd build cmake -G Ninja \ -DCMAKE_TOOLCHAIN_FILE=$DEVKITPRO/cmake/WiiU.cmake \ -DCMAKE_INSTALL_PATH=/opt/devkitpro/portlibs/wiiu-local \ -DCMAKE_PREFIX_PATH=/opt/devkitpro/portlibs/wiiu \ -DSDL_ALTIVEC=OFF \ -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} \ .. cmake --build . --target all -j4 sudo cmake --build . --target install - name: Configure shell: bash run: | cmake -B build -G "${{ matrix.config.generator }}" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} . - name: Build shell: bash run: | cmake --build build --target all -j4 - name: Create Package if: success() shell: bash run: | cd build mkdir package mkdir "package/${{ matrix.config.subdir_name }}" cp ../changelog.txt "package/${{ matrix.config.subdir_name }}/" cp ../LICENSE "package/${{ matrix.config.subdir_name }}/License.TheXTech.txt" cp meta.xml "package/${{ matrix.config.subdir_name }}/" cp icon.png "package/${{ matrix.config.subdir_name }}/" cp thextech.rpx "package/${{ matrix.config.subdir_name }}/thextech.rpx" cp thextech.wuhb "package/${{ matrix.config.subdir_name }}/thextech.wuhb" cat ../docs/README_WIIU.md ../README.md >> "package/${{ matrix.config.subdir_name }}/README.md" cd package zip -9 -r "thextech-wiiu-${BRANCH_NAME}.zip" "${{ matrix.config.subdir_name }}" rm -Rf "${{ matrix.config.subdir_name }}" cd ../.. - name: Upload artifact if: success() uses: actions/upload-artifact@v4 continue-on-error: true with: path: build/package/*.zip name: ${{ matrix.config.name }} ${{ matrix.config.build_type }} - name: Deploy to builds.wohlsoft.ru if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true' continue-on-error: true shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi UPLOAD_LIST="set ssl:verify-certificate no;" for q in ./build/package/*.zip; do UPLOAD_LIST="${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;" done lftp -e "${UPLOAD_LIST} exit" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }} - name: List Build Directory if: always() shell: bash run: | git status ls -lR build ================================================ FILE: .github/workflows/windows-ci.yml ================================================ name: Windows CI on: push: branches: - main - stable* - versus-ci pull_request: branches: - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: name: "${{ matrix.config.name }} | ${{ matrix.config.build_type }}" runs-on: ${{ matrix.config.os }} container: ${{ matrix.config.container }} strategy: fail-fast: false matrix: config: - { name: "Windows - 64-bit", os: windows-latest, extra_options: "-DCMAKE_PREFIX_PATH=C:/WohlMinGWw64/mingw64 \ -DTHEXTECH_EXECUTABLE_NAME=thextech \ -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_windows_mingw_toolchain_x64.cmake", generator: "Ninja", build_type: "MinSizeRel", extra_path: "/c/WohlMinGWw64/mingw64/bin", executable_name_suffix: "-win64", package_filename_suffix: "win64", upload_directory: "www/win32/", mingw_download: "https://wohlsoft.ru/docs/Software/MinGW/x86_64-12.2.0-release-posix-seh-rt_v10-rev1.7z", mingw_install_dir: "C:/WohlMinGWw64/", ninja_download: "https://wohlsoft.ru/docs/Software/Ninja-Build/ninja-win.zip", ninja_install_dir: "C:/WohlMinGWw64/mingw64/bin", lftp_download: "https://wohlsoft.ru/docs/Software/lftp-4.4.15.win64-openssl-1.0.1g.7z", lftp_install_dir: "C:/WohlMinGWw64/mingw64/" } - { name: "Windows - 32-bit", os: windows-latest, extra_options: "-DCMAKE_PREFIX_PATH=C:/WohlMinGWw64/mingw32 \ -DTHEXTECH_EXECUTABLE_NAME=thextech \ -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_windows_mingw_toolchain_x32.cmake", generator: "Ninja", build_type: "MinSizeRel", extra_path: "/c/WohlMinGWw64/mingw32/bin", executable_name_suffix: "", package_filename_suffix: "win32", upload_directory: "www/win32/", mingw_download: "https://wohlsoft.ru/docs/Software/MinGW/i686-12.2.0-release-posix-dwarf-rt_v10-rev1.7z", mingw_install_dir: "C:/WohlMinGWw64/", ninja_download: "https://wohlsoft.ru/docs/Software/Ninja-Build/ninja-win.zip", ninja_install_dir: "C:/WohlMinGWw64/mingw32/bin", lftp_download: "https://wohlsoft.ru/docs/Software/lftp-4.4.15.win64-openssl-1.0.1g.7z", lftp_install_dir: "C:/WohlMinGWw64/mingw32/" } - { name: "Windows - 32-bit (legacy floats)", os: windows-latest, extra_options: "-DCMAKE_PREFIX_PATH=C:/WohlMinGWw64/mingw32 \ -DTHEXTECH_EXECUTABLE_NAME=thextech \ -DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/ci_windows_mingw_toolchain_x32.cmake \ -DTHEXTECH_FIXED_POINT=OFF", generator: "Ninja", build_type: "MinSizeRel", extra_path: "/c/WohlMinGWw64/mingw32/bin", executable_name_suffix: "", package_filename_suffix: "win32-legacy-floats", upload_directory: "www/win32/", mingw_download: "https://wohlsoft.ru/docs/Software/MinGW/i686-12.2.0-release-posix-dwarf-rt_v10-rev1.7z", mingw_install_dir: "C:/WohlMinGWw64/", ninja_download: "https://wohlsoft.ru/docs/Software/Ninja-Build/ninja-win.zip", ninja_install_dir: "C:/WohlMinGWw64/mingw32/bin", lftp_download: "https://wohlsoft.ru/docs/Software/lftp-4.4.15.win64-openssl-1.0.1g.7z", lftp_install_dir: "C:/WohlMinGWw64/mingw32/" } - { name: "Windows - ARM64", os: windows-2022, extra_options: "-A ARM64 -DTHEXTECH_EXECUTABLE_NAME=thextech", generator: "Visual Studio 17 2022", build_type: "MinSizeRel", extra_path: "/c/WohlLFTP/bin", executable_name_suffix: "-arm64", package_filename_suffix: "arm64", upload_directory: "www/win-arm/", lftp_download: "https://wohlsoft.ru/docs/Software/lftp-4.4.15.win64-openssl-1.0.1g.7z", lftp_install_dir: "C:/WohlLFTP/" } steps: - name: Check for the upload support id: upload-check shell: bash run: | if [[ "${{ secrets.builds_login }}" != '' && \ "${{ secrets.builds_password }}" != '' && \ "${{ secrets.builds_host }}" != '' ]]; then echo "available=true" >> $GITHUB_OUTPUT; else echo "available=false" >> $GITHUB_OUTPUT; fi - name: Install Dependencies shell: bash run: | if [[ ! -z "${{ matrix.config.deps_cmdline }}" ]]; then eval ${{ matrix.config.deps_cmdline }} fi cmake --version - uses: TheXTech/checkout@v0.1 - uses: TheXTech/branch-name@v0.1 - name: Pull submodules shell: bash run: | git submodule update --init --recursive - name: Download MinGW if: matrix.config.mingw_download shell: bash run: C:\\msys64\\usr\\bin\\wget.exe -d -nv -t 5 -O mingw.7z "${{ matrix.config.mingw_download }}" - name: Extract MinGW if: matrix.config.mingw_install_dir shell: bash run: | 7z x mingw.7z -o"${{ matrix.config.mingw_install_dir }}" - name: Download Ninja if: matrix.config.ninja_download shell: bash run: C:\\msys64\\usr\\bin\\wget.exe -d -nv -t 5 -O ninja.zip "${{ matrix.config.ninja_download }}" - name: Extract Ninja if: matrix.config.ninja_install_dir shell: bash run: | 7z x ninja.zip -o"${{ matrix.config.ninja_install_dir }}" - name: Download LFTP if: matrix.config.lftp_download shell: bash run: C:\\msys64\\usr\\bin\\wget.exe -d -nv -t 5 -O lftp.7z "${{ matrix.config.lftp_download }}" - name: Extract LFTP if: matrix.config.lftp_install_dir shell: bash run: | 7z x lftp.7z bin etc -o"${{ matrix.config.lftp_install_dir }}" - name: Download SMBX assets shell: bash run: C:\\msys64\\usr\\bin\\wget.exe -d -nv -t 5 -O smbx13.7z "https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-smbx13-assets-full.7z" - name: Download AoD assets shell: bash run: C:\\msys64\\usr\\bin\\wget.exe -d -nv -t 5 -O aod.7z "https://wohlsoft.ru/projects/TheXTech/_downloads/thextech-adventure-of-demo-assets-full.7z" - name: Unpack all assets shell: bash run: | mkdir -p smbx13 cd smbx13 7z x ../smbx13.7z cd .. rm smbx13.7z mkdir -p aod cd aod 7z x ../aod.7z cd .. rm aod.7z - name: Apply update to translations shell: bash run: | ASSETS_ROOT1="$PWD/smbx13" ASSETS_ROOT2="$PWD/aod" cd .github/ci-helper bash translate_update.sh "${ASSETS_ROOT1}" bash translate_update.sh "${ASSETS_ROOT2}" cd ../.. - name: Configure shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} echo "PATH environment: ${PATH}" fi if [[ "${{ secrets.DISCORD_APP_ID }}" != '' ]]; then LOCAL_EXTRA_SETUP="-DTHEXTECH_ENABLE_DISCORD_RPC=ON -DTHEXTECH_DISCORD_APPID=\"${{ secrets.DISCORD_APP_ID }}\"" fi cmake -B build -G "${{ matrix.config.generator }}" -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.extra_options }} ${LOCAL_EXTRA_SETUP} . - name: Build shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi export MAKEFLAGS=--keep-going cmake --build build --config ${{ matrix.config.build_type }} --parallel 3 - name: List dependent libraries if: success() && runner.os == 'Linux' shell: bash run: | file build/output/bin/thextech ldd build/output/bin/thextech - name: Create Package if: success() shell: bash # ======================================= Adventures of Demo ======================================= # The side game about Demo and siblings from the A2XT universe by raocow and his fan community. # ======================================= Super Mario Bros. X - a fan-game ======================================= # Was made in 2009 by Andrew Spinks "Redigit", and supported up to 2011 by it's original author. run: | bash .github/ci-helper/pack-game.sh \ "${{ runner.os }}" \ "thextech-adventures-of-demo" \ "advdemo${{ matrix.config.executable_name_suffix }}" \ "thextech-adventures-of-demo-${{ matrix.config.package_filename_suffix }}-${BRANCH_NAME}" \ "aod" bash .github/ci-helper/pack-game.sh \ "${{ runner.os }}" \ "thextech-super-mario-bros-x" \ "smbx${{ matrix.config.executable_name_suffix }}" \ "thextech-super-mario-bros-x-${{ matrix.config.package_filename_suffix }}-${BRANCH_NAME}" \ "smbx13" bash .github/ci-helper/pack-game.sh \ "${{ runner.os }}" \ "thextech-bin" \ "thextech${{ matrix.config.executable_name_suffix }}" \ "thextech-bin-${{ matrix.config.package_filename_suffix }}-${BRANCH_NAME}" \ "none" - name: Upload artifact if: success() uses: actions/upload-artifact@v4 continue-on-error: true with: path: build/package/*.7z name: ${{ matrix.config.name }} ${{ matrix.config.build_type }} - name: Deploy to builds.wohlsoft.ru if: success() && github.event_name != 'pull_request' && steps.upload-check.outputs.available == 'true' continue-on-error: true shell: bash run: | if [[ ! -z "${{ matrix.config.extra_path }}" ]]; then export PATH=${{ matrix.config.extra_path }}:${PATH} fi UPLOAD_LIST="set ssl:verify-certificate no;" if [[ "${{ runner.os }}" == 'Windows' ]]; then for q in ./build/package/*.7z; do UPLOAD_LIST="${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;" done elif [[ "${{ runner.os }}" == 'Linux' ]]; then for q in ./build/package/*.tar.gz; do UPLOAD_LIST="${UPLOAD_LIST} put -O ${{ matrix.config.upload_directory }} $q;" done fi lftp -e "${UPLOAD_LIST} exit" -u ${{ secrets.builds_login }},${{ secrets.builds_password }} ${{ secrets.builds_host }} - name: List Build Directory if: always() shell: bash run: | git status ls -lR build ================================================ FILE: .gitignore ================================================ # This file is used to ignore files which are generated # ---------------------------------------------------------------------------- *~ *.autosave *.a *.core *.moc *.o *.obj *.orig *.rej *.so *.so.* *_pch.h.cpp *_resource.rc *.qm .#* *.*# core !core/ tags .DS_Store .directory *.debug Makefile* *.prl *.app moc_*.cpp ui_*.h qrc_*.cpp Thumbs.db *.res /.qmake.cache /.qmake.stash # qtcreator generated files *.pro.user* *.txt.user* # xemacs temporary files *.flc # Vim temporary files .*.swp # Visual Studio generated files *.ib_pdb_index *.idb *.ilk *.pdb *.sln *.suo *.vcproj *vcproj.*.*.user *.ncb *.sdf *.opensdf *.vcxproj *vcxproj.* build-* build/ # MinGW generated files *.Debug *.Release # Python byte code *.pyc # Binaries # -------- *.dll *.exe # CLion and like environments .idea/* cmake-build-*/* .vscode/* .vs/* CMakeSettings.json # PVS-Studio related stuff *.PVS-Studio.* .PVS-Studio/ # Vendored assets packages shouldn't appear at the GIT history /assets/ # clangd related stuff .cache/ ================================================ FILE: .gitmodules ================================================ [submodule "3rdparty/PGE_File_Formats"] path = 3rdparty/PGE_File_Formats url = https://github.com/WohlSoft/PGE-File-Library-STL.git branch = wip-mdx [submodule "3rdparty/LuaJIT"] path = 3rdparty/LuaJIT url = https://github.com/WohlSoft/LuaJIT.git branch = v2.1 [submodule "3rdparty/luabind"] path = 3rdparty/luabind url = https://github.com/WohlSoft/luabind-deboostified.git branch = master [submodule "3rdparty/AudioCodecs"] path = 3rdparty/AudioCodecs url = https://github.com/WohlSoft/AudioCodecs.git branch = master [submodule "3rdparty/SDL-Mixer-X"] path = 3rdparty/SDL-Mixer-X url = https://github.com/WohlSoft/SDL-Mixer-X.git branch = master [submodule "3rdparty/FreeImageLite"] path = 3rdparty/FreeImageLite url = https://github.com/WohlSoft/libFreeImage.git branch = master [submodule "3rdparty/freetype"] path = 3rdparty/freetype url = https://github.com/TheXTech/freetype.git branch = master [submodule "3rdparty/thextech-discord-rpc"] path = 3rdparty/thextech-discord-rpc url = https://github.com/TheXTech/thextech-discord-rpc.git branch = main [submodule "3rdparty/glew-cmake"] path = 3rdparty/glew-cmake url = https://github.com/TheXTech/thextech-glew-cmake.git branch = master [submodule "3rdparty/DirManager"] path = 3rdparty/DirManager url = https://github.com/WohlSoft/DirManager.git branch = master [submodule "3rdparty/IniProcessor"] path = 3rdparty/IniProcessor url = https://github.com/WohlSoft/IniProcessing.git branch = master [submodule "3rdparty/FileMapper"] path = 3rdparty/FileMapper url = https://github.com/WohlSoft/FileMapper.git branch = master [submodule "3rdparty/angle-shader-translator"] path = 3rdparty/angle-shader-translator url = https://github.com/TheXTech/angle-shader-translator-library.git branch = dist-no-spirv [submodule "3rdparty/luau"] path = 3rdparty/luau url = https://github.com/TheXTech/luau.git branch = master [submodule "mbediso"] path = 3rdparty/mbediso url = https://github.com/ds-sloth/mbediso/ branch = main [submodule "3rdparty/SDL_net"] path = 3rdparty/SDL_net url = https://github.com/TheXTech/SDL_net.git branch = SDL2 ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.5...3.10) if(POLICY CMP0069) # Allow CMAKE_INTERPROCEDURAL_OPTIMIZATION (lto) to be set cmake_policy(SET CMP0069 NEW) set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) endif() if(POLICY CMP0079) # Allow linking subprojects against each other cmake_policy(SET CMP0079 NEW) set(CMAKE_POLICY_DEFAULT_CMP0079 NEW) endif() project(TheXTech LANGUAGES C CXX) if(APPLE) enable_language(OBJC) endif() include(CheckCCompilerFlag) include(CheckCXXCompilerFlag) include(ExternalProject) include(CheckLibraryExists) include(CheckFunctionExists) include(GNUInstallDirs) include(FindBacktrace) include(TestBigEndian) include(CheckIncludeFile) include(cmake/git_info.cmake) message("== Current GIT hash [${GIT_COMMIT_HASH}], branch [${GIT_BRANCH}], package [${PACKAGE_SUFFIX}] ==") if(NOT WIN32 AND NOT NINTENDO_SWITCH AND (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)) check_cxx_compiler_flag("-no-pie" HAS_NO_PIE) endif() function(pge_set_nopie _target) set_target_properties(${_target} PROPERTIES POSITION_INDEPENDENT_CODE False ) if(HAS_NO_PIE AND (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)) set_property(TARGET ${_target} APPEND_STRING PROPERTY LINK_FLAGS " -no-pie") endif() endfunction() set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_POSITION_INDEPENDENT_CODE FALSE) set(CMAKE_INSTALL_RPATH ".") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) if(EMSCRIPTEN) set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) endif() if(APPLE) if(CMAKE_HOST_SYSTEM_VERSION VERSION_LESS 9) set(XTECH_PLIST_NAME "thextech.plist.tiger.in") set(XTECH_DEFAULT_ICNS "resources/tiger/thextech.icns") else() set(XTECH_PLIST_NAME "thextech.plist.in") set(XTECH_DEFAULT_ICNS "resources/thextech.icns") endif() if(NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET OR CMAKE_OSX_DEPLOYMENT_TARGET STREQUAL "") if(CMAKE_HOST_SYSTEM_VERSION VERSION_LESS 9) set(THEXTECH_MACOS_MIN_VERSION "10.4") else() set(THEXTECH_MACOS_MIN_VERSION "10.9") endif() else() set(THEXTECH_MACOS_MIN_VERSION "${CMAKE_OSX_DEPLOYMENT_TARGET}") endif() message("-> Mac OS X minimal version: ${THEXTECH_MACOS_MIN_VERSION}") endif() if(ANDROID) set(DEPENDENCIES_INSTALL_DIR ${CMAKE_BINARY_DIR}/output-deps) set(FDROID_BUILD OFF CACHE BOOL "Is this build a part of the F-Droid workflow?") mark_as_advanced(FDROID_BUILD) else() set(DEPENDENCIES_INSTALL_DIR ${CMAKE_BINARY_DIR}/output) endif() set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/output/lib) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/output/lib) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/output/bin) foreach(OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES}) message("--> ${OUTPUTCONFIG}") string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}") set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") endforeach( OUTPUTCONFIG CMAKE_CONFIGURATION_TYPES ) set(PGE_INSTALL_DIRECTORY "TheXTech") include(cmake/build_props.cmake) if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) # Update if necessary set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffloat-store") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffloat-store") endif() if(WIN32 AND NOT EMSCRIPTEN) set(CMAKE_SHARED_LIBRARY_PREFIX "") endif() if(UNIX) check_include_file(/opt/vc/include/bcm_host.h BCMHOST_H) endif() string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) if(CMAKE_BUILD_TYPE_LOWER STREQUAL "debug") add_definitions(-DDEBUG_BUILD) if(CMAKE_COMPILER_IS_GNUCXX) add_definitions(-D_GLIBCXX_DEBUG=1 -D_GLIBCXX_ASSERTIONS=1) endif() endif() # Version include(version.cmake) # Default GIT version include(cmake/git_version.cmake) configure_file(lib/CrashHandler/backtrace.h.in generated-include/lib/CrashHandler/backtrace.h) include_directories(${CMAKE_CURRENT_BINARY_DIR}/generated-include) if(NINTENDO_SWITCH) include_directories($ENV{DEVKITPRO}/portlibs/switch/include) endif() #if(WIN32 OR EMSCRIPTEN OR HAIKU) # set(USE_SYSTEM_LIBS_DEFAULT OFF) #else() # set(USE_SYSTEM_LIBS_DEFAULT ON) #endif() set(USE_SYSTEM_LIBS_DEFAULT OFF) if(UNIX) option(PORTMASTER "Make a special build for PortMaster" OFF) endif() if(HAIKU OR NINTENDO_SWITCH OR NINTENDO_WII OR NINTENDO_WIIU OR XTECH_MACOSX_TIGER OR PORTMASTER) set(USE_SYSTEM_SDL2_DEFAULT ON) # Should be used on Haiku, own SDL2 Haiku build on CMake is broken else() set(USE_SYSTEM_SDL2_DEFAULT ${USE_SYSTEM_LIBS_DEFAULT}) endif() if(NINTENDO_DS) set(THEXTECH_ENABLE_TTF_SUPPORT_DEFAULT OFF) else() set(THEXTECH_ENABLE_TTF_SUPPORT_DEFAULT ON) endif() if((APPLE AND NOT XTECH_MACOSX_TIGER) OR WIN32 OR (CMAKE_SYSTEM_NAME STREQUAL "Linux")) set(THEXTECH_USE_ANGLE_TRANSLATOR_DEFAULT ON) else() set(THEXTECH_USE_ANGLE_TRANSLATOR_DEFAULT OFF) endif() option(USE_SYSTEM_LIBS "Use dependent libraries like SDL2, FreeImageLite and MixerX, installed in the system" ${USE_SYSTEM_LIBS_DEFAULT}) option(USE_SYSTEM_SDL2 "Use SDL2 from a system even prefering system libraries" ${USE_SYSTEM_SDL2_DEFAULT}) option(USE_STATIC_LIBC "Link libc and libstdc++ statically" OFF) if(NINTENDO_3DS OR NINTENDO_WII OR NINTENDO_WIIU OR NINTENDO_DS) set(USE_STATIC_LIBC ON) endif() if(NINTENDO_3DS) option(THEXTECH_CUSTOM_AUDIO_LIBRARY "Use the custom audio library instead of MixerX" OFF) endif() if(ANDROID OR VITA OR NINTENDO_DS OR NINTENDO_3DS OR NINTENDO_WII OR NINTENDO_WIIU OR NINTENDO_SWITCH OR PGE_MIN_PORT OR THEXTECH_NO_SDL_BUILD OR PORTMASTER) set(THEXTECH_FORCE_FULLSCREEN ON) else() option(THEXTECH_FORCE_FULLSCREEN "Force the game to only use fullscreen mode" OFF) endif() if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux" AND "${TARGET_PROCESSOR}" MATCHES "arm.*") message("-- Desktop OpenGL build will be disabled on unsupported platform (${CMAKE_SYSTEM_NAME} on ${TARGET_PROCESSOR})") set(THEXTECH_BUILD_GL_DESKTOP_DEFAULT OFF) else() set(THEXTECH_BUILD_GL_DESKTOP_DEFAULT ON) endif() option(THEXTECH_BUILD_GL_DESKTOP_MODERN "On SDL builds, include the OpenGL 2.0+ Core/Compatibility renderer" ${THEXTECH_BUILD_GL_DESKTOP_DEFAULT}) option(THEXTECH_BUILD_GL_ES_MODERN "On SDL builds, include the OpenGL ES 2.0+ renderer" ON) option(THEXTECH_BUILD_GL_DESKTOP_LEGACY "On SDL builds, include the OpenGL 1.1+ renderer" ${THEXTECH_BUILD_GL_DESKTOP_DEFAULT}) option(THEXTECH_BUILD_GL_ES_LEGACY "On SDL builds, include the OpenGL ES 1.1 renderer" OFF) option(THEXTECH_USE_ANGLE_TRANSLATOR "On modern OpenGL builds, include the ANGLE shader translator to improve shader support in OpenGL 3.0+ Core/Compatibility profiles" ${THEXTECH_USE_ANGLE_TRANSLATOR_DEFAULT}) option(RANGE_ARR_USE_HEAP "Store data of RangeArr<> template in a heap" OFF) option(RANGE_ARR_UNSAFE_MODE "Disable all range checks at RangeArr<> template" OFF) option(ENABLE_ANTICHEAT_TRAP "Enable anti-cheating trap for the \"redigitiscool\" cheat code" OFF) option(ENABLE_OLD_CREDITS "Use original Redigit's credits without changes" OFF) option(ENABLE_LOGGING "Enable debug logging written into a file (may not work on some platfors)" ON) option(THEXTECH_ENABLE_LUA "Enable lua scripting support" OFF) option(THEXTECH_ENABLE_LUAU "Enable luau build testing" OFF) option(THEXTECH_ENABLE_LUNA_AUTOCODE "Enable LunaDLL Autocode scripting support" ON) option(THEXTECH_ENABLE_SDL_NET "Enable SDL_net build testing" OFF) option(THEXTECH_ENABLE_EDITOR "Enable runtime support for editor features" ON) option(THEXTECH_ENABLE_TTF_SUPPORT "Enable TTF fonts support (FreeType required)" ${THEXTECH_ENABLE_TTF_SUPPORT_DEFAULT}) option(THEXTECH_CLI_BUILD "Minimal CLI build for testing and benchmarking on desktop platforms" OFF) option(THEXTECH_NO_SDL_BUILD "Do not use SDL in the CLI build; useful as a starting point for new ports" OFF) option(THEXTECH_ENABLE_WIP_FEATURES "Enable experimental and incomplete features (disabled by default)" OFF) mark_as_advanced(THEXTECH_ENABLE_WIP_FEATURES) option(THEXTECH_NO_BUILD_DATE "Disables the using of the build timestamp in order to make the build reproducible" OFF) option(THEXTECH_ENABLE_AUDIO_FX "Enable real-time audio effects support" ON) option(THEXTECH_FIXED_POINT "Experimental: use fixed-point arithmetic for gameplay logic" ON) option(ENABLE_ADDRESS_SANITIZER "Enable the Address Sanitizer GCC feature" OFF) # ============ Customization ============== set(LIB_SRC_EXTRA) set(THEXTECH_PACKAGE_NAME "thextech" CACHE STRING "Name of package archive file") set(THEXTECH_EXECUTABLE_NAME "thextech" CACHE STRING "Name of executable file") set(THEXTECH_DIRECTORY_PREFIX "TheXTech" CACHE STRING "Name used for directories on installed targets (should vary across forks, not asset packs)") set(THEXTECH_INSTALLER_PACKAGE_NAME "${THEXTECH_EXECUTABLE_NAME}" CACHE STRING "The package name for the package manager") if(APPLE) set(THEXTECH_PRELOAD_ENVIRONMENT "" CACHE STRING "Path to resources root to pack") option(THEXTECH_PRELOAD_ENVIRONMENT_MANUALLY "Make application look for assets inside the bundle, but don't actually put any assets. The 'Template' app will be produced for manual addition of assets." OFF) set(THEXTECH_BUNDLE_NAME "TheXTech" CACHE STRING "Name of bundle folder") set(THEXTECH_ICON_NAME "thextech.icns" CACHE STRING "Name of bundle icon file") set(THEXTECH_CUSTOM_ICON_PATH "" CACHE STRING "Name of icon file to pack into the bundle") if(NOT THEXTECH_CUSTOM_ICON_PATH STREQUAL "") message("Use custom macOS icon: ${THEXTECH_CUSTOM_ICON_PATH}") list(APPEND LIB_SRC_EXTRA "${THEXTECH_CUSTOM_ICON_PATH}" ) endif() endif() if(NOT APPLE AND NOT ANDROID AND NOT EMSCRIPTEN) set(THEXTECH_UNIX_INSTALL TRUE) endif() # ============ Customization ==end========= # ============ Platform-conf ============== if(EMSCRIPTEN) # config for web app deployment manifest set(THEXTECH_MANIFEST_NAME "TheXTech Engine ${THEXTECH_VERSION_STRING}" CACHE STRING "Web app manifest name, used as title after installation") set(THEXTECH_MANIFEST_ID "wohlsoft-thextech" CACHE STRING "Web app ID, uniquely identifies installed app") set(THEXTECH_MANIFEST_DESC "" CACHE STRING "Web app description") set(THEXTECH_DEPLOY_URL "http://localhost:8080/" CACHE STRING "Fully qualified HTTPS URL where application will be deployed (HTTP may only be used for local testing)") # versioned ID if(GIT_BRANCH STREQUAL "main") set(GIT_BRANCH_IF_NOT_MAIN "") else() set(GIT_BRANCH_IF_NOT_MAIN "-${GIT_BRANCH}") endif() set(THEXTECH_MANIFEST_ID_V "${THEXTECH_MANIFEST_ID}${GIT_BRANCH_IF_NOT_MAIN}${THEXTECH_VERSION_REL}") string(CONFIGURE "${THEXTECH_MANIFEST_NAME}" THEXTECH_MANIFEST_NAME_OUT) string(CONFIGURE "${THEXTECH_MANIFEST_ID_V}" THEXTECH_MANIFEST_ID_OUT) string(CONFIGURE "${THEXTECH_MANIFEST_DESC}" THEXTECH_MANIFEST_DESC_OUT) endif() if(VITA) message("Enabling PS Vita Support and fast-math flags.") # Fast Math flags for Vita, ensuring -DVITA is passed to the compiler. set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffast-math -mtune=cortex-a9 -mfpu=neon -DVITA") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffast-math -mtune=cortex-a9 -mfpu=neon -DVITA") # Disable annoying warning for Parameter passing. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-psabi") set(THEXTECH_BUILD_GL_DESKTOP_MODERN OFF CACHE BOOL "" FORCE) set(THEXTECH_BUILD_GL_DESKTOP_LEGACY OFF CACHE BOOL "" FORCE) set(THEXTECH_BUILD_GL_ES_LEGACY OFF CACHE BOOL "" FORCE) # note: to test GLESv2 on Vita, remove the below line, install VitaGL with shader support and Northfear's SDL2 fork, and enable USE_SYSTEM_SDL2 # As of August 2025, Northfear's SDL2 fork invalidly reports the OpenGL profile as Compatibility instead of ES, and VitaGL doesn't support clearing buffers, and its shader transpiler is imperfect set(THEXTECH_BUILD_GL_ES_MODERN OFF CACHE BOOL "" FORCE) endif() if(NINTENDO_SWITCH) message("Enabling Nintendo Switch Support and fast-math flags.") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE -DNINTENDO_SWITCH -D__SWITCH__") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE -DNINTENDO_SWITCH -D__SWITCH__") endif() if(NINTENDO_WII OR NINTENDO_3DS OR NINTENDO_DS) set(PGE_MIN_PORT ON) set(THEXTECH_NO_ARGV_HANDLING ON) add_compile_definitions(PGE_MIN_PORT) set(THEXTECH_BUILD_GL_DESKTOP_MODERN OFF CACHE BOOL "" FORCE) set(THEXTECH_BUILD_GL_DESKTOP_LEGACY OFF CACHE BOOL "" FORCE) set(THEXTECH_BUILD_GL_ES_MODERN OFF CACHE BOOL "" FORCE) set(THEXTECH_BUILD_GL_ES_LEGACY OFF CACHE BOOL "" FORCE) endif() if(NINTENDO_WIIU) set(THEXTECH_NO_ARGV_HANDLING ON) set(THEXTECH_BUILD_GL_DESKTOP_MODERN OFF CACHE BOOL "" FORCE) set(THEXTECH_BUILD_GL_DESKTOP_LEGACY OFF CACHE BOOL "" FORCE) set(THEXTECH_BUILD_GL_ES_MODERN OFF CACHE BOOL "" FORCE) set(THEXTECH_BUILD_GL_ES_LEGACY OFF CACHE BOOL "" FORCE) endif() if(PORTMASTER) message("Special build for PortMaster.") set(THEXTECH_BUILD_GL_DESKTOP_MODERN OFF CACHE BOOL "" FORCE) set(THEXTECH_BUILD_GL_DESKTOP_LEGACY OFF CACHE BOOL "" FORCE) set(THEXTECH_BUILD_GL_ES_MODERN ON CACHE BOOL "" FORCE) set(THEXTECH_BUILD_GL_ES_LEGACY OFF CACHE BOOL "" FORCE) set(PGE_SHARED_SDLMIXER OFF CACHE BOOL "" FORCE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") endif() if(THEXTECH_CLI_BUILD) set(THEXTECH_BUILD_GL_DESKTOP_MODERN OFF CACHE BOOL "" FORCE) set(THEXTECH_BUILD_GL_DESKTOP_LEGACY OFF CACHE BOOL "" FORCE) set(THEXTECH_BUILD_GL_ES_MODERN OFF CACHE BOOL "" FORCE) set(THEXTECH_BUILD_GL_ES_LEGACY OFF CACHE BOOL "" FORCE) endif() if(THEXTECH_CLI_BUILD OR PGE_MIN_PORT OR EMSCRIPTEN OR PORTMASTER) set(RANGE_ARR_UNSAFE_MODE ON) endif() if(NINTENDO_DS) set(THEXTECH_NO_SDL_BUILD ON CACHE BOOL "" FORCE) endif() if(ANDROID) set(THEXTECH_BUILD_GL_DESKTOP_MODERN OFF CACHE BOOL "" FORCE) set(THEXTECH_BUILD_GL_DESKTOP_LEGACY OFF CACHE BOOL "" FORCE) set(THEXTECH_BUILD_GL_ES_LEGACY ON CACHE BOOL "") endif() if(APPLE OR WIN32) set(THEXTECH_BUILD_GL_ES_MODERN OFF CACHE BOOL "" FORCE) set(THEXTECH_BUILD_GL_ES_LEGACY OFF CACHE BOOL "" FORCE) endif() # 32-bit Android special case, temporarily disabled for some experiments # if(ANDROID AND CMAKE_SIZEOF_VOID_P EQUAL 4) # set(THEXTECH_BUILD_GL_ES_MODERN OFF CACHE BOOL "" FORCE) # endif() if(EMSCRIPTEN) set(THEXTECH_BUILD_GL_DESKTOP_MODERN OFF CACHE BOOL "" FORCE) set(THEXTECH_BUILD_GL_DESKTOP_LEGACY OFF CACHE BOOL "" FORCE) set(THEXTECH_BUILD_GL_ES_LEGACY OFF CACHE BOOL "" FORCE) endif() if(NINTENDO_SWITCH) set(THEXTECH_BUILD_GL_ES_MODERN ON CACHE BOOL "" FORCE) set(THEXTECH_BUILD_GL_ES_LEGACY OFF CACHE BOOL "" FORCE) set(THEXTECH_BUILD_GL_DESKTOP_LEGACY OFF CACHE BOOL "" FORCE) set(THEXTECH_BUILD_GL_DESKTOP_MODERN OFF CACHE BOOL "" FORCE) endif() if(THEXTECH_NO_SDL_BUILD) set(THEXTECH_ENABLE_AUDIO_FX OFF CACHE BOOL "" FORCE) set(MBEDISO_THREADS "NONE") else() set(MBEDISO_THREADS "SDL2") endif() if(EMSCRIPTEN) message("Is EMSCRIPTEN!") set(PGE_PRELOAD_ENVIRONMENT "/home/vitaly/.PGE_Project/_emscripten/thextech" CACHE STRING "Path to resources root to pack") set(CMAKE_EXECUTABLE_SUFFIX ".html") # set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse -msse2") # -DIS_CXX -s USE_PTHREADS=1 # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s WASM=1 -s 'BINARYEN_METHOD=\"native-wasm\"'") #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EMTERPRETIFY=1 -s EMTERPRETIFY_ASYNC=1 -s 'EMTERPRETIFY_FILE=\"pge_engine.binary\"'") # -s \"EMTERPRETIFY_WHITELIST=['_main']\" if(CMAKE_BUILD_TYPE_LOWER STREQUAL "debug" OR CMAKE_BUILD_TYPE_LOWER STREQUAL "relwithdebinfo") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1") endif() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -s USE_SDL=0") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s USE_SDL=0") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s \"DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=['\\$autoResumeAudioContext', '\\$dynCall']\"") #-s USE_SDL=2 set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s LLD_REPORT_UNDEFINED -s ERROR_ON_UNDEFINED_SYMBOLS=1 -s ASYNCIFY=1 -lidbfs.js") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s \"EXPORTED_FUNCTIONS=['_main', '_unlockLoadingCustomState']\"") # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s 'BINARYEN_TRAP_MODE=\"clamp\"'") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s TOTAL_MEMORY=83886080 --no-heap-copy -s ALLOW_MEMORY_GROWTH=1") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORTED_RUNTIME_METHODS=ccall") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s BINARYEN_EXTRA_PASSES=coalesce-locals") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s MAX_WEBGL_VERSION=2") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s GL_ENABLE_GET_PROC_ADDRESS") # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s \"EXTRA_EXPORTED_RUNTIME_METHODS=['Pointer_stringify']\"") # set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --embed-file \"${_LANGAUGES_TEMP_FOLDER}\"@\"languages/\"") # set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --preload-file \"${PGE_PRELOAD_CONFIG_PACK}\"@\"configs/${PGE_PRELOAD_CONFIG_PACK_NAME}/\"") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --preload-file \"${PGE_PRELOAD_ENVIRONMENT}\"@\"/\"") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --shell-file '${CMAKE_CURRENT_SOURCE_DIR}/resources/emscripten/shell_minimal.html'") # set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --use-preload-cache") # set(LINK_FLAGS "${LINK_FLAGS} -DIS_LINK -s USE_PTHREADS=1 -s FORCE_FILESYSTEM=1 --embed-file '${CMAKE_CURRENT_SOURCE_DIR}/@languages'") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions") # bump to -fwasm-exceptions once widely supported set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fexceptions") # bump to -fwasm-exceptions once widely supported endif() # ============ Platform-conf ==end========= option(PGEFL_QT_SUPPORT "Build PGE-FL with Qt support [Unneeded]" OFF) set(Moondust_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) # Don't install PGE-FL libraries set(PGEFL_ENABLE_RWOPS ON) if(PGE_MIN_PORT) set(PGEFL_DISABLE_SMBX38A ON) endif() add_subdirectory(3rdparty/PGE_File_Formats) mark_as_advanced(WITH_UNIT_TESTS PGEFL_QT_SUPPORT) if(USE_STATIC_LIBC) set(BUILD_SHARED_LIBS OFF) endif() include(lib/Allocator/pool-allocator.cmake) include(lib/pge_video_rec/pge-video-rec.cmake) include(3rdparty/DirManager/dirman.cmake) if(NOT VITA AND NOT NINTENDO_SWITCH AND NOT NINTENDO_3DS AND NOT NINTENDO_WII AND NOT NINTENDO_WIIU AND NOT NINTENDO_DS) include(3rdparty/FileMapper/FileMapper.cmake) endif() include(lib/fmt/fmt.cmake) include(3rdparty/IniProcessor/IniProcessor.cmake) include(lib/Utils/Utils.cmake) include(lib/Logger/logger.cmake) include(lib/AppPath/app_path.cmake) include(lib/tclap/tclap.cmake) include(lib/md5/md5.cmake) include(lib/sdl_proxy/sdl_proxy.cmake) if(WIN32) include(lib/CrashHandler/StackWalker/StackWalker.cmake) endif() include(cmake/library_zlib.cmake) add_definitions("-DMOONDUST_LOGGER_FILENAME_PREFIX=\"TheXTech\"") if(THEXTECH_ENABLE_TTF_SUPPORT) # option(THEXTECH_ENABLE_TTF_HARFBUZZ "Enable the HarfBuzz library use" OFF) # mark_as_advanced(THEXTECH_ENABLE_TTF_HARFBUZZ) # Temporarily disable # if(THEXTECH_ENABLE_TTF_HARFBUZZ AND NOT NINTENDO_3DS) # include(cmake/library_HarfBuzz.cmake) # else() # set(FT_DISABLE_HARFBUZZ ON) # endif() set(FT_DISABLE_HARFBUZZ ON) include(cmake/library_FreeType.cmake) endif() if(PGE_MIN_PORT) set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) set(ENABLE_UBSAN OFF CACHE BOOL "" FORCE) endif() if(THEXTECH_ENABLE_LUA) include(cmake/library_luajit.cmake) include(cmake/library_luabind.cmake) endif() if(NOT THEXTECH_CLI_BUILD AND NOT THEXTECH_NO_SDL_BUILD) include(cmake/library_discord_rpc.cmake) endif() set(A2XT_LIBS pgefl ${UTILS_LIBS}) set(A2XT_INCS src lib 3rdparty) set(A2XT_DEPS) add_library(A2XT_Int INTERFACE) if(THEXTECH_NO_SDL_BUILD) add_subdirectory(lib/sdl_proxy_rwops) get_target_property(SDL_PROXY_RWOPS_INCS sdl_proxy_rwops INTERFACE_INCLUDE_DIRECTORIES) target_include_directories(pgefl PRIVATE ${SDL_PROXY_RWOPS_INCS}) add_dependencies(pgefl sdl_proxy_rwops) target_link_libraries(A2XT_Int INTERFACE sdl_proxy_rwops) endif() if(THEXTECH_ENABLE_TTF_SUPPORT) list(APPEND A2XT_DEPS FREETYPE_Local) list(APPEND A2XT_LIBS PGE_FreeType) if(TARGET PGE_HarfBuzz) list(APPEND A2XT_DEPS HARFBUZZ_Local) list(APPEND A2XT_LIBS PGE_HarfBuzz) endif() endif() if(THEXTECH_ENABLE_LUA) list(APPEND A2XT_DEPS LuaBind_Local) list(APPEND A2XT_INCS script/include) endif() target_compile_definitions(A2XT_Int INTERFACE -DINI_PROCESSING_USE_MDX_PARSER # IniProcessor should use the MDX tokenizer for lists ) include(cmake/library_glew.cmake) include(cmake/library_syslibs.cmake) if(USE_SYSTEM_LIBS) find_package(SDL2 REQUIRED) find_library(MIXERX_LIB NAMES SDL2_mixer_ext) find_path(MIXERX_HEAD_DIR SDL_mixer_ext.h PATH_SUFFIXES SDL2) find_library(FREEIMAGELITE_LIB NAMES FreeImageLite) find_path(FREEIMAGELITE_HEAD_DIR NAMES FreeImageLite.h) if(MINGW AND NOT SDL2_LIBRARIES) set(SDL2_LIBRARIES mingw32 SDL2main SDL2) endif() list(APPEND A2XT_LIBS ${FREEIMAGELITE_LIB} ${MIXERX_LIB} "${SDL2_LIBRARIES}" ${THEXTECH_SYSLIBS}) list(APPEND A2XT_INCS ${SDL2_INCLUDE_DIRS} ${MIXERX_HEAD_DIR} ${FREEIMAGELITE_HEAD_DIR}) target_include_directories(pgefl PRIVATE ${SDL2_INCLUDE_DIRS}) elseif(NOT THEXTECH_NO_SDL_BUILD) include(cmake/library_FreeImage.cmake) list(APPEND A2XT_DEPS FreeImage_Local) list(APPEND A2XT_LIBS PGE_FreeImage) include(cmake/library_SDLMixerX.cmake) list(APPEND A2XT_INCS "${DEPENDENCIES_INSTALL_DIR}/include" ${SDL2_INCLUDE_DIRS}) target_include_directories(pgefl PRIVATE ${SDL2_INCLUDE_DIRS}) # depend on RWops from AudioCodecs_Local SDL2 build add_dependencies(pgefl AudioCodecs_Local) if(NOT USE_SYSTEM_ZLIB) # Depend on ZLib at AudioCodecs if(TARGET FREETYPE_Local) add_dependencies(FREETYPE_Local AudioCodecs_Local) endif() add_dependencies(FreeImage_Local AudioCodecs_Local) endif() if(NOT THEXTECH_NO_MIXER_X) list(APPEND A2XT_DEPS SDLMixerX_Local) else() list(APPEND A2XT_DEPS AudioCodecs_Local) endif() if(PGE_SHARED_SDLMIXER) list(APPEND A2XT_LIBS PGE_SDLMixerX) else() list(APPEND A2XT_LIBS PGE_SDLMixerX_static) endif() # UNIX seems to be too general -- how to target desktop Linux specifically? # if(UNIX AND (THEXTECH_BUILD_GL_DESKTOP_MODERN OR THEXTECH_BUILD_GL_DESKTOP_LEGACY OR THEXTECH_BUILD_GL_ES_MODERN OR THEXTECH_BUILD_GL_ES_LEGACY)) # list(APPEND A2XT_LIBS GL) # endif() if(THEXTECH_ENABLE_VENDORED_GLEW) list(APPEND A2XT_DEPS GLEW_Local) list(APPEND A2XT_LIBS PGE_GLEW) endif() if(WIN32 AND (THEXTECH_BUILD_GL_DESKTOP_MODERN OR THEXTECH_BUILD_GL_DESKTOP_LEGACY)) # glu32 not needed or provided on ARM if("${TARGET_PROCESSOR}" STREQUAL "i386" OR "${TARGET_PROCESSOR}" STREQUAL "x86_64") list(APPEND A2XT_LIBS glu32) endif() list(APPEND A2XT_LIBS opengl32 gdi32) endif() if(PGE_SHARED_SDLMIXER) if(NOT WIN32 AND NOT EMSCRIPTEN AND NOT APPLE AND NOT ANDROID AND NOT NINTENDO_SWITCH) if(THEXTECH_BUILD_GL_ES_MODERN AND NOT THEXTECH_BUILD_GL_DESKTOP_MODERN AND NOT THEXTECH_BUILD_GL_DESKTOP_LEGACY AND NOT THEXTECH_BUILD_GL_ES_LEGACY) list(APPEND A2XT_LIBS GLESv2) else() find_library(_LIB_GL GL) if(_LIB_GL) list(APPEND A2XT_LIBS ${_LIB_GL}) endif() endif() else() list(APPEND A2XT_LIBS shell32) endif() endif() if(THEXTECH_CUSTOM_AUDIO_LIBRARY) list(APPEND A2XT_LIBS gme vorbisidec ogg) endif() list(APPEND A2XT_LIBS PGE_ZLib) endif() if(THEXTECH_ENABLE_DISCORD_RPC) list(APPEND A2XT_DEPS DiscordPRC_Local) list(APPEND A2XT_LIBS PGE_DiscordRPC) set(THEXTECH_ENABLE_INTEGRATOR ON) # This feature needs for an Integrator endif() if(PGE_ENABLE_VIDEO_REC) list(APPEND A2XT_LIBS ${VIDEO_REC_LIBS}) list(APPEND A2XT_DEPS ${VIDEO_REC_DEPS}) list(APPEND A2XT_INCS ${VIDEO_REC_INCS}) endif() if(THEXTECH_BUILD_GL_DESKTOP_MODERN AND THEXTECH_USE_ANGLE_TRANSLATOR) add_subdirectory(3rdparty/angle-shader-translator) message("== Building ANGLE shader translator ==") # suppress some known warnings on GCC / Clang if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") target_compile_options(angle_shader_translator PRIVATE -Wno-unused-parameter) target_compile_options(angle_shader_translator PRIVATE -Wno-c++17-attribute-extensions) elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") target_compile_options(angle_shader_translator PRIVATE -Wno-unused-parameter) target_compile_options(angle_shader_translator PRIVATE -Wno-c++17-extensions) elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") target_compile_options(angle_shader_translator PRIVATE /wd4100) # unreferenced formal parameter endif() target_link_libraries(A2XT_Int INTERFACE angle_shader_translator) endif() if(THEXTECH_ENABLE_SDL_NET) message("== Building SDL_net... but it doesn't do anything! ==") set(SDL_NET_PGE_DEPENDENT_BUILD ON) add_subdirectory(3rdparty/SDL_net EXCLUDE_FROM_ALL) target_link_libraries(SDL2_net PRIVATE A2XT_Int) target_compile_definitions(A2XT_Int INTERFACE -DTHEXTECH_ENABLE_SDL_NET ) endif() if(THEXTECH_ENABLE_LUAU) message("== Building Luau... but it doesn't do anything! ==") add_subdirectory(3rdparty/luau EXCLUDE_FROM_ALL) if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") target_compile_options(Luau.Compiler PRIVATE "-w") target_compile_options(Luau.Ast PRIVATE "-w") target_compile_options(Luau.VM PRIVATE "-w") elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") target_compile_options(Luau.Compiler PRIVATE /wd4100) # unreferenced formal parameter target_compile_options(Luau.Ast PRIVATE /wd4100) # unreferenced formal parameter target_compile_options(Luau.VM PRIVATE /wd4100) # unreferenced formal parameter endif() target_link_libraries(A2XT_Int INTERFACE Luau.Compiler Luau.VM) endif() if(NINTENDO_DS AND CALICO_ROOT) add_library(xtbootstrap src/core/16m/boot_16m.c) set_property(TARGET xtbootstrap PROPERTY INTERPROCEDURAL_OPTIMIZATION FALSE) target_link_libraries(A2XT_Int INTERFACE xtbootstrap) endif() add_subdirectory(3rdparty/mbediso EXCLUDE_FROM_ALL) if(NOT THEXTECH_NO_SDL_BUILD) if(NOT USE_SYSTEM_SDL2) target_include_directories(mbediso PRIVATE "${DEPENDENCIES_INSTALL_DIR}/include") add_dependencies(mbediso AudioCodecs_Local) else() target_include_directories(mbediso PRIVATE "${SDL2_INCLUDE_DIRS}") endif() endif() target_link_libraries(A2XT_Int INTERFACE mbediso) target_compile_definitions(A2XT_Int INTERFACE -DPGE_USE_ARCHIVES # DirMan should use the archives library -DPGE_FILES_PRESENT # DirMan should use the Files checkSuffix routine ) if(ANDROID) set_target_properties(lz4 PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(lz4_static PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(mbediso PROPERTIES POSITION_INDEPENDENT_CODE ON) endif() target_link_libraries(A2XT_Int INTERFACE ${A2XT_LIBS}) target_include_directories(A2XT_Int INTERFACE ${A2XT_INCS}) set(LIB_SRC lib/util.cpp lib/fmt_impl.cpp lib/Graphics/image_size.cpp lib/Graphics/size.cpp lib/Archives/archives_rwops.cpp lib/Archives/archives_mount.cpp lib/Archives/archives_dir.cpp ${APPPATH_SRCS} ${DIRMANAGER_SRCS} ${FMT_SRCS} ${MD5_SRCS} ${POOLALLOC_SRCS} ${INIPROCESSOR_SRCS} ${LOGGER_SRCS} ${UTILS_SRCS} ${LIB_SRC_EXTRA} ${SDLPROXY_SRCS} ) if(THEXTECH_FIXED_POINT) list(APPEND LIB_SRC lib/fixed_point.cpp ) target_compile_definitions(A2XT_Int INTERFACE -DTHEXTECH_FIXED_POINT ) else() list(APPEND LIB_SRC lib/floating_point.cpp ) endif() if(NOT THEXTECH_NO_SDL_BUILD) list(APPEND LIB_SRC lib/Graphics/graphics_funcs.cpp ) endif() if(NOT ANDROID AND NOT EMSCRIPTEN AND NOT VITA AND NOT NINTENDO_3DS AND NOT NINTENDO_WII AND NOT NINTENDO_WIIU AND NOT NINTENDO_SWITCH AND NOT THEXTECH_CLI_BUILD AND NOT PGE_MIN_PORT) list(APPEND LIB_SRC lib/InterProcess/editor_pipe.h lib/InterProcess/editor_pipe.cpp lib/InterProcess/intproc.h lib/InterProcess/intproc.cpp src/capabilities.cpp ) set(THEXTECH_INTERPROC_SUPPORTED ON) endif() # Integrations with external applications if(THEXTECH_ENABLE_INTEGRATOR) list(APPEND LIB_SRC lib/Integrator/integrator.h lib/Integrator/integrator.cpp) if(THEXTECH_ENABLE_DISCORD_RPC) list(APPEND LIB_SRC lib/Integrator/int_discorcrpc.h lib/Integrator/int_discorcrpc.cpp ) endif() endif() if(THEXTECH_ENABLE_SDL_NET) list(APPEND LIB_SRC src/main/client.cpp src/main/client_methods.cpp) endif() if(NOT NINTENDO_3DS AND NOT NINTENDO_WII AND NOT NINTENDO_WIIU AND NOT NINTENDO_SWITCH AND NOT VITA AND NOT PGE_MIN_PORT) list(APPEND LIB_SRC ${FILEMAPPER_SRCS}) set(THEXTECH_FILEMAPPER_SUPPORTED ON) endif() if(NINTENDO_3DS OR NINTENDO_WII OR NOT PGE_MIN_PORT) list(APPEND LIB_SRC ${STACK_WALKER_SRCS} lib/CrashHandler/crash_handler.cpp ) set(THEXTECH_CRASHHANDLER_SUPPORTED ON) endif() if(PGE_ENABLE_VIDEO_REC) list(APPEND LIB_SRC ${VIDEO_REC_SRCS}) endif() if(APPLE) file(GLOB PGE_FILE_ICONS "${CMAKE_CURRENT_SOURCE_DIR}/resources/file_icons/*.icns") list(APPEND LIB_SRC ${XTECH_DEFAULT_ICNS} ${PGE_FILE_ICONS} ${CMAKE_CURRENT_SOURCE_DIR}/resources/PkgInfo ) if(XTECH_MACOSX_TIGER) set(THEXTECH_MAC_EXEX_GENERATED "${CMAKE_BINARY_DIR}/generated/TheXTechRun") configure_file("${CMAKE_CURRENT_SOURCE_DIR}/resources/tiger/TheXTechRun.in" "${THEXTECH_MAC_EXEX_GENERATED}_tmp/TheXTechRun") file(COPY "${THEXTECH_MAC_EXEX_GENERATED}_tmp/TheXTechRun" DESTINATION "${CMAKE_BINARY_DIR}/generated/" FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE ) list(APPEND LIB_SRC "${THEXTECH_MAC_EXEX_GENERATED}") endif() endif() set(THEXTECH_SRC src/game_main.cpp src/globals.cpp src/npc.cpp src/sound.cpp src/sound_spatial.cpp src/sound/sound_msgsnd.cpp src/frame_timer.cpp src/global_dirs.cpp src/global_strings.cpp src/config/config_base.cpp src/config/config_hooks.cpp src/config/config_legacy_compat.cpp src/config/config_main.cpp src/main/world_loop.cpp src/main/world_file.cpp src/main/game_info.cpp src/main/game_loop.cpp src/main/gameplay_timer.cpp src/main/player_frames.cpp src/main/setup_vars.cpp src/main/setup_physics.cpp src/main/speedrunner.cpp src/main/record.cpp src/main/game_save.cpp src/main/level_save_info.cpp src/main/level_medals.cpp src/main/main_config.cpp src/main/level_file.cpp src/main/menu_loop.cpp src/main/menu_main.cpp src/main/screen_pause.cpp src/main/screen_connect.cpp src/main/screen_options.cpp src/main/screen_quickreconnect.cpp src/main/screen_textentry.cpp src/main/screen_prompt.cpp src/main/screen_progress.cpp src/main/screen_content.cpp src/main/hints.cpp src/main/game_strings.cpp src/main/translate.cpp src/main/translate_episode.cpp src/main/menu_controls.cpp src/main/cheat_code.cpp src/main/outro_loop.cpp src/main/trees.cpp src/main/block_table.cpp src/main/asset_pack.cpp src/main/screen_asset_pack.cpp src/graphics/gfx_update2.cpp src/graphics/gfx_update.cpp src/graphics/gfx_background.cpp src/graphics/gfx_print.cpp src/graphics/gfx_message.cpp src/graphics/gfx_editor.cpp src/graphics/gfx_frame.cpp src/graphics/gfx_camera.cpp src/graphics/gfx_world.cpp src/graphics/gfx_credits.cpp src/graphics/gfx_hud.cpp src/graphics/gfx_enter_screen.cpp src/graphics/gfx_draw_player.cpp src/graphics/gfx_special_frames.cpp src/graphics/gfx_screen.cpp src/graphics/gfx_keyhole.cpp src/graphics/gfx_marquee.cpp src/control/duplicate.cpp src/control/controls.cpp src/core/xerror.cpp src/script/msg_preprocessor.cpp src/script/msg_macros.cpp src/logic/object_graph.cpp src/change_res.cpp src/effect.cpp src/collision.cpp src/load_gfx.cpp src/layers.cpp src/saved_layers.cpp src/custom.cpp src/graphics.cpp src/main.cpp src/blocks.cpp src/gfx.cpp src/frm_main.cpp src/player.cpp src/rand.cpp src/sorting.cpp src/screen.cpp src/screen_fader.cpp src/message.cpp src/std_picture.cpp src/phys_env.cpp src/npc/npc_hit.cpp src/npc/npc_kill.cpp src/npc/npc_update.cpp src/npc/npc_update/npc_movement_logic.cpp src/npc/npc_update/npc_block_logic.cpp src/npc/npc_update/npc_collide.cpp src/npc/npc_update/npc_walking_logic.cpp src/npc/npc_update/npc_effects.cpp src/npc/npc_update/npc_special_maybe_held.cpp src/npc/npc_update/npc_generator.cpp src/npc/npc_frames.cpp src/npc/npc_bonus.cpp src/npc/npc_queues.cpp src/npc/section_overlap.cpp src/npc/npc_activation.cpp src/player/player_update.cpp src/player/player_npc_logic.cpp src/player/player_block_logic.cpp src/player/player_vine_logic.cpp src/player/player_screen_logic.cpp src/player/player_fairy_logic.cpp src/player/player_action_logic.cpp src/player/player_movement_logic.cpp src/player/player_pinched_logic.cpp src/player/player_death_logic.cpp src/player/player_vehicle_logic.cpp src/player/player_char5_logic.cpp src/player/player_warp_logic.cpp src/fontman/font_manager.cpp src/fontman/font_manager_private.cpp src/fontman/raster_font.cpp src/fontman/legacy_font.cpp src/fontman/hardcoded_font.cpp src/fontman/utf8_helpers.cpp ) if(NOT CMAKE_BUILD_TYPE_LOWER STREQUAL "debug") # some performance-critical files get special treatment, even though the executable overall is normally -Os set_source_files_properties( src/main/trees.cpp src/main/block_table.cpp PROPERTIES COMPILE_FLAGS -O2) endif() if(NOT THEXTECH_NO_SDL_BUILD) list(APPEND THEXTECH_SRC src/sound_thread.cpp) endif() if(THEXTECH_ENABLE_TTF_SUPPORT) add_definitions(-DTHEXTECH_ENABLE_TTF_SUPPORT) list(APPEND THEXTECH_SRC src/fontman/ttf_font.cpp ) endif() # Luna Autocode script engine list(APPEND THEXTECH_SRC src/script/luna/lunaglobals.cpp src/script/luna/lunacounter.cpp src/script/luna/lunacounter_record.cpp ) if(THEXTECH_ENABLE_LUNA_AUTOCODE) add_definitions(-DTHEXTECH_ENABLE_LUNA_AUTOCODE) list(APPEND THEXTECH_SRC src/script/luna/luna.cpp src/script/luna/autocode.cpp src/script/luna/autocode_manager.cpp src/script/luna/csprite.cpp src/script/luna/lunaspriteman.cpp src/script/luna/hitbox.cpp src/script/luna/lunaimgbox.cpp src/script/luna/sprite_component.cpp src/script/luna/sprite_funcs.cpp src/script/luna/lunalevels.cpp src/script/luna/lunaplayer.cpp src/script/luna/lunanpc.cpp src/script/luna/lunablock.cpp src/script/luna/lunalayer.cpp src/script/luna/lunamisc.cpp src/script/luna/lunalevel.cpp src/script/luna/lunarender.cpp src/script/luna/lunainput.cpp src/script/luna/lunavarbank.cpp src/script/luna/renderop_bitmap.cpp src/script/luna/renderop_rect.cpp src/script/luna/renderop_effect.cpp src/script/luna/renderop_string.cpp src/script/luna/mememu.cpp src/script/luna/levels/Docopoper-AbstractAssault.cpp src/script/luna/levels/Docopoper-Calleoca.cpp src/script/luna/levels/Docopoper-TheFloorisLava.cpp src/script/luna/levels/SAJewers-QraestoliaCaverns.cpp src/script/luna/levels/SAJewers-Snowboardin.cpp src/script/luna/levels/Talkhaus-Science_Final_Battle.cpp src/script/luna/levels/KilArmoryCode.cpp ) endif() if(THEXTECH_ENABLE_AUDIO_FX) add_definitions(-DTHEXTECH_ENABLE_AUDIO_FX) list(APPEND THEXTECH_SRC src/sound/fx/spc_echo.cpp src/sound/fx/reverb.cpp ) endif() if(THEXTECH_ENABLE_EDITOR) add_definitions(-DTHEXTECH_ENABLE_EDITOR) list(APPEND THEXTECH_SRC src/editor/editor.cpp src/editor/new_editor.cpp src/editor/write_common.cpp src/editor/write_level.cpp src/editor/write_world.cpp src/editor/editor_custom.cpp src/editor/editor_strings.cpp src/editor/magic_block.cpp ) endif() if(THEXTECH_ENABLE_LUAU) target_compile_definitions(A2XT_Int INTERFACE -DTHEXTECH_ENABLE_LUAU) list(APPEND THEXTECH_SRC src/script/luau/test.cpp ) endif() if(NINTENDO_3DS) add_definitions(-DWINDOW_CUSTOM -DMSGBOX_CUSTOM -DEVENTS_CUSTOM -DRENDER_CUSTOM) list(APPEND THEXTECH_SRC src/control/input_3ds.cpp src/core/3ds/render_3ds.cpp src/core/3ds/window_3ds.cpp src/core/3ds/msgbox_3ds.cpp src/core/3ds/events_3ds.cpp # src/core/3ds/3ds-audio-lib.cpp src/core/sdl/sdl_core.cpp src/core/power/power_3ds.cpp ) elseif(NINTENDO_WII) add_definitions(-DWINDOW_CUSTOM -DMSGBOX_CUSTOM -DEVENTS_CUSTOM -DRENDER_CUSTOM) list(APPEND THEXTECH_SRC src/control/input_wii.cpp src/control/input_wii_gc.cpp src/core/wii/render_wii.cpp src/core/wii/window_wii.cpp src/core/wii/msgbox_wii.cpp src/core/wii/events_wii.cpp src/core/sdl/sdl_core.cpp src/core/power/power_wii.cpp ) elseif(NINTENDO_DS) add_definitions(-DWINDOW_CUSTOM -DMSGBOX_CUSTOM -DEVENTS_CUSTOM -DRENDER_CUSTOM -DTHEXTECH_NO_SDL_CORE) list(REMOVE_ITEM THEXTECH_SRC src/sound.cpp src/sound/sound_msgsnd.cpp) list(APPEND THEXTECH_SRC src/control/input_16m.cpp src/core/16m/render_16m.cpp src/core/16m/msgbox_16m.cpp src/core/16m/window_16m.cpp src/core/16m/events_16m.cpp src/core/16m/sound_16m.cpp src/core/16m/sound_stream_16m.cpp src/core/power/power_16m.cpp ) elseif(THEXTECH_NO_SDL_BUILD) add_definitions(-DWINDOW_CUSTOM -DMSGBOX_CUSTOM -DEVENTS_CUSTOM -DRENDER_CUSTOM -DTHEXTECH_NO_SDL_CORE) list(APPEND THEXTECH_SRC src/core/null/render_null.cpp src/core/null/window_null.cpp src/core/null/msgbox_null.cpp src/core/null/events_null.cpp src/core/power/power_null.cpp ) elseif(THEXTECH_CLI_BUILD) add_definitions(-DWINDOW_CUSTOM -DMSGBOX_CUSTOM -DEVENTS_CUSTOM -DRENDER_CUSTOM) list(APPEND THEXTECH_SRC src/core/null/render_null.cpp src/core/null/window_null.cpp src/core/null/msgbox_null.cpp src/core/null/events_null.cpp src/core/sdl/sdl_core.cpp src/core/power/power_null.cpp ) else() add_definitions(-DCORE_EVERYTHING_SDL) list(APPEND THEXTECH_SRC src/control/joystick.cpp src/control/keyboard.cpp src/control/touchscreen.cpp src/core/base/render_base.cpp src/core/base/window_base.cpp src/core/base/msgbox_base.cpp src/core/base/events_base.cpp src/core/sdl/render_sdl.cpp src/core/sdl/window_sdl.cpp src/core/sdl/msgbox_sdl.cpp src/core/sdl/events_sdl.cpp src/core/sdl/sdl_core.cpp src/core/power/power_sdl.cpp lib/Graphics/xt_qoi.cpp ) if(NOT THEXTECH_FORCE_FULLSCREEN AND NOT EMSCRIPTEN) add_definitions(-DRENDER_FULLSCREEN_TYPES_SUPPORTED) endif() if(NINTENDO_WIIU) list(APPEND THEXTECH_SRC src/core/wiiu/msgbox_wiiu.cpp ) else() list(APPEND THEXTECH_SRC src/core/sdl/msgbox_sdl.cpp ) endif() if(THEXTECH_BUILD_GL_DESKTOP_MODERN OR THEXTECH_BUILD_GL_DESKTOP_LEGACY OR THEXTECH_BUILD_GL_ES_MODERN OR THEXTECH_BUILD_GL_ES_LEGACY) list(APPEND THEXTECH_SRC src/core/opengl/gl_program_object.cpp src/core/opengl/render_gl_frontend.cpp src/core/opengl/render_gl_backend.cpp src/core/opengl/render_gl_init.cpp src/core/opengl/render_gl_shaders.cpp src/core/opengl/render_gl_shader_lighting.cpp ) endif() if(THEXTECH_BUILD_GL_DESKTOP_MODERN OR THEXTECH_BUILD_GL_ES_MODERN) list(APPEND THEXTECH_SRC src/core/opengl/gl_program_bank.cpp src/core/opengl/gl_particle_system.cpp ) endif() if(THEXTECH_BUILD_GL_DESKTOP_MODERN AND THEXTECH_USE_ANGLE_TRANSLATOR) add_definitions(-DTHEXTECH_USE_ANGLE_TRANSLATOR) list(APPEND THEXTECH_SRC src/core/opengl/gl_shader_translator.cpp ) endif() endif() list(APPEND THEXTECH_SRC src/core/language/language_common.cpp) # Notes: # - First, algorithm will try to detect language using SDL's method if available (Supported by SDL 2.0.14 and newer) # - Then, falls to the system specific code to detect the language # If there is a necessary to disable use of SDL-based language detection, # define the macro `THEXTECH_DISABLE_SDL_LOCALE` which will completely disable the SDL-based language detector if(THEXTECH_NO_SDL_BUILD) add_definitions(-DTHEXTECH_DISABLE_SDL_LOCALE) endif() if(WIN32) list(APPEND THEXTECH_SRC src/core/language/language_win32.cpp) elseif(APPLE) list(APPEND THEXTECH_SRC src/core/language/language_apple.cpp) elseif(ANDROID) list(APPEND THEXTECH_SRC src/core/language/language_android.cpp) elseif(NINTENDO_DS) # FIXME: Implement the Nintendo DS language initializer add_definitions(-DTHEXTECH_DISABLE_SDL_LOCALE) list(APPEND THEXTECH_SRC src/core/language/language_dummy.cpp) elseif(NINTENDO_3DS) add_definitions(-DTHEXTECH_DISABLE_SDL_LOCALE) # SDL for 3DS doesn't support language detection list(APPEND THEXTECH_SRC src/core/language/language_3ds.cpp) elseif(NINTENDO_WII) add_definitions(-DTHEXTECH_DISABLE_SDL_LOCALE) # SDL for Wii doesn't support language detection list(APPEND THEXTECH_SRC src/core/language/language_wii.cpp) elseif(NINTENDO_WIIU) add_definitions(-DTHEXTECH_DISABLE_SDL_LOCALE) # SDL for WiiU doesn't support language detection list(APPEND THEXTECH_SRC src/core/language/language_wiiu.cpp) elseif(NINTENDO_SWITCH) add_definitions(-DTHEXTECH_DISABLE_SDL_LOCALE) # SDL for Switch doesn't support language detection list(APPEND THEXTECH_SRC src/core/language/language_switch.cpp) elseif(VITA) add_definitions(-DTHEXTECH_DISABLE_SDL_LOCALE) list(APPEND THEXTECH_SRC src/core/language/language_vita.cpp) elseif(UNIX) list(APPEND THEXTECH_SRC src/core/language/language_unix.cpp) else() add_definitions(-DTHEXTECH_DISABLE_SDL_LOCALE) list(APPEND THEXTECH_SRC src/core/language/language_dummy.cpp) endif() # Add heads into the list file(GLOB THEXTECH_HEADS_SRC *.h lib/*.h lib/AppPath/*.h lib/CrashHandler/*.h lib/Graphics/*.h lib/tclap/*.h lib/fmt/*.h lib/sorting/*.h lib/Archives/*.h lib/json/*.hpp src/*.h src/*.hpp src/script/*.h src/script/luna/*.h src/script/luna/level/*.h src/control/*.h src/core/*.h src/core/16m/*.h src/core/3ds/*.h src/core/base/*.h src/core/language/*.h src/core/minport/*.h src/core/null/*.h src/core/opengl/*.h src/core/power/*.h src/core/sdl/*.h src/core/vita/*.h src/core/wii/*.h src/core/wiiu/*.h src/editor/*.h src/main/*.h src/main/translate/*.h src/main/*.hpp src/npc/*.h src/npc/*.hpp src/sound/*.h src/sound/fx/*.h src/sound/fx/*.hpp src/fontman/*.h ) list(APPEND THEXTECH_SRC ${THEXTECH_HEADS_SRC}) # ---------------------------------------------------- # Non-buildable meta files such as change log, Readmes, etc. set(THEXTECH_EXTRA_RESOURCES changelog.txt README.md README.RUS.md README.ESP.md resources/thextech.plist.in resources/thextech.plist.tiger.in resources/emscripten/manifest.json.in resources/emscripten/shell_minimal.html resources/emscripten/sw.js.in resources/flatpak/appdata.xml.in resources/flatpak/build.yml resources/haiku/PackageInfo.in resources/haiku/icon.hvif resources/haiku/icon.rdef resources/vita/frag.cgf resources/vita/vert.cgv resources/vita/sce_sys/icon0.png resources/vita/sce_sys/livearea/contents/bg.png resources/vita/sce_sys/livearea/contents/startup.png resources/vita/sce_sys/livearea/contents/template.xml.in resources/nds/icon.bmp resources/switch/splash_logo.png resources/switch/thextech-logo.jpg resources/switch/thextech-logo.png resources/tiger/TheXTechRun.in resources/wii/icon.png resources/wii/meta.xml.in resources/wiiu/icon.png resources/wiiu/meta.xml.in resources/wiiu/wuhb-splash.png ) list(APPEND THEXTECH_SRC ${THEXTECH_EXTRA_RESOURCES}) set_source_files_properties(${THEXTECH_EXTRA_RESOURCES} PROPERTIES HEADER_FILE_ONLY ON) source_group("Resources" FILES ${THEXTECH_EXTRA_RESOURCES}) # ------------------ Unit tests ---------------------- option(WITH_UNIT_TESTS "Enable unit testing" OFF) if(WITH_UNIT_TESTS) enable_testing() add_subdirectory(test) endif() # ---------------------------------------------------- if(WIN32 AND NOT EMSCRIPTEN) list(APPEND THEXTECH_SRC resources/thextech.rc) endif() if(THEXTECH_ENABLE_LUA) add_subdirectory(script) target_link_libraries(A2XT_Int INTERFACE XTechLua ${libLuaBind_Lib} ${libLuaJit_Lib}) if(UNIX) find_library(LIB_DL_PATH dl) if(LIB_DL_PATH) target_link_libraries(A2XT_Int INTERFACE ${LIB_DL_PATH}) endif() endif() endif() if(ANDROID) set(CMAKE_DEBUG_POSTFIX "") add_library(thextech SHARED ${THEXTECH_SRC} ${LIB_SRC}) target_compile_options(thextech PRIVATE -fPIC) target_link_options(thextech PUBLIC "-Wl,-z,max-page-size=16384") target_link_options(thextech PUBLIC "-Wl,-z,common-page-size=16384") # Include own-built libraries into the APK set(APK_PACK_LIBS ${CMAKE_SOURCE_DIR}/android-project/thextech/jniLibs/${CMAKE_BUILD_TYPE_LOWER}/${ANDROID_ABI}) add_custom_target(SDL_ApkPackLibs_makeDir ALL COMMAND ${CMAKE_COMMAND} -E make_directory "${APK_PACK_LIBS}") if(PGE_SHARED_SDLMIXER) # add_library(SDL2_mixer_ext SHARED IMPORTED DEPENDS SDLMixerX_Local AudioCodecs_Local) # set_target_properties(SDL2_mixer_ext PROPERTIES IMPORTED_LOCATION "${DEPENDENCIES_INSTALL_DIR}/lib/libSDL2_mixer_ext.so") add_custom_target(SDL_ApkPackLibs_mixerx ALL COMMAND ${CMAKE_COMMAND} -E copy "${DEPENDENCIES_INSTALL_DIR}/lib/libSDL2_mixer_ext.so" "${APK_PACK_LIBS}" DEPENDS SDL_ApkPackLibs_makeDir ${DEPENDENCIES_INSTALL_DIR}/lib/libSDL2_mixer_ext.so AudioCodecs_Local ) add_dependencies(thextech SDL_ApkPackLibs_mixerx) # add_library(SDL2 SHARED IMPORTED DEPENDS AudioCodecs_Local) # set_target_properties(SDL2 PROPERTIES IMPORTED_LOCATION "${DEPENDENCIES_INSTALL_DIR}/lib/libSDL2.so") add_custom_target(SDL_ApkPackLibs_sdl2 ALL COMMAND ${CMAKE_COMMAND} -E copy "${DEPENDENCIES_INSTALL_DIR}/lib/libSDL2.so" "${APK_PACK_LIBS}" DEPENDS SDL_ApkPackLibs_makeDir ${DEPENDENCIES_INSTALL_DIR}/lib/libSDL2.so AudioCodecs_Local ) add_dependencies(thextech SDL_ApkPackLibs_sdl2) endif() # add_library(hidapi SHARED IMPORTED DEPENDS AudioCodecs_Local) # set_target_properties(hidapi PROPERTIES IMPORTED_LOCATION "${DEPENDENCIES_INSTALL_DIR}/lib/libhidapi.so") # libhidapi.so is NO LONGER required since SDL 2.0.18 # add_custom_target(SDL_ApkPackLibs_hidapi ALL # COMMAND ${CMAKE_COMMAND} -E copy "${DEPENDENCIES_INSTALL_DIR}/lib/libhidapi.so" "${APK_PACK_LIBS}" # DEPENDS SDL_ApkPackLibs_makeDir "${DEPENDENCIES_INSTALL_DIR}/lib/libhidapi.so" AudioCodecs_Local # ) # add_dependencies(thextech SDL_ApkPackLibs_hidapi) elseif(NINTENDO_WII OR NINTENDO_WIIU OR NINTENDO_3DS OR NINTENDO_DS) add_executable(thextech ${THEXTECH_SRC} ${LIB_SRC}) set_target_properties(thextech PROPERTIES SUFFIX ".elf") else() add_executable(thextech ${THEXTECH_SRC} ${LIB_SRC}) pge_set_nopie(thextech) endif() add_dependencies(thextech git_version) target_include_directories(thextech PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/generated-include") set_target_properties(thextech PROPERTIES OUTPUT_NAME "${THEXTECH_EXECUTABLE_NAME}" ) if(CMAKE_BUILD_TYPE_LOWER STREQUAL "debug" OR CMAKE_BUILD_TYPE_LOWER STREQUAL "relwithdebinfo") target_compile_definitions(thextech PRIVATE -DTHEXTECH_DEBUG_INFO) endif() if(APPLE) if(XTECH_MACOSX_TIGER) set(THEXTECH_EXECUTABLE_NAME_OUT TheXTechRun) else() set(THEXTECH_EXECUTABLE_NAME_OUT ${THEXTECH_EXECUTABLE_NAME}) endif() set_target_properties(thextech PROPERTIES OUTPUT_NAME "${THEXTECH_BUNDLE_NAME}" MACOSX_BUNDLE TRUE MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/resources/${XTECH_PLIST_NAME}" MACOSX_BUNDLE_BUNDLE_NAME "${THEXTECH_BUNDLE_NAME}" MACOSX_BUNDLE_EXECUTABLE_NAME "${THEXTECH_EXECUTABLE_NAME_OUT}" MACOSX_BUNDLE_GUI_IDENTIFIER "ru.wohlsoft.thextech" MACOSX_BUNDLE_SHORT_VERSION_STRING "${THEXTECH_VERSION_STRING}" MACOSX_BUNDLE_LONG_VERSION_STRING "${THEXTECH_VERSION_STRING}" MACOSX_BUNDLE_ICON_FILE "${THEXTECH_ICON_NAME}" CPACK_BUNDLE_NAME "${THEXTECH_BUNDLE_NAME}" MACOSX_BUNDLE_INFO_STRING "TheXTech classic game engine" ) set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/${XTECH_DEFAULT_ICNS}" PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") set_source_files_properties(${PGE_FILE_ICONS} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/resources/PkgInfo PROPERTIES MACOSX_PACKAGE_LOCATION "") if(XTECH_MACOSX_TIGER) set_source_files_properties("${THEXTECH_MAC_EXEX_GENERATED}" PROPERTIES MACOSX_PACKAGE_LOCATION "MacOS") endif() if(THEXTECH_CUSTOM_ICON_PATH AND NOT THEXTECH_CUSTOM_ICON_PATH STREQUAL "") set_source_files_properties("${THEXTECH_CUSTOM_ICON_PATH}" PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") endif() if(THEXTECH_PRELOAD_ENVIRONMENT AND NOT THEXTECH_PRELOAD_ENVIRONMENT STREQUAL "") add_custom_command(TARGET thextech POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory "$/../Resources/assets" COMMAND ${CMAKE_COMMAND} -E copy_directory "${THEXTECH_PRELOAD_ENVIRONMENT}" "$/../Resources/assets" COMMENT "Copying assets into bundle..." ) target_compile_definitions(thextech PRIVATE -DUSE_BUNDLED_ASSETS) endif() if(THEXTECH_PRELOAD_ENVIRONMENT_MANUALLY) target_compile_definitions(thextech PRIVATE -DUSE_BUNDLED_ASSETS) endif() if(XTECH_MACOSX_TIGER) # Use X11 mode on Mac OS X Tiger target_compile_definitions(thextech PRIVATE -DUSE_APPLE_X11) endif() check_library_exists(iconv iconv_open "" HAVE_LIBICONV) if(HAVE_LIBICONV) target_link_libraries(A2XT_Int INTERFACE iconv) endif() if(PGE_USE_LUAJIT) # Required to link on 64-bit macOS # See: http://luajit.org/install.html set_property(TARGET thextech APPEND_STRING PROPERTY LINK_FLAGS " -pagezero_size 10000 -image_base 100000000" ) endif() endif() if(ANDROID) target_link_libraries(A2XT_Int INTERFACE log) if(THEXTECH_BUILD_GL_ES_LEGACY OR THEXTECH_BUILD_GL_ES_MODERN) target_link_libraries(A2XT_Int INTERFACE EGL) endif() endif() if(NINTENDO_SWITCH) target_link_libraries(A2XT_Int INTERFACE nx) if(THEXTECH_BUILD_GL_DESKTOP_MODERN) target_link_libraries(A2XT_Int INTERFACE glad) endif() # Set explicit flags for this project target_compile_definitions(thextech PRIVATE -D__SWITCH__ -DNO_INTPTROC) endif() if(VITA) if(THEXTECH_BUILD_GL_ES_MODERN) target_link_libraries(A2XT_Int INTERFACE vitaGL vitashark) endif() # Link Vita specific libraries to the generic interface. target_link_libraries(A2XT_Int INTERFACE ${VITA_ADDTL_LIBS}) # Set explicit flags for this project target_compile_definitions(thextech PRIVATE -DVITA -DNO_SCREENSHOT -DTHEXTECH_PRELOAD_LEVELS -DNO_INTPTROC) endif() if(WIN32 AND WINDOWS_STORE) target_compile_definitions(thextech PRIVATE -DTHEXTECH_WINRT) endif() if(NINTENDO_3DS) target_compile_definitions(thextech PRIVATE -DPGE_SDL_MUTEX -DLOW_MEM -DTHEXTECH_PRELOAD_LEVELS -D__3DS__) elseif(NINTENDO_WII) target_compile_definitions(thextech PRIVATE -DPGE_SDL_MUTEX -DLOW_MEM -DTHEXTECH_PRELOAD_LEVELS -D__WII__) elseif(NINTENDO_WIIU) target_compile_definitions(thextech PRIVATE -DPGE_SDL_MUTEX) elseif(NINTENDO_DS) target_link_libraries(thextech PRIVATE mm9) target_compile_definitions(thextech PRIVATE -DPGE_NO_THREADING -DLOW_MEM -DTHEXTECH_PRELOAD_LEVELS -D__16M__) if(CALICO_ROOT) target_compile_definitions(thextech PRIVATE -D__CALICO__) elseif(BLOCKSDS) target_compile_definitions(thextech PRIVATE -D__BLOCKS__) endif() if(NOT BLOCKSDS) target_link_libraries(thextech PRIVATE fat) set(CMAKE_EXE_LINKER_FLAGS "-L${CALICO_ROOT}/lib -L${NDS_ROOT}/lib -specs=${CALICO_ROOT}/share/ds9.specs -T ${CMAKE_CURRENT_SOURCE_DIR}/cmake/overlays-16m.ld") endif() if(BLOCKSDS) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--defsym,__dtcm_data_size=8") endif() elseif(PGE_MIN_PORT) target_compile_definitions(thextech PRIVATE -DLOW_MEM) endif() if(VITA OR NINTENDO_3DS OR NINTENDO_WII OR NINTENDO_WIIU OR NINTENDO_SWITCH) # Run in-game message box to show assert failures on platforms without SDL's message box target_compile_definitions(thextech PRIVATE -DTHEXTECH_ASSERTS_INGAME_MESSAGE) endif() if(VITA OR NINTENDO_DS OR NINTENDO_3DS OR NINTENDO_WII OR NINTENDO_SWITCH OR ANDROID OR EMSCRIPTEN) # Disable language tools at non-desktop platforms target_compile_definitions(thextech PRIVATE -DTHEXTECH_DISABLE_LANG_TOOLS) endif() if((UNIX OR NINTENDO_SWITCH OR ANDROID OR APPLE) AND NOT EMSCRIPTEN) # Allow hotswapping renderer on fully tested platforms target_compile_definitions(thextech PRIVATE -DTHEXTECH_RESTART_RENDERER_SUPPORTED) endif() if(THEXTECH_NO_SDL_BUILD) target_compile_definitions(thextech PRIVATE -DPGE_NO_THREADING -DTHEXTECH_NO_SDL_BUILD) endif() if(THEXTECH_CLI_BUILD) target_compile_definitions(thextech PRIVATE -DTHEXTECH_CLI_BUILD) endif() if(THEXTECH_BUILD_GL_DESKTOP_MODERN) target_compile_definitions(thextech PRIVATE -DTHEXTECH_BUILD_GL_DESKTOP_MODERN) endif() if(THEXTECH_BUILD_GL_ES_MODERN) target_compile_definitions(thextech PRIVATE -DTHEXTECH_BUILD_GL_ES_MODERN) endif() if(THEXTECH_BUILD_GL_DESKTOP_MODERN OR THEXTECH_BUILD_GL_ES_MODERN) target_compile_definitions(thextech PRIVATE -DTHEXTECH_BUILD_GL_MODERN) endif() if(THEXTECH_BUILD_GL_DESKTOP_LEGACY) target_compile_definitions(thextech PRIVATE -DTHEXTECH_BUILD_GL_DESKTOP_LEGACY) endif() if(THEXTECH_BUILD_GL_ES_LEGACY) target_compile_definitions(thextech PRIVATE -DTHEXTECH_BUILD_GL_ES_LEGACY) endif() if(THEXTECH_ENABLE_WIP_FEATURES) target_compile_definitions(thextech PRIVATE -DTHEXTECH_WIP_FEATURES) endif() target_compile_definitions(thextech PRIVATE -DTHEXTECH_DIRECTORY_PREFIX="${THEXTECH_DIRECTORY_PREFIX}") if(THEXTECH_NO_BUILD_DATE) target_compile_definitions(thextech PRIVATE -DDISABLE_XTECH_BUILD_DATE) endif() if(THEXTECH_ENABLE_LUA) target_compile_definitions(thextech PRIVATE -DENABLE_XTECH_LUA) endif() if(THEXTECH_ENABLE_INTEGRATOR) target_compile_definitions(thextech PRIVATE -DENABLE_XTECH_INTEGRATOR) endif() if(THEXTECH_ENABLE_DISCORD_RPC) target_compile_definitions(thextech PRIVATE -DENABLE_XTECH_DISCORD_RPC -DXTECH_DISCORD_APPID=${THEXTECH_DISCORD_APPID}) endif() if(PGE_ENABLE_VIDEO_REC) target_compile_definitions(thextech PRIVATE -DPGE_ENABLE_VIDEO_REC) if(PGE_VIDEO_REC_WEBM_SUPPORTED) target_compile_definitions(thextech PRIVATE -DPGE_VIDEO_REC_WEBM_SUPPORTED) endif() endif() if(ENABLE_ADDRESS_SANITIZER) target_compile_options(thextech PRIVATE -fsanitize=address) target_link_options(thextech PRIVATE -fsanitize=address) endif() target_include_directories(thextech PRIVATE ${A2XT_INCS}) target_link_libraries(thextech PRIVATE A2XT_Int) if(THEXTECH_ENABLE_SDL_NET) target_link_libraries(thextech PRIVATE SDL2_net) endif() if(RANGE_ARR_USE_HEAP) target_compile_definitions(thextech PRIVATE -DRANGE_ARR_USE_HEAP) endif() if(RANGE_ARR_UNSAFE_MODE) target_compile_definitions(thextech PRIVATE -DRANGE_ARR_UNSAFE_MODE) endif() if(ENABLE_ANTICHEAT_TRAP) target_compile_definitions(thextech PRIVATE -DENABLE_ANTICHEAT_TRAP) endif() if(ENABLE_OLD_CREDITS) target_compile_definitions(thextech PRIVATE -DENABLE_OLD_CREDITS) set_target_properties(thextech PROPERTIES OUTPUT_NAME "smbx") endif() if(NOT ENABLE_LOGGING) target_compile_definitions(thextech PRIVATE -DDISABLE_LOGGING -DNO_FILE_LOGGING) endif() if(EMSCRIPTEN) target_compile_definitions(thextech PRIVATE -DPGE_NO_THREADING -DNO_FILE_LOGGING) endif() if(THEXTECH_NO_ARGV_HANDLING) target_compile_definitions(thextech PRIVATE -DTHEXTECH_NO_ARGV_HANDLING) endif() if(THEXTECH_INTERPROC_SUPPORTED) target_compile_definitions(thextech PRIVATE -DTHEXTECH_INTERPROC_SUPPORTED) endif() if(THEXTECH_FILEMAPPER_SUPPORTED) target_compile_definitions(thextech PRIVATE -DTHEXTECH_FILEMAPPER_SUPPORTED) endif() if(THEXTECH_CRASHHANDLER_SUPPORTED) target_compile_definitions(thextech PRIVATE -DTHEXTECH_CRASHHANDLER_SUPPORTED) endif() # ======================= Render specific global macros ======================== if(EMSCRIPTEN) target_compile_definitions(thextech PRIVATE -DNO_WINDOW_FOCUS_TRACKING # Don't track for window focus ) endif() if(NOT EMSCRIPTEN AND NOT NINTENDO_3DS AND NOT NINTENDO_WII AND NOT NINTENDO_WIIU AND NOT PGE_MIN_PORT AND NOT THEXTECH_CLI_BUILD) target_compile_definitions(thextech PRIVATE -DUSE_SCREENSHOTS_AND_RECS # Built-in screenshots and GIF recording ) endif() if(ANDROID) target_compile_definitions(thextech PRIVATE -DUSE_RENDER_BLOCKING ) endif() if(THEXTECH_FORCE_FULLSCREEN) target_compile_definitions(thextech PRIVATE -DRENDER_FULLSCREEN_ALWAYS # Game works always fullscreen -DNO_WINDOW_FOCUS_TRACKING # Don't track for window focus ) endif() # ============================================================================== if(NOT USE_SYSTEM_LIBS AND A2XT_DEPS) add_dependencies(thextech ${A2XT_DEPS}) endif() if(THEXTECH_IS_BIG_ENDIAN) target_compile_definitions(thextech PRIVATE -DTHEXTECH_BIG_ENDIAN -DSOUND_FX_BIG_ENDIAN) endif() if(CMAKE_SIZEOF_VOID_P MATCHES "8") target_compile_definitions(thextech PRIVATE -DTHEXTECH_WORDSIZE=64) elseif(CMAKE_SIZEOF_VOID_P MATCHES "4") target_compile_definitions(thextech PRIVATE -DTHEXTECH_WORDSIZE=32) else() message(WARNING "Unknown word size ${CMAKE_SIZEOF_VOID_P}!") endif() if(USE_STATIC_LIBC AND NOT USE_SYSTEM_LIBS) if(NOT APPLE AND NOT MSVC) # target_link_libraries(thextech PRIVATE -static) set_property(TARGET thextech APPEND_STRING PROPERTY LINK_FLAGS " -static-libgcc -static-libstdc++") endif() if(MSVC) target_compile_options(thextech PRIVATE /MT) target_link_options(thextech PRIVATE /INCREMENTAL:NO /NODEFAULTLIB:MSVCRT) endif() endif() if (Backtrace_FOUND) set_property(TARGET thextech APPEND_STRING PROPERTY LINK_FLAGS " ${Backtrace_LIBRARIES}") endif() if(WIN32) set_target_properties(thextech PROPERTIES WIN32_EXECUTABLE ON) target_compile_definitions(thextech PRIVATE -DNOMINMAX) target_link_libraries(thextech PRIVATE "version" dbghelp) # needed by StackWalker endif() include(cmake/deploy.cmake) if(APPLE) install(TARGETS thextech DESTINATION .) install(FILES changelog.txt DESTINATION .) install(FILES README.md DESTINATION . RENAME ReadMe.txt) install(FILES LICENSE DESTINATION . RENAME License.txt) elseif(WIN32) # For MinGW toolchain, copy missing DLLs if(MINGW) function(find_mingw_dll _FieldName _FileName _DestList _SearchPaths) find_file(MINGWDLL_${_FieldName} ${_FileName} PATH_SUFFIXES bin PATHS "${_SearchPaths}") if(MINGWDLL_${_FieldName}) list(APPEND ${_DestList} "${MINGWDLL_${_FieldName}}") set(${_DestList} ${${_DestList}} PARENT_SCOPE) endif() endfunction() set(MINGW_BIN_PATH "") set(MINGW_DLLS) find_mingw_dll(LIBGCCDW "libgcc_s_dw2-1.dll" MINGW_DLLS "${MINGW_BIN_PATH}") find_mingw_dll(LIBGCCSJLJ "libgcc_s_sjlj-1.dll" MINGW_DLLS "${MINGW_BIN_PATH}") find_mingw_dll(LIBGCCSEC "libgcc_s_seh-1.dll" MINGW_DLLS "${MINGW_BIN_PATH}") find_mingw_dll(MINGWEX "libmingwex-0.dll" MINGW_DLLS "${MINGW_BIN_PATH}") find_mingw_dll(WINPTHREAD "libwinpthread-1.dll" MINGW_DLLS "${MINGW_BIN_PATH}") find_mingw_dll(WINPTHREADGC3 "pthreadGC-3.dll" MINGW_DLLS "${MINGW_BIN_PATH}") find_mingw_dll(STDCPP "libstdc++-6.dll" MINGW_DLLS "${MINGW_BIN_PATH}") message("MinGW DLLs: [${MINGW_DLLS}]") file(COPY ${MINGW_DLLS} DESTINATION "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/") endif() install(TARGETS thextech DESTINATION .) file(GLOB BUILT_DLLS "${DEPENDENCIES_INSTALL_DIR}/bin/*-*.dll") file(GLOB BUILT_DLLS2 "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/*-*.dll") list(APPEND BUILT_DLLS ${BUILT_DLLS2}) if(NOT USE_SYSTEM_SDL2) list(APPEND BUILT_DLLS "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/SDL2.dll" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/SDL2_mixer_ext.dll" ) endif() install(FILES ${BUILT_DLLS} DESTINATION .) install(FILES changelog.txt DESTINATION .) install(FILES README.md DESTINATION . RENAME ReadMe.txt) install(FILES LICENSE DESTINATION . RENAME License.TheXTech.txt) elseif(VITA) include(cmake/package_vita.cmake) elseif(NINTENDO_3DS) if(NOT "${THEXTECH_VERSION_REL}" STREQUAL "") set(SMDH_VERSION "git branch ${GIT_BRANCH} #${GIT_COMMIT_HASH}") else() set(SMDH_VERSION "Based on SMBX 1.3 by Redigit") endif() ctr_generate_smdh(OUTPUT "thextech.smdh" NAME "TheXTech ${THEXTECH_VERSION_STRING}" DESCRIPTION "${SMDH_VERSION}" AUTHOR "Wohlstand and ds-sloth" ICON "${CMAKE_SOURCE_DIR}/resources/icon/thextech_48.png" ) ctr_create_3dsx(thextech SMDH "thextech.smdh") elseif(NINTENDO_WII) string(TIMESTAMP XTECH_WIIMETA_RELEASE_DATE "%Y%m%d") if(NOT "${THEXTECH_VERSION_REL}" STREQUAL "") set(XTECH_WIIMETA_SHORT_DESC "git ${GIT_BRANCH} #${GIT_COMMIT_HASH}") else() set(XTECH_WIIMETA_SHORT_DESC "Based on SMBX 1.3 by Redigit") endif() configure_file(${CMAKE_SOURCE_DIR}/resources/wii/icon.png ${CMAKE_BINARY_DIR}/package/icon.png COPYONLY) configure_file(${CMAKE_SOURCE_DIR}/resources/wii/meta.xml.in ${CMAKE_BINARY_DIR}/package/meta.xml) ogc_create_dol(thextech) add_custom_command(TARGET thextech POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/thextech.dol ${CMAKE_BINARY_DIR}/package/boot.dol COMMENT "Copying thextech executable into package") elseif(NINTENDO_WIIU) string(TIMESTAMP XTECH_WIIMETA_RELEASE_DATE "%Y%m%d") configure_file(${CMAKE_SOURCE_DIR}/resources/wiiu/icon.png ${CMAKE_BINARY_DIR}/icon.png COPYONLY) configure_file(${CMAKE_SOURCE_DIR}/resources/wiiu/meta.xml.in ${CMAKE_BINARY_DIR}/meta.xml) wut_create_rpx(thextech) wut_create_wuhb(thextech CONTENT NAME "TheXTech v${THEXTECH_VERSION_STRING}" SHORTNAME "TheXTech" AUTHOR "Wohlstand and ds-sloth" ICON "${CMAKE_SOURCE_DIR}/resources/switch/thextech-logo.png" TVSPLASH "${CMAKE_SOURCE_DIR}/resources/wiiu/wuhb-splash.png" DRCSPLASH "${CMAKE_SOURCE_DIR}/resources/wiiu/wuhb-splash.png" ) elseif(NINTENDO_DS) if(NOT "${THEXTECH_VERSION_REL}" STREQUAL "") set(ROM_VERSION "${GIT_BRANCH} #${GIT_COMMIT_HASH}") else() set(ROM_VERSION "Based on SMBX 1.3") endif() nds_create_rom(thextech NAME "TheXTech ${THEXTECH_VERSION_STRING}" SUBTITLE1 "Wohlstand and ds-sloth" SUBTITLE2 "${ROM_VERSION}" ICON "${CMAKE_SOURCE_DIR}/resources/nds/icon.bmp" ) elseif(NINTENDO_SWITCH) include(cmake/package_switch.cmake) elseif(EMSCRIPTEN) add_custom_command( TARGET thextech POST_BUILD COMMAND ${CMAKE_COMMAND} -E rename "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${THEXTECH_EXECUTABLE_NAME}.html" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/index.html") add_custom_command( TARGET thextech POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "${PGE_PRELOAD_ENVIRONMENT}/graphics/ui/icon/thextech.ico" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/favicon.ico") add_custom_command( TARGET thextech POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "${PGE_PRELOAD_ENVIRONMENT}/graphics/ui/icon/thextech_32.png" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/thextech_32.png") add_custom_command( TARGET thextech POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy "${PGE_PRELOAD_ENVIRONMENT}/graphics/ui/icon/thextech_256.png" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/thextech_256.png") configure_file(${CMAKE_SOURCE_DIR}/resources/emscripten/manifest.json.in ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/manifest.json) configure_file(${CMAKE_SOURCE_DIR}/resources/emscripten/sw.js.in ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/sw.js) elseif(HAIKU) string(TIMESTAMP XTECH_HAIKU_COPYRIGHT_YEAR "%Y") message("-- Haiku package version: ${THEXTECH_HAIKU_VERSION_STRING} (Yer ${XTECH_HAIKU_COPYRIGHT_YEAR})") set(DESKTOP_NAME "TheXTech" CACHE STRING "Name for the application icon") set(XTECH_HAIKU_SHORT_DESC "TheXTech is an open-source continuation of the SMBX 1.3 platformer game engine. Its highest priority is full compatibility with the SMBX 1.3 content standard, but it also adds a number of enhancements and bugfixes." CACHE STRING "Description for the installable Haiku package.") configure_file("${CMAKE_SOURCE_DIR}/resources/haiku/PackageInfo.in" "${CMAKE_BINARY_DIR}/package/.PackageInfo") set_target_properties(thextech PROPERTIES LINK_FLAGS "-Wl,-rpath,'$ORIGIN/../lib'") add_custom_command( TARGET thextech POST_BUILD COMMAND rc -o "${CMAKE_BINARY_DIR}/icon.rsrc" "${CMAKE_SOURCE_DIR}/resources/haiku/icon.rdef" COMMENT "Preparing icon file...") add_custom_command( TARGET thextech POST_BUILD COMMAND resattr -o "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${THEXTECH_EXECUTABLE_NAME}" "${CMAKE_BINARY_DIR}/icon.rsrc" COMMENT "Assigning icon to ${THEXTECH_EXECUTABLE_NAME}...") if(NOT EXISTS "${CMAKE_BINARY_DIR}/package/apps/${THEXTECH_DIRECTORY_PREFIX}") file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/package/apps/${THEXTECH_DIRECTORY_PREFIX}/bin") file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/package/apps/${THEXTECH_DIRECTORY_PREFIX}/lib") file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/package/apps/${THEXTECH_DIRECTORY_PREFIX}/doc") endif() if(NOT EXISTS "${CMAKE_BINARY_DIR}/package/data/deskbar/menu/Applications") file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/package/data/deskbar/menu/Applications") endif() add_custom_target(make_hkpg DEPENDS "${CMAKE_BINARY_DIR}/package/apps/${THEXTECH_DIRECTORY_PREFIX}/bin/${THEXTECH_EXECUTABLE_NAME}" COMMENT "Creating HPKG package..." ) add_custom_command( OUTPUT "${CMAKE_BINARY_DIR}/package/apps/${THEXTECH_DIRECTORY_PREFIX}/bin/${THEXTECH_EXECUTABLE_NAME}" DEPENDS "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${THEXTECH_EXECUTABLE_NAME}" COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${THEXTECH_EXECUTABLE_NAME}" "${CMAKE_BINARY_DIR}/package/apps/${THEXTECH_DIRECTORY_PREFIX}/bin/${THEXTECH_EXECUTABLE_NAME}" ) add_custom_command( TARGET make_hkpg PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/changelog.txt" "${CMAKE_BINARY_DIR}/package/apps/${THEXTECH_DIRECTORY_PREFIX}/doc/changelog.txt" ) add_custom_command( TARGET make_hkpg PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/README.md" "${CMAKE_BINARY_DIR}/package/apps/${THEXTECH_DIRECTORY_PREFIX}/doc/ReadMe.txt" ) add_custom_command( TARGET make_hkpg PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/LICENSE" "${CMAKE_BINARY_DIR}/package/apps/${THEXTECH_DIRECTORY_PREFIX}/doc/License.TheXTech.txt" ) add_custom_command( TARGET make_hkpg PRE_BUILD COMMAND resattr -o "${CMAKE_BINARY_DIR}/package/apps/${THEXTECH_DIRECTORY_PREFIX}/bin/${THEXTECH_EXECUTABLE_NAME}" "${CMAKE_BINARY_DIR}/icon.rsrc" COMMENT "Assigning icon to ${THEXTECH_EXECUTABLE_NAME}...") add_custom_command( TARGET make_hkpg PRE_BUILD COMMAND cp -a "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/*.so*" "${CMAKE_BINARY_DIR}/package/apps/TheXTech/lib/" ) add_custom_command( TARGET make_hkpg PRE_BUILD COMMAND ${CMAKE_COMMAND} -E create_symlink "../../../../apps/TheXTech/bin/${THEXTECH_EXECUTABLE_NAME}" "${CMAKE_BINARY_DIR}/package/data/deskbar/menu/Applications/${DESKTOP_NAME}" ) add_custom_command( TARGET make_hkpg POST_BUILD COMMAND package create -C "${CMAKE_BINARY_DIR}/package/" "${CMAKE_BINARY_DIR}/${THEXTECH_INSTALLER_PACKAGE_NAME}_bin-${THEXTECH_HAIKU_VERSION_STRING}-${TARGET_PROCESSOR}.hpkg" ) else() install(TARGETS thextech RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} ) if(THEXTECH_UNIX_INSTALL) # set up application metadata if(NOT HAIKU) # application launcher if(NOT EXISTS "${DEPENDENCIES_INSTALL_DIR}/share/applications") file(MAKE_DIRECTORY "${DEPENDENCIES_INSTALL_DIR}/share/applications") endif() set(DESKTOP_EXEC ${THEXTECH_EXECUTABLE_NAME}) set(DESKTOP_ICON ${THEXTECH_EXECUTABLE_NAME}) set(DESKTOP_WMCLASS ${THEXTECH_EXECUTABLE_NAME}) set(DESKTOP_NAME "TheXTech" CACHE STRING "Name for the application icon") set(DESKTOP_GENERIC_NAME "TheXTech Engine" CACHE STRING "") set(DESKTOP_COMMENT "TheXTech - the modern C++ port and successor of the SMBX engine" CACHE STRING "Short description of the application") configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/icon.desktop.in" "${DEPENDENCIES_INSTALL_DIR}/share/applications/${THEXTECH_EXECUTABLE_NAME}.desktop") install(FILES "${DEPENDENCIES_INSTALL_DIR}/share/applications/${THEXTECH_EXECUTABLE_NAME}.desktop" DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications) # flatpak manifest set(FLATPAK_BUILD OFF CACHE BOOL "Is this build a part of a flatpak workflow?") mark_as_advanced(FLATPAK_BUILD) if(FLATPAK_BUILD) set(FLATPAK_DESCRIPTION "TheXTech is an open-source continuation of the SMBX 1.3 platformer game engine. Its highest priority is full compatibility with the SMBX 1.3 content standard, but it also adds a number of enhancements and bugfixes." CACHE STRING "Description for the installable flatpak package.") configure_file("${CMAKE_CURRENT_SOURCE_DIR}/resources/flatpak/appdata.xml.in" "${DEPENDENCIES_INSTALL_DIR}/share/metainfo/${THEXTECH_EXECUTABLE_NAME}.metainfo.xml") install(FILES "${DEPENDENCIES_INSTALL_DIR}/share/metainfo/${THEXTECH_EXECUTABLE_NAME}.metainfo.xml" DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/metainfo) endif() # application icons set(THEXTECH_ICON_PATH "${CMAKE_CURRENT_SOURCE_DIR}/resources/icon/") install(FILES "${THEXTECH_ICON_PATH}/thextech_16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps/" RENAME "${THEXTECH_EXECUTABLE_NAME}.png") install(FILES "${THEXTECH_ICON_PATH}/thextech_32.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps/" RENAME "${THEXTECH_EXECUTABLE_NAME}.png") install(FILES "${THEXTECH_ICON_PATH}/thextech_48.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/48x48/apps/" RENAME "${THEXTECH_EXECUTABLE_NAME}.png") install(FILES "${THEXTECH_ICON_PATH}/thextech_128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps/" RENAME "${THEXTECH_EXECUTABLE_NAME}.png") install(FILES "${THEXTECH_ICON_PATH}/thextech_256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps/" RENAME "${THEXTECH_EXECUTABLE_NAME}.png") install(FILES "${THEXTECH_ICON_PATH}/thextech_512.png" OPTIONAL DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps/" RENAME "${THEXTECH_EXECUTABLE_NAME}.png") endif() # install readmes install(FILES changelog.txt DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/${THEXTECH_EXECUTABLE_NAME}) install(FILES README.md DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/${THEXTECH_EXECUTABLE_NAME} RENAME ReadMe.txt) install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/${THEXTECH_EXECUTABLE_NAME} RENAME License.TheXTech.txt) else() install(FILES changelog.txt DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/thextech) install(FILES README.md DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/thextech RENAME ReadMe.txt) install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/thextech RENAME License.TheXTech.txt) endif() endif() # ================== Clean-up ================== add_custom_target(clean_external) add_custom_command( TARGET clean_external POST_BUILD COMMAND ${CMAKE_COMMAND} -E remove_directory "${CMAKE_BINARY_DIR}/external/" COMMAND ${CMAKE_COMMAND} -E remove_directory "${CMAKE_BINARY_DIR}/output/include" COMMAND ${CMAKE_COMMAND} -E remove_directory "${CMAKE_BINARY_DIR}/output/lib" COMMAND ${CMAKE_COMMAND} -E remove_directory "${CMAKE_BINARY_DIR}/output/share" ) ================================================ FILE: CONTRIBUTING.md ================================================ We're glad you're interested in the project! Well-implemented PRs that improve TheXTech's compatibility with SMBX64 or add/improve our ports are always welcome. We are also very interested in PRs that add quality-of-life features to base SMBX64 content. # Project Roadmap A broad overview of the goals for the project in the next few versions: ## 1.3.7 (released) * [Multiple resolution system to play game at non-800x600 resolutions](https://github.com/TheXTech/TheXTech/pull/328) * [In-game configuration system](https://github.com/TheXTech/TheXTech/pull/734) * Shared-screen and split-screen >2P multiplayer ## 1.3.7.x * Compressed archive support for episodes and asset packs (`.xte` / `.xta`) * Updated file formats system * NetPlay (aka Online Play) system ## 1.(3.)8 * See [here](https://github.com/TheXTech/TheXTech/issues/952). ## ??? * [Luau scripting system](https://github.com/TheXTech/TheXTech/issues/472) for **Classic Events** and **NPC's** * GLSL ES shader API * TAS System * Rewinding Please see the [Issues](https://github.com/TheXTech/TheXTech/issues) and [milestones](https://github.com/TheXTech/TheXTech/milestones) that developers have endorsed for a fine-grained list of the development tasks and targets we are currently considering. ## An important note on the scope of TheXTech TheXTech is based on the [SMBX64](https://wohlsoft.ru/pgewiki/SMBX64) standard, and it also aims for limited compatibility with the other SMBX branches, as well as Low-end device support, NetPlay support, and TAS/Rewind system support. Therefore, any topics (including but not limited to issues, PR's, and discussions) whose purpose is to make the base TheXTech engine with one of the following situations, will be rejected as **out-of-scope**, even with Lua Script System support: 1. Imitating features of non-SMBX games 2. Featuring major gameplay changes 3. Affecting low-end device support - We monitor code size, static and dynamic memory usage, and CPU performance 4. Breaks legacy SMBX64 logic 5. Affects past works 6. Spamming new content ideas 7. Causing the engine slowdown 8. Affecting TAS (Tool-assisted Speedrun) Mechanic, Netplay, or rewinding 9. Mentioning unofficial engine features 10. Create lots of forks - For anything platform games to be made from scratch, You should use **Moondust Engine** instead, but currently is in progress. Related comments will also be marked as "off-topic". Related conversations of partial issues and PR's will be locked as "off-topic". ## A note on "bugfixes" TheXTech is a faithful reproduction of the SMBX 1.3 engine, so a number of things that may appear to be bugs to a user or contributor may actually be TheXTech reproducing SMBX 1.3 logic intended. Please see [Types of Bugs](https://github.com/TheXTech/TheXTech/wiki/Types-of-bugs) for a categorization of types of bugs. PRs fixing native bugs and critical vanilla bugs are always welcome (but you may be asked to justify that the bug in question is indeed critical). Fixes for other vanilla bugs are not a good first contribution to the engine, due to the considerations we need to employ when faced with these. You are welcome to file an issue for any vanilla peculiarities you discover, but we are unlikely to fix them. # TheXTech content standard We are in the process of developing a content standard for TheXTech, including new engine features such as multistars, more flexible warps, and world map sections. We are also developing a scripting layer including TheXTech-specific Lua and GLSL ES APIs. We are being very careful in how we create the TheXTech standard, and we are generally hesitant to accept PRs that add new content to the standard. We intend to maintain indefinite compatibility with all content created for TheXTech 1.3.7+, and this makes it difficult to remove or refactor features once added. # Ports and limitations We maintain ports for a wide variety of systems, and you should take care when developing new features to ensure that the appropriate ports of the game will continue to run with our minimum system requirements (16 MB of RAM and 200 MHz CPU). If you are interested in contributing new ports to any such systems, please see [Porting](PORTING.md). ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {one line to give the program's name and a brief idea of what it does.} Copyright (C) {year} {name of author} This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: {project} Copyright (C) {year} {fullname} This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: PORTING.md ================================================ TheXTech is a cross-platform engine and is built to be easy to port to new systems. It has very limited dependencies and can be built for most POSIX-like targets. # System Requirements The target must have at least **16 MB RAM** (working on Wii with ~48 MB available), a **200 MHz CPU** (working on old Nintendo 3DS with 268 MHz ARM11), and a **C++11-compliant compiler**. On some of our existing ports, storage or filesystem bandwidth is a pressing consideration. Full compliance with the SMBX64 standard requires support for the AND and OR bitwise / logical blend modes, but the game includes tools used by our SDL2 ports to approximate these with alpha blending. Some features require the target to support compiling GLSL ES 1.00 and 3.00 shaders. # Port types and guidelines Most versions of the game use SDL2 + OpenGL for rendering and SDL Mixer X for software mixing. Your port may make use of these APIs, but is not required to. ## SDL2 If your target supports the SDL2 joystick, window, message box, render, audio, and threading APIs, porting to your target may be as simple as pointing CMake to the appropriate toolchain file. You may need to edit the `CMakeLists.txt` build instructions to enable / disable features for your target as appropriate. Use the Switch, Wii U, or Vita port for reference. ## Custom + SDL2 audio If your target supports the SDL2 audio and threading APIs but is missing other APIs, you may create custom bindings for many of the subsystems under `src/core/`. Use the 3DS and Wii port for reference. ## Custom We maintain a "null" port designed to compile on virtually any POSIX-compliant system. This port does **not** depend on SDL2 but uses a shim layer to delegate most SDL2 calls to their POSIX equivalents. This port may be used in a command-line environment with a gameplay recording file to verify gameplay logic before you invest the time in creating a full port for your target. The null port may be built by passing `-DTHEXTECH_CLI_BUILD=On -DTHEXTECH_NO_SDL_BUILD=On` to CMake in addition to specifying your toolchain. You may initially extend the null port with a minimal renderer for your target system that (for instance) draws rectangles for all drawn textures. This facilitates early testing and development. We previously maintained a custom audio library that used the 3DS's DSP for hardware-accelerated mixing. This source code may be checked to understand how to implement a MixerX-like audio library. It may also be possible to replace the audio at a different level of abstraction (for instance, replacing `sound.cpp` entirely). ================================================ FILE: README.ESP.md ================================================

TheXTech

# TheXTech Motor SMBX, reescrito en C ++ a partir de VisualBasic 6. | | | | |:-------------:|:---------------:|:-------------:| | [English](README.md) | [Русский](README.RUS.md) | Español | ----------- # Preguntas Frecuentes ## ¿Qué es esto? Es una continuación directa del motor SMBX 1.3. Originalmente fue escrito en VB6 para Windows, y más tarde, fue portado / reescrito en C++ y se convirtió en un motor multiplataforma. Reproduce enteramente el antiguo motor SMBX 1.3 (aparte de su Editor), incluye varios de sus errores lógicos (se reparan los errores críticos que hacen que el juego se bloquee o se falle) y también agrega muchas actualizaciones y características nuevas. ## ¿Por qué lo hiciste? Tengo diversos fines para realizarlo: - Es un modelo de investigación bastante conveniente que deseo usar en el desarrollo del motor Moondust. - Proveer una copia plenamente compatible del motor antiguo para plataformas modernas, lo cual posibilita jugar niveles y episodios viejos con la misma sensación que si se hubieran jugado en el juego SMBX original basado en VB6. - Para que funcione sin la necesidad de usar Wine en plataformas que no sean Windows y que se encuentre disponible en plataformas que no sean x86 / x64. - Optimizarlo para utilizar menos recursos de hardware que el juego original con base en VB6. ## Tienes Moondust Engine, ¿por qué has pasado bastante más de un mes para producir esta cosa? Lo necesito para el desarrollo de Moondust Engine de manera directa, es muchísimo más simple de hackear y examinar que un ambiente VB6 antiguo e inconveniente. ## ¿Cuál es el futuro de Moondust Engine ahora que existe TheXTech? Continuaré desarrollando el motor Moondust debido a que aún tengo que conseguir el segundo objetivo del proyecto. A partir de su fundación, el Proyecto Moondust poseía 2 fines: 1) Salvar SMBX 2) Dar un grupo de herramientas flexible para nuevos juegos de plataformas. La apertura del código fuente de SMBX y la introducción de TheXTech ha resuelto el primer objetivo: SMBX ha sido salvado y ahora es un software multiplataforma gratuito de código abierto. Moondust Engine se usará para el segundo objetivo: dar un grupo de herramientas para nuevos juegos. A diferencia de TheXTech, Moondust Engine da un elevado nivel de flexibilidad que posibilita a cualquier persona edificar algo nuevo a partir de cero sin heredar un viejo juego base. No obstante, TheXTech se necesita para Moondust Engine como modelo de investigacion funcional para desarrollar el nuevo motor. Va a ser parecido a las transportaciónes GZDoom y Chocolate Doom del juego Doom: GZDoom es un motor potente y servible, la mejor alternativa para los modders; Chocolate Doom es una adaptacion precisa del juego original a plataformas modernas para representar el juego original, incluidos los errores. El motor Moondust pretende ser como GZDoom, en lo que TheXTech es un semejante de Chocolate Doom para representar un juego original en plataformas modernas. ## ¿Los niveles y episodios con LunaDLL Autocode funcionarán en Este juego? Sí, lo harán. A partir de la versión 1.3.6 de TheXTech, hay una implementación incorporada del lenguaje de scripting LunaDLL Autocode, llamado LunaScript. Con este sistema, es posible ejecutar episodios LunaDLL en cualquier hardware, incluso con una arquitectura de procesador que no sea x86. ## ¿Puede LunaLua funcionar en esto? No, LunaLua no funcionará: este proyecto es binario-incompatible con LunaLua. Esto también significa que el contenido SMBX2 es incompatible. El sistema de secuencias de comandos planificado en el lenguaje lua no podrá garantizar la compatibilidad. Por lo tanto, después de la posible aparición de soporte para scripts lua, será aconsejable portar o crear desde cero específicamente para TheXTech. ## ¿Por qué el código aquí es tan malo? El autor original escribió la mayor parte del código en la carpeta "* src *" en VB6. Hice una conversión completa del código con un esfuerzo por lograr una reproducción precisa. Por lo tanto, gran parte del código es idéntico a lo que se escribió originalmente en VB6. La plataforma VB6 tenía muchos desafíos y limitaciones como: - Todas las variables son globales y accesibles desde todos los módulos y se forman de forma predeterminada sin ninguna inclusión o importación. La razón por la que existe "globals.h": tiene una lista completa de variables disponibles globalmente. - Soporte limitado e inconveniente para las clases, por lo tanto, el código tiende a abusar de una tonelada de variables y matrices globales (también una falta inicial de experiencia del autor original fue otro factor que llevó a este problema). - Todas las funciones de todos los módulos son globales y se pueden llamar directamente desde cada módulo. Excepto las llamadas marcadas como "privadas". Por lo tanto, tuve un trabajo adicional para proporcionar inclusiones en archivos donde se solicitan estas llamadas. - ¿Por qué tanto `if-elseif-elseif-elseif-elseif -....?` Sí, aquí probablemente sea correcto usar `switch ()` (en VB6 el operador `Select Case` analógico). Otro factor que muestra que el autor original tenía poca experiencia cuando codificó este proyecto. - ¿Por qué la lasaña `if () {if {} if {....}}`? Dos razones: 1) inexperiencia del autor original, 2) solución para no verificar todas las condiciones de expresión que pueden causar un bloqueo. En C ++ con múltiples condiciones divididas por el operador `&&`, nunca se ejecuta cuando una de ellas obtiene un resultado falso. En VB6, TODAS las condiciones en la obtención de expresiones siempre se ejecutarán. Esta diferencia provocó la siguiente situación: en VB6, una expresión `if A <5 And Array (A) = 1 Then` provocará un bloqueo cuando A sea más de 5. En C ++ el mismo` if (A <5 && Array [ Una expresión] == 1) `nunca fallará porque una segunda verificación nunca se ejecutará si una primera verificación dio un resultado falso. - ¿Por qué expresiones tan largas como `if (id == 1 || id == 3 || id == 4 || ... id == N)`? En lugar de hacer un montón de condiciones como esta, sería mejor usar clases con un polimorfismo y separar la lógica de cada objeto entre diferentes clases. También debería poder resolverse teniendo que usar punteros de función (que no son posibles en VB6 sin workaronds, pero son posibles en C ++). Pero, nuevamente, la inexperiencia del autor original combinada con un montón de límites de VB6 causaron estas construcciones. ## ¿Como usar esto? Aquí hay muchas formas de jugar con él: - hay algunos paquetes listos para usar, simplemente tómelos y juegue como lo hizo con SMBX. - [usuarios de macOS, omitan esto]: use la misma manera que el juego original: coloque el archivo ejecutable en la carpeta raíz del juego con un "thextech.ini" que contenga el siguiente texto: ``` [Main] force-portable = true ``` , music.ini, sounds.ini y una carpeta adicional "graphics/ui". Una nota importante: todos los gráficos predeterminados deben convertirse a PNG, use la herramienta GIFs2PNG de PGE Project en su carpeta "gráficos" con un interruptor "-d". No use el interruptor "-r" para mantener los GIF originales junto con los PNG recién creados si planea continuar usando SMBX original escrito en VB6. - use esto para el modo de depuración: en su directorio de inicio, cree la carpeta ".PGE_Project/thextech" (en macOS, la "`~/TheXTech Games/Debug Assets/`") donde debe colocar un set completo de recursos del juego y cosas de mundos, esta carpeta funcionará como la raíz del juego en el juego original. Este modo le permite ejecutar un archivo ejecutable desde cualquier ubicación de la carpeta en su computadora y usar la misma ubicación de recursos para todas las compilaciones (excepto las que están marcadas como portátiles por el archivo INI). ## ¿Cómo agregar episodios personalizados para la versión de macOS? Si tiene una compilación empaquetada de TheXTech, todos los recursos predeterminados están dentro de su .app: "Content/Resources/assets/". Puedes modificar el contenido, ¡pero no es recomendable! En cambio, después de la primera ejecución de un juego, en su directorio de inicio aparecerá el siguiente directorio: '' ~/TheXTech Episodes '' En este directorio, encontrará una carpeta vacía de "batalla" y "mundos" para colocar sus cosas personalizadas. En la ruta "`~/Library/Application Support/PGE Project/thextech` ", se almacenarán los registros, las configuraciones y las partidas guardadas. Si desea reemplazar los recursos predeterminados por los suyos, puede modificar el contenido del paquete de la aplicación o hacer una nueva compilación con los argumentos de CMake necesarios que se necesitan para empaquetar la raíz de los recursos personalizados y el icono en el nuevo paquete o crear la compilación sin recursos (si no das argumentos, resultará la compilación sin recursos). Por lo tanto, debes colocar el contenido completo de la raíz del juego en la carpeta "`~/TheXTech Games/Debug Assets/` ", incluye recursos predeterminados (gráficos, música, sonidos, niveles de introducción y salida, niveles de batalla predeterminados y carpetas de mundos). ## ¿Qué diferencia hay con esto en comparación con la versión original en VB6? - En primer lugar, está escrito en C ++, mientras que el original (como ya sabemos) está escrito en VB6. - No tiene editor. En cambio, tiene una integración profunda con Moondust Editor que permite usarlo con la misma funcionalidad que en el editor original (la funcionalidad de "mano mágica" se mantuvo para permitir la edición en tiempo real del nivel durante la prueba, es necesario usar la comunicación IPC con Moondust Editor para tener la capacidad de usarlo mejor). - Soporte completo de UTF-8 en rutas de nombre de archivo y datos de texto internos (el juego original tenía solamente soporte ANSI de 8 bits). - Para gráficos y control, usa una biblioteca SDL2, mientras que el juego original ha usado llamadas WinAPI y biblioteca GDI. - Utiliza PGE-FL que tiene un mejor soporte de formatos de archivo. - Un soporte para mapas del mundo WLDX permite líneas de crédito ilimitadas y música personalizada sin que sea necesario usar un music.ini para reemplazos de música. - Algunas características exclusivas de LVLX ahora funcionan: envolvimiento de sección vertical, warp bidireccional, mensaje personalizado de "Estrella requerida", evento de entrada de warp, capacidad para deshabilitar la impresión de estrellas en episodios HUB para puertas específicas, capacidad para deshabilitar la visualización entre escenas al pasar a otro nivel a través de un warp. - Soporte incorporado para extensos music.ini y sounds.ini de episodios y niveles para anular los recursos de musica y sonidos predeterminados. - Los mapas del mundo ahora admiten un directorio personalizado para almacenar recursos específicos como baldosas/escenas/caminos/niveles personalizados y ya no enviar spam a la carpeta raíz del episodio con recursos del mapa del mundo. - El formato de configuración por defecto es INI, el viejo formato config.dat ya no es compatible, principalmente por valores de codigo de teclas incompatibles (SDL_Scancode versus enumeración de VirtualKeys de Windows API). - El juego se guarda ahora usando el formato SAVX en lugar del clásico SAV. Sin embargo, si ya tiene un juego guardado antiguo, aún puede reanudar su juego usando el nuevo motor ahora (el próximo intento de guardado de juegos dará como resultado un archivo SAVX, el juego guardado antiguo en formato SAV se mantendrá intacto). - Soporte PNG incorporado para gráficos personalizados y predeterminados. Sin embargo, los GIF enmascarados todavía son compatibles por compatibilidad con versiones anteriores, sin realizar una conversión automática inesperada como lo hace SMBX-38A. - ¡Los puntos de control ahora tienen puntos múltiples! ¡Puedes usarlos en tus niveles varias veces sin límites! - Utiliza un algoritmo de descompresión diferida para acelerar la carga del juego y reducir el uso de memoria. - ¡Para música y SFX, la librería MixerX se usa para brindar soporte a una amplia cantidad de formatos de sonido y música! - No incorpora ningún gráfico: NO hay gráficos realmente codificados, ¡ahora todo está representado por gráficos externos! - Se han ampliado algunos límites internos. - Grabador GIF integrado con la tecla F11 (F10 en macOS, F11 está reservado por la interfaz de usuario del sistema para una acción de "mostrar escritorio") - Comienza más rápido: la carga del juego es casi instantánea (depende de una computadora y del rendimiento de su HDD/SSD). - Utiliza menos RAM (80 ... 150 MB en lugar de 600 ... 800 MB como usualmente), y está libre de pérdidas de memoria dadas por la interfaz MCI utilizada originalmente por SMBX en VB6. - no sobrecarga la CPU (la razón fue una mala manera de procesar bucles infinitos, también hice la corrección de la compilación de VB6 en mi rama "smbx-experiment") - Puede funcionar en una "tostadora" (una computadora débil) mientras que VB6-SMBX no funcionará. - es completamente multiplataforma y no depende de Windows, y ya no depende del procesador x86: también puede funcionar en procesadores ARM y MIPS (VB6-SMBX no funcionará en ARM en absoluto, con el emulador x86 funcionará 20 veces más lento de lo habitual). ## ¿Cómo compilarlo? Puede leer una guía sobre cómo compilar este proyecto desde la fuente aquí: https://github.com/Wohlstand/TheXTech/wiki/Building-the-game Para compilarlo, necesita tener lo siguiente: - CMake - Ninja opcionalmente (para acelerar el proceso de construcción) - Compilador C/C ++ compatible (GCC, Clang, MinGW, MSVC 2017 y 2019, posiblemente también se construirá en 2015, pero no se ha probado) - Git (necesario para extraer submódulos y clonar la fuente de las líbrerias dependientes para construirlos en su lugar) - Mercurial (necesario para clonar un repositorio oficial de SDL2 para construirlo en su lugar aquí) - Opcionalmente: dependencias instaladas en todo el sistema: SDL2, libFreeImageLite (una implementación modificada de FreeImage), librería de sonido MixerX, colección de librerías AudioCodecs. Tenerlos instalados en el sistema aumenta la velocidad de compilación. Sin embargo, es posible compilar todas estas dependencias en su lugar aquí con un costo adicional de tiempo de compilación agregado. ================================================ FILE: README.RUS.md ================================================

TheXTech

# TheXTech SMBX-движок, переписанный на C++ с VisualBasic 6 | | | | |:-------------:|:---------------:|:-------------:| | [English](README.md) | Русский | [Español](README.ESP.md) | ----------- # Часто задаваемые вопросы ## Что это такое? Это прямое продолжение старого движка SMBX 1.3. Изначально движок был написан на VB6 под Windows, а позже был портирован/переписан на C++: и с тех пор движок стал кросс-платформенным. Он полностью повторяет старый движок (кроме редактора), включая множество логических багов (часть багов была исправлена, первым делом те, которые ломают игру), а также содержит множество обновлений и новых возможностей. ## Зачем ты это сделал? Зачем? У меня имеется несколько целей, почему я это сделал: - Это - очень удобная живая модель для исследований, которую я хочу использовать для разработки Moondust-движка. - Чтобы предоставить полностью совместимую со старым SMBX-движком игру для современных платформ, чтобы можно было играть старые уровни и эпизоды, сохранив первозданную атмосферу оригинальной игры SMBX, писанной на VB6. - Чтобы игра работала на не-Windows платформах без необходимости использовать Wine, а также запускать игру на других процессорах, отличных от x86 (например ARM). - Чтобы была возможность оптимизировать игру, используя меньше аппаратных ресурсов, в отличии от оригинальной игры, основанной на VB6. ## У тебя есть Moondust-движок, зачем ты портатил больше месяца на то, чтобы создать эту вещь? Мне оно нужно для разработки Moondust-движка напрямую. С ним мне намного проще исследовать и экспериментировать, чем через старую и неудобную среду VB6. ## Какое будущее у движка Mooddust (ранее PGE) с тех пор как существует TheXTech? Я продолжу разработку Moondust-движка, поскльку мне всё равно надо исполнить вторую цель Moondust Project. С момента основания у Moondust Project было две цели: 1) спасти SMBX; 2) предоставить гибкий набор для создания новых игр-платформеров. Открытие исходников SMBX и связанное с этим появление TheXTech, полностью исполнило первую цель: SMBX спасён, и теперь это свободное ПО с открытым исходным кодом. Moondust-движок останется для исполнения второй цели - предоставить набор для новых игр. В отличии от TheXTech, Moondust-движок даёт максимальную гибкость, которая позволяет каждому создать что-либо с нуля, без необходимости наследовать старую игровую базу. Однако, TheXTech нужен для Moondust-движка в качестве модели для исследований, чтобы разработать новый движок. В итоге, результат будет схож с портами игры Doom: GZDoom и Chocolate Doom. GZDoom - мощный и функциональный движок, лучший выбор для моддеров. Chocolate Doom - это высокоточный порт оригинальной игры на современные платформы с целью предоставить оригинальную игру, включая даже баги. Moondust-движок стремится быть похожим на GZDoom в то время, как TheXTech будет аналогом Chocolate Doom с целью повторить оригинальную игру на современных платформах. ## Смогут ли уровни и эпизоды с LunaDLL Autocode работать в этой игре? Да, смогут. Начиная с версии TheXTech 1.3.6, имеется встроенная реализация скриптового языка LunaDLL Autocode, именуемая LunaScript. С этой системой можно запускать LunaDLL-эпизоды на любом оборудовании, даже с процессорной архитектурой, отличной от x86. ## Сможет LunaLua работать в этой игре? Нет, LunaLua не сможет работать: данный проект бинарно несовместим с LunaLua. Это ещё значит, что контент, предназначенный для SMBX2, также будет несовместим. Планируемая скриптовая система на языке lua не сможет гарантировать совместимость. Поэтому, после возможного появления поддержки lua-скриптов, будет целесообразно портировать, либо создавать с нуля конкретно для TheXTech. ## Почему здесь такой плохой код? Изначально, большинство кода в папке "src" было написано на VB6 его изначальным автором. Я сделал полное преобразование кода с упором на максимально точное воспроизведение игры. То есть, большинство кода практически идентично тому, что было изначально написано на VB6. Платформа VB6 имела огромное количество всевозможных трудностей и ограничений: - Все переменные глобальны и доступны из каждого модуля и формы по умолчанию, без каких-либо включений или импортов. Это и есть причина, почему существует "globals.h": этот файл содержит в себе весь полный список глобально доступных переменных. - Ограниченная и неудобная поддержка классов, почему большая часть кода нагло злоупотребляла большим колличеством глобальных переменных и массивов (также свою лепту привнесло отсутствие опыта у изначального автора, которое стало дополнительным фактором существования всего этого безобразия). - Все функции во всех модулях глобальны, и могут вызываться из кажого модуля напрямую (за исключением вызовов, помеченных как "приватные"). Из-за чего мне пришлось проделать дополнительную работу по включению заголовков в файлы, где эти вызовы применялись. - Почему так много `if-elseif-elseif-elseif-elseif-....`? Да, здесь, скорее всего, было бы правильно использовать оператор `switch()` (аналог в VB6: `Select Case`). Ещё одна причина, которая объясняет, что у изначального автора на тот момент времени не хватало опыта, пока он работал над этим проектом. - Почему такие глубокие лазаньи из `if() { if {} if { .... } }`? На то есть две причины: 1) мало опыта у изначального автора, 2) костыль, предназначенный для того, чтобы не проверять все условия логического выражения в случаях, способных вызвать крэш. В C++, множество условий, разделённых с помощью оператора `&&`, никогда не будут проверены, если одно из них даёт отрицательный результат. В VB6 наборот, ВСЕ узлы выражения исполняются, не зависимо от их реузльтата. Эта разница даёт следующее: в VB6, выражение `if A < 5 And Array(A) = 1 Then` спровоцирует ошибку, если A будет больше пяти. В C++, аналогичное выражение `if(A < 5 && Array[A] == 1)` никогда не спровоцирует ошибку, потому что вторая часть выражения никогда не проверяется, если первая дала отрицательный результат. - Почему такие длинные выражения по типу `if(id == 1 || id == 3 || id == 4 || ... id == N)`? Вместо того, чтобы создавать кучи подобных выражений, было бы лучше использовать классы и полиморфизм с отдельной логикой для каждого объекта между разными классами. Это также должно решаться с помощью указателей на функции (которые невозможны в VB6 без костылей, однако легко возможны в C++). Однако снова, отсутствие опыта у изначального автора одновременно с огромной кучей ограничений VB6 спровоцировало появление подобных конструкций. ## Как пользоваться игрой? Имеется несколько способов, как пользоваться этой игрой: - Имеются уже готовые к работе сборки, просто взять использовать также, как SMBX. - [Пропустить этот пункт пользователям macOS]: Смешать исполняемый файл игры с существующими игровыми ресурсами: положить исполняемый файл в кореневую папку ресурсов совместно с INI-файлом "thextech.ini", содержащим следующий текст: ```ini [Main] force-portable = true ``` , файлами "music.ini", "sounds.ini" и с дополнительной папкой "graphics/ui". Важно: вся штатная графика должна быть преобразована в PNG. Используйте утилиту GIFs2PNG из комплекта Moondust Project, применив на папке "graphics" с флагом "-d". Не используйте флаг "-r", чтобы сохранить исходные GIF-файлы совместно с новоиспечёнными PNG, если есть в планах продолжить использовать оригинальную игру, написанную на VB6. - Использовать это в отладочном режиме: в Вашем домашнем каталоге, создайте папку `.PGE_Project` (обязательно должно начинаться с точки), и затем внутри `.PGE_Project`, создать папку "thextech" (на macOS вместо этого нужно создать путь "`~/TheXTech Games/Debug Assets/`"). Это отладочный корень игры, в коротом необходимо разместить полный комплект ресурсов игры и данные эпизодов. Этот каталог будет действовать аналогично корневому каталогу оригинальной игры. Этот режим позволяет запускать исполняемый файл игры из абсолютно любого места на Вашем компьютере, и при этом использовать одно общее местоположение игровых ресурсов для всех сборок (за исключением тех, которые были помечены как портативные с помощью INI-файла). ## Как добавить сторонние эпизоды в версии для macOS? Если у вас укомплектованная сборка TheXTech, то все встроенные ресурсы находятся внутри вашего .app: "Content/Resources/assets/". Можно изменить содержиеме, однако, этого не рекомендуется делать! Вместо этого, после первого запуска игры в вашем домашнем каталоге появятся следующая директория: ``` ~/TheXTech Games/<имя игры>/ ``` Внутри этой директории будут две пустые папки: "battle" и "worlds", в которые вы сможете положить ваши эпизоды. В подпапку "settings" будут сохранены настройки и сохранения игры. Логи будут сохранены в папке "`~/Library/Application Support/PGE Project/thextech`". Если же вы хотите заменить встроенные ресурсы на собственные, можно изменить содержимое пакета приложения, или собрать новую сборку с указанием необходимых флагов CMake, с помощью которых вы сможете упаковать ваш собственный набор ресурсов и иконку для нового пакета, либо же сделать безресурсную сборку (по умолчанию, если не указать никаких аргументов, будет собираться безресурсная сборка по-умолчанию). Поэтому вам нужно разместить полный комплект игровых ресурсов в папку "`~/TheXTech Games/Debug Assets/`", включая все основные ресурсы (графику, музыку, звуки, вводный и завершающий уровни, уровни битв и папки миров по умолчанию). ## В чём разница между этой и оригинальной сборкой игры, созданной на VB6? - Первым делом, эта игра написана на C++, когда оригинал (как мы уже знаем) был создан на VisualBasic6. - До выпуска 1.3.6, редактор полностью отсутствовал. Начиная с 1.3.6 имеет встроенный мобильный редактор, работающий в полноэкранном режиме. Им можно управлять не только клавиатурой и мышкой, но и геймпадов или сенсорным экраном. Также, игра имеет глубокую интеграцию с редактором Moondust, которая позволяет использовать игру с той же функциональностью, что и через оригинальный редактор (Была сохранена функциональность "волшебной руки", которая позволяет редактировать уровень в реальном времени во время тестирования). - Полная поддержка UTF-8 в именах файлов, путях и внутренних текстовых ресурсах (оригинальная игра поддерживала только 8битные кодировки ANSI). - Игра использует SDL2 для графики и управления в то время, как оригинальная игра использовала вызовы WinAPI и библиотеки GDI. - Игра использует библиотеку PGE-FL для улучшенной поддержки форматов файлов. - Имеется поддержка карт мира WLDX, которые позволяют сохранять неограниченное число строк титров, а также встроенная поддежка использования сторонней музыки без необходимости использовать music.ini. - В игре работают некоторые уникальные возможности уровней LVLX: вертикальное соединение; двунаправленные проходы; возможность указать собственное сообщение, чтобы рассказать о необходимом числе звёзд для взода в проход; событие входа в проход; возможность отключить отображение числа звёзд над определёнными дверями в корридорных мирах; возможность отключить входной экран при переходе между уровнями через проход. - Встроенная поддержка файлов "music.ini" и "sounds.ini" для замены музыки и звуков на отдельных уровнях, и даже в целых эпизодах. - Карты мира отныне поддерживают собственную директорию, чтобы размещать в ней любые специфичные ресурсы (ландшафт, декорации, пути, уровни), и при этом больше не засорять корневую папкпу эпизода ресурсами карты мира. - Формат настроек теперь INI, старый формат config.dat больше не поддерживается, первым делом из-за несовместимых форматов кодов клавиш (SDL_Scancode против VirtualKeys из Windows API). - Сохранения игры отныне используют формат SAVX вместо классического SAV. Однако, если у вас уже имеется старое сохранение, вы всё ещё можете продожить игру, используя новый движок (при следующем сохранеии игры, будет создан файл в новом SAVX-формате, старое сохранение игры останется нетронутым). - Встроенная поддкржика PNG для сторонней и встроенной графики. Масковые GIF по прежнему поддерживаются для обратной совместимости, однако, без автопреобразований, как это обычно делает SMBX-38A. - Контрольные точки отныне можно использовать по много раз! Вы сможете использовать их на ваших уровнях по несколько раз без каких либо ограничений! - Игра использует алгоритм ленивой распаковки, чтобы ускорить время загрузки игры и уменьшить использование памяти. - Был полностью переработан алгоритм поиска объектов, чего позволило полностью решить проблему сильного замедления на слабых машинах из-за горизонтального движения слоёв (См. подробное описание проблемы тут: [Проблема Доктора Пеппера](https://wohlsoft.ru/pgewiki/DrPepper_Problem/ru)) - Игра использует библиотеку MixerX для звука и музыки, которая поддерживает огрномное число различных аудиоформатов! - Игра не внедряет в сборку какую-либо графику: отныне в игре больше НИКАКИХ истинно "внедрённых" ресурсов, теперь вся графика представлена в виде внешних ресурсов! - Расширены некоторые встроенные ограничения. - Встроення запись GIF с помощью клавиши F11 (на macOS используется клавиша F10, F11 используется системой, чтобы показать/скрыть рабочий сто). - Игра запускается быстрее: игра загружается почти мгновенно (зависит от компьютера и производительности его диска). - Игра использует меньше оперативной памяти (вместо 600...800 мегабайт, игра теперь в среднем использует 80...150 мегабайт), а также, она теперь чиста от утечек в памяти, вызванных MCI-интерфейсом, который использовался в оригинальной игре. - Игра больше не перегружает процессор до 100% (проблема была из-за неправильного способа обработки вечных циклов, я также исправил этот деффект и в VB6-сборке в ветке "smbx-experiments"). - Игра способна работать даже на "тостере" (старом/слабом компьютере) в то время как VB6-SMBX не можеты. - Игра полностью кросс-платформенна, и больше не зависит от Windows, а также больше не зависит от процессоров x86: игра сможет работать на ARM и MIPS-процессорах тоже (VB6-SMBX зависит от Windows и x86). ## Как собрать игру? Вы можете почитать подробно инструкцию сборки этого проекта из исходников тут: https://github.com/Wohlstand/TheXTech/wiki/Building-the-game Чтобы собрать игру, нужно иметь несколько вещей: - CMake - Ninja, не обязательно (чтобы ускорить процесс сборки) - Совместимый компилятор C/C++ (GCC, Clang, MinGW, MSVC 2017 и 2019, вожможно будет собираться и в MSVC 2015, но не проверено) - Git (нужен для загрузки подмодулей и клонирования исходников зависисимых библиотек для их сборки на месте) - Mercurial (необходим для загрузки исходников с официального репозитория SDL2, чтобы собрать его на месте) - Не обязательно: зависимости, установленные в систему: SDL2, libFreeImageLite (модифицированная реализация FreeImage), звуковая библиотека MixerX, коллекция библиотек AudioCodecs. Если все эти зависимости установлены в системе, процесс сборки будет идти значительно быстрее. Однако, возможно собирать все зависимости прямо на месте, но при этом сборка будет идти дольше. ## Локализация Некоторые части TheXTech можно локазиловать (напримет, загрузчик на Android), Вы можете помочь перевести их, используя платформу WebLate: https://hosted.weblate.org/projects/thextech/ ## Используемые инструменты * GCC, MinGW-w64 и Clang - основные компиляторы, используемые во время разработки игры. Редко MSVC, который используется исключительно для Windows-сборок под процессоры ARM64. * [Qt Creator](https://www.qt.io/product/development-tools) - среда разработки из комплекта Qt, в основном используемя при разработке. * [JetBrans CLion](https://www.jetbrains.com/ru-ru/clion/) - среда разработки для C/C++ на базе Intelliji Idea, бесплатна по [лицензии для СПО](https://www.jetbrains.com/ru-ru/community/opensource/). * [PVS-Studio](https://pvs-studio.com/ru/pvs-studio/?utm_source=website&utm_medium=github&utm_campaign=open_source) - статический анализатор кода для C, C++, C# и Java. Используется для периодических проверок кода на возможные ошибки. Бесплатен по [лицензии для СПО](https://pvs-studio.com/ru/order/open-source-license/). ================================================ FILE: README.md ================================================

TheXTech

# TheXTech SMBX engine, rewritten into C++ from VisualBasic 6. | | | | |:-------------:|:---------------:|:-------------:| | English | [Русский](README.RUS.md) | [Español](README.ESP.md) | ----------- # Frequently Asked Questions ## What is this? This is a direct continuation of the SMBX 1.3 engine. Originally it was written in VB6 for Windows, and later, it got ported/rewritten into C++ and became a cross-platform engine. It completely reproduces the old SMBX 1.3 engine (aside from its Editor), includes many of its logical bugs (critical bugs that lead the game to crash or freeze got fixed), and also adds a lot of new updates and features. ## Why did you make it? I have several purposes for making it: - It's a very convenient research model I want to use in developent of the Moondust Engine. - To provide a fully-compatible replica of the old engine for modern platforms, allowing to play old levels and episodes with the same feeling as if they were played on the original VB6-based SMBX game. - To make it work without the necessity to use Wine on non-Windows platforms and making it available on non-x86/x64 platforms. - Optimizing it to use fewer hardware resources than the original VB6-based game. ## You have Moondust Engine, why you have spent over a month to craft this thing? I need it for Moondust Engine development directly, it's much easier to hack and inspect than an old, inconvenient VB6 environment. ## What's the future of Moondust Engine now that TheXTech exists? I'll continue developing the Moondust Engine as I have yet to reach the second goal of the project. Since it's foundation, the Moondust Project had two goals: 1) save SMBX; 2) give a flexible toolkit for new platform games. The opening of SMBX's source-code and introducing TheXTech has solved the first goal: SMBX has been saved and now it's free/opensource cross-platform software. Moondust Engine will be used for the second goal - giving a toolkit for new games. Unlike TheXTech, Moondust Engine gives a high degree of flexibility that allows anyone to build something new from scratch without inheriting an old game base. However, TheXTech is needed for Moondust Engine as a working research model to develop the new engine. It will be similar to GZDoom and Chocolate Doom ports of the Doom game: GZDoom is a powerful and functional engine, the best choice for modders; Chocolate Doom is an accurate port of the original game to a modern platform with the purpose to represent the original game including even bugs. The Moondust Engine intends to be like GZDoom while TheXTech is an analog of Chocolate Doom to represent an original game on modern platforms. ## Can levels and episodes with LunaDLL Autocode work on this? Yes, can. Since the TheXTech version 1.3.6, there is a built-in implementation of the LunaDLL Autocode language, called LunaScript. This system allows the running of LunaDLL-episodes on any hardware including non-x86 processor architectures. ## Can LunaLua work on this? No, LunaLua won't work: this project is binary-incompatible with LunaLua. This also means that SMBX2 content is incompatible. The planned lua-based scripting system won't guarantee compatibility. Therefore, after the possible appearance of the Lua scripts support, it will be reasonable to port, or create new from the ground up and target to TheXTech exclusively. ## Why is the code here so bad? The original author wrote most of the code in the "*src*" folder in VB6. I did a whole conversion of the code with an effort to accurate reproduction. So, a lot of the code is identical to what was written in VB6 originally. The VB6 platform had a lot of challenges and limitations such as: - All variables are global and accessible from every module and form by default without any includes or imports. The reason why "globals.h" exists: it has a full list of globally available variables. - Limited and inconvenient support for classes, therefore the code tends to abuse a ton of global variables and arrays (also an initial lack of experience of the original author was an another factor that lead to this mess). - All functions in all modules are global and can be called from each module directly. Except calls marked as "private". Therefore I had an additional work to provide inclusions into files where these calls are requested. - Why so much `if-elseif-elseif-elseif-elseif-....?` Yes, here probably will be correct to use `switch()` (in VB6 the `Select Case` analogue) operator. Another factor that shows the original author had a low amount of experience when he coded this project. - Why the `if() { if {} if { .... } }` lasagna? Two reasons: 1) inexperience of original author, 2) workaround to not check all conditions of expression which may cause a crash. In C++ with multiple conditions splitted by `&&` operator, never executing when one of them gets a false result. In VB6, ALL conditions in expression getting be always executed. This difference caused the next situation: in VB6, an expression `if A < 5 And Array(A) = 1 Then` will cause a crash when A is more than 5. In C++ the same `if(A < 5 && Array[A] == 1)` expression will never crash because a second check gets be never executed if a first check gave a false result. - Why so long expressions like `if(id == 1 || id == 3 || id == 4 || ... id == N)`? Rather making a ton of conditions like this, it's would be better to use classes with a polymorphism and separate the logic of every object between different classes. Also should be solvable with having to use of function pointers (which aren't possible in VB6 without workaronds, but possible in C++). But, again, the original author's inexperience combined with a bunch of VB6 limits caused these constructions. ## How to use this? Here are many ways to play games with it: - There are some ready-to-use packages, just download them, unpack and run them as you did it with SMBX, absolutely same. - [macOS users, skip this]: Mix the game with existing assets directory: put an executable file into the game root folder with an "thextech.ini" that contains next text: ```ini [Main] force-portable = true ``` , music.ini, sounds.ini and additional "graphics/ui" folder. An important note: all default graphics must be converted into PNG, use GIFs2PNG tool from Moondust Project over your "graphics" folder with a "-d" switch. Don't use "-r" switch to keep original GIFs together with new-made PNGs if you plan to continue the use of original VB6-written SMBX. - Use it for debug mode: in your home directory, create the ".PGE_Project/thextech" folder (on macOS the "`~/TheXTech Games/Debug Assets/`") where you should put a full set of game resources and worlds stuff, this folder will work like a game root in the original game. This mode allows you to run an executable file from any folder location of your computer and use the same location of resources for all builds (except these are marked as portable by an INI file). ## How to add custom episodes for the macOS version? If you have a bundled build of TheXTech, all default resources are inside your .app: "Content/Resources/assets/". You can modify the content, but it's not recommended! Instead, after the first run of a game, in your home directory will appear the next directory: ``` ~/TheXTech Games// ``` In this directory, you will find an empty "battle" and "worlds" folder to put your custom stuff. At the "settings" sub-directory the game settings and game saves will be stored. At the "`~/Library/Application Support/PGE Project/thextech/logs/`" path logs will be stored. If you want to replace default assets with your own, you can modify the content of the app bundle or compile a new build with giving of the necessary CMake arguments which needed to pack your custom assets root and icon into the new bundle or make the assets-less build (if you give no arguments, the assets-less build will result). Therefore, you need to put the full content of the game root into the "`~/TheXTech Games/Debug Assets/`" folder, include default assets (graphics, music, sounds, intro and outro levels, default battle and worlds folders). ## What is different with this thing in comparison to the original VB6 build? - First off, it's written in C++ while original (as we already know) is written in VB6. - Before the version 1.3.6, it had no Editor at all. Since the version 1.3.6, it has an embedded mobile editor that works in a full screen. It can be controlled by keyboard and mouse as well, as by gamepad or by touch screen. In addition, it has a deep integration with Moondust Editor that allows to use game with the same functionality as was possible in the original editor (the "magic hand" functionality was kept to allow real-time editing of a level while testing). - Full support of UTF-8 in filename paths and internal text data (original game had the only 8bit ANSI support). - For graphics and controlling, it uses an SDL2 library while original game have used WinAPI calls and GDI library. - It uses PGE-FL library that has better file formats support. - A support for WLDX world maps are allowing unlimited credits lines and custom music without it being necessary to use a music.ini for music replacements. - Some LVLX exclusive features now working: vertical section wrap, two-way warps, custom "star needed" message, warp enter event, ability to disable stars printing in HUB episodes for specific doors, ability to disable interscene showing when going to another level through a warp. - Built-in support for episode and level wide music.ini and sounds.ini to override default music and sounds assets. - World maps now supports a custom directory to store any specific resources like custom tiles/scenes/paths/levels and not spam the episode root folder with world map resources anymore. - Default config format is INI, old config.dat format is no longer supported, mainly because of incompatible key code values (SDL_Scancode versus VirtualKeys enum of Windows API). - Game saves now using the SAVX format instead of a classic SAV. However, if you already have an old gamesave, you still can resume your game by using a new engine now (next gamesave attempt will result a SAVX file, old gamesave in SAV format will be kept untouched). - Built-in PNG support for custom and default graphics. Masked GIFs are still supported for backward compatibility, however, without making an unexpected auto-conversion like SMBX-38A does. - Checkpoints now have multi-points! You can use them in your levels multiple times without limits! - It does use of lazy-decompress algorithm to speed-up the loading of a game and reduce the memory usage. - It has the completely reworked objects search algorithm which resolves the lag problem on slow systems after horizontal move of layers (See description of the [DrPepper Problem](https://wohlsoft.ru/pgewiki/DrPepper_Problem)). - For music and SFX, the MixerX library is used to give a support for a wide amount of sound and music formats! - It doesn't embeds any graphics: there are NO trurely hardcoded graphics, everything is now represented by external graphics! - Some internal limits have been expanded. - Built-in GIF recorder by F11 key (F10 on macOS, F11 is reserved by system UI for a "show desktop" action) - It starts faster: the loading of the game is almost instant (depend on a computer and it's HDD/SSD performance). - It uses less RAM (80...150 MB instead of 600...800 MB like usually), and it's free from memory leaks given by the MCI interface used by VB6 SMBX originally. - it doesn't overload CPU (the reason was a bad way to process infinite loops, I did the fix of VB6 build too at my "smbx-experiments" branch) - it able to work on "toaster" (a weak computer) while VB6-SMBX won't work. - it's fully cross-platform and doesn't depend on Windows, and it no longer depends on x86 processor: it can work on ARM and MIPS processors too (VB6-SMBX won't work on ARM at all, with x86 emulator it will 20x times slower than usualy). ## How to build it? You can read a guide how to build this project from source here: https://github.com/Wohlstand/TheXTech/wiki/Building-the-game To build it, you need to have the following things: - CMake - Ninja optionally (to speed-up the build process) - Compatible C/C++ compiler (GCC, Clang, MinGW, MSVC 2017 and 2019, possibly will build also on 2015, but wasn't tested) - Git (required to pull submodules and clone source of dependent libraries to build them in place) - Mercurial (required to clone an official SDL2 repository to build it in place here) - Optionally: system-wide installed dependencies: SDL2, libFreeImageLite (a modded implementation of the FreeImage), MixerX sound library, AudioCodecs collection of libraries. Having them be installed in a system gives a major build speed up. However, it's possible to build all these dependencies in place here with a cost of extra build time being added. ## Localization Some parts of TheXTech (such as Android launcher) can be localized into many languages, you may help to translate them using WebLate platform: https://hosted.weblate.org/projects/thextech/ ## Used software * GCC, MinGW-w64, and Clang - main compilers used during the development of the game. The MSVC is used sometimes which is used for Windows builds for ARM64 processors. * [Qt Creator](https://www.qt.io/product/development-tools) - IDE from the Qt toolkit, mainly used during the development. * [JetBrans CLion](https://www.jetbrains.com/ru-ru/clion/) - Intelliji Idea based C/C++ IDE, free for the [Open Source development](https://www.jetbrains.com/ru-ru/community/opensource/). * [PVS-Studio](https://pvs-studio.com/pvs-studio/?utm_source=website&utm_medium=github&utm_campaign=open_source) - static analyzer for C, C++, C#, and Java code. Is used for periodical checking of the code for possible bugs. It's free for the [Open Source development](https://pvs-studio.com/en/order/open-source-license/). ================================================ FILE: android-project/.gitignore ================================================ # Folders /.idea/* /.gradle/* /build/* /local.properties /SDL-default/* /thextech/jni/SDL/* /thextech/build/* /thextech/release/* /thextech/apk/release/* /thextech/fdroid/release/* /thextech/debug/* /thextech/.cxx/* .externalNativeBuild /thextech/src/main/assets/languages/* # Files *.iml *.apk # Key files *.jks *.keystore ================================================ FILE: android-project/build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { mavenCentral() google() } dependencies { classpath 'com.android.tools.build:gradle:8.13.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { google() mavenCentral() } } tasks.register('clean', Delete) { delete rootProject.layout.buildDirectory } ================================================ FILE: android-project/build_init.sh ================================================ #!/bin/bash # Remove old version if presented if [[ -d SDL-default ]]; then rm -Rf SDL-default fi # Unpack recent SDL2 tarball if [[ ! -d SDL-default ]]; then wget https://github.com/WohlSoft/PGE-Project/raw/master/_Libs/_sources/SDL-default.tar.gz tar -xf SDL-default.tar.gz rm SDL-default.tar.gz fi if [[ ! -d thextech/jni/SDL ]]; then mkdir thextech/jni/SDL fi # Make a link for SDL2 sources if [[ ! -e thextech/jni/SDL/src ]]; then ln -s ../../../SDL-default/src thextech/jni/SDL/src fi # Make a link for SDL2 includes if [[ ! -e thextech/jni/SDL/include ]]; then ln -s ../../../SDL-default/src thextech/jni/SDL/include fi # Remove old SDL2 Java files rm -f thextech/src/main/java/org/libsdl/app/*.java # Copy SDL2 Java files cp SDL-default/android-project/app/src/main/java/org/libsdl/app/*.java thextech/src/main/java/org/libsdl/app # Copy Android NDK makefile cp SDL-default/Android.mk thextech/jni/SDL echo "Done!" ================================================ FILE: android-project/gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionSha256Sum=ed1a8d686605fd7c23bdf62c7fc7add1c5b23b2bbc3721e661934ef4a4911d7c distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: android-project/gradle.properties ================================================ # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. android.enableJetifier=false android.nonFinalResIds=true android.nonTransitiveRClass=true android.useAndroidX=true org.gradle.jvmargs=-Xmx1536m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true ================================================ FILE: android-project/gradlew ================================================ #!/bin/sh # # Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # SPDX-License-Identifier: Apache-2.0 # ############################################################################## # # Gradle start up script for POSIX generated by Gradle. # # Important for running: # # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is # noncompliant, but you have some other compliant shell such as ksh or # bash, then to run this script, type that shell name before the whole # command line, like: # # ksh Gradle # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», # «${var#prefix}», «${var%suffix}», and «$( cmd )»; # * compound commands having a testable exit status, especially «case»; # * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # # (2) This script targets any POSIX shell, so it avoids extensions provided # by Bash, Ksh, etc; in particular arrays are avoided. # # The "traditional" practice of packing multiple parameters into a # space-separated string is a well documented source of bugs and security # problems, so this is (mostly) avoided, by progressively accumulating # options in "$@", and eventually passing that to Java. # # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; # see the in-line comments for details. # # There are tweaks for specific operating systems such as AIX, CygWin, # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. # ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link app_path=$0 # Need this for daisy-chained symlinks. while APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path [ -h "$app_path" ] do ls=$( ls -ld "$app_path" ) link=${ls#*' -> '} case $link in #( /*) app_path=$link ;; #( *) app_path=$APP_HOME$link ;; esac done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s ' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum warn () { echo "$*" } >&2 die () { echo echo "$*" echo exit 1 } >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "$( uname )" in #( CYGWIN* ) cygwin=true ;; #( Darwin* ) darwin=true ;; #( MSYS* | MINGW* ) msys=true ;; #( NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD=$JAVA_HOME/jre/sh/java else JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD=java if ! command -v java >/dev/null 2>&1 then die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi # Collect all arguments for the java command, stacking in reverse order: # * args from the command line # * the main class name # * -classpath # * -D...appname settings # * --module-path (only if needed) # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) # Now convert the arguments - kludge to limit ourselves to /bin/sh for arg do if case $arg in #( -*) false ;; # don't mess with options #( /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath [ -e "$t" ] ;; #( *) false ;; esac then arg=$( cygpath --path --ignore --mixed "$arg" ) fi # Roll the args list around exactly as many times as the number of # args, so each arg winds up back in the position where it started, but # possibly modified. # # NB: a `for` loop captures its iteration list before it begins, so # changing the positional parameters here affects neither the number of # iterations, nor the values presented in `arg`. shift # remove old arg set -- "$@" "$arg" # push replacement arg done fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ org.gradle.wrapper.GradleWrapperMain \ "$@" # Stop when "xargs" is not available. if ! command -v xargs >/dev/null 2>&1 then die "xargs is not available" fi # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. # # In Bash we could simply go: # # readarray ARGS < <( xargs -n1 <<<"$var" ) && # set -- "${ARGS[@]}" "$@" # # but POSIX shell has neither arrays nor command substitution, so instead we # post-process each arg (as a line of input to sed) to backslash-escape any # character that might be a shell metacharacter, then use eval to reverse # that process (while maintaining the separation between arguments), and wrap # the whole thing up as a single "set" statement. # # This will of course break if any of these variables contains a newline or # an unmatched quote. # eval "set -- $( printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | xargs -n1 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | tr '\n' ' ' )" '"$@"' exec "$JAVACMD" "$@" ================================================ FILE: android-project/gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @rem SPDX-License-Identifier: Apache-2.0 @rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute echo. 1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. 1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! set EXIT_CODE=%ERRORLEVEL% if %EXIT_CODE% equ 0 set EXIT_CODE=1 if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: android-project/settings.gradle ================================================ include ':thextech' ================================================ FILE: android-project/thextech/build.gradle ================================================ def buildAsLibrary = project.hasProperty('BUILD_AS_LIBRARY'); def buildAsApplication = !buildAsLibrary if (buildAsApplication) { apply plugin: 'com.android.application' } else { apply plugin: 'com.android.library' } def release_store_password = System.env.RELEASE_STORE_PASSWORD def release_key_password = System.env.RELEASE_KEY_PASSWORD def release_key_alias = System.env.RELEASE_KEY_ALIAS def pin_alias = System.env.PIN_ALIAS def db_pass_alias = System.env.DB_PASS_ALIAS def getGitHash() { def gitExec = providers.exec { commandLine('git', 'rev-parse', '--short', 'HEAD') }.standardOutput.asText.get() return gitExec.trim() } def getGitBranch() { def gitExec = providers.exec { commandLine('git', 'rev-parse', '--abbrev-ref', 'HEAD') }.standardOutput.asText.get() return gitExec.trim() } def parseVersion(verNum) { def cmakeVersion = file('../../version.cmake') if (cmakeVersion.canRead()) { def input = new FileInputStream(cmakeVersion) def rawVersion = input.getText() def verMajorEx = ~/(?m)THEXTECH_VERSION_1 (\d+)/ def verMajorM = verMajorEx.matcher(rawVersion) verNum[0] = verMajorM[0][1].toInteger() def verMinorEx = ~/(?m)THEXTECH_VERSION_2 (\d+)/ def verMinorM = verMinorEx.matcher(rawVersion) verNum[1] = verMinorM[0][1].toInteger() def verRevisionEx = ~/(?m)THEXTECH_VERSION_3 (\d+)/ def verRevisionM = verRevisionEx.matcher(rawVersion) verNum[2] = verRevisionM[0][1].toInteger() def verPatchEx = ~/(?m)THEXTECH_VERSION_4 (\d+)/ def verPatchM = verPatchEx.matcher(rawVersion) verNum[3] = verPatchM[0][1].toInteger() def verRelEx = ~/(?m)THEXTECH_VERSION_REL "(-?\w*)"/ def verRelM = verRelEx.matcher(rawVersion) verNum[5] = verRelM[0][1] return verNum } else { throw new GradleException("Can't read ../../version.cmake file") } } def cmakeToVersionCode(verNum) { def ret = "" ret += verNum[0].toString() ret += "." + verNum[1].toString() if (verNum[2] > 0 || verNum[3] > 0) { ret += "." + verNum[2].toString() if (verNum[3] > 0) { ret += "." + verNum[3].toString() } } if (verNum[5] != "") { ret += verNum[5] } ret += " #" + getGitHash() return ret } static def cmakeToClearVersionCode(verNum) { def ret = "" ret += verNum[0].toString() ret += "." + verNum[1].toString() if (verNum[2] > 0 || verNum[3] > 0) { ret += "." + verNum[2].toString() if (verNum[3] > 0) { ret += "." + verNum[3].toString() } } if (verNum[5] != "") { ret += verNum[5] } return ret } def verNum = [ 0: 0, // major 1: 0, // minor 2: 0, // revision 3: 0, // patch 4: 0, // CI build number 5: "" // release type ] parseVersion(verNum) def xTechBranchOrig = getGitBranch() def xTechBranch = xTechBranchOrig.replace('.', '').replace('-', '') def xTechVersionString = cmakeToVersionCode(verNum) def xTechVersionStringClear = cmakeToClearVersionCode(verNum) def xTechVersionType = verNum[5] def xTechVersionNumber = (verNum[0] * 1000000) + (verNum[1] * 10000) + (verNum[2] * 100) + verNum[3] println "Project version: " + xTechVersionString + " (Clear version: " + xTechVersionStringClear + ", version code: " + xTechVersionNumber.toString() android { compileSdkVersion 36 // Note: The NDK 23 is SIGNIFICANTLY important, because of the support for non-NEON and older Android versions // The NDK 24 removes the support for Android 4.1, 4.2, and 4.3. // See details: https://github.com/android/ndk/wiki/Changelog-r24 ndkVersion = "23.2.8568313" buildFeatures { buildConfig = true } dependenciesInfo { // Disables dependency metadata when building APKs. includeInApk = false // Disables dependency metadata when building Android App Bundles. includeInBundle = false } defaultConfig { if (buildAsApplication) { applicationId "ru.wohlsoft.thextech" } minSdkVersion 16 targetSdkVersion 36 versionName xTechVersionString versionCode xTechVersionNumber externalNativeBuild { cmake { abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64" arguments "-DANDROID_PLATFORM=16", "-DANDROID_STL=c++_static" } } ndk{ abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64" } } signingConfigs { releaseci { storeFile file("../release-key.jks") storePassword = release_store_password keyAlias = release_key_alias keyPassword = release_key_password } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' if(xTechVersionType == "-dev") { applicationIdSuffix = '.dev.' + xTechBranch android.sourceSets.release.res.srcDirs += project.projectDir.toString() + '/icon/devel' manifestPlaceholders.dstAppName = "TheXTech DEV [" + xTechBranchOrig + "]" } else { android.sourceSets.release.res.srcDirs += project.projectDir.toString() + '/icon/main' manifestPlaceholders.dstAppName = "@string/app_name" } } releaseci { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' buildConfigField "String", "PIN_ALIAS", "\"$pin_alias\"" buildConfigField "String", "DB_PASS_ALIAS", "\"$db_pass_alias\"" signingConfig = signingConfigs.releaseci if(xTechVersionType == "-dev") { applicationIdSuffix = '.dev.' + xTechBranch android.sourceSets.releaseci.res.srcDirs += project.projectDir.toString() + '/icon/devel' manifestPlaceholders.dstAppName = "TheXTech DEV [" + xTechBranchOrig + "]" } else { android.sourceSets.releaseci.res.srcDirs += project.projectDir.toString() + '/icon/main' manifestPlaceholders.dstAppName = "@string/app_name" } } debug { applicationIdSuffix '.debug' versionNameSuffix '-DEVDEBUG' manifestPlaceholders.dstAppName = "TheXTech [DEBUG]" debuggable true android.sourceSets.debug.res.srcDirs += project.projectDir.toString() + '/icon/debug' } } flavorDimensions = ["dist"] productFlavors { apk { dimension "dist" isDefault = true } fdroid { dimension "dist" externalNativeBuild { cmake { arguments += ["-DTHEXTECH_NO_BUILD_DATE=ON", "-DFDROID_BUILD=TRUE", "-DOVERRIDE_GIT_BRANCH=\"\"", "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"] } } versionName = xTechVersionStringClear if(project.hasProperty('applicationIdSuffix')) { applicationIdSuffix += '.fdroid' } else { applicationIdSuffix = '.fdroid' } } } println "Resource paths per config:" println "Main: " + android.sourceSets.main.res.srcDirs println "Debug: " + android.sourceSets.debug.res.srcDirs println "Release: " + android.sourceSets.release.res.srcDirs println "Release-CI: " + android.sourceSets.releaseci.res.srcDirs lint { abortOnError = false } namespace = 'ru.wohlsoft.thextech' if (!project.hasProperty('EXCLUDE_NATIVE_LIBS')) { sourceSets { main { jniLibs.srcDirs += "jniLibs" } debug { jniLibs.srcDirs += "jniLibs/debug" } release { jniLibs.srcDirs += ["jniLibs/release", "jniLibs/minsizerel"] } releaseci { jniLibs.srcDirs += ["jniLibs/release", "jniLibs/minsizerel"] } } externalNativeBuild { cmake { path "../../CMakeLists.txt" } } } if (buildAsLibrary) { libraryVariants.all { variant -> variant.outputs.each { output -> def outputFile = output.outputFile if (outputFile != null && outputFile.name.endsWith(".aar")) { def fileName = "ru.wohlsoft.thextech.aar"; output.outputFile = new File(outputFile.parent, fileName); } } } } } dependencies { implementation fileTree(include: ['*.jar'], dir: 'jniLibs') //noinspection GradleDependency: the version 1.6.1 is last that support minSDK 16, newer version will require MinSDK 21 implementation 'androidx.appcompat:appcompat:1.6.1' //noinspection GradleDependency: the version 2.1.4 is last that support minSDK 16, newer version will require MinSDK 21 implementation 'androidx.constraintlayout:constraintlayout:2.1.4' def lifecycle_version = "2.6.2" //noinspection GradleDependency: the version 2.6.2 is last that support minSDK 16, newer version will require MinSDK 19 implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" //noinspection GradleDependency implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" //noinspection GradleDependency: the version 1.11.0 is last that support minSDK 16, newer version will require MinSDK 19 implementation 'com.google.android.material:material:1.11.0' implementation 'androidx.preference:preference:1.2.1' // constraints { // // Workaround to fix the build: https://dev.to/retyui/fix-react-native-android-builds-duplicate-class-kotlincollections-found-in-modules-jetified-kotlin-stdlib-180-2ca7 // implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0") { // because("kotlin-stdlib-jdk7 is now a part of kotlin-stdlib") // } // implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0") { // because("kotlin-stdlib-jdk8 is now a part of kotlin-stdlib") // } // } } ================================================ FILE: android-project/thextech/icon/debug/mipmap-anydpi-v26/ic_launcher.xml ================================================ ================================================ FILE: android-project/thextech/icon/debug/mipmap-anydpi-v26/ic_launcher_round.xml ================================================ ================================================ FILE: android-project/thextech/icon/debug/values/ic_launcher_background.xml ================================================ #C1A4A7 ================================================ FILE: android-project/thextech/icon/devel/mipmap-anydpi-v26/ic_launcher.xml ================================================ ================================================ FILE: android-project/thextech/icon/devel/mipmap-anydpi-v26/ic_launcher_round.xml ================================================ ================================================ FILE: android-project/thextech/icon/devel/values/ic_launcher_background.xml ================================================ #BF8054 ================================================ FILE: android-project/thextech/icon/main/mipmap-anydpi-v26/ic_launcher.xml ================================================ ================================================ FILE: android-project/thextech/icon/main/mipmap-anydpi-v26/ic_launcher_round.xml ================================================ ================================================ FILE: android-project/thextech/icon/main/values/ic_launcher_background.xml ================================================ #696F91 ================================================ FILE: android-project/thextech/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in [sdk]/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: android-project/thextech/src/main/AndroidManifest.xml ================================================ ================================================ FILE: android-project/thextech/src/main/assets/buttons/Credits.txt ================================================ Button pictures made by ZeZeinzer (Discord BlueBoi#8980) Additions and mods by 0lhi and Wohlstand ================================================ FILE: android-project/thextech/src/main/generate_patternPath.sh ================================================ #!/bin/bash for q in lvl LVL wld WLD lvlx LVLX wldx WLDX; do echo " " echo " " echo " " echo " " echo " " echo " " echo " " echo "" done ================================================ FILE: android-project/thextech/src/main/java/javautil/FileUtils.java ================================================ package javautil; import android.annotation.SuppressLint; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.provider.DocumentsContract; import android.provider.MediaStore; import android.provider.OpenableColumns; import android.text.TextUtils; import android.util.Log; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.util.Arrays; import java.util.List; import java.util.UUID; public class FileUtils { public static String FALLBACK_COPY_FOLDER = "upload_part"; private static final String TAG = "FileUtils"; private static Uri contentUri = null; private boolean m_wasCopiedToInternal = false; Context context; public FileUtils(Context context) { this.context = context; } public boolean isInternalCopy() { return m_wasCopiedToInternal; } @SuppressLint("NewApi") public String getPath(final Uri uri) { // check here to KITKAT or new version final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; String selection = null; String[] selectionArgs = null; // DocumentProvider if (isKitKat) { // ExternalStorageProvider if (isExternalStorageDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; String fullPath = getPathFromExtSD(split); if (fullPath == null || !fileExists(fullPath)) { Log.d(TAG, "Copy files as a fallback"); fullPath = copyFileToInternalStorage(uri, FALLBACK_COPY_FOLDER); } else m_wasCopiedToInternal = false; if (!fullPath.equals("")) return fullPath; else return null; } // DownloadsProvider if (isDownloadsDocument(uri)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { final String id; Cursor cursor = null; try { cursor = context.getContentResolver().query(uri, new String[] { MediaStore.MediaColumns.DISPLAY_NAME }, null, null, null); if (cursor != null && cursor.moveToFirst()) { String fileName = cursor.getString(0); String path = Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName; if (!TextUtils.isEmpty(path)) { m_wasCopiedToInternal = false; return path; } } } finally { if (cursor != null) cursor.close(); } id = DocumentsContract.getDocumentId(uri); if (!TextUtils.isEmpty(id)) { if (id.startsWith("raw:")) return id.replaceFirst("raw:", ""); String[] contentUriPrefixesToTry = new String[] { "content://downloads/public_downloads", "content://downloads/my_downloads" }; for (String contentUriPrefix : contentUriPrefixesToTry) { try { final Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.parseLong(id)); return getDataColumn(context, contentUri, null, null); } catch (NumberFormatException e) { //In Android 8 and Android P the id is not a number String ret = uri.getPath(); assert(ret != null); return ret.replaceFirst("^/document/raw:", "").replaceFirst("^raw:", ""); } } } } else { final String id = DocumentsContract.getDocumentId(uri); if (id.startsWith("raw:")) { m_wasCopiedToInternal = true; return id.replaceFirst("raw:", ""); } try { contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.parseLong(id)); } catch (NumberFormatException e) { e.printStackTrace(); } if (contentUri != null) return getDataColumn(context, contentUri, null, null); } } // MediaProvider if (isMediaDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; Log.d(TAG, "MEDIA DOCUMENT TYPE: " + type); Uri contentUri = null; if ("image".equals(type)) contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; else if ("video".equals(type)) contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; else if ("audio".equals(type)) contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; else if ("document".equals(type)) contentUri = MediaStore.Files.getContentUri(MediaStore.getVolumeName(uri)); selection = "_id=?"; selectionArgs = new String[] { split[1] }; return getDataColumn(context, contentUri, selection, selectionArgs); } if (isGoogleDriveUri(uri)) return getDriveFilePath(uri); if (isWhatsAppFile(uri)) return getFilePathForWhatsApp(uri); if ("content".equalsIgnoreCase(uri.getScheme())) { if (isGooglePhotosUri(uri)) { m_wasCopiedToInternal = false; return uri.getLastPathSegment(); } if (isGoogleDriveUri(uri) || isSamsungMyFilesAppFile(uri)) return getDriveFilePath(uri); // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) if (Build.VERSION.SDK_INT >= 30 && !Environment.isExternalStorageManager()) { // return getFilePathFromURI(context,uri); return copyFileToInternalStorage(uri, FALLBACK_COPY_FOLDER); // return getRealPathFromURI(context,uri); } else { return getDataColumn(context, uri, null, null); } } if ("file".equalsIgnoreCase(uri.getScheme())) { m_wasCopiedToInternal = false; return uri.getPath(); } } else { String scheme = uri.getScheme(); if (isWhatsAppFile(uri)) return getFilePathForWhatsApp(uri); if(scheme == null) return copyFileToInternalStorage(uri, FALLBACK_COPY_FOLDER); if (scheme.equalsIgnoreCase(ContentResolver.SCHEME_FILE)) { String ret = uri.getPath(); Log.d(TAG, "Got URI path from File intent: " + ret); return ret; } else if (scheme.equalsIgnoreCase(ContentResolver.SCHEME_CONTENT)) { String[] projection = { MediaStore.Images.Media.DATA }; Cursor cursor = null; try { cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); assert(cursor != null); int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); if (cursor.moveToFirst()) { String ret = cursor.getString(column_index); cursor.close(); return ret; } cursor.close(); } catch (Exception e) { e.printStackTrace(); } } } return copyFileToInternalStorage(uri, FALLBACK_COPY_FOLDER); } private static boolean fileExists(String filePath) { File file = new File(filePath); return file.exists(); } private static String getPathFromExtSD(String[] pathData) { final String type = pathData[0]; final String relativePath = File.separator + pathData[1]; String fullPath = ""; Log.d(TAG, "MEDIA EXTSD TYPE: " + type); Log.d(TAG, "Relative path: " + relativePath); // on my Sony devices (4.4.4 & 5.1.1), `type` is a dynamic string // something like "71F8-2C0A", some kind of unique id per storage // don't know any API that can get the root path of that storage based on its id. // // so no "primary" type, but let the check here for other devices if ("primary".equalsIgnoreCase(type)) { fullPath = Environment.getExternalStorageDirectory() + relativePath; if (fileExists(fullPath)) { return fullPath; } } if ("home".equalsIgnoreCase(type)) { fullPath = "/storage/emulated/0/Documents" + relativePath; if (fileExists(fullPath)) { return fullPath; } } // Environment.isExternalStorageRemovable() is `true` for external and internal storage // so we cannot relay on it. // // instead, for each possible path, check if file exists // we'll start with secondary storage as this could be our (physically) removable sd card fullPath = System.getenv("SECONDARY_STORAGE") + relativePath; if (fileExists(fullPath)) { return fullPath; } fullPath = System.getenv("EXTERNAL_STORAGE") + relativePath; if (fileExists(fullPath)) { return fullPath; } return null; } private String getDriveFilePath(Uri uri) { //Uri returnUri = uri; Cursor returnCursor = context.getContentResolver().query(uri, null, null, null, null); assert(returnCursor != null); /* * Get the column indexes of the data in the Cursor, * * move to the first row in the Cursor, get the data, * * and display it. * */ int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); // int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE); returnCursor.moveToFirst(); String name = (returnCursor.getString(nameIndex)); // String size = (Long.toString(returnCursor.getLong(sizeIndex))); File file = new File(context.getCacheDir(), name); try { InputStream inputStream = context.getContentResolver().openInputStream(uri); assert(inputStream != null); FileOutputStream outputStream = new FileOutputStream(file); int read = 0; int maxBufferSize = 1024 * 1024; int bytesAvailable = inputStream.available(); //int bufferSize = 1024; int bufferSize = Math.min(bytesAvailable, maxBufferSize); final byte[] buffers = new byte[bufferSize]; while ((read = inputStream.read(buffers)) != -1) outputStream.write(buffers, 0, read); Log.e(TAG, "Size " + file.length()); inputStream.close(); outputStream.close(); Log.e(TAG, "Path " + file.getPath()); Log.e(TAG, "Size " + file.length()); } catch (Exception e) { String err = e.getMessage(); assert(err != null); Log.e(TAG, err); } returnCursor.close(); m_wasCopiedToInternal = true; return file.getPath(); } /*** * Used for Android Q+ * @param uri Input URI file * @param newDirName if you want to create a directory, you can set this variable * @return path to copied file */ private String copyFileToInternalStorage(Uri uri, String newDirName) { Cursor returnCursor = context.getContentResolver().query(uri, new String[] { OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE }, null, null, null); assert(returnCursor != null); String name; /* * Get the column indexes of the data in the Cursor, * * move to the first row in the Cursor, get the data, * * and display it. * */ int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); // int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE); returnCursor.moveToFirst(); try { name = (returnCursor.getString(nameIndex)); } catch (Exception e) { if(e.getMessage() != null) Log.e(TAG, e.getMessage()); else Log.e(TAG, ""); name = "unknown.lvlx"; } // String size = (Long.toString(returnCursor.getLong(sizeIndex))); File output; if (!newDirName.equals("")) { String random_collision_avoidance = UUID.randomUUID().toString(); File dir = new File(context.getFilesDir() + File.separator + newDirName + File.separator + random_collision_avoidance); if (!dir.exists()) { if(!dir.mkdirs()) Log.e(TAG, "Can't create directory: " + dir.getPath()); } output = new File(context.getFilesDir() + File.separator + newDirName + File.separator + random_collision_avoidance + File.separator + name); } else { output = new File(context.getFilesDir() + File.separator + name); } try { InputStream inputStream = context.getContentResolver().openInputStream(uri); if(inputStream == null) throw new Exception("InputStream is NULL"); FileOutputStream outputStream = new FileOutputStream(output); int read = 0; int bufferSize = 1024; final byte[] buffers = new byte[bufferSize]; while ((read = inputStream.read(buffers)) != -1) { outputStream.write(buffers, 0, read); } inputStream.close(); outputStream.close(); } catch (Exception e) { if(e.getMessage() != null) Log.e(TAG, e.getMessage()); else Log.e(TAG, ""); } returnCursor.close(); m_wasCopiedToInternal = true; return output.getPath(); } private String getFilePathForWhatsApp(Uri uri) { return copyFileToInternalStorage(uri, "whatsapp"); } private String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { Cursor cursor = null; final String column = "_data"; final String[] projection = { column }; try { cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); if (cursor != null && cursor.moveToFirst()) { List columns = Arrays.asList(cursor.getColumnNames()); Log.d(TAG, "Available columns: " + String.join(", ", columns)); final int index = cursor.getColumnIndexOrThrow(column); m_wasCopiedToInternal = false; return cursor.getString(index); } } catch(Exception e) { if(e.getMessage() != null) Log.e(TAG, e.getMessage()); else Log.e(TAG, ""); return copyFileToInternalStorage(uri, FALLBACK_COPY_FOLDER); } finally { if(cursor != null) cursor.close(); } return copyFileToInternalStorage(uri, FALLBACK_COPY_FOLDER); } private static boolean isExternalStorageDocument(Uri uri) { return "com.android.externalstorage.documents".equals(uri.getAuthority()); } private static boolean isDownloadsDocument(Uri uri) { return "com.android.providers.downloads.documents".equals(uri.getAuthority()); } private boolean isMediaDocument(Uri uri) { return "com.android.providers.media.documents".equals(uri.getAuthority()); } private boolean isGooglePhotosUri(Uri uri) { return "com.google.android.apps.photos.content".equals(uri.getAuthority()); } public boolean isWhatsAppFile(Uri uri) { return "com.whatsapp.provider.media".equals(uri.getAuthority()); } public boolean isSamsungMyFilesAppFile(Uri uri) { return "com.sec.android.app.myfiles.FileProvider".equals(uri.getAuthority()); } private boolean isGoogleDriveUri(Uri uri) { return "com.google.android.apps.docs.storage".equals(uri.getAuthority()) || "com.google.android.apps.docs.storage.legacy".equals(uri.getAuthority()); } } ================================================ FILE: android-project/thextech/src/main/java/javautil/README.md ================================================ # Android Filename Picker using Java A library for getting filenames in an Android device Original got from the repository: https://github.com/saparkhid/AndroidFileNamePicker Referred at: https://stackoverflow.com/a/60642994 ================================================ FILE: android-project/thextech/src/main/java/org/libsdl/app/HIDDevice.java ================================================ package org.libsdl.app; import android.hardware.usb.UsbDevice; interface HIDDevice { public int getId(); public int getVendorId(); public int getProductId(); public String getSerialNumber(); public int getVersion(); public String getManufacturerName(); public String getProductName(); public UsbDevice getDevice(); public boolean open(); public int sendFeatureReport(byte[] report); public int sendOutputReport(byte[] report); public boolean getFeatureReport(byte[] report); public void setFrozen(boolean frozen); public void close(); public void shutdown(); } ================================================ FILE: android-project/thextech/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java ================================================ package org.libsdl.app; import android.content.Context; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothGattService; import android.hardware.usb.UsbDevice; import android.os.Handler; import android.os.Looper; import android.util.Log; import android.os.*; //import com.android.internal.util.HexDump; import java.lang.Runnable; import java.util.Arrays; import java.util.LinkedList; import java.util.UUID; class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice { private static final String TAG = "hidapi"; private HIDDeviceManager mManager; private BluetoothDevice mDevice; private int mDeviceId; private BluetoothGatt mGatt; private boolean mIsRegistered = false; private boolean mIsConnected = false; private boolean mIsChromebook = false; private boolean mIsReconnecting = false; private boolean mFrozen = false; private LinkedList mOperations; GattOperation mCurrentOperation = null; private Handler mHandler; private static final int TRANSPORT_AUTO = 0; private static final int TRANSPORT_BREDR = 1; private static final int TRANSPORT_LE = 2; private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000; static public final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3"); static public final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3"); static public final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3"); static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 }; static class GattOperation { private enum Operation { CHR_READ, CHR_WRITE, ENABLE_NOTIFICATION } Operation mOp; UUID mUuid; byte[] mValue; BluetoothGatt mGatt; boolean mResult = true; private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) { mGatt = gatt; mOp = operation; mUuid = uuid; } private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) { mGatt = gatt; mOp = operation; mUuid = uuid; mValue = value; } public void run() { // This is executed in main thread BluetoothGattCharacteristic chr; switch (mOp) { case CHR_READ: chr = getCharacteristic(mUuid); //Log.v(TAG, "Reading characteristic " + chr.getUuid()); if (!mGatt.readCharacteristic(chr)) { Log.e(TAG, "Unable to read characteristic " + mUuid.toString()); mResult = false; break; } mResult = true; break; case CHR_WRITE: chr = getCharacteristic(mUuid); //Log.v(TAG, "Writing characteristic " + chr.getUuid() + " value=" + HexDump.toHexString(value)); chr.setValue(mValue); if (!mGatt.writeCharacteristic(chr)) { Log.e(TAG, "Unable to write characteristic " + mUuid.toString()); mResult = false; break; } mResult = true; break; case ENABLE_NOTIFICATION: chr = getCharacteristic(mUuid); //Log.v(TAG, "Writing descriptor of " + chr.getUuid()); if (chr != null) { BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); if (cccd != null) { int properties = chr.getProperties(); byte[] value; if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) { value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE; } else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) { value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE; } else { Log.e(TAG, "Unable to start notifications on input characteristic"); mResult = false; return; } mGatt.setCharacteristicNotification(chr, true); cccd.setValue(value); if (!mGatt.writeDescriptor(cccd)) { Log.e(TAG, "Unable to write descriptor " + mUuid.toString()); mResult = false; return; } mResult = true; } } } } public boolean finish() { return mResult; } private BluetoothGattCharacteristic getCharacteristic(UUID uuid) { BluetoothGattService valveService = mGatt.getService(steamControllerService); if (valveService == null) return null; return valveService.getCharacteristic(uuid); } static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) { return new GattOperation(gatt, Operation.CHR_READ, uuid); } static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) { return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value); } static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) { return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid); } } public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) { mManager = manager; mDevice = device; mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier()); mIsRegistered = false; mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management"); mOperations = new LinkedList(); mHandler = new Handler(Looper.getMainLooper()); mGatt = connectGatt(); // final HIDDeviceBLESteamController finalThis = this; // mHandler.postDelayed(new Runnable() { // @Override // public void run() { // finalThis.checkConnectionForChromebookIssue(); // } // }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL); } public String getIdentifier() { return String.format("SteamController.%s", mDevice.getAddress()); } public BluetoothGatt getGatt() { return mGatt; } // Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead // of TRANSPORT_LE. Let's force ourselves to connect low energy. private BluetoothGatt connectGatt(boolean managed) { if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) { try { return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE); } catch (Exception e) { return mDevice.connectGatt(mManager.getContext(), managed, this); } } else { return mDevice.connectGatt(mManager.getContext(), managed, this); } } private BluetoothGatt connectGatt() { return connectGatt(false); } protected int getConnectionState() { Context context = mManager.getContext(); if (context == null) { // We are lacking any context to get our Bluetooth information. We'll just assume disconnected. return BluetoothProfile.STATE_DISCONNECTED; } BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE); if (btManager == null) { // This device doesn't support Bluetooth. We should never be here, because how did // we instantiate a device to start with? return BluetoothProfile.STATE_DISCONNECTED; } return btManager.getConnectionState(mDevice, BluetoothProfile.GATT); } public void reconnect() { if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) { mGatt.disconnect(); mGatt = connectGatt(); } } protected void checkConnectionForChromebookIssue() { if (!mIsChromebook) { // We only do this on Chromebooks, because otherwise it's really annoying to just attempt // over and over. return; } int connectionState = getConnectionState(); switch (connectionState) { case BluetoothProfile.STATE_CONNECTED: if (!mIsConnected) { // We are in the Bad Chromebook Place. We can force a disconnect // to try to recover. Log.v(TAG, "Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback. Forcing a reconnect."); mIsReconnecting = true; mGatt.disconnect(); mGatt = connectGatt(false); break; } else if (!isRegistered()) { if (mGatt.getServices().size() > 0) { Log.v(TAG, "Chromebook: We are connected to a controller, but never got our registration. Trying to recover."); probeService(this); } else { Log.v(TAG, "Chromebook: We are connected to a controller, but never discovered services. Trying to recover."); mIsReconnecting = true; mGatt.disconnect(); mGatt = connectGatt(false); break; } } else { Log.v(TAG, "Chromebook: We are connected, and registered. Everything's good!"); return; } break; case BluetoothProfile.STATE_DISCONNECTED: Log.v(TAG, "Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us. Attempting a disconnect/reconnect, but we may not be able to recover."); mIsReconnecting = true; mGatt.disconnect(); mGatt = connectGatt(false); break; case BluetoothProfile.STATE_CONNECTING: Log.v(TAG, "Chromebook: We're still trying to connect. Waiting a bit longer."); break; } final HIDDeviceBLESteamController finalThis = this; mHandler.postDelayed(new Runnable() { @Override public void run() { finalThis.checkConnectionForChromebookIssue(); } }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL); } private boolean isRegistered() { return mIsRegistered; } private void setRegistered() { mIsRegistered = true; } private boolean probeService(HIDDeviceBLESteamController controller) { if (isRegistered()) { return true; } if (!mIsConnected) { return false; } Log.v(TAG, "probeService controller=" + controller); for (BluetoothGattService service : mGatt.getServices()) { if (service.getUuid().equals(steamControllerService)) { Log.v(TAG, "Found Valve steam controller service " + service.getUuid()); for (BluetoothGattCharacteristic chr : service.getCharacteristics()) { if (chr.getUuid().equals(inputCharacteristic)) { Log.v(TAG, "Found input characteristic"); // Start notifications BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); if (cccd != null) { enableNotification(chr.getUuid()); } } } return true; } } if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) { Log.e(TAG, "Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us."); mIsConnected = false; mIsReconnecting = true; mGatt.disconnect(); mGatt = connectGatt(false); } return false; } ////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////// private void finishCurrentGattOperation() { GattOperation op = null; synchronized (mOperations) { if (mCurrentOperation != null) { op = mCurrentOperation; mCurrentOperation = null; } } if (op != null) { boolean result = op.finish(); // TODO: Maybe in main thread as well? // Our operation failed, let's add it back to the beginning of our queue. if (!result) { mOperations.addFirst(op); } } executeNextGattOperation(); } private void executeNextGattOperation() { synchronized (mOperations) { if (mCurrentOperation != null) return; if (mOperations.isEmpty()) return; mCurrentOperation = mOperations.removeFirst(); } // Run in main thread mHandler.post(new Runnable() { @Override public void run() { synchronized (mOperations) { if (mCurrentOperation == null) { Log.e(TAG, "Current operation null in executor?"); return; } mCurrentOperation.run(); // now wait for the GATT callback and when it comes, finish this operation } } }); } private void queueGattOperation(GattOperation op) { synchronized (mOperations) { mOperations.add(op); } executeNextGattOperation(); } private void enableNotification(UUID chrUuid) { GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid); queueGattOperation(op); } public void writeCharacteristic(UUID uuid, byte[] value) { GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value); queueGattOperation(op); } public void readCharacteristic(UUID uuid) { GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid); queueGattOperation(op); } ////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////// BluetoothGattCallback overridden methods ////////////////////////////////////////////////////////////////////////////////////////////////////// public void onConnectionStateChange(BluetoothGatt g, int status, int newState) { //Log.v(TAG, "onConnectionStateChange status=" + status + " newState=" + newState); mIsReconnecting = false; if (newState == 2) { mIsConnected = true; // Run directly, without GattOperation if (!isRegistered()) { mHandler.post(new Runnable() { @Override public void run() { mGatt.discoverServices(); } }); } } else if (newState == 0) { mIsConnected = false; } // Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent. } public void onServicesDiscovered(BluetoothGatt gatt, int status) { //Log.v(TAG, "onServicesDiscovered status=" + status); if (status == 0) { if (gatt.getServices().size() == 0) { Log.v(TAG, "onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack."); mIsReconnecting = true; mIsConnected = false; gatt.disconnect(); mGatt = connectGatt(false); } else { probeService(this); } } } public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { //Log.v(TAG, "onCharacteristicRead status=" + status + " uuid=" + characteristic.getUuid()); if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) { mManager.HIDDeviceFeatureReport(getId(), characteristic.getValue()); } finishCurrentGattOperation(); } public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { //Log.v(TAG, "onCharacteristicWrite status=" + status + " uuid=" + characteristic.getUuid()); if (characteristic.getUuid().equals(reportCharacteristic)) { // Only register controller with the native side once it has been fully configured if (!isRegistered()) { Log.v(TAG, "Registering Steam Controller with ID: " + getId()); mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0, 0, 0, 0); setRegistered(); } } finishCurrentGattOperation(); } public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { // Enable this for verbose logging of controller input reports //Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue())); if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) { mManager.HIDDeviceInputReport(getId(), characteristic.getValue()); } } public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { //Log.v(TAG, "onDescriptorRead status=" + status); } public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { BluetoothGattCharacteristic chr = descriptor.getCharacteristic(); //Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid()); if (chr.getUuid().equals(inputCharacteristic)) { boolean hasWrittenInputDescriptor = true; BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic); if (reportChr != null) { Log.v(TAG, "Writing report characteristic to enter valve mode"); reportChr.setValue(enterValveMode); gatt.writeCharacteristic(reportChr); } } finishCurrentGattOperation(); } public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { //Log.v(TAG, "onReliableWriteCompleted status=" + status); } public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { //Log.v(TAG, "onReadRemoteRssi status=" + status); } public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { //Log.v(TAG, "onMtuChanged status=" + status); } ////////////////////////////////////////////////////////////////////////////////////////////////////// //////// Public API ////////////////////////////////////////////////////////////////////////////////////////////////////// @Override public int getId() { return mDeviceId; } @Override public int getVendorId() { // Valve Corporation final int VALVE_USB_VID = 0x28DE; return VALVE_USB_VID; } @Override public int getProductId() { // We don't have an easy way to query from the Bluetooth device, but we know what it is final int D0G_BLE2_PID = 0x1106; return D0G_BLE2_PID; } @Override public String getSerialNumber() { // This will be read later via feature report by Steam return "12345"; } @Override public int getVersion() { return 0; } @Override public String getManufacturerName() { return "Valve Corporation"; } @Override public String getProductName() { return "Steam Controller"; } @Override public UsbDevice getDevice() { return null; } @Override public boolean open() { return true; } @Override public int sendFeatureReport(byte[] report) { if (!isRegistered()) { Log.e(TAG, "Attempted sendFeatureReport before Steam Controller is registered!"); if (mIsConnected) { probeService(this); } return -1; } // We need to skip the first byte, as that doesn't go over the air byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1); //Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(actual_report)); writeCharacteristic(reportCharacteristic, actual_report); return report.length; } @Override public int sendOutputReport(byte[] report) { if (!isRegistered()) { Log.e(TAG, "Attempted sendOutputReport before Steam Controller is registered!"); if (mIsConnected) { probeService(this); } return -1; } //Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(report)); writeCharacteristic(reportCharacteristic, report); return report.length; } @Override public boolean getFeatureReport(byte[] report) { if (!isRegistered()) { Log.e(TAG, "Attempted getFeatureReport before Steam Controller is registered!"); if (mIsConnected) { probeService(this); } return false; } //Log.v(TAG, "getFeatureReport"); readCharacteristic(reportCharacteristic); return true; } @Override public void close() { } @Override public void setFrozen(boolean frozen) { mFrozen = frozen; } @Override public void shutdown() { close(); BluetoothGatt g = mGatt; if (g != null) { g.disconnect(); g.close(); mGatt = null; } mManager = null; mIsRegistered = false; mIsConnected = false; mOperations.clear(); } } ================================================ FILE: android-project/thextech/src/main/java/org/libsdl/app/HIDDeviceManager.java ================================================ package org.libsdl.app; import android.app.Activity; import android.app.AlertDialog; import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.os.Build; import android.util.Log; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.hardware.usb.*; import android.os.Handler; import android.os.Looper; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; public class HIDDeviceManager { private static final String TAG = "hidapi"; private static final String ACTION_USB_PERMISSION = "org.libsdl.app.USB_PERMISSION"; private static HIDDeviceManager sManager; private static int sManagerRefCount = 0; public static HIDDeviceManager acquire(Context context) { if (sManagerRefCount == 0) { sManager = new HIDDeviceManager(context); } ++sManagerRefCount; return sManager; } public static void release(HIDDeviceManager manager) { if (manager == sManager) { --sManagerRefCount; if (sManagerRefCount == 0) { sManager.close(); sManager = null; } } } private Context mContext; private HashMap mDevicesById = new HashMap(); private HashMap mBluetoothDevices = new HashMap(); private int mNextDeviceId = 0; private SharedPreferences mSharedPreferences = null; private boolean mIsChromebook = false; private UsbManager mUsbManager; private Handler mHandler; private BluetoothManager mBluetoothManager; private List mLastBluetoothDevices; private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) { UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); handleUsbDeviceAttached(usbDevice); } else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) { UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); handleUsbDeviceDetached(usbDevice); } else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) { UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)); } } }; private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); // Bluetooth device was connected. If it was a Steam Controller, handle it if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); Log.d(TAG, "Bluetooth device connected: " + device); if (isSteamController(device)) { connectBluetoothDevice(device); } } // Bluetooth device was disconnected, remove from controller manager (if any) if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); Log.d(TAG, "Bluetooth device disconnected: " + device); disconnectBluetoothDevice(device); } } }; private HIDDeviceManager(final Context context) { mContext = context; HIDDeviceRegisterCallback(); mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE); mIsChromebook = mContext.getPackageManager().hasSystemFeature("org.chromium.arc.device_management"); // if (shouldClear) { // SharedPreferences.Editor spedit = mSharedPreferences.edit(); // spedit.clear(); // spedit.commit(); // } // else { mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0); } } public Context getContext() { return mContext; } public int getDeviceIDForIdentifier(String identifier) { SharedPreferences.Editor spedit = mSharedPreferences.edit(); int result = mSharedPreferences.getInt(identifier, 0); if (result == 0) { result = mNextDeviceId++; spedit.putInt("next_device_id", mNextDeviceId); } spedit.putInt(identifier, result); spedit.commit(); return result; } private void initializeUSB() { mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE); if (mUsbManager == null) { return; } /* // Logging for (UsbDevice device : mUsbManager.getDeviceList().values()) { Log.i(TAG,"Path: " + device.getDeviceName()); Log.i(TAG,"Manufacturer: " + device.getManufacturerName()); Log.i(TAG,"Product: " + device.getProductName()); Log.i(TAG,"ID: " + device.getDeviceId()); Log.i(TAG,"Class: " + device.getDeviceClass()); Log.i(TAG,"Protocol: " + device.getDeviceProtocol()); Log.i(TAG,"Vendor ID " + device.getVendorId()); Log.i(TAG,"Product ID: " + device.getProductId()); Log.i(TAG,"Interface count: " + device.getInterfaceCount()); Log.i(TAG,"---------------------------------------"); // Get interface details for (int index = 0; index < device.getInterfaceCount(); index++) { UsbInterface mUsbInterface = device.getInterface(index); Log.i(TAG," ***** *****"); Log.i(TAG," Interface index: " + index); Log.i(TAG," Interface ID: " + mUsbInterface.getId()); Log.i(TAG," Interface class: " + mUsbInterface.getInterfaceClass()); Log.i(TAG," Interface subclass: " + mUsbInterface.getInterfaceSubclass()); Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol()); Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount()); // Get endpoint details for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++) { UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi); Log.i(TAG," ++++ ++++ ++++"); Log.i(TAG," Endpoint index: " + epi); Log.i(TAG," Attributes: " + mEndpoint.getAttributes()); Log.i(TAG," Direction: " + mEndpoint.getDirection()); Log.i(TAG," Number: " + mEndpoint.getEndpointNumber()); Log.i(TAG," Interval: " + mEndpoint.getInterval()); Log.i(TAG," Packet size: " + mEndpoint.getMaxPacketSize()); Log.i(TAG," Type: " + mEndpoint.getType()); } } } Log.i(TAG," No more devices connected."); */ // Register for USB broadcasts and permission completions IntentFilter filter = new IntentFilter(); filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION); mContext.registerReceiver(mUsbBroadcast, filter); for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) { handleUsbDeviceAttached(usbDevice); } } UsbManager getUSBManager() { return mUsbManager; } private void shutdownUSB() { try { mContext.unregisterReceiver(mUsbBroadcast); } catch (Exception e) { // We may not have registered, that's okay } } private boolean isHIDDeviceInterface(UsbDevice usbDevice, UsbInterface usbInterface) { if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) { return true; } if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) { return true; } return false; } private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) { final int XB360_IFACE_SUBCLASS = 93; final int XB360_IFACE_PROTOCOL = 1; // Wired final int XB360W_IFACE_PROTOCOL = 129; // Wireless final int[] SUPPORTED_VENDORS = { 0x0079, // GPD Win 2 0x044f, // Thrustmaster 0x045e, // Microsoft 0x046d, // Logitech 0x056e, // Elecom 0x06a3, // Saitek 0x0738, // Mad Catz 0x07ff, // Mad Catz 0x0e6f, // PDP 0x0f0d, // Hori 0x1038, // SteelSeries 0x11c9, // Nacon 0x12ab, // Unknown 0x1430, // RedOctane 0x146b, // BigBen 0x1532, // Razer Sabertooth 0x15e4, // Numark 0x162e, // Joytech 0x1689, // Razer Onza 0x1949, // Lab126, Inc. 0x1bad, // Harmonix 0x20d6, // PowerA 0x24c6, // PowerA 0x2c22, // Qanba 0x2dc8, // 8BitDo 0x9886, // ASTRO Gaming }; if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC && usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS && (usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL || usbInterface.getInterfaceProtocol() == XB360W_IFACE_PROTOCOL)) { int vendor_id = usbDevice.getVendorId(); for (int supportedVid : SUPPORTED_VENDORS) { if (vendor_id == supportedVid) { return true; } } } return false; } private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) { final int XB1_IFACE_SUBCLASS = 71; final int XB1_IFACE_PROTOCOL = 208; final int[] SUPPORTED_VENDORS = { 0x03f0, // HP 0x044f, // Thrustmaster 0x045e, // Microsoft 0x0738, // Mad Catz 0x0b05, // ASUS 0x0e6f, // PDP 0x0f0d, // Hori 0x10f5, // Turtle Beach 0x1532, // Razer Wildcat 0x20d6, // PowerA 0x24c6, // PowerA 0x2dc8, // 8BitDo 0x2e24, // Hyperkin 0x3537, // GameSir }; if (usbInterface.getId() == 0 && usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC && usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS && usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) { int vendor_id = usbDevice.getVendorId(); for (int supportedVid : SUPPORTED_VENDORS) { if (vendor_id == supportedVid) { return true; } } } return false; } private void handleUsbDeviceAttached(UsbDevice usbDevice) { connectHIDDeviceUSB(usbDevice); } private void handleUsbDeviceDetached(UsbDevice usbDevice) { List devices = new ArrayList(); for (HIDDevice device : mDevicesById.values()) { if (usbDevice.equals(device.getDevice())) { devices.add(device.getId()); } } for (int id : devices) { HIDDevice device = mDevicesById.get(id); mDevicesById.remove(id); device.shutdown(); HIDDeviceDisconnected(id); } } private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) { for (HIDDevice device : mDevicesById.values()) { if (usbDevice.equals(device.getDevice())) { boolean opened = false; if (permission_granted) { opened = device.open(); } HIDDeviceOpenResult(device.getId(), opened); } } } private void connectHIDDeviceUSB(UsbDevice usbDevice) { synchronized (this) { int interface_mask = 0; for (int interface_index = 0; interface_index < usbDevice.getInterfaceCount(); interface_index++) { UsbInterface usbInterface = usbDevice.getInterface(interface_index); if (isHIDDeviceInterface(usbDevice, usbInterface)) { // Check to see if we've already added this interface // This happens with the Xbox Series X controller which has a duplicate interface 0, which is inactive int interface_id = usbInterface.getId(); if ((interface_mask & (1 << interface_id)) != 0) { continue; } interface_mask |= (1 << interface_id); HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_index); int id = device.getId(); mDevicesById.put(id, device); HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), usbInterface.getId(), usbInterface.getInterfaceClass(), usbInterface.getInterfaceSubclass(), usbInterface.getInterfaceProtocol()); } } } } private void initializeBluetooth() { Log.d(TAG, "Initializing Bluetooth"); if (Build.VERSION.SDK_INT >= 31 /* Android 12 */ && mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH_CONNECT, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) { Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH_CONNECT"); return; } if (Build.VERSION.SDK_INT <= 30 /* Android 11.0 (R) */ && mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) { Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH"); return; } if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18 /* Android 4.3 (JELLY_BEAN_MR2) */)) { Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE"); return; } // Find bonded bluetooth controllers and create SteamControllers for them mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE); if (mBluetoothManager == null) { // This device doesn't support Bluetooth. return; } BluetoothAdapter btAdapter = mBluetoothManager.getAdapter(); if (btAdapter == null) { // This device has Bluetooth support in the codebase, but has no available adapters. return; } // Get our bonded devices. for (BluetoothDevice device : btAdapter.getBondedDevices()) { Log.d(TAG, "Bluetooth device available: " + device); if (isSteamController(device)) { connectBluetoothDevice(device); } } // NOTE: These don't work on Chromebooks, to my undying dismay. IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); mContext.registerReceiver(mBluetoothBroadcast, filter); if (mIsChromebook) { mHandler = new Handler(Looper.getMainLooper()); mLastBluetoothDevices = new ArrayList(); // final HIDDeviceManager finalThis = this; // mHandler.postDelayed(new Runnable() { // @Override // public void run() { // finalThis.chromebookConnectionHandler(); // } // }, 5000); } } private void shutdownBluetooth() { try { mContext.unregisterReceiver(mBluetoothBroadcast); } catch (Exception e) { // We may not have registered, that's okay } } // Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly. // This function provides a sort of dummy version of that, watching for changes in the // connected devices and attempting to add controllers as things change. public void chromebookConnectionHandler() { if (!mIsChromebook) { return; } ArrayList disconnected = new ArrayList(); ArrayList connected = new ArrayList(); List currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT); for (BluetoothDevice bluetoothDevice : currentConnected) { if (!mLastBluetoothDevices.contains(bluetoothDevice)) { connected.add(bluetoothDevice); } } for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) { if (!currentConnected.contains(bluetoothDevice)) { disconnected.add(bluetoothDevice); } } mLastBluetoothDevices = currentConnected; for (BluetoothDevice bluetoothDevice : disconnected) { disconnectBluetoothDevice(bluetoothDevice); } for (BluetoothDevice bluetoothDevice : connected) { connectBluetoothDevice(bluetoothDevice); } final HIDDeviceManager finalThis = this; mHandler.postDelayed(new Runnable() { @Override public void run() { finalThis.chromebookConnectionHandler(); } }, 10000); } public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) { Log.v(TAG, "connectBluetoothDevice device=" + bluetoothDevice); synchronized (this) { if (mBluetoothDevices.containsKey(bluetoothDevice)) { Log.v(TAG, "Steam controller with address " + bluetoothDevice + " already exists, attempting reconnect"); HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice); device.reconnect(); return false; } HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice); int id = device.getId(); mBluetoothDevices.put(bluetoothDevice, device); mDevicesById.put(id, device); // The Steam Controller will mark itself connected once initialization is complete } return true; } public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) { synchronized (this) { HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice); if (device == null) return; int id = device.getId(); mBluetoothDevices.remove(bluetoothDevice); mDevicesById.remove(id); device.shutdown(); HIDDeviceDisconnected(id); } } public boolean isSteamController(BluetoothDevice bluetoothDevice) { // Sanity check. If you pass in a null device, by definition it is never a Steam Controller. if (bluetoothDevice == null) { return false; } // If the device has no local name, we really don't want to try an equality check against it. if (bluetoothDevice.getName() == null) { return false; } return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0); } private void close() { shutdownUSB(); shutdownBluetooth(); synchronized (this) { for (HIDDevice device : mDevicesById.values()) { device.shutdown(); } mDevicesById.clear(); mBluetoothDevices.clear(); HIDDeviceReleaseCallback(); } } public void setFrozen(boolean frozen) { synchronized (this) { for (HIDDevice device : mDevicesById.values()) { device.setFrozen(frozen); } } } ////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////// private HIDDevice getDevice(int id) { synchronized (this) { HIDDevice result = mDevicesById.get(id); if (result == null) { Log.v(TAG, "No device for id: " + id); Log.v(TAG, "Available devices: " + mDevicesById.keySet()); } return result; } } ////////////////////////////////////////////////////////////////////////////////////////////////////// ////////// JNI interface functions ////////////////////////////////////////////////////////////////////////////////////////////////////// public boolean initialize(boolean usb, boolean bluetooth) { Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")"); if (usb) { initializeUSB(); } if (bluetooth) { initializeBluetooth(); } return true; } public boolean openDevice(int deviceID) { Log.v(TAG, "openDevice deviceID=" + deviceID); HIDDevice device = getDevice(deviceID); if (device == null) { HIDDeviceDisconnected(deviceID); return false; } // Look to see if this is a USB device and we have permission to access it UsbDevice usbDevice = device.getDevice(); if (usbDevice != null && !mUsbManager.hasPermission(usbDevice)) { HIDDeviceOpenPending(deviceID); try { final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31 int flags; if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) { flags = FLAG_MUTABLE; } else { flags = 0; } if (Build.VERSION.SDK_INT >= 33 /* Android 14.0 (U) */) { Intent intent = new Intent(HIDDeviceManager.ACTION_USB_PERMISSION); intent.setPackage(mContext.getPackageName()); mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, intent, flags)); } else { mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags)); } } catch (Exception e) { Log.v(TAG, "Couldn't request permission for USB device " + usbDevice); HIDDeviceOpenResult(deviceID, false); } return false; } try { return device.open(); } catch (Exception e) { Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); } return false; } public int sendOutputReport(int deviceID, byte[] report) { try { //Log.v(TAG, "sendOutputReport deviceID=" + deviceID + " length=" + report.length); HIDDevice device; device = getDevice(deviceID); if (device == null) { HIDDeviceDisconnected(deviceID); return -1; } return device.sendOutputReport(report); } catch (Exception e) { Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); } return -1; } public int sendFeatureReport(int deviceID, byte[] report) { try { //Log.v(TAG, "sendFeatureReport deviceID=" + deviceID + " length=" + report.length); HIDDevice device; device = getDevice(deviceID); if (device == null) { HIDDeviceDisconnected(deviceID); return -1; } return device.sendFeatureReport(report); } catch (Exception e) { Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); } return -1; } public boolean getFeatureReport(int deviceID, byte[] report) { try { //Log.v(TAG, "getFeatureReport deviceID=" + deviceID); HIDDevice device; device = getDevice(deviceID); if (device == null) { HIDDeviceDisconnected(deviceID); return false; } return device.getFeatureReport(report); } catch (Exception e) { Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); } return false; } public void closeDevice(int deviceID) { try { Log.v(TAG, "closeDevice deviceID=" + deviceID); HIDDevice device; device = getDevice(deviceID); if (device == null) { HIDDeviceDisconnected(deviceID); return; } device.close(); } catch (Exception e) { Log.e(TAG, "Got exception: " + Log.getStackTraceString(e)); } } ////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////// Native methods ////////////////////////////////////////////////////////////////////////////////////////////////////// private native void HIDDeviceRegisterCallback(); private native void HIDDeviceReleaseCallback(); native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number, int interface_class, int interface_subclass, int interface_protocol); native void HIDDeviceOpenPending(int deviceID); native void HIDDeviceOpenResult(int deviceID, boolean opened); native void HIDDeviceDisconnected(int deviceID); native void HIDDeviceInputReport(int deviceID, byte[] report); native void HIDDeviceFeatureReport(int deviceID, byte[] report); } ================================================ FILE: android-project/thextech/src/main/java/org/libsdl/app/HIDDeviceUSB.java ================================================ package org.libsdl.app; import android.hardware.usb.*; import android.os.Build; import android.util.Log; import java.util.Arrays; class HIDDeviceUSB implements HIDDevice { private static final String TAG = "hidapi"; protected HIDDeviceManager mManager; protected UsbDevice mDevice; protected int mInterfaceIndex; protected int mInterface; protected int mDeviceId; protected UsbDeviceConnection mConnection; protected UsbEndpoint mInputEndpoint; protected UsbEndpoint mOutputEndpoint; protected InputThread mInputThread; protected boolean mRunning; protected boolean mFrozen; public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_index) { mManager = manager; mDevice = usbDevice; mInterfaceIndex = interface_index; mInterface = mDevice.getInterface(mInterfaceIndex).getId(); mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier()); mRunning = false; } public String getIdentifier() { return String.format("%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex); } @Override public int getId() { return mDeviceId; } @Override public int getVendorId() { return mDevice.getVendorId(); } @Override public int getProductId() { return mDevice.getProductId(); } @Override public String getSerialNumber() { String result = null; if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) { try { result = mDevice.getSerialNumber(); } catch (SecurityException exception) { //Log.w(TAG, "App permissions mean we cannot get serial number for device " + getDeviceName() + " message: " + exception.getMessage()); } } if (result == null) { result = ""; } return result; } @Override public int getVersion() { return 0; } @Override public String getManufacturerName() { String result = null; if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) { result = mDevice.getManufacturerName(); } if (result == null) { result = String.format("%x", getVendorId()); } return result; } @Override public String getProductName() { String result = null; if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) { result = mDevice.getProductName(); } if (result == null) { result = String.format("%x", getProductId()); } return result; } @Override public UsbDevice getDevice() { return mDevice; } public String getDeviceName() { return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")"; } @Override public boolean open() { mConnection = mManager.getUSBManager().openDevice(mDevice); if (mConnection == null) { Log.w(TAG, "Unable to open USB device " + getDeviceName()); return false; } // Force claim our interface UsbInterface iface = mDevice.getInterface(mInterfaceIndex); if (!mConnection.claimInterface(iface, true)) { Log.w(TAG, "Failed to claim interfaces on USB device " + getDeviceName()); close(); return false; } // Find the endpoints for (int j = 0; j < iface.getEndpointCount(); j++) { UsbEndpoint endpt = iface.getEndpoint(j); switch (endpt.getDirection()) { case UsbConstants.USB_DIR_IN: if (mInputEndpoint == null) { mInputEndpoint = endpt; } break; case UsbConstants.USB_DIR_OUT: if (mOutputEndpoint == null) { mOutputEndpoint = endpt; } break; } } // Make sure the required endpoints were present if (mInputEndpoint == null || mOutputEndpoint == null) { Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName()); close(); return false; } // Start listening for input mRunning = true; mInputThread = new InputThread(); mInputThread.start(); return true; } @Override public int sendFeatureReport(byte[] report) { int res = -1; int offset = 0; int length = report.length; boolean skipped_report_id = false; byte report_number = report[0]; if (report_number == 0x0) { ++offset; --length; skipped_report_id = true; } res = mConnection.controlTransfer( UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT, 0x09/*HID set_report*/, (3/*HID feature*/ << 8) | report_number, mInterface, report, offset, length, 1000/*timeout millis*/); if (res < 0) { Log.w(TAG, "sendFeatureReport() returned " + res + " on device " + getDeviceName()); return -1; } if (skipped_report_id) { ++length; } return length; } @Override public int sendOutputReport(byte[] report) { int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000); if (r != report.length) { Log.w(TAG, "sendOutputReport() returned " + r + " on device " + getDeviceName()); } return r; } @Override public boolean getFeatureReport(byte[] report) { int res = -1; int offset = 0; int length = report.length; boolean skipped_report_id = false; byte report_number = report[0]; if (report_number == 0x0) { /* Offset the return buffer by 1, so that the report ID will remain in byte 0. */ ++offset; --length; skipped_report_id = true; } res = mConnection.controlTransfer( UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN, 0x01/*HID get_report*/, (3/*HID feature*/ << 8) | report_number, mInterface, report, offset, length, 1000/*timeout millis*/); if (res < 0) { Log.w(TAG, "getFeatureReport() returned " + res + " on device " + getDeviceName()); return false; } if (skipped_report_id) { ++res; ++length; } byte[] data; if (res == length) { data = report; } else { data = Arrays.copyOfRange(report, 0, res); } mManager.HIDDeviceFeatureReport(mDeviceId, data); return true; } @Override public void close() { mRunning = false; if (mInputThread != null) { while (mInputThread.isAlive()) { mInputThread.interrupt(); try { mInputThread.join(); } catch (InterruptedException e) { // Keep trying until we're done } } mInputThread = null; } if (mConnection != null) { UsbInterface iface = mDevice.getInterface(mInterfaceIndex); mConnection.releaseInterface(iface); mConnection.close(); mConnection = null; } } @Override public void shutdown() { close(); mManager = null; } @Override public void setFrozen(boolean frozen) { mFrozen = frozen; } protected class InputThread extends Thread { @Override public void run() { int packetSize = mInputEndpoint.getMaxPacketSize(); byte[] packet = new byte[packetSize]; while (mRunning) { int r; try { r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000); } catch (Exception e) { Log.v(TAG, "Exception in UsbDeviceConnection bulktransfer: " + e); break; } if (r < 0) { // Could be a timeout or an I/O error } if (r > 0) { byte[] data; if (r == packetSize) { data = packet; } else { data = Arrays.copyOfRange(packet, 0, r); } if (!mFrozen) { mManager.HIDDeviceInputReport(mDeviceId, data); } } } } } } ================================================ FILE: android-project/thextech/src/main/java/org/libsdl/app/SDL.java ================================================ package org.libsdl.app; import android.content.Context; import java.lang.Class; import java.lang.reflect.Method; /** SDL library initialization */ public class SDL { // This function should be called first and sets up the native code // so it can call into the Java classes public static void setupJNI() { SDLActivity.nativeSetupJNI(); SDLAudioManager.nativeSetupJNI(); SDLControllerManager.nativeSetupJNI(); } // This function should be called each time the activity is started public static void initialize() { setContext(null); SDLActivity.initialize(); SDLAudioManager.initialize(); SDLControllerManager.initialize(); } // This function stores the current activity (SDL or not) public static void setContext(Context context) { SDLAudioManager.setContext(context); mContext = context; } public static Context getContext() { return mContext; } public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException { loadLibrary(libraryName, mContext); } public static void loadLibrary(String libraryName, Context context) throws UnsatisfiedLinkError, SecurityException, NullPointerException { if (libraryName == null) { throw new NullPointerException("No library name provided."); } try { // Let's see if we have ReLinker available in the project. This is necessary for // some projects that have huge numbers of local libraries bundled, and thus may // trip a bug in Android's native library loader which ReLinker works around. (If // loadLibrary works properly, ReLinker will simply use the normal Android method // internally.) // // To use ReLinker, just add it as a dependency. For more information, see // https://github.com/KeepSafe/ReLinker for ReLinker's repository. // Class relinkClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker"); Class relinkListenerClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener"); Class contextClass = context.getClassLoader().loadClass("android.content.Context"); Class stringClass = context.getClassLoader().loadClass("java.lang.String"); // Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if // they've changed during updates. Method forceMethod = relinkClass.getDeclaredMethod("force"); Object relinkInstance = forceMethod.invoke(null); Class relinkInstanceClass = relinkInstance.getClass(); // Actually load the library! Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass); loadMethod.invoke(relinkInstance, context, libraryName, null, null); } catch (final Throwable e) { // Fall back try { System.loadLibrary(libraryName); } catch (final UnsatisfiedLinkError ule) { throw ule; } catch (final SecurityException se) { throw se; } } } protected static Context mContext; } ================================================ FILE: android-project/thextech/src/main/java/org/libsdl/app/SDLActivity.java ================================================ package org.libsdl.app; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.UiModeManager; import android.content.ClipboardManager; import android.content.ClipData; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.hardware.Sensor; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.text.Editable; import android.text.InputType; import android.text.Selection; import android.util.DisplayMetrics; import android.util.Log; import android.util.SparseArray; import android.view.Display; import android.view.Gravity; import android.view.InputDevice; import android.view.KeyEvent; import android.view.PointerIcon; import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import java.util.Hashtable; import java.util.Locale; /** SDL Activity */ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener { private static final String TAG = "SDL"; private static final int SDL_MAJOR_VERSION = 2; private static final int SDL_MINOR_VERSION = 32; private static final int SDL_MICRO_VERSION = 10; /* // Display InputType.SOURCE/CLASS of events and devices // // SDLActivity.debugSource(device.getSources(), "device[" + device.getName() + "]"); // SDLActivity.debugSource(event.getSource(), "event"); public static void debugSource(int sources, String prefix) { int s = sources; int s_copy = sources; String cls = ""; String src = ""; int tst = 0; int FLAG_TAINTED = 0x80000000; if ((s & InputDevice.SOURCE_CLASS_BUTTON) != 0) cls += " BUTTON"; if ((s & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) cls += " JOYSTICK"; if ((s & InputDevice.SOURCE_CLASS_POINTER) != 0) cls += " POINTER"; if ((s & InputDevice.SOURCE_CLASS_POSITION) != 0) cls += " POSITION"; if ((s & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) cls += " TRACKBALL"; int s2 = s_copy & ~InputDevice.SOURCE_ANY; // keep class bits s2 &= ~( InputDevice.SOURCE_CLASS_BUTTON | InputDevice.SOURCE_CLASS_JOYSTICK | InputDevice.SOURCE_CLASS_POINTER | InputDevice.SOURCE_CLASS_POSITION | InputDevice.SOURCE_CLASS_TRACKBALL); if (s2 != 0) cls += "Some_Unknown"; s2 = s_copy & InputDevice.SOURCE_ANY; // keep source only, no class; if (Build.VERSION.SDK_INT >= 23) { tst = InputDevice.SOURCE_BLUETOOTH_STYLUS; if ((s & tst) == tst) src += " BLUETOOTH_STYLUS"; s2 &= ~tst; } tst = InputDevice.SOURCE_DPAD; if ((s & tst) == tst) src += " DPAD"; s2 &= ~tst; tst = InputDevice.SOURCE_GAMEPAD; if ((s & tst) == tst) src += " GAMEPAD"; s2 &= ~tst; if (Build.VERSION.SDK_INT >= 21) { tst = InputDevice.SOURCE_HDMI; if ((s & tst) == tst) src += " HDMI"; s2 &= ~tst; } tst = InputDevice.SOURCE_JOYSTICK; if ((s & tst) == tst) src += " JOYSTICK"; s2 &= ~tst; tst = InputDevice.SOURCE_KEYBOARD; if ((s & tst) == tst) src += " KEYBOARD"; s2 &= ~tst; tst = InputDevice.SOURCE_MOUSE; if ((s & tst) == tst) src += " MOUSE"; s2 &= ~tst; if (Build.VERSION.SDK_INT >= 26) { tst = InputDevice.SOURCE_MOUSE_RELATIVE; if ((s & tst) == tst) src += " MOUSE_RELATIVE"; s2 &= ~tst; tst = InputDevice.SOURCE_ROTARY_ENCODER; if ((s & tst) == tst) src += " ROTARY_ENCODER"; s2 &= ~tst; } tst = InputDevice.SOURCE_STYLUS; if ((s & tst) == tst) src += " STYLUS"; s2 &= ~tst; tst = InputDevice.SOURCE_TOUCHPAD; if ((s & tst) == tst) src += " TOUCHPAD"; s2 &= ~tst; tst = InputDevice.SOURCE_TOUCHSCREEN; if ((s & tst) == tst) src += " TOUCHSCREEN"; s2 &= ~tst; if (Build.VERSION.SDK_INT >= 18) { tst = InputDevice.SOURCE_TOUCH_NAVIGATION; if ((s & tst) == tst) src += " TOUCH_NAVIGATION"; s2 &= ~tst; } tst = InputDevice.SOURCE_TRACKBALL; if ((s & tst) == tst) src += " TRACKBALL"; s2 &= ~tst; tst = InputDevice.SOURCE_ANY; if ((s & tst) == tst) src += " ANY"; s2 &= ~tst; if (s == FLAG_TAINTED) src += " FLAG_TAINTED"; s2 &= ~FLAG_TAINTED; if (s2 != 0) src += " Some_Unknown"; Log.v(TAG, prefix + "int=" + s_copy + " CLASS={" + cls + " } source(s):" + src); } */ public static boolean mIsResumedCalled, mHasFocus; public static final boolean mHasMultiWindow = (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */); // Cursor types // private static final int SDL_SYSTEM_CURSOR_NONE = -1; private static final int SDL_SYSTEM_CURSOR_ARROW = 0; private static final int SDL_SYSTEM_CURSOR_IBEAM = 1; private static final int SDL_SYSTEM_CURSOR_WAIT = 2; private static final int SDL_SYSTEM_CURSOR_CROSSHAIR = 3; private static final int SDL_SYSTEM_CURSOR_WAITARROW = 4; private static final int SDL_SYSTEM_CURSOR_SIZENWSE = 5; private static final int SDL_SYSTEM_CURSOR_SIZENESW = 6; private static final int SDL_SYSTEM_CURSOR_SIZEWE = 7; private static final int SDL_SYSTEM_CURSOR_SIZENS = 8; private static final int SDL_SYSTEM_CURSOR_SIZEALL = 9; private static final int SDL_SYSTEM_CURSOR_NO = 10; private static final int SDL_SYSTEM_CURSOR_HAND = 11; protected static final int SDL_ORIENTATION_UNKNOWN = 0; protected static final int SDL_ORIENTATION_LANDSCAPE = 1; protected static final int SDL_ORIENTATION_LANDSCAPE_FLIPPED = 2; protected static final int SDL_ORIENTATION_PORTRAIT = 3; protected static final int SDL_ORIENTATION_PORTRAIT_FLIPPED = 4; protected static int mCurrentOrientation; protected static Locale mCurrentLocale; // Handle the state of the native layer public enum NativeState { INIT, RESUMED, PAUSED } public static NativeState mNextNativeState; public static NativeState mCurrentNativeState; /** If shared libraries (e.g. SDL or the native application) could not be loaded. */ public static boolean mBrokenLibraries = true; // Main components protected static SDLActivity mSingleton; protected static SDLSurface mSurface; protected static DummyEdit mTextEdit; protected static boolean mScreenKeyboardShown; protected static ViewGroup mLayout; protected static SDLClipboardHandler mClipboardHandler; protected static Hashtable mCursors; protected static int mLastCursorID; protected static SDLGenericMotionListener_API12 mMotionListener; protected static HIDDeviceManager mHIDDeviceManager; // This is what SDL runs in. It invokes SDL_main(), eventually protected static Thread mSDLThread; protected static SDLGenericMotionListener_API12 getMotionListener() { if (mMotionListener == null) { if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) { mMotionListener = new SDLGenericMotionListener_API26(); } else if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { mMotionListener = new SDLGenericMotionListener_API24(); } else { mMotionListener = new SDLGenericMotionListener_API12(); } } return mMotionListener; } /** * This method returns the name of the shared object with the application entry point * It can be overridden by derived classes. */ protected String getMainSharedObject() { String library; String[] libraries = SDLActivity.mSingleton.getLibraries(); if (libraries.length > 0) { library = "lib" + libraries[libraries.length - 1] + ".so"; } else { library = "libmain.so"; } return getContext().getApplicationInfo().nativeLibraryDir + "/" + library; } /** * This method returns the name of the application entry point * It can be overridden by derived classes. */ protected String getMainFunction() { return "SDL_main"; } /** * This method is called by SDL before loading the native shared libraries. * It can be overridden to provide names of shared libraries to be loaded. * The default implementation returns the defaults. It never returns null. * An array returned by a new implementation must at least contain "SDL2". * Also keep in mind that the order the libraries are loaded may matter. * @return names of shared libraries to be loaded (e.g. "SDL2", "main"). */ protected String[] getLibraries() { return new String[] { "SDL2", // "SDL2_image", // "SDL2_mixer", // "SDL2_net", // "SDL2_ttf", "main" }; } // Load the .so public void loadLibraries() { for (String lib : getLibraries()) { SDL.loadLibrary(lib, this); } } /** * This method is called by SDL before starting the native application thread. * It can be overridden to provide the arguments after the application name. * The default implementation returns an empty array. It never returns null. * @return arguments for the native application. */ protected String[] getArguments() { return new String[0]; } public static void initialize() { // The static nature of the singleton and Android quirkyness force us to initialize everything here // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values mSingleton = null; mSurface = null; mTextEdit = null; mLayout = null; mClipboardHandler = null; mCursors = new Hashtable(); mLastCursorID = 0; mSDLThread = null; mIsResumedCalled = false; mHasFocus = true; mNextNativeState = NativeState.INIT; mCurrentNativeState = NativeState.INIT; } protected SDLSurface createSDLSurface(Context context) { return new SDLSurface(context); } // Setup @Override protected void onCreate(Bundle savedInstanceState) { Log.v(TAG, "Device: " + Build.DEVICE); Log.v(TAG, "Model: " + Build.MODEL); Log.v(TAG, "onCreate()"); super.onCreate(savedInstanceState); try { Thread.currentThread().setName("SDLActivity"); } catch (Exception e) { Log.v(TAG, "modify thread properties failed " + e.toString()); } // Load shared libraries String errorMsgBrokenLib = ""; try { loadLibraries(); mBrokenLibraries = false; /* success */ } catch(UnsatisfiedLinkError e) { System.err.println(e.getMessage()); mBrokenLibraries = true; errorMsgBrokenLib = e.getMessage(); } catch(Exception e) { System.err.println(e.getMessage()); mBrokenLibraries = true; errorMsgBrokenLib = e.getMessage(); } if (!mBrokenLibraries) { String expected_version = String.valueOf(SDL_MAJOR_VERSION) + "." + String.valueOf(SDL_MINOR_VERSION) + "." + String.valueOf(SDL_MICRO_VERSION); String version = nativeGetVersion(); if (!version.equals(expected_version)) { mBrokenLibraries = true; errorMsgBrokenLib = "SDL C/Java version mismatch (expected " + expected_version + ", got " + version + ")"; } } if (mBrokenLibraries) { mSingleton = this; AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this); dlgAlert.setMessage("An error occurred while trying to start the application. Please try again and/or reinstall." + System.getProperty("line.separator") + System.getProperty("line.separator") + "Error: " + errorMsgBrokenLib); dlgAlert.setTitle("SDL Error"); dlgAlert.setPositiveButton("Exit", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog,int id) { // if this button is clicked, close current activity SDLActivity.mSingleton.finish(); } }); dlgAlert.setCancelable(false); dlgAlert.create().show(); return; } // Set up JNI SDL.setupJNI(); // Initialize state SDL.initialize(); // So we can call stuff from static callbacks mSingleton = this; SDL.setContext(this); mClipboardHandler = new SDLClipboardHandler(); mHIDDeviceManager = HIDDeviceManager.acquire(this); // Set up the surface mSurface = createSDLSurface(this); mLayout = new RelativeLayout(this); mLayout.addView(mSurface); // Get our current screen orientation and pass it down. mCurrentOrientation = SDLActivity.getCurrentOrientation(); // Only record current orientation SDLActivity.onNativeOrientationChanged(mCurrentOrientation); try { if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) { mCurrentLocale = getContext().getResources().getConfiguration().locale; } else { mCurrentLocale = getContext().getResources().getConfiguration().getLocales().get(0); } } catch(Exception ignored) { } setContentView(mLayout); setWindowStyle(false); getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(this); // Get filename from "Open with" of another application Intent intent = getIntent(); if (intent != null && intent.getData() != null) { String filename = intent.getData().getPath(); if (filename != null) { Log.v(TAG, "Got filename: " + filename); SDLActivity.onNativeDropFile(filename); } } } protected void pauseNativeThread() { mNextNativeState = NativeState.PAUSED; mIsResumedCalled = false; if (SDLActivity.mBrokenLibraries) { return; } SDLActivity.handleNativeState(); } protected void resumeNativeThread() { mNextNativeState = NativeState.RESUMED; mIsResumedCalled = true; if (SDLActivity.mBrokenLibraries) { return; } SDLActivity.handleNativeState(); } // Events @Override protected void onPause() { Log.v(TAG, "onPause()"); super.onPause(); if (mHIDDeviceManager != null) { mHIDDeviceManager.setFrozen(true); } if (!mHasMultiWindow) { pauseNativeThread(); } } @Override protected void onResume() { Log.v(TAG, "onResume()"); super.onResume(); if (mHIDDeviceManager != null) { mHIDDeviceManager.setFrozen(false); } if (!mHasMultiWindow) { resumeNativeThread(); } } @Override protected void onStop() { Log.v(TAG, "onStop()"); super.onStop(); if (mHasMultiWindow) { pauseNativeThread(); } } @Override protected void onStart() { Log.v(TAG, "onStart()"); super.onStart(); if (mHasMultiWindow) { resumeNativeThread(); } } public static int getCurrentOrientation() { int result = SDL_ORIENTATION_UNKNOWN; Activity activity = (Activity)getContext(); if (activity == null) { return result; } Display display = activity.getWindowManager().getDefaultDisplay(); switch (display.getRotation()) { case Surface.ROTATION_0: result = SDL_ORIENTATION_PORTRAIT; break; case Surface.ROTATION_90: result = SDL_ORIENTATION_LANDSCAPE; break; case Surface.ROTATION_180: result = SDL_ORIENTATION_PORTRAIT_FLIPPED; break; case Surface.ROTATION_270: result = SDL_ORIENTATION_LANDSCAPE_FLIPPED; break; } return result; } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); Log.v(TAG, "onWindowFocusChanged(): " + hasFocus); if (SDLActivity.mBrokenLibraries) { return; } mHasFocus = hasFocus; if (hasFocus) { mNextNativeState = NativeState.RESUMED; SDLActivity.getMotionListener().reclaimRelativeMouseModeIfNeeded(); SDLActivity.handleNativeState(); nativeFocusChanged(true); } else { nativeFocusChanged(false); if (!mHasMultiWindow) { mNextNativeState = NativeState.PAUSED; SDLActivity.handleNativeState(); } } } @Override public void onLowMemory() { Log.v(TAG, "onLowMemory()"); super.onLowMemory(); if (SDLActivity.mBrokenLibraries) { return; } SDLActivity.nativeLowMemory(); } @Override public void onConfigurationChanged(Configuration newConfig) { Log.v(TAG, "onConfigurationChanged()"); super.onConfigurationChanged(newConfig); if (SDLActivity.mBrokenLibraries) { return; } if (mCurrentLocale == null || !mCurrentLocale.equals(newConfig.locale)) { mCurrentLocale = newConfig.locale; SDLActivity.onNativeLocaleChanged(); } } @Override protected void onDestroy() { Log.v(TAG, "onDestroy()"); if (mHIDDeviceManager != null) { HIDDeviceManager.release(mHIDDeviceManager); mHIDDeviceManager = null; } SDLAudioManager.release(this); if (SDLActivity.mBrokenLibraries) { super.onDestroy(); return; } if (SDLActivity.mSDLThread != null) { // Send Quit event to "SDLThread" thread SDLActivity.nativeSendQuit(); // Wait for "SDLThread" thread to end try { SDLActivity.mSDLThread.join(); } catch(Exception e) { Log.v(TAG, "Problem stopping SDLThread: " + e); } } SDLActivity.nativeQuit(); super.onDestroy(); } @Override public void onBackPressed() { // Check if we want to block the back button in case of mouse right click. // // If we do, the normal hardware back button will no longer work and people have to use home, // but the mouse right click will work. // boolean trapBack = SDLActivity.nativeGetHintBoolean("SDL_ANDROID_TRAP_BACK_BUTTON", false); if (trapBack) { // Exit and let the mouse handler handle this button (if appropriate) return; } // Default system back button behavior. if (!isFinishing()) { super.onBackPressed(); } } // Called by JNI from SDL. public static void manualBackButton() { mSingleton.pressBackButton(); } // Used to get us onto the activity's main thread public void pressBackButton() { runOnUiThread(new Runnable() { @Override public void run() { if (!SDLActivity.this.isFinishing()) { SDLActivity.this.superOnBackPressed(); } } }); } // Used to access the system back behavior. public void superOnBackPressed() { super.onBackPressed(); } @Override public boolean dispatchKeyEvent(KeyEvent event) { if (SDLActivity.mBrokenLibraries) { return false; } int keyCode = event.getKeyCode(); // Ignore certain special keys so they're handled by Android if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_CAMERA || keyCode == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */ keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */ ) { return false; } return super.dispatchKeyEvent(event); } /* Transition to next state */ public static void handleNativeState() { if (mNextNativeState == mCurrentNativeState) { // Already in same state, discard. return; } // Try a transition to init state if (mNextNativeState == NativeState.INIT) { mCurrentNativeState = mNextNativeState; return; } // Try a transition to paused state if (mNextNativeState == NativeState.PAUSED) { if (mSDLThread != null) { nativePause(); } if (mSurface != null) { mSurface.handlePause(); } mCurrentNativeState = mNextNativeState; return; } // Try a transition to resumed state if (mNextNativeState == NativeState.RESUMED) { if (mSurface.mIsSurfaceReady && mHasFocus && mIsResumedCalled) { if (mSDLThread == null) { // This is the entry point to the C app. // Start up the C app thread and enable sensor input for the first time // FIXME: Why aren't we enabling sensor input at start? mSDLThread = new Thread(new SDLMain(), "SDLThread"); mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true); mSDLThread.start(); // No nativeResume(), don't signal Android_ResumeSem } else { nativeResume(); } mSurface.handleResume(); mCurrentNativeState = mNextNativeState; } } } // Messages from the SDLMain thread static final int COMMAND_CHANGE_TITLE = 1; static final int COMMAND_CHANGE_WINDOW_STYLE = 2; static final int COMMAND_TEXTEDIT_HIDE = 3; static final int COMMAND_SET_KEEP_SCREEN_ON = 5; protected static final int COMMAND_USER = 0x8000; protected static boolean mFullscreenModeActive; /** * This method is called by SDL if SDL did not handle a message itself. * This happens if a received message contains an unsupported command. * Method can be overwritten to handle Messages in a different class. * @param command the command of the message. * @param param the parameter of the message. May be null. * @return if the message was handled in overridden method. */ protected boolean onUnhandledMessage(int command, Object param) { return false; } /** * A Handler class for Messages from native SDL applications. * It uses current Activities as target (e.g. for the title). * static to prevent implicit references to enclosing object. */ protected static class SDLCommandHandler extends Handler { @Override public void handleMessage(Message msg) { Context context = SDL.getContext(); if (context == null) { Log.e(TAG, "error handling message, getContext() returned null"); return; } switch (msg.arg1) { case COMMAND_CHANGE_TITLE: if (context instanceof Activity) { ((Activity) context).setTitle((String)msg.obj); } else { Log.e(TAG, "error handling message, getContext() returned no Activity"); } break; case COMMAND_CHANGE_WINDOW_STYLE: if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) { if (context instanceof Activity) { Window window = ((Activity) context).getWindow(); if (window != null) { if ((msg.obj instanceof Integer) && ((Integer) msg.obj != 0)) { int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE; window.getDecorView().setSystemUiVisibility(flags); window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); SDLActivity.mFullscreenModeActive = true; } else { int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_VISIBLE; window.getDecorView().setSystemUiVisibility(flags); window.addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); SDLActivity.mFullscreenModeActive = false; } if (Build.VERSION.SDK_INT >= 28 /* Android 9 (Pie) */) { window.getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; } } } else { Log.e(TAG, "error handling message, getContext() returned no Activity"); } } break; case COMMAND_TEXTEDIT_HIDE: if (mTextEdit != null) { // Note: On some devices setting view to GONE creates a flicker in landscape. // Setting the View's sizes to 0 is similar to GONE but without the flicker. // The sizes will be set to useful values when the keyboard is shown again. mTextEdit.setLayoutParams(new RelativeLayout.LayoutParams(0, 0)); InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0); mScreenKeyboardShown = false; mSurface.requestFocus(); } break; case COMMAND_SET_KEEP_SCREEN_ON: { if (context instanceof Activity) { Window window = ((Activity) context).getWindow(); if (window != null) { if ((msg.obj instanceof Integer) && ((Integer) msg.obj != 0)) { window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } else { window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } } } break; } default: if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) { Log.e(TAG, "error handling message, command is " + msg.arg1); } } } } // Handler for the messages Handler commandHandler = new SDLCommandHandler(); // Send a message from the SDLMain thread boolean sendCommand(int command, Object data) { Message msg = commandHandler.obtainMessage(); msg.arg1 = command; msg.obj = data; boolean result = commandHandler.sendMessage(msg); if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) { if (command == COMMAND_CHANGE_WINDOW_STYLE) { // Ensure we don't return until the resize has actually happened, // or 500ms have passed. boolean bShouldWait = false; if (data instanceof Integer) { // Let's figure out if we're already laid out fullscreen or not. Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); DisplayMetrics realMetrics = new DisplayMetrics(); display.getRealMetrics(realMetrics); boolean bFullscreenLayout = ((realMetrics.widthPixels == mSurface.getWidth()) && (realMetrics.heightPixels == mSurface.getHeight())); if ((Integer) data == 1) { // If we aren't laid out fullscreen or actively in fullscreen mode already, we're going // to change size and should wait for surfaceChanged() before we return, so the size // is right back in native code. If we're already laid out fullscreen, though, we're // not going to change size even if we change decor modes, so we shouldn't wait for // surfaceChanged() -- which may not even happen -- and should return immediately. bShouldWait = !bFullscreenLayout; } else { // If we're laid out fullscreen (even if the status bar and nav bar are present), // or are actively in fullscreen, we're going to change size and should wait for // surfaceChanged before we return, so the size is right back in native code. bShouldWait = bFullscreenLayout; } } if (bShouldWait && (SDLActivity.getContext() != null)) { // We'll wait for the surfaceChanged() method, which will notify us // when called. That way, we know our current size is really the // size we need, instead of grabbing a size that's still got // the navigation and/or status bars before they're hidden. // // We'll wait for up to half a second, because some devices // take a surprisingly long time for the surface resize, but // then we'll just give up and return. // synchronized (SDLActivity.getContext()) { try { SDLActivity.getContext().wait(500); } catch (InterruptedException ie) { ie.printStackTrace(); } } } } } return result; } // C functions we call public static native String nativeGetVersion(); public static native int nativeSetupJNI(); public static native int nativeRunMain(String library, String function, Object arguments); public static native void nativeLowMemory(); public static native void nativeSendQuit(); public static native void nativeQuit(); public static native void nativePause(); public static native void nativeResume(); public static native void nativeFocusChanged(boolean hasFocus); public static native void onNativeDropFile(String filename); public static native void nativeSetScreenResolution(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, float rate); public static native void onNativeResize(); public static native void onNativeKeyDown(int keycode); public static native void onNativeKeyUp(int keycode); public static native boolean onNativeSoftReturnKey(); public static native void onNativeKeyboardFocusLost(); public static native void onNativeMouse(int button, int action, float x, float y, boolean relative); public static native void onNativeTouch(int touchDevId, int pointerFingerId, int action, float x, float y, float p); public static native void onNativeAccel(float x, float y, float z); public static native void onNativeClipboardChanged(); public static native void onNativeSurfaceCreated(); public static native void onNativeSurfaceChanged(); public static native void onNativeSurfaceDestroyed(); public static native String nativeGetHint(String name); public static native boolean nativeGetHintBoolean(String name, boolean default_value); public static native void nativeSetenv(String name, String value); public static native void onNativeOrientationChanged(int orientation); public static native void nativeAddTouch(int touchId, String name); public static native void nativePermissionResult(int requestCode, boolean result); public static native void onNativeLocaleChanged(); /** * This method is called by SDL using JNI. */ public static boolean setActivityTitle(String title) { // Called from SDLMain() thread and can't directly affect the view return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title); } /** * This method is called by SDL using JNI. */ public static void setWindowStyle(boolean fullscreen) { // Called from SDLMain() thread and can't directly affect the view mSingleton.sendCommand(COMMAND_CHANGE_WINDOW_STYLE, fullscreen ? 1 : 0); } /** * This method is called by SDL using JNI. * This is a static method for JNI convenience, it calls a non-static method * so that is can be overridden */ public static void setOrientation(int w, int h, boolean resizable, String hint) { if (mSingleton != null) { mSingleton.setOrientationBis(w, h, resizable, hint); } } /** * This can be overridden */ public void setOrientationBis(int w, int h, boolean resizable, String hint) { int orientation_landscape = -1; int orientation_portrait = -1; /* If set, hint "explicitly controls which UI orientations are allowed". */ if (hint.contains("LandscapeRight") && hint.contains("LandscapeLeft")) { orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; } else if (hint.contains("LandscapeLeft")) { orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; } else if (hint.contains("LandscapeRight")) { orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; } /* exact match to 'Portrait' to distinguish with PortraitUpsideDown */ boolean contains_Portrait = hint.contains("Portrait ") || hint.endsWith("Portrait"); if (contains_Portrait && hint.contains("PortraitUpsideDown")) { orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; } else if (contains_Portrait) { orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; } else if (hint.contains("PortraitUpsideDown")) { orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; } boolean is_landscape_allowed = (orientation_landscape != -1); boolean is_portrait_allowed = (orientation_portrait != -1); int req; /* Requested orientation */ /* No valid hint, nothing is explicitly allowed */ if (!is_portrait_allowed && !is_landscape_allowed) { if (resizable) { /* All orientations are allowed, respecting user orientation lock setting */ req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER; } else { /* Fixed window and nothing specified. Get orientation from w/h of created window */ req = (w > h ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); } } else { /* At least one orientation is allowed */ if (resizable) { if (is_portrait_allowed && is_landscape_allowed) { /* hint allows both landscape and portrait, promote to full user */ req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER; } else { /* Use the only one allowed "orientation" */ req = (is_landscape_allowed ? orientation_landscape : orientation_portrait); } } else { /* Fixed window and both orientations are allowed. Choose one. */ if (is_portrait_allowed && is_landscape_allowed) { req = (w > h ? orientation_landscape : orientation_portrait); } else { /* Use the only one allowed "orientation" */ req = (is_landscape_allowed ? orientation_landscape : orientation_portrait); } } } Log.v(TAG, "setOrientation() requestedOrientation=" + req + " width=" + w +" height="+ h +" resizable=" + resizable + " hint=" + hint); mSingleton.setRequestedOrientation(req); } /** * This method is called by SDL using JNI. */ public static void minimizeWindow() { if (mSingleton == null) { return; } Intent startMain = new Intent(Intent.ACTION_MAIN); startMain.addCategory(Intent.CATEGORY_HOME); startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mSingleton.startActivity(startMain); } /** * This method is called by SDL using JNI. */ public static boolean shouldMinimizeOnFocusLoss() { /* if (Build.VERSION.SDK_INT >= 24) { if (mSingleton == null) { return true; } if (mSingleton.isInMultiWindowMode()) { return false; } if (mSingleton.isInPictureInPictureMode()) { return false; } } return true; */ return false; } /** * This method is called by SDL using JNI. */ public static boolean isScreenKeyboardShown() { if (mTextEdit == null) { return false; } if (!mScreenKeyboardShown) { return false; } InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); return imm.isAcceptingText(); } /** * This method is called by SDL using JNI. */ public static boolean supportsRelativeMouse() { // DeX mode in Samsung Experience 9.0 and earlier doesn't support relative mice properly under // Android 7 APIs, and simply returns no data under Android 8 APIs. // // This is fixed in Samsung Experience 9.5, which corresponds to Android 8.1.0, and // thus SDK version 27. If we are in DeX mode and not API 27 or higher, as a result, // we should stick to relative mode. // if (Build.VERSION.SDK_INT < 27 /* Android 8.1 (O_MR1) */ && isDeXMode()) { return false; } return SDLActivity.getMotionListener().supportsRelativeMouse(); } /** * This method is called by SDL using JNI. */ public static boolean setRelativeMouseEnabled(boolean enabled) { if (enabled && !supportsRelativeMouse()) { return false; } return SDLActivity.getMotionListener().setRelativeMouseEnabled(enabled); } /** * This method is called by SDL using JNI. */ public static boolean sendMessage(int command, int param) { if (mSingleton == null) { return false; } return mSingleton.sendCommand(command, param); } /** * This method is called by SDL using JNI. */ public static Context getContext() { return SDL.getContext(); } /** * This method is called by SDL using JNI. */ public static boolean isAndroidTV() { UiModeManager uiModeManager = (UiModeManager) getContext().getSystemService(UI_MODE_SERVICE); if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) { return true; } if (Build.MANUFACTURER.equals("MINIX") && Build.MODEL.equals("NEO-U1")) { return true; } if (Build.MANUFACTURER.equals("Amlogic") && Build.MODEL.equals("X96-W")) { return true; } return Build.MANUFACTURER.equals("Amlogic") && Build.MODEL.startsWith("TV"); } public static double getDiagonal() { DisplayMetrics metrics = new DisplayMetrics(); Activity activity = (Activity)getContext(); if (activity == null) { return 0.0; } activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); double dWidthInches = metrics.widthPixels / (double)metrics.xdpi; double dHeightInches = metrics.heightPixels / (double)metrics.ydpi; return Math.sqrt((dWidthInches * dWidthInches) + (dHeightInches * dHeightInches)); } /** * This method is called by SDL using JNI. */ public static boolean isTablet() { // If our diagonal size is seven inches or greater, we consider ourselves a tablet. return (getDiagonal() >= 7.0); } /** * This method is called by SDL using JNI. */ public static boolean isChromebook() { if (getContext() == null) { return false; } return getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management"); } /** * This method is called by SDL using JNI. */ public static boolean isDeXMode() { if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) { return false; } try { final Configuration config = getContext().getResources().getConfiguration(); final Class configClass = config.getClass(); return configClass.getField("SEM_DESKTOP_MODE_ENABLED").getInt(configClass) == configClass.getField("semDesktopModeEnabled").getInt(config); } catch(Exception ignored) { return false; } } /** * This method is called by SDL using JNI. */ public static DisplayMetrics getDisplayDPI() { return getContext().getResources().getDisplayMetrics(); } /** * This method is called by SDL using JNI. */ public static boolean getManifestEnvironmentVariables() { try { if (getContext() == null) { return false; } ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA); Bundle bundle = applicationInfo.metaData; if (bundle == null) { return false; } String prefix = "SDL_ENV."; final int trimLength = prefix.length(); for (String key : bundle.keySet()) { if (key.startsWith(prefix)) { String name = key.substring(trimLength); String value = bundle.get(key).toString(); nativeSetenv(name, value); } } /* environment variables set! */ return true; } catch (Exception e) { Log.v(TAG, "exception " + e.toString()); } return false; } // This method is called by SDLControllerManager's API 26 Generic Motion Handler. public static View getContentView() { return mLayout; } static class ShowTextInputTask implements Runnable { /* * This is used to regulate the pan&scan method to have some offset from * the bottom edge of the input region and the top edge of an input * method (soft keyboard) */ static final int HEIGHT_PADDING = 15; public int x, y, w, h; public ShowTextInputTask(int x, int y, int w, int h) { this.x = x; this.y = y; this.w = w; this.h = h; /* Minimum size of 1 pixel, so it takes focus. */ if (this.w <= 0) { this.w = 1; } if (this.h + HEIGHT_PADDING <= 0) { this.h = 1 - HEIGHT_PADDING; } } @Override public void run() { RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING); params.leftMargin = x; params.topMargin = y; if (mTextEdit == null) { mTextEdit = new DummyEdit(SDL.getContext()); mLayout.addView(mTextEdit, params); } else { mTextEdit.setLayoutParams(params); } mTextEdit.setVisibility(View.VISIBLE); mTextEdit.requestFocus(); InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(mTextEdit, 0); mScreenKeyboardShown = true; } } /** * This method is called by SDL using JNI. */ public static boolean showTextInput(int x, int y, int w, int h) { // Transfer the task to the main thread as a Runnable return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h)); } public static boolean isTextInputEvent(KeyEvent event) { // Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT if (event.isCtrlPressed()) { return false; } return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE; } public static boolean handleKeyEvent(View v, int keyCode, KeyEvent event, InputConnection ic) { int deviceId = event.getDeviceId(); int source = event.getSource(); if (source == InputDevice.SOURCE_UNKNOWN) { InputDevice device = InputDevice.getDevice(deviceId); if (device != null) { source = device.getSources(); } } // if (event.getAction() == KeyEvent.ACTION_DOWN) { // Log.v("SDL", "key down: " + keyCode + ", deviceId = " + deviceId + ", source = " + source); // } else if (event.getAction() == KeyEvent.ACTION_UP) { // Log.v("SDL", "key up: " + keyCode + ", deviceId = " + deviceId + ", source = " + source); // } // Dispatch the different events depending on where they come from // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD // // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and // SOURCE_JOYSTICK, while its key events arrive from the keyboard source // So, retrieve the device itself and check all of its sources if (SDLControllerManager.isDeviceSDLJoystick(deviceId)) { // Note that we process events with specific key codes here if (event.getAction() == KeyEvent.ACTION_DOWN) { if (SDLControllerManager.onNativePadDown(deviceId, keyCode) == 0) { return true; } } else if (event.getAction() == KeyEvent.ACTION_UP) { if (SDLControllerManager.onNativePadUp(deviceId, keyCode) == 0) { return true; } } } if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) { // on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses // they are ignored here because sending them as mouse input to SDL is messy if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) { switch (event.getAction()) { case KeyEvent.ACTION_DOWN: case KeyEvent.ACTION_UP: // mark the event as handled or it will be handled by system // handling KEYCODE_BACK by system will call onBackPressed() return true; } } } if (event.getAction() == KeyEvent.ACTION_DOWN) { if (isTextInputEvent(event)) { if (ic != null) { ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1); } else { SDLInputConnection.nativeCommitText(String.valueOf((char) event.getUnicodeChar()), 1); } } onNativeKeyDown(keyCode); return true; } else if (event.getAction() == KeyEvent.ACTION_UP) { onNativeKeyUp(keyCode); return true; } return false; } /** * This method is called by SDL using JNI. */ public static Surface getNativeSurface() { if (SDLActivity.mSurface == null) { return null; } return SDLActivity.mSurface.getNativeSurface(); } // Input /** * This method is called by SDL using JNI. */ public static void initTouch() { int[] ids = InputDevice.getDeviceIds(); for (int id : ids) { InputDevice device = InputDevice.getDevice(id); /* Allow SOURCE_TOUCHSCREEN and also Virtual InputDevices because they can send TOUCHSCREEN events */ if (device != null && ((device.getSources() & InputDevice.SOURCE_TOUCHSCREEN) == InputDevice.SOURCE_TOUCHSCREEN || device.isVirtual())) { int touchDevId = device.getId(); /* * Prevent id to be -1, since it's used in SDL internal for synthetic events * Appears when using Android emulator, eg: * adb shell input mouse tap 100 100 * adb shell input touchscreen tap 100 100 */ if (touchDevId < 0) { touchDevId -= 1; } nativeAddTouch(touchDevId, device.getName()); } } } // Messagebox /** Result of current messagebox. Also used for blocking the calling thread. */ protected final int[] messageboxSelection = new int[1]; /** * This method is called by SDL using JNI. * Shows the messagebox from UI thread and block calling thread. * buttonFlags, buttonIds and buttonTexts must have same length. * @param buttonFlags array containing flags for every button. * @param buttonIds array containing id for every button. * @param buttonTexts array containing text for every button. * @param colors null for default or array of length 5 containing colors. * @return button id or -1. */ public int messageboxShowMessageBox( final int flags, final String title, final String message, final int[] buttonFlags, final int[] buttonIds, final String[] buttonTexts, final int[] colors) { messageboxSelection[0] = -1; // sanity checks if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) { return -1; // implementation broken } // collect arguments for Dialog final Bundle args = new Bundle(); args.putInt("flags", flags); args.putString("title", title); args.putString("message", message); args.putIntArray("buttonFlags", buttonFlags); args.putIntArray("buttonIds", buttonIds); args.putStringArray("buttonTexts", buttonTexts); args.putIntArray("colors", colors); // trigger Dialog creation on UI thread runOnUiThread(new Runnable() { @Override public void run() { messageboxCreateAndShow(args); } }); // block the calling thread synchronized (messageboxSelection) { try { messageboxSelection.wait(); } catch (InterruptedException ex) { ex.printStackTrace(); return -1; } } // return selected value return messageboxSelection[0]; } protected void messageboxCreateAndShow(Bundle args) { // TODO set values from "flags" to messagebox dialog // get colors int[] colors = args.getIntArray("colors"); int backgroundColor; int textColor; int buttonBorderColor; int buttonBackgroundColor; int buttonSelectedColor; if (colors != null) { int i = -1; backgroundColor = colors[++i]; textColor = colors[++i]; buttonBorderColor = colors[++i]; buttonBackgroundColor = colors[++i]; buttonSelectedColor = colors[++i]; } else { backgroundColor = Color.TRANSPARENT; textColor = Color.TRANSPARENT; buttonBorderColor = Color.TRANSPARENT; buttonBackgroundColor = Color.TRANSPARENT; buttonSelectedColor = Color.TRANSPARENT; } // create dialog with title and a listener to wake up calling thread final AlertDialog dialog = new AlertDialog.Builder(this).create(); dialog.setTitle(args.getString("title")); dialog.setCancelable(false); dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface unused) { synchronized (messageboxSelection) { messageboxSelection.notify(); } } }); // create text TextView message = new TextView(this); message.setGravity(Gravity.CENTER); message.setText(args.getString("message")); if (textColor != Color.TRANSPARENT) { message.setTextColor(textColor); } // create buttons int[] buttonFlags = args.getIntArray("buttonFlags"); int[] buttonIds = args.getIntArray("buttonIds"); String[] buttonTexts = args.getStringArray("buttonTexts"); final SparseArray