Compare commits

..

1 Commits

Author SHA1 Message Date
mniip
e812406a15 Refactor zoom window 2023-04-14 10:46:10 +02:00
676 changed files with 20032 additions and 24754 deletions

201
.github/build.sh vendored
View File

@ -40,45 +40,11 @@ x86-android-bionic-static) ;;
x86_64-android-bionic-static) ;; x86_64-android-bionic-static) ;;
arm-android-bionic-static) ;; arm-android-bionic-static) ;;
aarch64-android-bionic-static) ;; aarch64-android-bionic-static) ;;
wasm32-emscripten-emscripten-static) ;;
*) >&2 echo "configuration $BSH_HOST_ARCH-$BSH_HOST_PLATFORM-$BSH_HOST_LIBC-$BSH_STATIC_DYNAMIC is not supported" && exit 1;; *) >&2 echo "configuration $BSH_HOST_ARCH-$BSH_HOST_PLATFORM-$BSH_HOST_LIBC-$BSH_STATIC_DYNAMIC is not supported" && exit 1;;
esac esac
if [[ $BSH_HOST_PLATFORM == android ]]; then
android_platform=android-31
if [[ -z "${JAVA_HOME_8_X64-}" ]]; then
>&2 echo "JAVA_HOME_8_X64 not set"
exit 1
fi
if [[ -z "${ANDROID_SDK_ROOT-}" ]]; then
>&2 echo "ANDROID_SDK_ROOT not set"
exit 1
fi
if [[ -z "${ANDROID_NDK_LATEST_HOME-}" ]]; then
>&2 echo "ANDROID_NDK_LATEST_HOME not set"
exit 1
fi
fi
if [[ -z ${BSH_NO_PACKAGES-} ]]; then if [[ -z ${BSH_NO_PACKAGES-} ]]; then
case $BSH_HOST_PLATFORM in case $BSH_BUILD_PLATFORM in
android)
(
export PATH=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$ANDROID_SDK_ROOT/tools/bin:$PATH
sdkmanager "platforms;$android_platform"
)
;;
windows)
if [[ $BSH_BUILD_PLATFORM-$BSH_HOST_LIBC == windows-mingw ]]; then
pacman -S --noconfirm --needed mingw-w64-ucrt-x86_64-gcc
if [[ $BSH_STATIC_DYNAMIC == static ]]; then
pacman -S --noconfirm --needed mingw-w64-ucrt-x86_64-{cmake,7zip,jq} patch
else
pacman -S --noconfirm --needed mingw-w64-ucrt-x86_64-{pkgconf,bzip2,luajit,jsoncpp,curl,SDL2,libpng,meson,fftw,jq}
fi
export PKG_CONFIG=$(which pkg-config.exe)
fi
;;
linux) linux)
sudo apt update sudo apt update
if [[ $BSH_STATIC_DYNAMIC == static ]]; then if [[ $BSH_STATIC_DYNAMIC == static ]]; then
@ -86,6 +52,9 @@ if [[ -z ${BSH_NO_PACKAGES-} ]]; then
else else
sudo apt install libluajit-5.1-dev libcurl4-openssl-dev libfftw3-dev zlib1g-dev libsdl2-dev libbz2-dev libjsoncpp-dev sudo apt install libluajit-5.1-dev libcurl4-openssl-dev libfftw3-dev zlib1g-dev libsdl2-dev libbz2-dev libjsoncpp-dev
fi fi
if [[ $BSH_HOST_PLATFORM-$BSH_HOST_LIBC == windows-mingw ]]; then
sudo apt install g++-mingw-w64-x86-64
fi
;; ;;
darwin) darwin)
brew install pkg-config binutils brew install pkg-config binutils
@ -93,14 +62,6 @@ if [[ -z ${BSH_NO_PACKAGES-} ]]; then
brew install luajit curl fftw zlib sdl2 bzip2 jsoncpp brew install luajit curl fftw zlib sdl2 bzip2 jsoncpp
fi fi
;; ;;
emscripten)
git clone https://github.com/emscripten-core/emsdk.git --branch 3.1.30
cd emsdk
./emsdk install latest
./emsdk activate latest
. ./emsdk_env.sh
cd ..
;;
esac esac
fi fi
@ -114,6 +75,34 @@ function inplace_sed() {
fi fi
} }
function subst_version() {
local path=$1
if [[ $BSH_HOST_PLATFORM == darwin ]]; then
inplace_sed "s|SUBST_MACOS_MIN_VER|$macos_min_ver|g" $path
else
inplace_sed "s|SUBST_DATE|$(date --iso-8601)|g" $path
fi
inplace_sed "s|SUBST_SAVE_VERSION|$save_version|g" $path
inplace_sed "s|SUBST_MINOR_VERSION|$minor_version|g" $path
inplace_sed "s|SUBST_BUILD_NUM|$build_num|g" $path
}
if [[ $BSH_HOST_PLATFORM == android ]]; then
android_platform=android-30
if [[ -z "${JAVA_HOME_8_X64-}" ]]; then
>&2 echo "JAVA_HOME_8_X64 not set"
exit 1
fi
if [[ -z "${ANDROID_SDK_ROOT-}" ]]; then
>&2 echo "ANDROID_SDK_ROOT not set"
exit 1
fi
if [[ -z "${ANDROID_NDK_LATEST_HOME-}" ]]; then
>&2 echo "ANDROID_NDK_LATEST_HOME not set"
exit 1
fi
fi
if [[ $BSH_HOST_PLATFORM-$BSH_HOST_LIBC == windows-msvc ]]; then if [[ $BSH_HOST_PLATFORM-$BSH_HOST_LIBC == windows-msvc ]]; then
case $BSH_HOST_ARCH in case $BSH_HOST_ARCH in
x86_64) vs_env_arch=x64;; x86_64) vs_env_arch=x64;;
@ -142,9 +131,9 @@ elif [[ $BSH_HOST_PLATFORM == darwin ]]; then
elif [[ $BSH_HOST_PLATFORM == android ]]; then elif [[ $BSH_HOST_PLATFORM == android ]]; then
case $BSH_HOST_ARCH in case $BSH_HOST_ARCH in
x86_64) android_toolchain_prefix=x86_64-linux-android ; android_system_version=21; android_arch_abi=x86_64 ;; x86_64) android_toolchain_prefix=x86_64-linux-android ; android_system_version=21; android_arch_abi=x86_64 ;;
x86) android_toolchain_prefix=i686-linux-android ; android_system_version=21; android_arch_abi=x86 ;; x86) android_toolchain_prefix=i686-linux-android ; android_system_version=19; android_arch_abi=x86 ;;
aarch64) android_toolchain_prefix=aarch64-linux-android ; android_system_version=21; android_arch_abi=arm64-v8a ;; aarch64) android_toolchain_prefix=aarch64-linux-android ; android_system_version=21; android_arch_abi=arm64-v8a ;;
arm) android_toolchain_prefix=armv7a-linux-androideabi; android_system_version=21; android_arch_abi=armeabi-v7a;; arm) android_toolchain_prefix=armv7a-linux-androideabi; android_system_version=19; android_arch_abi=armeabi-v7a;;
esac esac
android_toolchain_dir=$ANDROID_NDK_LATEST_HOME/toolchains/llvm/prebuilt/linux-x86_64 android_toolchain_dir=$ANDROID_NDK_LATEST_HOME/toolchains/llvm/prebuilt/linux-x86_64
CC=$android_toolchain_dir/bin/$android_toolchain_prefix$android_system_version-clang CC=$android_toolchain_dir/bin/$android_toolchain_prefix$android_system_version-clang
@ -188,18 +177,13 @@ meson_configure=meson$'\t'setup
if [[ $BSH_DEBUG_RELEASE == release ]]; then if [[ $BSH_DEBUG_RELEASE == release ]]; then
meson_configure+=$'\t'-Dbuildtype=debugoptimized meson_configure+=$'\t'-Dbuildtype=debugoptimized
fi fi
if [[ $BSH_HOST_PLATFORM == darwin ]]; then
meson_configure+=$'\t'-Dmanifest_macos_min_ver=$macos_min_ver
else
meson_configure+=$'\t'-Dmanifest_date=$(date --iso-8601)
fi
meson_configure+=$'\t'-Dapp_name=$APP_NAME meson_configure+=$'\t'-Dapp_name=$APP_NAME
meson_configure+=$'\t'-Dapp_comment=$APP_COMMENT meson_configure+=$'\t'-Dapp_comment=$APP_COMMENT
meson_configure+=$'\t'-Dapp_exe=$APP_EXE meson_configure+=$'\t'-Dapp_exe=$APP_EXE
meson_configure+=$'\t'-Dapp_id=$APP_ID meson_configure+=$'\t'-Dapp_id=$APP_ID
meson_configure+=$'\t'-Dapp_data=$APP_DATA meson_configure+=$'\t'-Dapp_data=$APP_DATA
meson_configure+=$'\t'-Dapp_vendor=$APP_VENDOR meson_configure+=$'\t'-Dapp_vendor=$APP_VENDOR
meson_configure+=$'\t'-Dstrip=false meson_configure+=$'\t'-Db_strip=false
meson_configure+=$'\t'-Db_staticpic=false meson_configure+=$'\t'-Db_staticpic=false
meson_configure+=$'\t'-Dmod_id=$MOD_ID meson_configure+=$'\t'-Dmod_id=$MOD_ID
case $BSH_HOST_ARCH-$BSH_HOST_PLATFORM-$BSH_HOST_LIBC-$BSH_DEBUG_RELEASE in case $BSH_HOST_ARCH-$BSH_HOST_PLATFORM-$BSH_HOST_LIBC-$BSH_DEBUG_RELEASE in
@ -217,12 +201,6 @@ fi
if [[ $PACKAGE_MODE == nolua ]]; then if [[ $PACKAGE_MODE == nolua ]]; then
meson_configure+=$'\t'-Dlua=none meson_configure+=$'\t'-Dlua=none
fi fi
if [[ $PACKAGE_MODE == backendvs ]]; then
meson_configure+=$'\t'-Dbackend=vs
# meson 1.2.3 configures vs projects that bring their own manifest, which conflicts with ours
# TODO: remove this patch once https://github.com/mesonbuild/meson/pull/12472 makes it into a release that we can use
meson_configure+=$'\t'-Dwindows_utf8cp=false
fi
if [[ $BSH_STATIC_DYNAMIC == static ]]; then if [[ $BSH_STATIC_DYNAMIC == static ]]; then
meson_configure+=$'\t'-Dstatic=prebuilt meson_configure+=$'\t'-Dstatic=prebuilt
if [[ $BSH_HOST_PLATFORM == windows ]]; then if [[ $BSH_HOST_PLATFORM == windows ]]; then
@ -238,11 +216,6 @@ if [[ $BSH_STATIC_DYNAMIC == static ]]; then
c_link_args+=\'-static-libstdc++\', c_link_args+=\'-static-libstdc++\',
fi fi
else else
if [[ "$BSH_HOST_PLATFORM-$BSH_HOST_LIBC $BSH_BUILD_PLATFORM" == "windows-mingw windows" ]]; then
meson_configure+=$'\t'-Dworkaround_elusive_bzip2=true
meson_configure+=$'\t'-Dworkaround_elusive_bzip2_include_dir=/ucrt64/include
meson_configure+=$'\t'-Dworkaround_elusive_bzip2_lib_dir=/ucrt64/lib
fi
if [[ $BSH_BUILD_PLATFORM == linux ]]; then if [[ $BSH_BUILD_PLATFORM == linux ]]; then
meson_configure+=$'\t'-Dworkaround_elusive_bzip2=true meson_configure+=$'\t'-Dworkaround_elusive_bzip2=true
fi fi
@ -266,54 +239,52 @@ fi
if [[ $RELEASE_TYPE == stable ]]; then if [[ $RELEASE_TYPE == stable ]]; then
stable_or_beta=yes stable_or_beta=yes
fi fi
if [[ $stable_or_beta == yes ]]; then set +e
xyz=$(echo $RELEASE_NAME | cut -d 'v' -f 2 | cut -d 'b' -f 1) # $RELEASE_NAME is vX.Y.Z or vX.Y.Zb save_version=$(cat src/Config.template.h | sed -n 's/constexpr int SAVE_VERSION * = \([^;]*\);/\1/p')
display_version_major=$(echo $xyz | cut -d '.' -f 1) minor_version=$(cat src/Config.template.h | sed -n 's/constexpr int MINOR_VERSION * = \([^;]*\);/\1/p')
display_version_minor=$(echo $xyz | cut -d '.' -f 2) build_num=$(cat src/Config.template.h | sed -n 's/constexpr int BUILD_NUM * = \([^;]*\);/\1/p')
build_num=$(echo $xyz | cut -d '.' -f 3) if [[ -z ${save_version-} ]] || [[ -z ${minor_version-} ]] || [[ -z ${build_num-} ]]; then
if [[ $MOD_ID != 0 ]]; then >&2 echo "failed to extract version from Config.template.h"
meson_configure+=$'\t'-Ddisplay_version_major=$display_version_major exit 1
meson_configure+=$'\t'-Ddisplay_version_minor=$display_version_minor fi
meson_configure+=$'\t'-Dbuild_num=$build_num set -e
fi if [[ $stable_or_beta == yes ]] && [[ $MOD_ID != 0 ]]; then
save_version=$(echo $RELEASE_NAME | cut -d '.' -f 1)
minor_version=$(echo $RELEASE_NAME | cut -d '.' -f 2)
build_num=$(echo $RELEASE_NAME | cut -d '.' -f 3)
fi fi
if [[ $RELEASE_TYPE == snapshot ]]; then if [[ $RELEASE_TYPE == snapshot ]]; then
build_num=$(echo $RELEASE_NAME | cut -d '-' -f 2) # $RELEASE_NAME is snapshot-X
meson_configure+=$'\t'-Dsnapshot=true meson_configure+=$'\t'-Dsnapshot=true
if [[ $MOD_ID != 0 ]]; then meson_configure+=$'\t'-Dsnapshot_id=$(echo $RELEASE_NAME | cut -d '-' -f 2) # $RELEASE_NAME is snapshot-X
meson_configure+=$'\t'-Dbuild_num=$build_num
fi
fi fi
if [[ $RELEASE_TYPE == snapshot ]] && [[ $MOD_ID != 0 ]]; then if [[ $RELEASE_TYPE == snapshot ]] && [[ $MOD_ID != 0 ]]; then
>&2 echo "mods and snapshots do not mix" >&2 echo "mods and snapshots do not mix"
exit 1 exit 1
fi fi
if [[ $stable_or_beta == yes ]] && [[ $MOD_ID != 0 ]]; then
# mods and snapshots both check their snapshot_id against whatever version starcatcher.us/TPT has
meson_configure+=$'\t'-Dsnapshot_id=$(echo $RELEASE_NAME | cut -d '.' -f 3) # $RELEASE_NAME is vX.Y.Z
fi
if [[ $RELEASE_TYPE == snapshot ]] || [[ $MOD_ID != 0 ]]; then if [[ $RELEASE_TYPE == snapshot ]] || [[ $MOD_ID != 0 ]]; then
meson_configure+=$'\t'-Dupdate_server=starcatcher.us/TPT meson_configure+=$'\t'-Dupdate_server=starcatcher.us/TPT
if [[ $BSH_HOST_PLATFORM == emscripten ]]; then
meson_configure+=$'\t'-Dserver=tptserv.starcatcher.us
meson_configure+=$'\t'-Dstatic_server=tptserv.starcatcher.us/Static
fi
fi fi
if [[ $RELEASE_TYPE != dev ]]; then if [[ $RELEASE_TYPE != dev ]]; then
meson_configure+=$'\t'-Dignore_updates=false meson_configure+=$'\t'-Dignore_updates=false
fi fi
if [[ "$BSH_HOST_PLATFORM-$BSH_HOST_LIBC" == "windows-mingw" ]]; then if [[ $BSH_HOST_PLATFORM-$BSH_HOST_LIBC == windows-mingw ]]; then
meson_configure+=$'\t'--cross-file=.github/mingw-ghactions.ini if [[ $BSH_BUILD_PLATFORM == linux ]]; then
# there is some mingw bug that only ever manifests on ghactions which makes MakeIco.exe use tons of memory and fail meson_configure+=$'\t'--cross-file=.github/mingw-ghactions.ini
# TODO: remove this hack once we figure out how to fix that fi
meson_configure+=$'\t'-Dwindows_icons=false
fi fi
if [[ $BSH_DEBUG_RELEASE-$BSH_STATIC_DYNAMIC == release-static ]]; then if [[ $BSH_HOST_PLATFORM-$BSH_HOST_LIBC != windows-mingw ]] && [[ $BSH_STATIC_DYNAMIC == static ]]; then
# LTO simply doesn't work with MinGW. I have no idea why and I also don't care.
# It also has a tendency to not play well with dynamic libraries.
meson_configure+=$'\t'-Db_lto=true meson_configure+=$'\t'-Db_lto=true
fi fi
if [[ $BSH_HOST_PLATFORM-$BSH_HOST_ARCH == darwin-aarch64 ]]; then if [[ $BSH_HOST_PLATFORM-$BSH_HOST_ARCH == darwin-aarch64 ]]; then
meson_configure+=$'\t'--cross-file=.github/macaa64-ghactions.ini meson_configure+=$'\t'--cross-file=.github/macaa64-ghactions.ini
fi fi
if [[ $BSH_HOST_PLATFORM == emscripten ]]; then if [[ $RELEASE_TYPE == tptlibsdev ]] && ([[ $BSH_HOST_PLATFORM == windows ]] || [[ $BSH_STATIC_DYNAMIC == static ]]); then
meson_configure+=$'\t'--cross-file=.github/emscripten-ghactions.ini
fi
if [[ $RELEASE_TYPE == tptlibsdev ]] && ([[ $BSH_HOST_PLATFORM-$BSH_HOST_LIBC == windows-msvc ]] || [[ $BSH_STATIC_DYNAMIC == static ]]); then
if [[ -z ${TPTLIBSREMOTE-} ]]; then if [[ -z ${TPTLIBSREMOTE-} ]]; then
if [[ -z "${GITHUB_REPOSITORY_OWNER-}" ]]; then if [[ -z "${GITHUB_REPOSITORY_OWNER-}" ]]; then
>&2 echo "GITHUB_REPOSITORY_OWNER not set" >&2 echo "GITHUB_REPOSITORY_OWNER not set"
@ -323,6 +294,11 @@ if [[ $RELEASE_TYPE == tptlibsdev ]] && ([[ $BSH_HOST_PLATFORM-$BSH_HOST_LIBC ==
else else
tptlibsremote=$TPTLIBSREMOTE tptlibsremote=$TPTLIBSREMOTE
fi fi
if [[ "$BSH_HOST_ARCH-$BSH_HOST_PLATFORM-$BSH_HOST_LIBC-$BSH_STATIC_DYNAMIC $BSH_BUILD_PLATFORM" == "x86_64-windows-mingw-dynamic linux" ]]; then
>&2 echo "this configuration is not supported in tptlibsdev mode"
touch $ASSET_PATH
exit 0
fi
tptlibsbranch=$(echo $RELEASE_NAME | cut -d '-' -f 2-) # $RELEASE_NAME is tptlibsdev-BRANCH tptlibsbranch=$(echo $RELEASE_NAME | cut -d '-' -f 2-) # $RELEASE_NAME is tptlibsdev-BRANCH
if [[ -d build-tpt-libs ]] && [[ ${TPTLIBSRESET-} == yes ]]; then if [[ -d build-tpt-libs ]] && [[ ${TPTLIBSRESET-} == yes ]]; then
rm -rf build-tpt-libs rm -rf build-tpt-libs
@ -350,11 +326,7 @@ if [[ $RELEASE_TYPE == tptlibsdev ]] && ([[ $BSH_HOST_PLATFORM-$BSH_HOST_LIBC ==
meson_configure+=$'\t'-Dtpt_libs_vtag=$tpt_libs_vtag meson_configure+=$'\t'-Dtpt_libs_vtag=$tpt_libs_vtag
fi fi
if [[ $BSH_HOST_PLATFORM == android ]]; then if [[ $BSH_HOST_PLATFORM == android ]]; then
android_platform_jar=$ANDROID_SDK_ROOT/platforms/$android_platform/android.jar android_platform=android-30
if ! [[ -f $android_platform_jar ]]; then
>&2 echo "$android_platform_jar not found"
exit 1
fi
meson_configure+=$'\t'--cross-file=android/cross/$BSH_HOST_ARCH.ini meson_configure+=$'\t'--cross-file=android/cross/$BSH_HOST_ARCH.ini
cat << ANDROID_INI > .github/android-ghactions.ini cat << ANDROID_INI > .github/android-ghactions.ini
[constants] [constants]
@ -365,7 +337,7 @@ andriod_sdk_build_tools = '$ANDROID_SDK_ROOT/build-tools/32.0.0'
# android_ndk_toolchain_prefix comes from the correct cross-file in ./android/cross # android_ndk_toolchain_prefix comes from the correct cross-file in ./android/cross
android_ndk_toolchain_prefix = android_ndk_toolchain_prefix android_ndk_toolchain_prefix = android_ndk_toolchain_prefix
android_platform = '$android_platform' android_platform = '$android_platform'
android_platform_jar = '$android_platform_jar' android_platform_jar = '$ANDROID_SDK_ROOT/platforms/' + android_platform + '/android.jar'
java_runtime_jar = '$JAVA_HOME_8_X64/jre/lib/rt.jar' java_runtime_jar = '$JAVA_HOME_8_X64/jre/lib/rt.jar'
[binaries] [binaries]
@ -382,6 +354,7 @@ zipalign = andriod_sdk_build_tools / 'zipalign'
apksigner = andriod_sdk_build_tools / 'apksigner' apksigner = andriod_sdk_build_tools / 'apksigner'
ANDROID_INI ANDROID_INI
meson_configure+=$'\t'--cross-file=.github/android-ghactions.ini meson_configure+=$'\t'--cross-file=.github/android-ghactions.ini
meson_configure+=$'\t'-Dhttp=false
fi fi
meson_configure+=$'\t'-Dc_args=[$c_args] meson_configure+=$'\t'-Dc_args=[$c_args]
meson_configure+=$'\t'-Dcpp_args=[$c_args] meson_configure+=$'\t'-Dcpp_args=[$c_args]
@ -389,29 +362,6 @@ meson_configure+=$'\t'-Dc_link_args=[$c_link_args]
meson_configure+=$'\t'-Dcpp_link_args=[$c_link_args] meson_configure+=$'\t'-Dcpp_link_args=[$c_link_args]
$meson_configure build $meson_configure build
cd build cd build
function verify_version_component() {
local key=$1
local expected=$2
local actual=$(jq -r '.[] | select(.name == "'$key'") | .value' < meson-info/intro-buildoptions.json)
if [[ $actual != $expected ]]; then
>&2 echo "meson option $key expected to be $expected, is instead $actual"
exit 1
fi
}
if [[ $stable_or_beta == yes ]] && [[ $MOD_ID == 0 ]]; then
verify_version_component display_version_major $display_version_major
verify_version_component display_version_minor $display_version_minor
verify_version_component build_num $build_num
verify_version_component upstream_version_major $display_version_major
verify_version_component upstream_version_minor $display_version_minor
verify_version_component upstream_build_num $build_num
fi
if [[ $RELEASE_TYPE == snapshot ]] && [[ $MOD_ID == 0 ]]; then
verify_version_component build_num $build_num
verify_version_component upstream_build_num $build_num
fi
strip=strip strip=strip
objcopy=objcopy objcopy=objcopy
strip_target=$ASSET_PATH strip_target=$ASSET_PATH
@ -425,12 +375,9 @@ if [[ $PACKAGE_MODE == appimage ]]; then
meson configure -Dcan_install=no -Dignore_updates=true -Dbuild_render=false -Dbuild_font=false meson configure -Dcan_install=no -Dignore_updates=true -Dbuild_render=false -Dbuild_font=false
strip_target=$APP_EXE strip_target=$APP_EXE
fi fi
meson_compile=meson$'\t'compile if [[ $BSH_BUILD_PLATFORM == windows ]]; then
meson_compile+=$'\t'-v
if [[ $BSH_BUILD_PLATFORM == windows ]] && [[ $PACKAGE_MODE != backendvs ]]; then
set +e set +e
meson_compile+=$'\t'--ninja-args='["-d","keeprsp"]' ninja -v -d keeprsp
$meson_compile
ninja_code=$? ninja_code=$?
set -e set -e
cat $APP_EXE.exe.rsp cat $APP_EXE.exe.rsp
@ -444,7 +391,7 @@ if [[ $BSH_BUILD_PLATFORM == windows ]] && [[ $PACKAGE_MODE != backendvs ]]; the
fi fi
fi fi
else else
$meson_compile ninja -v
fi fi
if [[ $SEPARATE_DEBUG == yes ]] && [[ $BSH_HOST_PLATFORM-$BSH_HOST_LIBC != windows-msvc ]]; then if [[ $SEPARATE_DEBUG == yes ]] && [[ $BSH_HOST_PLATFORM-$BSH_HOST_LIBC != windows-msvc ]]; then
@ -465,6 +412,7 @@ if [[ $PACKAGE_MODE == dmg ]]; then
mkdir $appdir mkdir $appdir
mkdir $appdir/Contents mkdir $appdir/Contents
cp resources/Info.plist $appdir/Contents/Info.plist cp resources/Info.plist $appdir/Contents/Info.plist
subst_version $appdir/Contents/Info.plist
mkdir $appdir/Contents/MacOS mkdir $appdir/Contents/MacOS
cp $APP_EXE $appdir/Contents/MacOS/$APP_EXE cp $APP_EXE $appdir/Contents/MacOS/$APP_EXE
mkdir $appdir/Contents/Resources mkdir $appdir/Contents/Resources
@ -485,8 +433,6 @@ if [[ $PACKAGE_MODE == dmg ]]; then
cp ../LICENSE dmgroot/LICENSE cp ../LICENSE dmgroot/LICENSE
cp ../README.md dmgroot/README.md cp ../README.md dmgroot/README.md
hdiutil create -format UDZO -volname $APP_NAME -fs HFS+ -srcfolder dmgroot -o $ASSET_PATH hdiutil create -format UDZO -volname $APP_NAME -fs HFS+ -srcfolder dmgroot -o $ASSET_PATH
elif [[ $PACKAGE_MODE == emscripten ]]; then
tar cvf $ASSET_PATH $APP_EXE.js $APP_EXE.worker.js $APP_EXE.wasm
elif [[ $PACKAGE_MODE == appimage ]]; then elif [[ $PACKAGE_MODE == appimage ]]; then
# so far this can only happen with $BSH_HOST_PLATFORM-$BSH_HOST_LIBC == linux-gnu, but this may change later # so far this can only happen with $BSH_HOST_PLATFORM-$BSH_HOST_LIBC == linux-gnu, but this may change later
case $BSH_HOST_ARCH in case $BSH_HOST_ARCH in
@ -509,6 +455,7 @@ elif [[ $PACKAGE_MODE == appimage ]]; then
cp ../resources/icon_exe.svg $appdir/$APP_VENDOR-$APP_EXE.svg cp ../resources/icon_exe.svg $appdir/$APP_VENDOR-$APP_EXE.svg
cp resources/powder.desktop $appdir/$APP_ID.desktop cp resources/powder.desktop $appdir/$APP_ID.desktop
cp resources/appdata.xml $appdir/usr/share/metainfo/$APP_ID.appdata.xml cp resources/appdata.xml $appdir/usr/share/metainfo/$APP_ID.appdata.xml
subst_version $appdir/usr/share/metainfo/$APP_ID.appdata.xml
cp $appdir/$APP_VENDOR-$APP_EXE.svg $appdir/usr/share/icons/$APP_VENDOR-$APP_EXE.svg cp $appdir/$APP_VENDOR-$APP_EXE.svg $appdir/usr/share/icons/$APP_VENDOR-$APP_EXE.svg
cp $appdir/$APP_ID.desktop $appdir/usr/share/applications/$APP_ID.desktop cp $appdir/$APP_ID.desktop $appdir/usr/share/applications/$APP_ID.desktop
./appimagetool $appdir $ASSET_PATH ./appimagetool $appdir $ASSET_PATH

View File

@ -1,4 +0,0 @@
set -euo pipefail
IFS=$'\t\n'
gh release create --draft --verify-tag --title $RELEASE_NAME $GITHUB_REF_NAME

View File

@ -1,11 +0,0 @@
[binaries]
c = 'emcc'
cpp = 'em++'
strip = 'emstrip'
ar = 'emar'
[host_machine]
system = 'emscripten'
cpu_family = 'wasm32'
cpu = 'wasm32'
endian = 'little'

View File

@ -1,7 +1,6 @@
[binaries] [binaries]
c = [ 'clang', '-arch', 'arm64' ] c = [ 'clang', '-arch', 'arm64' ]
cpp = [ 'clang++', '-arch', 'arm64' ] cpp = [ 'clang++', '-arch', 'arm64' ]
objcpp = [ 'clang++', '-arch', 'arm64' ]
strip = 'strip' strip = 'strip'
[host_machine] [host_machine]

View File

@ -2,8 +2,8 @@
prefix = 'x86_64-w64-mingw32' prefix = 'x86_64-w64-mingw32'
[binaries] [binaries]
c = prefix + '-gcc' c = prefix + '-gcc-posix'
cpp = prefix + '-g++' cpp = prefix + '-g++-posix'
strip = prefix + '-strip' strip = prefix + '-strip'
windres = prefix + '-windres' windres = prefix + '-windres'

123
.github/prepare.py vendored
View File

@ -6,7 +6,6 @@ import subprocess
import sys import sys
ref = os.getenv('GITHUB_REF') ref = os.getenv('GITHUB_REF')
event_name = os.getenv('GITHUB_EVENT_NAME')
publish_hostport = os.getenv('PUBLISH_HOSTPORT') publish_hostport = os.getenv('PUBLISH_HOSTPORT')
def set_output(key, value): def set_output(key, value):
@ -17,35 +16,25 @@ match_stable = re.fullmatch(r'refs/tags/v([0-9]+)\.([0-9]+)\.([0-9]+)', ref)
match_beta = re.fullmatch(r'refs/tags/v([0-9]+)\.([0-9]+)\.([0-9]+)b', ref) match_beta = re.fullmatch(r'refs/tags/v([0-9]+)\.([0-9]+)\.([0-9]+)b', ref)
match_snapshot = re.fullmatch(r'refs/tags/snapshot-([0-9]+)', ref) match_snapshot = re.fullmatch(r'refs/tags/snapshot-([0-9]+)', ref)
match_tptlibsdev = re.fullmatch(r'refs/heads/tptlibsdev-(.*)', ref) match_tptlibsdev = re.fullmatch(r'refs/heads/tptlibsdev-(.*)', ref)
match_alljobs = re.fullmatch(r'refs/heads/(.*)-alljobs', ref)
do_release = False do_release = False
do_priority = 10
if event_name == 'pull_request':
do_priority = 0
if match_stable: if match_stable:
release_type = 'stable' release_type = 'stable'
release_name = 'v%s.%s.%s' % (match_stable.group(1), match_stable.group(2), match_stable.group(3)) release_name = 'v%s.%s.%s' % (match_stable.group(1), match_stable.group(2), match_stable.group(3))
do_release = True do_release = True
do_priority = 0
elif match_beta: elif match_beta:
release_type = 'beta' release_type = 'beta'
release_name = 'v%s.%s.%sb' % (match_beta.group(1), match_beta.group(2), match_beta.group(3)) release_name = 'v%s.%s.%sb' % (match_beta.group(1), match_beta.group(2), match_beta.group(3))
do_release = True do_release = True
do_priority = 0
elif match_snapshot: elif match_snapshot:
release_type = 'snapshot' release_type = 'snapshot'
release_name = 'snapshot-%s' % match_snapshot.group(1) release_name = 'snapshot-%s' % match_snapshot.group(1)
do_release = True do_release = True
do_priority = 0
elif match_tptlibsdev: elif match_tptlibsdev:
release_type = 'tptlibsdev' release_type = 'tptlibsdev'
release_name = 'tptlibsdev-%s' % match_tptlibsdev.group(1) release_name = 'tptlibsdev-%s' % match_tptlibsdev.group(1)
do_priority = 0
else: else:
release_type = 'dev' release_type = 'dev'
release_name = 'dev' release_name = 'dev'
if match_alljobs:
do_priority = 0
do_publish = publish_hostport and do_release do_publish = publish_hostport and do_release
set_output('release_type', release_type) set_output('release_type', release_type)
@ -62,23 +51,16 @@ if int(build_options['mod_id']) == 0 and os.path.exists('.github/mod_id.txt'):
build_options['mod_id'] = f.read() build_options['mod_id'] = f.read()
if int(build_options['mod_id']) == 0: if int(build_options['mod_id']) == 0:
if release_type == 'stable': if release_type == 'beta':
pass
elif release_type == 'beta':
build_options['app_name' ] += ' Beta' build_options['app_name' ] += ' Beta'
build_options['app_comment'] += ' - Beta' build_options['app_comment'] += ' - Beta'
build_options['app_exe' ] += 'beta' build_options['app_exe' ] += 'beta'
build_options['app_id' ] += 'beta' build_options['app_id' ] += 'beta'
elif release_type == 'snapshot': if release_type == 'snapshot':
build_options['app_name' ] += ' Snapshot' build_options['app_name' ] += ' Snapshot'
build_options['app_comment'] += ' - Snapshot' build_options['app_comment'] += ' - Snapshot'
build_options['app_exe' ] += 'snapshot' build_options['app_exe' ] += 'snapshot'
build_options['app_id' ] += 'snapshot' build_options['app_id' ] += 'snapshot'
else:
build_options['app_name' ] += ' Dev'
build_options['app_comment'] += ' - Dev'
build_options['app_exe' ] += 'dev'
build_options['app_id' ] += 'dev'
set_output('mod_id' , build_options['mod_id' ]) set_output('mod_id' , build_options['mod_id' ])
set_output('app_name' , build_options['app_name' ]) set_output('app_name' , build_options['app_name' ])
@ -95,61 +77,47 @@ app_name_slug = re.sub('[^A-Za-z0-9]', '_', app_name)
build_matrix = [] build_matrix = []
publish_matrix = [] publish_matrix = []
# consider disabling line wrapping to edit this monstrosity # consider disabling line wrapping to edit this monstrosity
for arch, platform, libc, statdyn, bplatform, runson, suffix, publish, artifact, dbgsuffix, mode, starcatcher, dbgrel, priority in [ for arch, platform, libc, statdyn, bplatform, runson, suffix, publish, artifact, dbgsuffix, mode, starcatcher, dbgrel in [
( 'x86_64', 'linux', 'gnu', 'static', 'linux', 'ubuntu-20.04', '', False, False, None, None, None, 'debug', 0 ), # priority = 0: static debug build ( 'x86_64', 'linux', 'gnu', 'static', 'linux', 'ubuntu-20.04', '', False, False, None, None, None, 'debug' ),
( 'x86_64', 'linux', 'gnu', 'static', 'linux', 'ubuntu-20.04', '', True, True, '.dbg', None, 'x86_64-lin-gcc-static', 'release', 10 ), ( 'x86_64', 'linux', 'gnu', 'static', 'linux', 'ubuntu-20.04', '', True, True, '.dbg', None, 'x86_64-lin-gcc-static', 'release' ),
( 'x86_64', 'linux', 'gnu', 'static', 'linux', 'ubuntu-20.04', '', False, True, '.dbg', 'appimage', None, 'release', 0 ), # priority = 0: appimage release ( 'x86_64', 'linux', 'gnu', 'static', 'linux', 'ubuntu-20.04', '', False, True, '.dbg', 'appimage', None, 'release' ),
( 'x86_64', 'linux', 'gnu', 'dynamic', 'linux', 'ubuntu-20.04', '', False, False, None, None, None, 'debug', 10 ), ( 'x86_64', 'linux', 'gnu', 'dynamic', 'linux', 'ubuntu-20.04', '', False, False, None, None, None, 'debug' ),
( 'x86_64', 'linux', 'gnu', 'dynamic', 'linux', 'ubuntu-20.04', '', False, False, None, 'nohttp', None, 'debug', 10 ), ( 'x86_64', 'linux', 'gnu', 'dynamic', 'linux', 'ubuntu-20.04', '', False, False, None, 'nohttp', None, 'debug' ),
( 'x86_64', 'linux', 'gnu', 'dynamic', 'linux', 'ubuntu-20.04', '', False, False, None, 'nolua', None, 'debug', 10 ), ( 'x86_64', 'linux', 'gnu', 'dynamic', 'linux', 'ubuntu-20.04', '', False, False, None, 'nolua', None, 'debug' ),
( 'x86_64', 'linux', 'gnu', 'dynamic', 'linux', 'ubuntu-20.04', '', False, False, None, None, None, 'release', 10 ), ( 'x86_64', 'linux', 'gnu', 'dynamic', 'linux', 'ubuntu-20.04', '', False, False, None, None, None, 'release' ),
# ( 'x86_64', 'windows', 'mingw', 'static', 'linux', 'ubuntu-20.04', '', False, False, None, None, None, 'debug', 10 ), # ubuntu-20.04 doesn't have windows TLS headers somehow and I haven't yet figured out how to get them; worse, it's a different toolchain # ( 'x86_64', 'windows', 'mingw', 'static', 'linux', 'ubuntu-20.04', '', False, False, None, None, None, 'debug' ), # ubuntu-20.04 doesn't have windows TLS headers somehow and I haven't yet figured out how to get them
# ( 'x86_64', 'windows', 'mingw', 'static', 'linux', 'ubuntu-20.04', '', False, True, '.dbg', None, None, 'release', 10 ), # ubuntu-20.04 doesn't have windows TLS headers somehow and I haven't yet figured out how to get them; worse, it's a different toolchain # ( 'x86_64', 'windows', 'mingw', 'static', 'linux', 'ubuntu-20.04', '', False, True, '.dbg', None, None, 'release' ), # ubuntu-20.04 doesn't have windows TLS headers somehow and I haven't yet figured out how to get them
# ( 'x86_64', 'windows', 'mingw', 'dynamic', 'linux', 'ubuntu-20.04', '', False, False, None, None, None, 'debug', 10 ), # ubuntu-20.04 doesn't have ucrt64-capable mingw >_> ( 'x86_64', 'windows', 'mingw', 'dynamic', 'linux', 'ubuntu-20.04', '', False, False, None, None, None, 'debug' ),
# ( 'x86_64', 'windows', 'mingw', 'dynamic', 'linux', 'ubuntu-20.04', '', False, False, None, None, None, 'release', 10 ), # ubuntu-20.04 doesn't have ucrt64-capable mingw >_> ( 'x86_64', 'windows', 'mingw', 'dynamic', 'linux', 'ubuntu-20.04', '', False, False, None, None, None, 'release' ),
( 'x86_64', 'windows', 'mingw', 'static', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'debug', 0 ), # priority = 0: static debug build ( 'x86_64', 'windows', 'mingw', 'static', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'debug' ),
( 'x86_64', 'windows', 'mingw', 'static', 'windows', 'windows-2019', '.exe', False, True, '.dbg', None, None, 'release', 10 ), ( 'x86_64', 'windows', 'mingw', 'static', 'windows', 'windows-2019', '.exe', False, True, '.dbg', None, None, 'release' ),
( 'x86_64', 'windows', 'mingw', 'dynamic', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'debug', 10 ), ( 'x86_64', 'windows', 'mingw', 'dynamic', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'debug' ),
( 'x86_64', 'windows', 'mingw', 'dynamic', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'release', 10 ), ( 'x86_64', 'windows', 'mingw', 'dynamic', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'release' ),
( 'x86_64', 'windows', 'msvc', 'static', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'debug', 0 ), # priority = 0: static debug build ( 'x86_64', 'windows', 'msvc', 'static', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'debug' ),
( 'x86_64', 'windows', 'msvc', 'static', 'windows', 'windows-2019', '.exe', True, True, '.pdb', None,'x86_64-win-msvc-static', 'release', 10 ), ( 'x86_64', 'windows', 'msvc', 'static', 'windows', 'windows-2019', '.exe', True, True, '.pdb', None,'x86_64-win-msvc-static', 'release' ),
( 'x86_64', 'windows', 'msvc', 'dynamic', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'debug', 10 ), ( 'x86_64', 'windows', 'msvc', 'dynamic', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'debug' ),
# ( 'x86_64', 'windows', 'msvc', 'dynamic', 'windows', 'windows-2019', '.exe', False, False, None, 'backendvs', None, 'debug', 0 ), # priority = 0: backend=vs build ( 'x86_64', 'windows', 'msvc', 'dynamic', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'release' ),
( 'x86_64', 'windows', 'msvc', 'dynamic', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'release', 10 ), ( 'x86', 'windows', 'msvc', 'static', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'debug' ),
( 'x86', 'windows', 'msvc', 'static', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'debug', 0 ), # priority = 0: static debug build ( 'x86', 'windows', 'msvc', 'static', 'windows', 'windows-2019', '.exe', True, True, '.pdb', None, 'i686-win-msvc-static', 'release' ),
( 'x86', 'windows', 'msvc', 'static', 'windows', 'windows-2019', '.exe', True, True, '.pdb', None, 'i686-win-msvc-static', 'release', 10 ), ( 'x86', 'windows', 'msvc', 'dynamic', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'debug' ),
( 'x86', 'windows', 'msvc', 'dynamic', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'debug', 10 ), ( 'x86', 'windows', 'msvc', 'dynamic', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'release' ),
( 'x86', 'windows', 'msvc', 'dynamic', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'release', 10 ), ( 'x86_64', 'darwin', 'macos', 'static', 'darwin', 'macos-11.0', '.dmg', False, False, None, 'dmg', None, 'debug' ),
( 'x86_64', 'darwin', 'macos', 'static', 'darwin', 'macos-12', '.dmg', False, False, None, 'dmg', None, 'debug', 0 ), # priority = 0: static debug build ( 'x86_64', 'darwin', 'macos', 'static', 'darwin', 'macos-11.0', '.dmg', True, True, None, 'dmg', 'x86_64-mac-gcc-static', 'release' ), # I have no idea how to separate debug info on macos
( 'x86_64', 'darwin', 'macos', 'static', 'darwin', 'macos-12', '.dmg', True, True, None, 'dmg', 'x86_64-mac-gcc-static', 'release', 10 ), # I have no idea how to separate debug info on macos ( 'x86_64', 'darwin', 'macos', 'dynamic', 'darwin', 'macos-11.0', '.dmg', False, False, None, 'dmg', None, 'debug' ),
( 'x86_64', 'darwin', 'macos', 'dynamic', 'darwin', 'macos-12', '.dmg', False, False, None, 'dmg', None, 'debug', 10 ), ( 'x86_64', 'darwin', 'macos', 'dynamic', 'darwin', 'macos-11.0', '.dmg', False, False, None, 'dmg', None, 'release' ),
( 'x86_64', 'darwin', 'macos', 'dynamic', 'darwin', 'macos-12', '.dmg', False, False, None, 'dmg', None, 'release', 10 ), ( 'aarch64', 'darwin', 'macos', 'static', 'darwin', 'macos-11.0', '.dmg', False, False, None, 'dmg', None, 'debug' ),
( 'aarch64', 'darwin', 'macos', 'static', 'darwin', 'macos-12', '.dmg', False, False, None, 'dmg', None, 'debug', 0 ), # priority = 0: static debug build ( 'aarch64', 'darwin', 'macos', 'static', 'darwin', 'macos-11.0', '.dmg', True, True, None, 'dmg', 'arm64-mac-gcc-static', 'release' ),
( 'aarch64', 'darwin', 'macos', 'static', 'darwin', 'macos-12', '.dmg', True, True, None, 'dmg', 'arm64-mac-gcc-static', 'release', 10 ), # ( 'aarch64', 'darwin', 'macos', 'dynamic', 'darwin', 'macos-11.0', '.dmg', False, False, None, 'dmg', None, 'debug' ), # macos-11.0 is x86_64 and I haven't yet figured out how to get homebrew to install aarch64 libs on x86_64
# ( 'aarch64', 'darwin', 'macos', 'dynamic', 'darwin', 'macos-12', '.dmg', False, False, None, 'dmg', None, 'debug', 10 ), # macos-11.0 is x86_64 and I haven't yet figured out how to get homebrew to install aarch64 libs on x86_64 # ( 'aarch64', 'darwin', 'macos', 'dynamic', 'darwin', 'macos-11.0', '.dmg', False, False, None, 'dmg', None, 'release' ), # macos-11.0 is x86_64 and I haven't yet figured out how to get homebrew to install aarch64 libs on x86_64
# ( 'aarch64', 'darwin', 'macos', 'dynamic', 'darwin', 'macos-12', '.dmg', False, False, None, 'dmg', None, 'release', 10 ), # macos-11.0 is x86_64 and I haven't yet figured out how to get homebrew to install aarch64 libs on x86_64 ( 'x86', 'android', 'bionic', 'static', 'linux', 'ubuntu-20.04', '.apk', False, False, None, None, None, 'debug' ),
( 'x86', 'android', 'bionic', 'static', 'linux', 'ubuntu-20.04', '.apk', False, False, None, None, None, 'debug', 0 ), # priority = 0: rarely used debug build ( 'x86', 'android', 'bionic', 'static', 'linux', 'ubuntu-20.04', '.apk', True, True, '.dbg', None, 'i686-and-gcc-static', 'release' ),
( 'x86', 'android', 'bionic', 'static', 'linux', 'ubuntu-20.04', '.apk', True, True, '.dbg', None, 'i686-and-gcc-static', 'release', 10 ), ( 'x86_64', 'android', 'bionic', 'static', 'linux', 'ubuntu-20.04', '.apk', False, False, None, None, None, 'debug' ),
( 'x86_64', 'android', 'bionic', 'static', 'linux', 'ubuntu-20.04', '.apk', False, False, None, None, None, 'debug', 0 ), # priority = 0: rarely used debug build ( 'x86_64', 'android', 'bionic', 'static', 'linux', 'ubuntu-20.04', '.apk', True, True, '.dbg', None, 'x86_64-and-gcc-static', 'release' ),
( 'x86_64', 'android', 'bionic', 'static', 'linux', 'ubuntu-20.04', '.apk', True, True, '.dbg', None, 'x86_64-and-gcc-static', 'release', 10 ), ( 'arm', 'android', 'bionic', 'static', 'linux', 'ubuntu-20.04', '.apk', False, False, None, None, None, 'debug' ),
( 'arm', 'android', 'bionic', 'static', 'linux', 'ubuntu-20.04', '.apk', False, False, None, None, None, 'debug', 0 ), # priority = 0: rarely used debug build ( 'arm', 'android', 'bionic', 'static', 'linux', 'ubuntu-20.04', '.apk', True, True, '.dbg', None, 'arm-and-gcc-static', 'release' ),
( 'arm', 'android', 'bionic', 'static', 'linux', 'ubuntu-20.04', '.apk', True, True, '.dbg', None, 'arm-and-gcc-static', 'release', 10 ), ( 'aarch64', 'android', 'bionic', 'static', 'linux', 'ubuntu-20.04', '.apk', False, False, None, None, None, 'debug' ),
( 'aarch64', 'android', 'bionic', 'static', 'linux', 'ubuntu-20.04', '.apk', False, False, None, None, None, 'debug', 0 ), # priority = 0: rarely used debug build ( 'aarch64', 'android', 'bionic', 'static', 'linux', 'ubuntu-20.04', '.apk', True, True, '.dbg', None, 'arm64-and-gcc-static', 'release' ),
( 'aarch64', 'android', 'bionic', 'static', 'linux', 'ubuntu-20.04', '.apk', True, True, '.dbg', None, 'arm64-and-gcc-static', 'release', 10 ),
( 'wasm32', 'emscripten', 'emscripten', 'static', 'linux', 'ubuntu-20.04', '.tar', False, False, None, None, None, 'debug', 0 ), # priority = 0: rarely used debug build
( 'wasm32', 'emscripten', 'emscripten', 'static', 'linux', 'ubuntu-20.04', '.tar', True, True, None, 'emscripten', 'wasm32-ems-static', 'release', 10 ), # I have no idea how to separate debug info on emscripten
]: ]:
if priority < do_priority:
continue
job_name = f'build'
if starcatcher:
job_name += f'+target=starcatcher-{starcatcher}'
else:
job_name += f'+target={arch}-{platform}-{libc}-{statdyn}-{dbgrel}'
if mode:
job_name += f'+mode={mode}'
if bplatform != platform:
job_name += f'+bplatform={bplatform}'
if not mode: if not mode:
mode = 'default' mode = 'default'
separate_debug = True separate_debug = True
@ -173,10 +141,6 @@ for arch, platform, libc, statdyn, bplatform, runso
debug_asset_path = f'{app_name_slug}-{arch}.AppImage.dbg' debug_asset_path = f'{app_name_slug}-{arch}.AppImage.dbg'
debug_asset_name = f'{app_name_slug}-{arch}.AppImage.dbg' debug_asset_name = f'{app_name_slug}-{arch}.AppImage.dbg'
starcatcher_name = f'powder-{release_name}-{starcatcher}{suffix}' starcatcher_name = f'powder-{release_name}-{starcatcher}{suffix}'
msys2_bash = (bplatform == 'windows' and libc == 'mingw')
shell = 'bash'
if msys2_bash:
shell = 'msys2 {0}'
build_matrix.append({ build_matrix.append({
'bsh_build_platform': bplatform, # part of the unique portion of the matrix 'bsh_build_platform': bplatform, # part of the unique portion of the matrix
'bsh_host_arch': arch, # part of the unique portion of the matrix 'bsh_host_arch': arch, # part of the unique portion of the matrix
@ -185,7 +149,6 @@ for arch, platform, libc, statdyn, bplatform, runso
'bsh_static_dynamic': statdyn, # part of the unique portion of the matrix 'bsh_static_dynamic': statdyn, # part of the unique portion of the matrix
'bsh_debug_release': dbgrel, # part of the unique portion of the matrix 'bsh_debug_release': dbgrel, # part of the unique portion of the matrix
'runs_on': runson, 'runs_on': runson,
'force_msys2_bash': msys2_bash and 'yes' or 'no',
'package_suffix': suffix, 'package_suffix': suffix,
'package_mode': mode, 'package_mode': mode,
'publish': publish and 'yes' or 'no', 'publish': publish and 'yes' or 'no',
@ -195,8 +158,6 @@ for arch, platform, libc, statdyn, bplatform, runso
'asset_name': asset_name, 'asset_name': asset_name,
'debug_asset_path': debug_asset_path, 'debug_asset_path': debug_asset_path,
'debug_asset_name': debug_asset_name, 'debug_asset_name': debug_asset_name,
'job_name': job_name,
'shell': shell,
}) })
if publish: if publish:
publish_matrix.append({ publish_matrix.append({

View File

@ -3,18 +3,4 @@
set -euo pipefail set -euo pipefail
IFS=$'\n\t' IFS=$'\n\t'
cat << NETRC > ~/.netrc LFTP_PASSWORD=$PUBLISH_PASSWORD lftp -c "open --user '$PUBLISH_USERNAME' --env-password -e 'set ftp:ssl-protect-data true; set ssl:verify-certificate false; cd "${PUBLISH_DIRECTORY:-.}"; put \"$PUBLISH_FILENAME\";' ftp://$PUBLISH_HOSTPORT"
machine $(echo $PUBLISH_HOSTPORT | cut -d ':' -f 1)
login $PUBLISH_USERNAME
password $PUBLISH_PASSWORD
NETRC
chmod 660 ~/.netrc
mountpoint=ftpmnt
mkdir $mountpoint
curlftpfs "$PUBLISH_HOSTPORT" $mountpoint -o ssl,ciphers='ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-GCM-SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_8_SHA256:TLS_AES_128_CCM_SHA256'
if [[ -z ${PUBLISH_ACCESSCHECK-} ]]; then
cp $PUBLISH_FILENAME $mountpoint/${PUBLISH_DIRECTORY:-.}/
fi
fusermount -u $mountpoint
rmdir $mountpoint

View File

@ -1,12 +0,0 @@
set -euo pipefail
IFS=$'\t\n'
temp=.temp
mkdir $temp
cp $ASSET_PATH $temp/$ASSET_NAME
(
cd $temp
gh release upload $GITHUB_REF_NAME $ASSET_NAME
)
rm -r $temp
echo browser_download_url=https://github.com/$GITHUB_REPOSITORY/releases/download/$GITHUB_REF_NAME/$ASSET_NAME >> $GITHUB_OUTPUT

3
.github/vs-env.sh vendored Executable file → Normal file
View File

@ -24,8 +24,5 @@ IFS=$'\t\n'
for i in $(MSYS_NO_PATHCONV=1 cmd /c "$vs_install_dir\\VC\\Auxiliary\\Build\\vcvarsall.bat" $VS_ENV_PARAMS \& env \& exit /b); do for i in $(MSYS_NO_PATHCONV=1 cmd /c "$vs_install_dir\\VC\\Auxiliary\\Build\\vcvarsall.bat" $VS_ENV_PARAMS \& env \& exit /b); do
set +e set +e
export "$i" 2>/dev/null export "$i" 2>/dev/null
echo $i | grep ERROR
set -e set -e
done done
cl

View File

@ -16,6 +16,7 @@ jobs:
prepare: prepare:
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
release_upload_url: ${{ steps.create_release.outputs.upload_url }}
do_release: ${{ steps.prepare.outputs.do_release }} do_release: ${{ steps.prepare.outputs.do_release }}
build_matrix: ${{ steps.prepare.outputs.build_matrix }} build_matrix: ${{ steps.prepare.outputs.build_matrix }}
publish_matrix: ${{ steps.prepare.outputs.publish_matrix }} publish_matrix: ${{ steps.prepare.outputs.publish_matrix }}
@ -31,76 +32,39 @@ jobs:
do_publish: ${{ steps.prepare.outputs.do_publish }} do_publish: ${{ steps.prepare.outputs.do_publish }}
steps: steps:
- run: git config --global core.autocrlf false - run: git config --global core.autocrlf false
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: actions/setup-python@v5 - uses: actions/setup-python@v4
with: with:
python-version: '3.10' python-version: '3.10'
- run: python -m pip install meson==1.2.3 ninja # TODO: go back to using latest meson once https://github.com/mesonbuild/meson/pull/12544 is live - run: python -m pip install meson ninja
- id: prepare - id: prepare
run: python ./.github/prepare.py run: python ./.github/prepare.py
env: env:
PUBLISH_HOSTPORT: ${{ secrets.STARCATCHER_PUBLISH_HOSTPORT }} PUBLISH_HOSTPORT: ${{ secrets.STARCATCHER_PUBLISH_HOSTPORT }}
GITHUB_REF: ${{ github.ref }} GITHUB_REF: ${{ github.ref }}
- if: steps.prepare.outputs.do_publish == 'yes'
run: sudo apt update && sudo apt install curlftpfs && bash -c './.github/starcatcher-publish.sh'
env:
PUBLISH_HOSTPORT: ${{ secrets.STARCATCHER_PUBLISH_HOSTPORT }}
PUBLISH_USERNAME: ${{ secrets.STARCATCHER_PUBLISH_USERNAME }}
PUBLISH_PASSWORD: ${{ secrets.STARCATCHER_PUBLISH_PASSWORD }}
PUBLISH_ACCESSCHECK: yes
- if: steps.prepare.outputs.do_release == 'yes' - if: steps.prepare.outputs.do_release == 'yes'
id: create_release id: create_release
uses: LBPHacker/create-release@v2
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_NAME: ${{ steps.prepare.outputs.release_name }} with:
run: bash -c './.github/create-release.sh' tag_name: ${{ github.ref }}
release_name: ${{ steps.prepare.outputs.release_name }}
draft: true
prerelease: false
build: build:
runs-on: ${{ matrix.runs_on }} runs-on: ${{ matrix.runs_on }}
name: ${{ matrix.job_name }}
needs: [prepare] needs: [prepare]
strategy: strategy:
fail-fast: false fail-fast: false
matrix: ${{ fromJSON(needs.prepare.outputs.build_matrix) }} matrix: ${{ fromJSON(needs.prepare.outputs.build_matrix) }}
defaults:
run:
shell: ${{ matrix.shell }}
steps: steps:
- if: matrix.force_msys2_bash == 'yes'
uses: msys2/setup-msys2@v2
with:
msystem: UCRT64
update: true
path-type: strict
cache: true
# this list doesn't have to mirror the one in build.sh perfectly
# but the packages listed here get cached properly and take less time to install
install: >-
git
curl
mingw-w64-ucrt-x86_64-gcc
mingw-w64-ucrt-x86_64-pkgconf
mingw-w64-ucrt-x86_64-bzip2
mingw-w64-ucrt-x86_64-luajit
mingw-w64-ucrt-x86_64-jsoncpp
mingw-w64-ucrt-x86_64-curl
mingw-w64-ucrt-x86_64-SDL2
mingw-w64-ucrt-x86_64-libpng
mingw-w64-ucrt-x86_64-meson
mingw-w64-ucrt-x86_64-python
mingw-w64-ucrt-x86_64-python-pip
mingw-w64-ucrt-x86_64-fftw
mingw-w64-ucrt-x86_64-cmake
mingw-w64-ucrt-x86_64-7zip
mingw-w64-ucrt-x86_64-jq
patch
- run: git config --global core.autocrlf false - run: git config --global core.autocrlf false
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- if: matrix.force_msys2_bash != 'yes' - uses: actions/setup-python@v4
uses: actions/setup-python@v5
with: with:
python-version: '3.10' python-version: '3.10'
- if: matrix.force_msys2_bash != 'yes' - run: python -m pip install meson ninja
run: python -m pip install meson==1.2.3 ninja # TODO: go back to using latest meson once https://github.com/mesonbuild/meson/pull/12544 is live
- if: matrix.bsh_build_platform == 'darwin' - if: matrix.bsh_build_platform == 'darwin'
run: brew install bash coreutils run: brew install bash coreutils
- run: bash -c './.github/build.sh' - run: bash -c './.github/build.sh'
@ -124,24 +88,30 @@ jobs:
PACKAGE_MODE: ${{ matrix.package_mode }} PACKAGE_MODE: ${{ matrix.package_mode }}
ASSET_PATH: ${{ matrix.asset_path }} ASSET_PATH: ${{ matrix.asset_path }}
DEBUG_ASSET_PATH: ${{ matrix.debug_asset_path }} DEBUG_ASSET_PATH: ${{ matrix.debug_asset_path }}
- if: needs.prepare.outputs.do_release == 'yes' && matrix.publish == 'yes' # TODO-NTL: ship licenses - uses: LBPHacker/upload-release-asset@v2 # TODO-NTL: ship licenses
if: needs.prepare.outputs.do_release == 'yes' && matrix.publish == 'yes'
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ASSET_PATH: build/${{ matrix.asset_path }} with:
ASSET_NAME: ${{ matrix.asset_name }} upload_url: ${{ needs.prepare.outputs.release_upload_url }}
run: bash -c './.github/upload-release-asset.sh' asset_path: build/${{ matrix.asset_path }}
- if: needs.prepare.outputs.do_release == 'yes' && matrix.publish == 'yes' && matrix.separate_debug == 'yes' asset_name: ${{ matrix.asset_name }}
asset_content_type: application/zip
- uses: LBPHacker/upload-release-asset@v2
if: needs.prepare.outputs.do_release == 'yes' && matrix.publish == 'yes' && matrix.separate_debug == 'yes'
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ASSET_PATH: build/${{ matrix.debug_asset_path }} with:
ASSET_NAME: ${{ matrix.debug_asset_name }} upload_url: ${{ needs.prepare.outputs.release_upload_url }}
run: bash -c './.github/upload-release-asset.sh' asset_path: build/${{ matrix.debug_asset_path }}
- uses: actions/upload-artifact@v4 asset_name: ${{ matrix.debug_asset_name }}
asset_content_type: application/zip
- uses: actions/upload-artifact@v3
if: matrix.artifact == 'yes' if: matrix.artifact == 'yes'
with: with:
path: build/${{ matrix.asset_path }} path: build/${{ matrix.asset_path }}
name: ${{ matrix.asset_name }} name: ${{ matrix.asset_name }}
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v3
if: matrix.artifact == 'yes' && matrix.separate_debug == 'yes' if: matrix.artifact == 'yes' && matrix.separate_debug == 'yes'
with: with:
path: build/${{ matrix.debug_asset_path }} path: build/${{ matrix.debug_asset_path }}
@ -155,12 +125,12 @@ jobs:
if: needs.prepare.outputs.do_publish == 'yes' if: needs.prepare.outputs.do_publish == 'yes'
steps: steps:
- run: git config --global core.autocrlf false - run: git config --global core.autocrlf false
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v3
with: with:
name: ${{ matrix.asset_name }} name: ${{ matrix.asset_name }}
- run: mv ${{ matrix.asset_path }} ${{ matrix.starcatcher_name }} - run: mv ${{ matrix.asset_path }} ${{ matrix.starcatcher_name }}
- run: sudo apt update && sudo apt install curlftpfs && bash -c './.github/starcatcher-publish.sh' - run: sudo apt update && sudo apt install lftp && bash -c './.github/starcatcher-publish.sh'
env: env:
PUBLISH_HOSTPORT: ${{ secrets.STARCATCHER_PUBLISH_HOSTPORT }} PUBLISH_HOSTPORT: ${{ secrets.STARCATCHER_PUBLISH_HOSTPORT }}
PUBLISH_USERNAME: ${{ secrets.STARCATCHER_PUBLISH_USERNAME }} PUBLISH_USERNAME: ${{ secrets.STARCATCHER_PUBLISH_USERNAME }}
@ -173,7 +143,7 @@ jobs:
if: needs.prepare.outputs.do_publish == 'yes' if: needs.prepare.outputs.do_publish == 'yes'
steps: steps:
- run: git config --global core.autocrlf false - run: git config --global core.autocrlf false
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- run: ./.github/starcatcher-release.sh - run: ./.github/starcatcher-release.sh
env: env:
RELEASE_NAME: ${{ needs.prepare.outputs.release_name }} RELEASE_NAME: ${{ needs.prepare.outputs.release_name }}

View File

@ -1,4 +1,4 @@
The Powder Toy - April 2024 The Powder Toy - January 2023
========================== ==========================
Get the latest version [from the Powder Toy website](https://powdertoy.co.uk/Download.html). Get the latest version [from the Powder Toy website](https://powdertoy.co.uk/Download.html).
@ -25,7 +25,7 @@ Thanks
* Skresanov Savely * Skresanov Savely
* cracker64 * cracker64
* Catelite * Catelite
* Victoria Hoyle * Bryan Hoyle
* Nathan Cousins * Nathan Cousins
* jacksonmj * jacksonmj
* Felix Wallin * Felix Wallin
@ -82,7 +82,6 @@ Controls
| I | Invert Pressure and Velocity map | | I | Invert Pressure and Velocity map |
| W | Cycle gravity modes (use with Ctrl when STK2 is out) | | W | Cycle gravity modes (use with Ctrl when STK2 is out) |
| Y | Cycle air modes | | Y | Cycle air modes |
| Ctrl + E | Cycle edge modes |
| B | Enter decoration editor menu | | B | Enter decoration editor menu |
| Ctrl + B | Toggle decorations on/off | | Ctrl + B | Toggle decorations on/off |
| N | Toggle Newtonian Gravity on/off | | N | Toggle Newtonian Gravity on/off |
@ -108,7 +107,6 @@ Controls
| Shift + R | Horizontal mirror for selected area when pasting stamps | | Shift + R | Horizontal mirror for selected area when pasting stamps |
| Ctrl + Shift + R | Vertical mirror for selected area when pasting stamps | | Ctrl + Shift + R | Vertical mirror for selected area when pasting stamps |
| R | Rotate selected area counterclockwise when pasting stamps | | R | Rotate selected area counterclockwise when pasting stamps |
| F11 | Toggle fullscreen |
Command Line Command Line
--------------------------------------------------------------------------- ---------------------------------------------------------------------------

View File

@ -2,12 +2,12 @@
<manifest <manifest
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
package="@APPID@" package="@APPID@"
android:versionCode="@BUILD_NUM@" android:versionCode="1"
android:versionName="@DISPLAY_VERSION_MAJOR@.@DISPLAY_VERSION_MINOR@.@BUILD_NUM@" android:versionName="1.0"
android:installLocation="auto" android:installLocation="auto"
> >
<uses-sdk <uses-sdk
android:minSdkVersion="21" android:minSdkVersion="19"
android:targetSdkVersion="30" android:targetSdkVersion="30"
/> />
<uses-feature <uses-feature
@ -33,18 +33,16 @@
android:name="android.hardware.type.pc" android:name="android.hardware.type.pc"
android:required="false" android:required="false"
/> />
@ANDROID_PERMISSIONS@ <uses-permission android:name="android.permission.VIBRATE" />
<application <application
android:label="@string/app_name" android:label="@string/app_name"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:allowBackup="true" android:allowBackup="true"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen" android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
@ANDROID_PROPERTIES@
> >
<activity <activity
android:name=".PowderActivity" android:name=".PowderActivity"
android:screenOrientation="landscape"
android:label="@string/app_name" android:label="@string/app_name"
android:alwaysRetainTaskState="true" android:alwaysRetainTaskState="true"
android:launchMode="singleInstance" android:launchMode="singleInstance"

View File

@ -1,42 +1,7 @@
package @APPID@; package @APPID@;
import org.libsdl.app.SDLActivity; import org.libsdl.app.SDLActivity;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.Enumeration;
import java.io.IOException;
import java.util.Base64;
public class PowderActivity extends SDLActivity public class PowderActivity extends SDLActivity
{ {
public static String getCertificateBundle()
{
String allPems = "";
try {
KeyStore ks = KeyStore.getInstance("AndroidCAStore");
if (ks != null) {
ks.load(null, null);
Enumeration<String> aliases = ks.aliases();
while (aliases.hasMoreElements()) {
String alias = (String)aliases.nextElement();
java.security.cert.X509Certificate cert = (java.security.cert.X509Certificate)ks.getCertificate(alias);
allPems += "-----BEGIN CERTIFICATE-----\n" + Base64.getMimeEncoder().encodeToString(cert.getEncoded()) + "\n-----END CERTIFICATE-----\n";;
}
}
} catch (IOException e) {
e.printStackTrace();
return "";
} catch (KeyStoreException e) {
e.printStackTrace();
return "";
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return "";
} catch (java.security.cert.CertificateException e) {
e.printStackTrace();
return "";
}
return allPems;
}
} }

View File

@ -1,5 +1,5 @@
[constants] [constants]
android_ndk_toolchain_prefix = 'armv7a-linux-androideabi21-' android_ndk_toolchain_prefix = 'armv7a-linux-androideabi19-'
[host_machine] [host_machine]
system = 'android' system = 'android'

View File

@ -1,5 +1,5 @@
[constants] [constants]
android_ndk_toolchain_prefix = 'i686-linux-android21-' android_ndk_toolchain_prefix = 'i686-linux-android19-'
[host_machine] [host_machine]
system = 'android' system = 'android'

View File

@ -1,272 +0,0 @@
#!/usr/bin/env bash
# mainly based on https://www.sh-zam.com/2019/05/debugging-krita-on-android.html
set -euo pipefail
IFS=$'\n\t'
function get_buildoption() {
jq -r '.[] | select(.name == "'$1'") | .value' < meson-info/intro-buildoptions.json
}
function get_cpp_compiler() {
jq -r '.[] | select(.name == "'$1'") | .target_sources.[] | select(.language == "cpp") | .compiler.[]' < meson-info/intro-targets.json
}
# customize
default_app_id=uk.co.powdertoy.tpt
default_app_exe=powder
default_lldb_server=/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/lib/clang/17/lib/linux/aarch64/lldb-server
default_lldb_client=/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/lldb.sh
in_build_site=no
if which jq >/dev/null && [[ -f meson-info/intro-buildoptions.json ]]; then
>&2 echo "[+] pwd is a build site, auto-detecting parameters"
in_build_site=yes
default_app_id=$(get_buildoption app_id)
default_app_exe=$(get_buildoption app_exe)
found_lldb=no
compiler_path=$(get_cpp_compiler $default_app_exe)
for line in $compiler_path; do
# iterate over the command array (might be for example [ "ccache", "aarch64-linux-android21-clang++" ])
if (echo $line | grep toolchains && basename $line | grep android | grep clang++) >/dev/null; then
arch=$(basename $line | cut -d '-' -f 1)
default_lldb_server=$(realpath $(dirname $line)/../lib/clang/*/lib/linux/$arch/lldb-server)
default_lldb_client=$(realpath $(dirname $line)/lldb.sh)
found_lldb=yes
fi
done
if [[ $found_lldb != yes ]]; then
>&2 echo "[-] cannot determine LLDB paths from compiler command array:"
for line in $compiler_path; do
>&2 echo "[-] - $line"
done
exit 1
fi
>&2 echo "[+] APP_ID: $default_app_id"
>&2 echo "[+] APP_EXE: $default_app_exe"
>&2 echo "[+] LLDB_SERVER: $default_lldb_server"
>&2 echo "[+] LLDB_CLIENT: $default_lldb_client"
else
>&2 echo "[+] pwd is not a build site, not auto-detecting parameters"
fi
app_id=${APP_ID:-$default_app_id}
app_exe=${APP_EXE:-$default_app_exe}
lldb_server=${LLDB_SERVER:-$default_lldb_server}
lldb_server_port=${LLDB_SERVER_PORT:-9998}
jdb_port=${JDB_PORT:-13456}
lldb_client=${LLDB_CLIENT:-$default_lldb_client}
meson=${MESON:-meson}
adb=${ADB:-adb}
jdb=${JDB:-jdb}
# don't customize unless necessary
app_activity=${APP_ACTIVITY:-PowderActivity}
lldb_server_staging=${LLDB_SERVER_STAGING:-/data/local/tmp/lldb-server}
lldb_server_remote=${LLDB_SERVER_REMOTE:-lldb-server}
pidof_retry_count=${PIDOF_RETRY_COUNT:-20}
pidof_retry_delay=${PIDOF_RETRY_DELAY:-0.1}
function check_which() {
if ! which $1 >/dev/null; then
>&2 echo "[-] can't run $1"
return 1
fi
}
function check_env() {
if ! [[ -f $lldb_server ]]; then
>&2 echo "[-] $lldb_server doesn't exist"
return 1
fi
check_which $lldb_client
check_which $adb
check_which $jdb
}
function check_adb() {
$adb shell whoami >/dev/null
}
function maybe_install_app() {
if [[ $in_build_site != yes ]]; then
return 0
fi
android_keystore=$(get_buildoption android_keystore)
android_keyalias=$(get_buildoption android_keyalias)
if [[ -z ${ANDROID_KEYSTORE_PASS-} ]]; then
>&2 echo "[-] ANDROID_KEYSTORE_PASS not set"
>&2 echo
>&2 cat << HELP
The current directory seems to be a build site, but ANDROID_KEYSTORE_PASS is not set, so android/$app_exe.apk cannot be invoked. If you don't have a keystore yet, create one with:
ANDROID_KEYSTORE_PASS=bagelsbagels keytool -genkey \\
-keystore $android_keystore \\
-alias $android_keyalias \\
-storepass:env ANDROID_KEYSTORE_PASS \\
-keypass:env ANDROID_KEYSTORE_PASS \\
-dname CN=bagels
Then try again with:
ANDROID_KEYSTORE_PASS=bagelsbagels $0
Naturally, replace bagelsbagels with an appropriate password.
HELP
exit 1
fi
>&2 echo "[+] meson compiling android/$app_exe.apk"
if ! $meson compile sign-apk; then
>&2 echo "[-] failed"
return 1
fi
>&2 echo "[+] adb installing android/$app_exe.apk"
if ! $adb install android/$app_exe.apk; then
>&2 echo "[-] failed"
return 1
fi
}
function check_debuggable() {
$adb shell run-as $app_id whoami >/dev/null
}
function find_lldb_server() {
$adb shell run-as $app_id pgrep $lldb_server_remote >/dev/null
}
function kill_lldb_server() {
if ! $adb shell run-as $app_id pkill $lldb_server_remote; then
>&2 echo "[-] failed"
return 1
fi
}
function adb_forward() {
>&2 echo "[+] adb forwarding tcp:$jdb_port jdwp:$pid"
if ! ($adb forward tcp:$jdb_port jdwp:$pid | grep $jdb_port >/dev/null); then
>&2 echo "[+] failed"
return 1
fi
}
function adb_unforward() {
$adb forward --remove tcp:$jdb_port
}
function undo_current_adb_forward() {
>&2 echo "[+] adb un-forwarding orphaned tcp:$jdb_port"
adb_unforward
}
function maybe_undo_previous_adb_forward() {
if $adb forward --list | grep tcp:$jdb_port; then
>&2 echo "[+] adb un-forwarding orphaned tcp:$jdb_port"
adb_unforward
fi
}
function maybe_kill_previous_lldb_server() {
if find_lldb_server; then
>&2 echo "[+] killing orphaned $lldb_server_remote"
kill_lldb_server
fi
}
function kill_current_lldb_server() {
>&2 echo "[+] killing $lldb_server_remote"
kill_lldb_server
}
function start_app() {
>&2 echo "[+] starting $app_id/.$app_activity"
set +e
$adb shell am start -D -n "$app_id/.$app_activity" >/dev/null
set -e
local i
local maybe_pid
local pidof_result
for ((i = 0; i <= $pidof_retry_count; i++)); do
set +e
maybe_pid=$($adb shell pidof $app_id)
pidof_result=$?
set -e
if [[ $pidof_result == 0 ]]; then
pid=$maybe_pid
break
fi
sleep $pidof_retry_delay
done
if [[ -z ${pid-} ]]; then
>&2 echo "[-] failed"
return 1
fi
echo $pid
}
function jdb_attach() {
>&2 echo "[+] attaching jdb in the background"
$jdb -attach localhost:$jdb_port >/dev/null 2>/dev/null &
disown $!
# at some point jdb exits because it doesn't have an stdin... fine by me
}
function maybe_deploy_lldb_server() {
if ! $adb shell [[ -f $lldb_server_staging ]]; then
>&2 echo "[+] $lldb_server_remote not present on host, deploying"
if ! ($adb push $lldb_server $lldb_server_staging && $adb shell chmod +x $lldb_server_staging); then
>&2 echo "[-] failed"
fi
fi
}
function start_lldb_server() {
if ! $adb shell run-as $app_id pgrep $lldb_server_remote >/dev/null; then
>&2 echo "[+] $lldb_server_remote not running on host, starting"
$adb shell run-as $app_id cp $lldb_server_staging /data/data/$app_id/$lldb_server_remote
$adb shell run-as $app_id ./$lldb_server_remote platform --server --listen "*:$lldb_server_port" >/dev/null 2>/dev/null &
disown $!
if ! $adb shell run-as $app_id pgrep $lldb_server_remote >/dev/null; then
>&2 echo "[-] failed"
return 1
fi
fi
}
function start_lldb() {
local pid=$1
>&2 echo "[+] starting $lldb_client"
local lldb_init=$(mktemp)
cat - << LLDB_INIT > $lldb_init
platform select remote-android
platform connect connect://localhost:$lldb_server_port
attach $pid
continue
LLDB_INIT
local lldb_status
set +e
$lldb_client --source $lldb_init
lldb_status=$?
set -e
>&2 echo "[+] $lldb_client exited with status $lldb_status"
rm $lldb_init
}
check_env
check_adb
maybe_install_app
check_debuggable
maybe_kill_previous_lldb_server
maybe_undo_previous_adb_forward
if [[ ${1-} == clean ]]; then
>&2 echo "[+] done"
exit 0
fi
maybe_deploy_lldb_server
start_lldb_server
pid=$(start_app)
adb_forward
jdb_attach
start_lldb $pid
kill_current_lldb_server
undo_current_adb_forward
>&2 echo "[+] done"

View File

@ -23,3 +23,6 @@ if subprocess.run([
apk_path, apk_path,
]).returncode: ]).returncode:
sys.exit(1) sys.exit(1)
with open(phony_path, 'w') as _:
pass

View File

@ -1 +1,5 @@
android_resources += fs.copyfile(rendered_icons['icon_exe'], 'ic_launcher.png') android_resources += configure_file(
input: rendered_icons['icon_exe'],
output: 'ic_launcher.png',
copy: true,
)

View File

@ -1,7 +1,6 @@
[binaries] [binaries]
c = [ 'clang', '-arch', 'arm64' ] c = [ 'clang', '-arch', 'arm64' ]
cpp = [ 'clang++', '-arch', 'arm64' ] cpp = [ 'clang++', '-arch', 'arm64' ]
objcpp = [ 'clang++', '-arch', 'arm64' ]
strip = 'strip' strip = 'strip'
[host_machine] [host_machine]

View File

@ -10,7 +10,7 @@ project(
'build.cpp_std=c++17', # used by to_array 'build.cpp_std=c++17', # used by to_array
'build.cpp_rtti=false', # used by to_array 'build.cpp_rtti=false', # used by to_array
], ],
meson_version: '>=0.64.0', meson_version: '>=0.61.0',
) )
if get_option('prepare') if get_option('prepare')
@ -18,7 +18,6 @@ if get_option('prepare')
subdir_done() subdir_done()
endif endif
fs = import('fs')
to_array = generator( to_array = generator(
executable('toarray', sources: 'resources/ToArray.cpp', native: true), executable('toarray', sources: 'resources/ToArray.cpp', native: true),
output: [ '@PLAINNAME@.cpp', '@PLAINNAME@.h' ], output: [ '@PLAINNAME@.cpp', '@PLAINNAME@.h' ],
@ -44,11 +43,11 @@ if c_compiler.get_id() in [ 'msvc' ]
host_libc = 'msvc' host_libc = 'msvc'
elif c_compiler.get_id() in [ 'gcc' ] and host_platform == 'windows' elif c_compiler.get_id() in [ 'gcc' ] and host_platform == 'windows'
host_libc = 'mingw' host_libc = 'mingw'
if get_option('b_lto')
warning('mingw does not like static + lto')
endif
elif host_platform in [ 'darwin' ] elif host_platform in [ 'darwin' ]
host_libc = 'macos' host_libc = 'macos'
elif host_platform in [ 'emscripten' ]
host_platform = 'emscripten'
host_libc = 'emscripten'
elif host_platform in [ 'android' ] elif host_platform in [ 'android' ]
host_platform = 'android' host_platform = 'android'
host_libc = 'bionic' host_libc = 'bionic'
@ -66,8 +65,8 @@ if static_variant != 'prebuilt' and host_platform == 'android'
warning('only prebuilt libs are supported for android') warning('only prebuilt libs are supported for android')
static_variant = 'prebuilt' static_variant = 'prebuilt'
endif endif
if static_variant == 'system' and host_platform == 'windows' and host_libc == 'msvc' if static_variant == 'system' and host_platform == 'windows'
warning('no way to find system libs for msvc on windows') warning('no way to find system libs on windows')
static_variant = 'prebuilt' static_variant = 'prebuilt'
endif endif
@ -79,19 +78,20 @@ tpt_libs_static = 'none'
if static_variant == 'prebuilt' if static_variant == 'prebuilt'
tpt_libs_static = 'static' tpt_libs_static = 'static'
endif endif
if static_variant == 'none' and host_platform == 'windows' and host_libc == 'msvc' if static_variant == 'none' and host_platform == 'windows'
tpt_libs_static = 'dynamic' tpt_libs_static = 'dynamic'
endif endif
tpt_libs_debug = is_debug ? 'debug' : 'release' tpt_libs_debug = is_debug ? 'debug' : 'release'
tpt_libs_variant = '@0@-@1@-@2@-@3@'.format(host_arch, host_platform, host_libc, tpt_libs_static) tpt_libs_variant = '@0@-@1@-@2@-@3@'.format(host_arch, host_platform, host_libc, tpt_libs_static)
tpt_libs_vtag = get_option('tpt_libs_vtag') tpt_libs_vtag = get_option('tpt_libs_vtag')
if tpt_libs_vtag == '' if tpt_libs_vtag == ''
tpt_libs_vtag = 'v20240112165024' tpt_libs_vtag = 'v20230205154205'
endif endif
if tpt_libs_static != 'none' if tpt_libs_static != 'none'
if tpt_libs_variant not in [ if tpt_libs_variant not in [
'x86_64-linux-gnu-static', 'x86_64-linux-gnu-static',
'x86_64-windows-mingw-static', 'x86_64-windows-mingw-static',
'x86_64-windows-mingw-dynamic',
'x86_64-windows-msvc-static', 'x86_64-windows-msvc-static',
'x86_64-windows-msvc-dynamic', 'x86_64-windows-msvc-dynamic',
'x86-windows-msvc-static', 'x86-windows-msvc-static',
@ -102,7 +102,6 @@ if tpt_libs_static != 'none'
'x86_64-android-bionic-static', 'x86_64-android-bionic-static',
'arm-android-bionic-static', 'arm-android-bionic-static',
'aarch64-android-bionic-static', 'aarch64-android-bionic-static',
'wasm32-emscripten-emscripten-static',
] ]
error('no prebuilt @0@ libraries are currently provided'.format(tpt_libs_variant)) error('no prebuilt @0@ libraries are currently provided'.format(tpt_libs_variant))
endif endif
@ -145,14 +144,7 @@ endif
lua_variant = get_option('lua') lua_variant = get_option('lua')
if lua_variant == 'auto' if lua_variant == 'auto'
if host_platform == 'emscripten' lua_variant = 'luajit'
lua_variant = 'lua5.2'
else
lua_variant = 'luajit'
endif
endif
if lua_variant == 'luajit' and host_platform == 'emscripten'
error('luajit does not work with emscripten')
endif endif
if lua_variant == 'none' if lua_variant == 'none'
lua_dep = [] lua_dep = []
@ -169,6 +161,9 @@ elif lua_variant == 'luajit'
endif endif
enable_http = get_option('http') enable_http = get_option('http')
if host_platform == 'android'
enable_http = false
endif
if host_platform == 'android' if host_platform == 'android'
android_ndk_toolchain_prefix = meson.get_external_property('android_ndk_toolchain_prefix') android_ndk_toolchain_prefix = meson.get_external_property('android_ndk_toolchain_prefix')
android_platform = meson.get_external_property('android_platform') android_platform = meson.get_external_property('android_platform')
@ -182,57 +177,19 @@ if host_platform == 'android'
error('tpt-libs android platform mismatch') error('tpt-libs android platform mismatch')
endif endif
endif endif
curl_dep = [] curl_dep = enable_http ? dependency('libcurl', static: is_static) : []
if enable_http and host_platform != 'emscripten'
curl_dep = dependency('libcurl', static: is_static) fftw_dep = dependency('fftw3f', static: is_static)
endif threads_dep = dependency('threads')
zlib_dep = dependency('zlib', static: is_static)
png_dep = dependency('libpng16', static: is_static)
sdl2_dep = dependency('sdl2', static: is_static)
bzip2_dep = dependency('bzip2', static: is_static)
json_dep = dependency('jsoncpp', static: is_static)
project_link_args = [] project_link_args = []
project_c_args = [] project_c_args = []
project_cpp_args = [] project_cpp_args = []
fftw_dep = dependency('fftw3f', static: is_static)
threads_dep = dependency('threads')
if host_platform == 'emscripten'
zlib_dep = []
png_dep = []
sdl2_dep = []
bzip2_dep = []
project_link_args += [
'--no-heap-copy',
'-s', 'WASM=1',
'-s', 'ALLOW_MEMORY_GROWTH=1',
'-s', 'FORCE_FILESYSTEM=1',
'-s', 'EXIT_RUNTIME=0',
'-s', 'EXPORTED_RUNTIME_METHODS=ccall,cwrap',
'-s', 'FS_DEBUG',
'-s', 'MODULARIZE',
'-s', 'EXPORT_NAME=create_' + app_exe,
'-Wl,-u,_emscripten_run_callback_on_thread',
'-lidbfs.js',
]
emcc_args = [
'-s', 'USE_SDL=2',
'-s', 'USE_BZIP2=1',
'-s', 'USE_LIBPNG',
'-s', 'USE_ZLIB=1',
'-s', 'DISABLE_EXCEPTION_CATCHING=0',
]
if is_debug
project_link_args += [ '--source-map-base=./' ]
emcc_args += [ '-gsource-map' ]
endif
project_link_args += emcc_args
project_c_args += emcc_args
project_cpp_args += emcc_args
else
zlib_dep = dependency('zlib', static: is_static)
png_dep = dependency('libpng16', static: is_static)
sdl2_dep = dependency('sdl2', static: is_static)
bzip2_dep = dependency('bzip2', static: is_static)
endif
json_dep = dependency('jsoncpp', static: is_static)
if is_msvc if is_msvc
if x86_sse_level >= 30 if x86_sse_level >= 30
warning('SSE3 configured to be enabled but unavailable in msvc') warning('SSE3 configured to be enabled but unavailable in msvc')
@ -318,15 +275,14 @@ else
endif endif
if host_platform == 'windows' if host_platform == 'windows'
args_ccomp_win = [ '-D_WIN32_WINNT=0x0501', '-DNOMINMAX' ] args_ccomp_win = [ '-D_WIN32_WINNT=0x0501' ]
windows_mod = import('windows') windows_mod = import('windows')
if is_static if is_static
args_ccomp_win += [ '-DCURL_STATICLIB' ] args_ccomp_win += [ '-DCURL_STATICLIB' ]
if host_arch == 'x86_64' if host_arch == 'x86_64'
args_ccomp_win += [ '-DZLIB_WINAPI' ] args_ccomp_win += [ '-DZLIB_WINAPI' ]
endif endif
endif else
if tpt_libs_static == 'dynamic'
foreach input_output_condition : tpt_libs.get_variable('config_dlls') foreach input_output_condition : tpt_libs.get_variable('config_dlls')
dll_input = input_output_condition[0] dll_input = input_output_condition[0]
dll_output = input_output_condition[1] dll_output = input_output_condition[1]
@ -338,7 +294,7 @@ if host_platform == 'windows'
do_copy = true do_copy = true
endif endif
if do_copy if do_copy
fs.copyfile(dll_input, dll_output) configure_file(input: dll_input, output: dll_output, copy: true)
endif endif
endforeach endforeach
endif endif
@ -358,11 +314,7 @@ else
ident_platform = 'UNKNOWN' ident_platform = 'UNKNOWN'
endif endif
project_deps = []
data_files = [] data_files = []
powder_deps = []
project_export_dynamic = false
subdir('src') subdir('src')
subdir('resources') subdir('resources')
@ -371,30 +323,8 @@ powder_files += data_files
render_files += data_files render_files += data_files
font_files += data_files font_files += data_files
if host_platform == 'emscripten'
project_link_args += [
'-o', app_exe + '.js', # so we get a .wasm, and a .js
]
endif
if get_option('export_lua_symbols')
if is_static and lua_variant != 'none' and not project_export_dynamic
if host_platform == 'windows'
error('Lua symbols are currently impossible to export correctly on Windows')
elif c_compiler.has_link_argument('-Wl,--export-dynamic-symbol')
project_link_args += [
'-Wl,--export-dynamic-symbol=lua_*',
'-Wl,--export-dynamic-symbol=luaL_*',
'-Wl,--export-dynamic-symbol=luaopen_*',
]
else
warning('your linker does not support -Wl,--export-dynamic-symbol so Meson will be instructed to export all symbols in order to enable loading Lua shared modules, which may blow up the size of the resulting binary')
project_export_dynamic = true
endif
endif
endif
if get_option('build_powder') if get_option('build_powder')
powder_deps += project_deps + [ powder_deps = [
threads_dep, threads_dep,
zlib_dep, zlib_dep,
png_dep, png_dep,
@ -426,17 +356,12 @@ if get_option('build_powder')
win_subsystem: is_debug ? 'console' : 'windows', win_subsystem: is_debug ? 'console' : 'windows',
link_args: project_link_args, link_args: project_link_args,
dependencies: powder_deps, dependencies: powder_deps,
export_dynamic: project_export_dynamic,
install: true,
) )
endif endif
endif endif
if get_option('build_render') if get_option('build_render')
if host_platform == 'emscripten' render_deps = [
error('render does not target emscripten')
endif
render_deps = project_deps + [
threads_dep, threads_dep,
zlib_dep, zlib_dep,
bzip2_dep, bzip2_dep,
@ -455,15 +380,11 @@ if get_option('build_render')
cpp_args: project_cpp_args, cpp_args: project_cpp_args,
link_args: render_link_args, link_args: render_link_args,
dependencies: render_deps, dependencies: render_deps,
export_dynamic: project_export_dynamic,
) )
endif endif
if get_option('build_font') if get_option('build_font')
if host_platform == 'emscripten' font_deps = [
error('font does not target emscripten')
endif
font_deps = project_deps + [
threads_dep, threads_dep,
zlib_dep, zlib_dep,
png_dep, png_dep,
@ -479,6 +400,5 @@ if get_option('build_font')
cpp_args: project_cpp_args, cpp_args: project_cpp_args,
link_args: project_link_args, link_args: project_link_args,
dependencies: font_deps, dependencies: font_deps,
export_dynamic: project_export_dynamic,
) )
endif endif

View File

@ -37,46 +37,11 @@ option(
description: 'Snapshot build' description: 'Snapshot build'
) )
option( option(
'display_version_major', 'snapshot_id',
type: 'integer', type: 'integer',
min: 0, min: 0,
value: 98, value: 0,
description: 'Major component of the display version, should more or less map to the MINOR version in semantic versioning' description: 'Snapshot ID, only relevant if \'snapshot\' is true'
)
option(
'display_version_minor',
type: 'integer',
min: 0,
value: 2,
description: 'Minor component of the display version, should more or less map to the PATCH version in semantic versioning'
)
option(
'build_num',
type: 'integer',
min: 0,
value: 365,
description: 'Build number, should be strictly monotonously increasing across public releases'
)
option(
'upstream_version_major',
type: 'integer',
min: 0,
value: 98,
description: 'Major component of the upstream display version, mod owners should not change this but merge upstream changes to it'
)
option(
'upstream_version_minor',
type: 'integer',
min: 0,
value: 2,
description: 'Minor component of the upstream display version, mod owners should not change this but merge upstream changes to it'
)
option(
'upstream_build_num',
type: 'integer',
min: 0,
value: 365,
description: 'Upstream build number, mod owners should not change this but merge upstream changes to it'
) )
option( option(
'mod_id', 'mod_id',
@ -133,7 +98,7 @@ option(
'update_server', 'update_server',
type: 'string', type: 'string',
value: '', value: '',
description: 'Update server, only used by snapshots and mods, see \'snapshot\' and \'mod_id\'' description: 'Update server, only used by snapshots and mods, see \'snapshot_id\' and \'mod_id\''
) )
option( option(
'workaround_noncpp_lua', 'workaround_noncpp_lua',
@ -237,12 +202,6 @@ option(
value: true, value: true,
description: 'Enforce encrypted HTTP traffic, may be disabled for debugging' description: 'Enforce encrypted HTTP traffic, may be disabled for debugging'
) )
option(
'secure_ciphers_only',
type: 'boolean',
value: false,
description: 'Use only secure ciphers for encrypted HTTP traffic, please review cipher list before enabling'
)
option( option(
'prepare', 'prepare',
type: 'boolean', type: 'boolean',
@ -255,59 +214,3 @@ option(
value: 'disabled', value: 'disabled',
description: 'Render icons with Inkscape (inkscape binary needs to be in PATH)' description: 'Render icons with Inkscape (inkscape binary needs to be in PATH)'
) )
option(
'resolve_vcs_tag',
type: 'combo',
choices: [ 'no', 'static_release_only', 'yes' ],
value: 'static_release_only',
description: 'Enable VCS tag resolution, introduces an always-stale custom target'
)
option(
'manifest_copyright',
type: 'string',
value: 'Copyright © 2008-2011 Stanislaw K Skowrenek, Copyright © 2011-2023 Simon Robertshaw, Copyright © 2016-2023 jacob1',
description: 'Copyright string, don\'t take too seriously, subject to change'
)
option(
'manifest_macos_min_ver',
type: 'string',
value: '',
description: 'MacOS minimum allowed platform version string, used by ghactions workflows, not useful otherwise'
)
option(
'manifest_date',
type: 'string',
value: '',
description: 'Build date string, used by ghactions workflows, not useful otherwise'
)
option(
'platform_clipboard',
type: 'boolean',
value: true,
description: 'Enable platform clipboard, allows copying simulation data between different windows'
)
option(
'use_bluescreen',
type: 'combo',
choices: [ 'no', 'yes', 'auto' ],
value: 'auto',
description: 'Show blue error screen upon unhandled signals and exceptions'
)
option(
'windows_icons',
type: 'boolean',
value: true,
description: 'Add icon resources to the executable on Windows'
)
option(
'windows_utf8cp',
type: 'boolean',
value: true,
description: 'Ask Windows nicely for UTF-8 as the codepage'
)
option(
'export_lua_symbols',
type: 'boolean',
value: false,
description: 'Export Lua symbols to enable loading of Lua shared modules'
)

View File

@ -18,7 +18,7 @@
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>icon_exe.icns</string> <string>icon_exe.icns</string>
<key>NSHumanReadableCopyright</key> <key>NSHumanReadableCopyright</key>
<string>@MANIFEST_COPYRIGHT@</string> <string>Copyright © 2008-2011 Stanislaw K Skowrenek, Copyright © 2011-2023 Simon Robertshaw, Copyright © 2016-2023 jacob1</string>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>English</string> <string>English</string>
<key>CFBundleDocumentTypes</key> <key>CFBundleDocumentTypes</key>
@ -57,13 +57,13 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>@DISPLAY_VERSION_MAJOR@.@DISPLAY_VERSION_MINOR@</string> <string>SUBST_SAVE_VERSION.SUBST_MINOR_VERSION</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>@DISPLAY_VERSION_MAJOR@.@DISPLAY_VERSION_MINOR@ (build @BUILD_NUM@)</string> <string>SUBST_SAVE_VERSION.SUBST_MINOR_VERSION (build SUBST_BUILD_NUM)</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>@MANIFEST_MACOS_MIN_VER@</string> <string>SUBST_MACOS_MIN_VER</string>
<key>NSPrincipalClass</key> <key>NSPrincipalClass</key>
<string>NSApplication</string> <string>NSApplication</string>
</dict> </dict>

View File

@ -1,137 +1,67 @@
#include <cstdint>
#include <cstring>
#include <fstream> #include <fstream>
#include <iostream>
#include <string>
#include <vector> #include <vector>
#include <cstdint>
static void writeU32LE(uint8_t *dest, uint32_t value)
{
dest[0] = uint8_t( value & 0xFF);
dest[1] = uint8_t((value >> 8) & 0xFF);
dest[2] = uint8_t((value >> 16) & 0xFF);
dest[3] = uint8_t((value >> 24) & 0xFF);
}
static uint32_t readU32BE(const uint8_t *src)
{
return uint32_t(src[3]) |
(uint32_t(src[2]) << 8) |
(uint32_t(src[1]) << 16) |
(uint32_t(src[0]) << 24);
}
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
if (argc < 3) if (argc < 3)
{ {
std::cerr << "usage: " << argv[0] << " OUTPUT INPUT..." << std::endl; return 1;
exit(1); }
auto *outputIcoPath = argv[1];
std::ofstream outputIco(outputIcoPath, std::ios::binary);
if (!outputIco)
{
return 2;
} }
auto images = argc - 2; auto images = argc - 2;
if (images > 255) std::vector<char> header(22 + images * 16);
auto *incondir = &header[0];
*reinterpret_cast<uint16_t *>(&incondir[0]) = 0; // reserved
*reinterpret_cast<uint16_t *>(&incondir[2]) = 1; // icon
*reinterpret_cast<uint16_t *>(&incondir[4]) = uint16_t(images);
std::vector<char> allData;
for (auto i = 0; i < images; ++i)
{ {
std::cerr << "too many images specified" << std::endl; auto *inputAnyPath = argv[i + 2];
exit(1); std::ifstream inputAny(inputAnyPath, std::ios::binary);
std::vector<char> data;
while (true)
{
char ch;
inputAny.read(&ch, 1);
if (inputAny.eof())
{
break;
}
if (!inputAny)
{
return 3;
}
data.push_back(ch);
}
if (*reinterpret_cast<uint64_t *>(&data[0]) != UINT64_C(0x0A1A0A0D474E5089)) // png magic
{
return 5;
}
auto width = uint8_t(data[19]);
auto height = uint8_t(data[23]);
auto *incondirentry = &header[6 + i * 16];
*reinterpret_cast<uint8_t *>(&incondirentry[0]) = width;
*reinterpret_cast<uint8_t *>(&incondirentry[1]) = height;
*reinterpret_cast<uint8_t *>(&incondirentry[2]) = 0; // no color palette
*reinterpret_cast<uint8_t *>(&incondirentry[3]) = 0; // reserved
*reinterpret_cast<uint16_t *>(&incondirentry[4]) = 1; // 1 color plane
*reinterpret_cast<uint16_t *>(&incondirentry[6]) = 32; // 32 bits per pixel
*reinterpret_cast<uint32_t *>(&incondirentry[8]) = uint32_t(data.size()); // data size
*reinterpret_cast<uint32_t *>(&incondirentry[12]) = uint32_t(header.size() + allData.size()); // data offset
allData.insert(allData.end(), data.begin(), data.end());
} }
std::string outputPath = argv[1]; outputIco.write(&header[0], header.size());
std::ofstream output(outputPath, std::ios::binary); outputIco.write(&allData[0], allData.size());
auto outputFailure = [&outputPath](std::string action) { if (!outputIco)
std::cerr << "failed to " << action << " " << outputPath << ": " << strerror(errno) << std::endl;
exit(1);
};
if (!output)
{ {
outputFailure("open"); return 4;
} }
std::vector<char> header(6 + images * 16, 0);
auto writeHeader = [&header, &output, &outputFailure]() {
output.seekp(0, std::ios_base::beg);
output.write(&header[0], header.size());
if (!output)
{
outputFailure("write");
}
};
writeHeader(); // make space for header
auto *headerU8 = reinterpret_cast<uint8_t *>(&header[0]);
headerU8[2] = 1;
headerU8[4] = images;
for (auto image = 0; image < images; ++image)
{
std::string inputPath = argv[2 + image];
std::ifstream input(inputPath, std::ios::binary);
auto inputFailure = [&inputPath](std::string action) {
std::cerr << "failed to " << action << " " << inputPath << ": " << strerror(errno) << std::endl;
exit(1);
};
auto imageFailure = [&inputPath](std::string failure) {
std::cerr << "failed to process " << inputPath << ": " << failure << std::endl;
exit(1);
};
if (!input)
{
inputFailure("open");
}
std::vector<char> buf;
input.seekg(0, std::ios_base::end);
buf.resize(input.tellg());
input.seekg(0, std::ios_base::beg);
input.read(&buf[0], buf.size());
if (!input)
{
inputFailure("read");
}
auto *bufU8 = reinterpret_cast<uint8_t *>(&buf[0]);
if (buf.size() < 0x21 ||
readU32BE(&bufU8[0]) != UINT32_C(0x89504E47) ||
readU32BE(&bufU8[4]) != UINT32_C(0x0D0A1A0A) ||
bufU8[0x18] != 8 ||
bufU8[0x19] != 6)
{
imageFailure("not a 32bpp RGBA PNG");
}
auto writeOffset = output.tellp();
output.write(&buf[0], buf.size());
if (!output)
{
outputFailure("write");
}
auto width = readU32BE(&bufU8[0x10]);
auto height = readU32BE(&bufU8[0x14]);
if (width == 256)
{
width = 0;
}
if (width > 255)
{
imageFailure("width exceeds U8 limit");
}
if (height == 256)
{
height = 0;
}
if (height > 255)
{
imageFailure("height exceeds U8 limit");
}
auto *entryU8 = headerU8 + 6 + image * 16;
entryU8[0] = width;
entryU8[1] = height;
entryU8[4] = 1;
entryU8[6] = 32;
if (buf.size() > UINT32_MAX)
{
imageFailure("data size exceeds U32 limit");
}
writeU32LE(&entryU8[8], uint32_t(buf.size()));
if (writeOffset > UINT32_MAX)
{
std::cerr << "output data size exceeds U32 limit" << std::endl;
exit(1);
}
writeU32LE(&entryU8[12], uint32_t(writeOffset));
}
writeHeader(); // actually write it out
return 0; return 0;
} }

View File

@ -29,6 +29,6 @@
<url type="bugtracker">https://github.com/The-Powder-Toy/The-Powder-Toy/issues</url> <url type="bugtracker">https://github.com/The-Powder-Toy/The-Powder-Toy/issues</url>
<url type="help">https://powdertoy.co.uk/Wiki/W/Main_Page.html</url> <url type="help">https://powdertoy.co.uk/Wiki/W/Main_Page.html</url>
<releases> <releases>
<release date="@MANIFEST_DATE@" version="@DISPLAY_VERSION_MAJOR@.@DISPLAY_VERSION_MINOR@.@BUILD_NUM@" /> <release date="SUBST_DATE" version="SUBST_SAVE_VERSION.SUBST_MINOR_VERSION.SUBST_BUILD_NUM" />
</releases> </releases>
</component> </component>

Binary file not shown.

View File

@ -32,65 +32,35 @@ else
endif endif
if host_platform == 'windows' if host_platform == 'windows'
windows_icons = get_option('windows_icons') make_ico = executable('makeico', sources: 'MakeIco.cpp', native: true)
windows_utf8cp = get_option('windows_utf8cp') generated_win_icos = {}
rc_conf_depends = [] win_icos = {
rc_conf_depend_files = [ 'icon_exe': [ 'icon_exe', 'icon_exe_48', 'icon_exe_32', 'icon_exe_16' ],
'resource.h', 'icon_cps': [ 'icon_cps', 'icon_cps_48', 'icon_cps_32', 'icon_cps_16' ],
] }
icon_exe_ico_path = '' foreach key, icons : win_icos
icon_cps_ico_path = '' command = [
winutf8_xml_path = '' make_ico,
if windows_icons '@OUTPUT@',
make_ico = executable('makeico', sources: 'MakeIco.cpp', native: true) ]
generated_win_icos = {} foreach ikey : icons
win_icos = { command += [ rendered_icons[ikey] ]
'icon_exe': [ 'icon_exe', 'icon_exe_48', 'icon_exe_32', 'icon_exe_16' ],
'icon_cps': [ 'icon_cps', 'icon_cps_48', 'icon_cps_32', 'icon_cps_16' ],
}
foreach key, icons : win_icos
command = [
make_ico,
'@OUTPUT@',
]
foreach ikey : icons
command += [ rendered_icons[ikey] ]
endforeach
generated_win_icos += { key: custom_target(
key + '-ico',
output: key + '.ico',
command: command,
) }
endforeach endforeach
rc_conf_depends += [ generated_win_icos += { key: custom_target(
key + '-ico',
output: key + '.ico',
command: command,
) }
endforeach
powder_files += windows_mod.compile_resources(
'powder-res.rc',
depends: [
generated_win_icos['icon_exe'], generated_win_icos['icon_exe'],
generated_win_icos['icon_cps'], generated_win_icos['icon_cps'],
] ],
icon_exe_ico_path = join_paths(meson.current_build_dir(), 'icon_exe.ico') depend_files: [
icon_cps_ico_path = join_paths(meson.current_build_dir(), 'icon_cps.ico') 'resource.h',
endif ],
if windows_utf8cp
rc_conf_depend_files += [
'winutf8.xml',
]
winutf8_xml_path = join_paths(meson.current_source_dir(), 'winutf8.xml')
endif
rc_conf_data = configuration_data()
rc_conf_data.merge_from(conf_data)
rc_conf_data.set('HAVE_ICONS', windows_icons ? 1 : 0)
rc_conf_data.set('HAVE_UTF8CP', windows_utf8cp ? 1 : 0)
rc_conf_data.set('RESOUCE_H', join_paths(meson.current_source_dir(), 'resource.h'))
rc_conf_data.set('WINUTF8_XML', winutf8_xml_path)
rc_conf_data.set('ICON_EXE_ICO', icon_exe_ico_path)
rc_conf_data.set('ICON_CPS_ICO', icon_cps_ico_path)
powder_files += windows_mod.compile_resources(
configure_file(
input: 'powder-res.template.rc',
output: 'powder-res.rc',
configuration: rc_conf_data,
),
depends: rc_conf_depends,
depend_files: rc_conf_depend_files,
) )
elif host_platform == 'darwin' elif host_platform == 'darwin'
configure_file( configure_file(

4
resources/powder-res.rc Normal file
View File

@ -0,0 +1,4 @@
#include "resource.h"
IDI_ICON ICON DISCARDABLE "icon_exe.ico"
IDI_DOC_ICON ICON DISCARDABLE "icon_cps.ico"

View File

@ -1,40 +0,0 @@
#pragma code_page(65001) // UTF-8
#include "@RESOUCE_H@"
#include <winuser.h>
#include <winver.h>
#include <ntdef.h>
#define HAVE_ICONS @HAVE_ICONS@
#define HAVE_UTF8CP @HAVE_UTF8CP@
#if HAVE_ICONS
IDI_ICON ICON DISCARDABLE "@ICON_EXE_ICO@"
IDI_DOC_ICON ICON DISCARDABLE "@ICON_CPS_ICO@"
#endif
#if HAVE_UTF8CP
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "@WINUTF8_XML@"
#endif
VS_VERSION_INFO VERSIONINFO
FILEVERSION @DISPLAY_VERSION_MAJOR@,@DISPLAY_VERSION_MINOR@,0,@BUILD_NUM@
PRODUCTVERSION @DISPLAY_VERSION_MAJOR@,@DISPLAY_VERSION_MINOR@,0,@BUILD_NUM@
{
BLOCK "StringFileInfo"
{
BLOCK "040904b0"
{
VALUE "CompanyName", "https://powdertoy.co.uk/\0"
VALUE "FileDescription", "@APPNAME@\0"
VALUE "FileVersion", "@DISPLAY_VERSION_MAJOR@.@DISPLAY_VERSION_MINOR@.0.@BUILD_NUM@\0"
VALUE "OriginalFilename", "@APPEXE@.exe\0"
VALUE "LegalCopyright", "@MANIFEST_COPYRIGHT@\0"
VALUE "ProductName", "@APPCOMMENT@\0"
VALUE "ProductVersion", "@DISPLAY_VERSION_MAJOR@.@DISPLAY_VERSION_MINOR@.0.@BUILD_NUM@\0"
VALUE "InternalName", "@APPID@\0"
}
}
BLOCK "VarFileInfo"
{
VALUE "Translation", 0x409, 65001
}
}

View File

@ -1,8 +0,0 @@
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity name="." version="6.0.0.0"/>
<application>
<windowsSettings>
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
</windowsSettings>
</application>
</assembly>

View File

@ -1,6 +1,4 @@
#pragma once #pragma once
#include "VcsTag.h"
#include "common/Version.h"
constexpr bool SET_WINDOW_ICON = @SET_WINDOW_ICON@; constexpr bool SET_WINDOW_ICON = @SET_WINDOW_ICON@;
constexpr bool DEBUG = @DEBUG@; constexpr bool DEBUG = @DEBUG@;
@ -13,27 +11,11 @@ constexpr bool LUACONSOLE = @LUACONSOLE@;
constexpr bool ALLOW_FAKE_NEWER_VERSION = @ALLOW_FAKE_NEWER_VERSION@; constexpr bool ALLOW_FAKE_NEWER_VERSION = @ALLOW_FAKE_NEWER_VERSION@;
constexpr bool USE_UPDATESERVER = @USE_UPDATESERVER@; constexpr bool USE_UPDATESERVER = @USE_UPDATESERVER@;
constexpr bool CAN_INSTALL = @CAN_INSTALL@; constexpr bool CAN_INSTALL = @CAN_INSTALL@;
constexpr bool USE_BLUESCREEN = @USE_BLUESCREEN@;
constexpr bool INSTALL_CHECK = @INSTALL_CHECK@; constexpr bool INSTALL_CHECK = @INSTALL_CHECK@;
constexpr bool IGNORE_UPDATES = @IGNORE_UPDATES@; constexpr bool IGNORE_UPDATES = @IGNORE_UPDATES@;
constexpr bool ENFORCE_HTTPS = @ENFORCE_HTTPS@; constexpr bool ENFORCE_HTTPS = @ENFORCE_HTTPS@;
constexpr bool SECURE_CIPHERS_ONLY = @SECURE_CIPHERS_ONLY@;
constexpr bool PLATFORM_CLIPBOARD = @PLATFORM_CLIPBOARD@;
constexpr bool USE_SYSTEM_CERT_PROVIDER = @USE_SYSTEM_CERT_PROVIDER@;
constexpr bool FFTW_PLAN_MEASURE = @FFTW_PLAN_MEASURE@;
constexpr bool ALLOW_QUIT = @ALLOW_QUIT@;
constexpr bool DEFAULT_TOUCH_UI = @DEFAULT_TOUCH_UI@;
constexpr bool ALLOW_DATA_FOLDER = @ALLOW_DATA_FOLDER@;
constexpr char PATH_SEP_CHAR = '@PATH_SEP_CHAR@'; constexpr char PATH_SEP_CHAR = '@PATH_SEP_CHAR@';
enum ForceWindowFrameOps
{
forceWindowFrameOpsNone, // usual behaviour
forceWindowFrameOpsEmbedded, // e.g. into a webpage; this sweeps a few emscripten limitations under the rug
forceWindowFrameOpsHandheld, // e.g. the system doesn't support windowed mode; includes odd setups like chromebooks
};
constexpr ForceWindowFrameOps FORCE_WINDOW_FRAME_OPS = @FORCE_WINDOW_FRAME_OPS@;
constexpr char SERVER[] = "@SERVER@"; constexpr char SERVER[] = "@SERVER@";
constexpr char STATICSERVER[] = "@STATICSERVER@"; constexpr char STATICSERVER[] = "@STATICSERVER@";
constexpr char UPDATESERVER[] = "@UPDATESERVER@"; constexpr char UPDATESERVER[] = "@UPDATESERVER@";
@ -46,17 +28,13 @@ constexpr char APPID[] = "@APPID@";
constexpr char APPDATA[] = "@APPDATA@"; constexpr char APPDATA[] = "@APPDATA@";
constexpr char APPVENDOR[] = "@APPVENDOR@"; constexpr char APPVENDOR[] = "@APPVENDOR@";
constexpr int SAVE_VERSION = 97;
constexpr int MINOR_VERSION = 0;
constexpr int BUILD_NUM = 352;
constexpr int SNAPSHOT_ID = @SNAPSHOT_ID@;
constexpr int MOD_ID = @MOD_ID@; constexpr int MOD_ID = @MOD_ID@;
constexpr int FUTURE_SAVE_VERSION = 97;
struct DisplayVersionWithBuild constexpr int FUTURE_MINOR_VERSION = 0;
{
Version<2> displayVersion;
size_t build;
};
constexpr DisplayVersionWithBuild APP_VERSION = { { @DISPLAY_VERSION_MAJOR@, @DISPLAY_VERSION_MINOR@ }, @BUILD_NUM@ };
constexpr DisplayVersionWithBuild UPSTREAM_VERSION = { { @UPSTREAM_VERSION_MAJOR@, @UPSTREAM_VERSION_MINOR@ }, @UPSTREAM_BUILD_NUM@ };
constexpr auto DISPLAY_VERSION = APP_VERSION.displayVersion;
constexpr char IDENT_RELTYPE = SNAPSHOT ? 'S' : (BETA ? 'B' : 'R'); constexpr char IDENT_RELTYPE = SNAPSHOT ? 'S' : (BETA ? 'B' : 'R');
@ -65,6 +43,3 @@ constexpr char STATICSCHEME[] = "https://";
constexpr char LOCAL_SAVE_DIR[] = "Saves"; constexpr char LOCAL_SAVE_DIR[] = "Saves";
constexpr char STAMPS_DIR[] = "stamps"; constexpr char STAMPS_DIR[] = "stamps";
constexpr char BRUSH_DIR[] = "Brushes"; constexpr char BRUSH_DIR[] = "Brushes";
constexpr int httpMaxConcurrentStreams = 50;
constexpr int httpConnectTimeoutS = 15;

View File

@ -10,19 +10,12 @@
#include "Format.h" #include "Format.h"
#include "graphics/Graphics.h" #include "graphics/Graphics.h"
ByteString format::UnixtimeToDate(time_t unixtime, ByteString dateFormat, bool local) ByteString format::UnixtimeToDate(time_t unixtime, ByteString dateFormat)
{ {
struct tm * timeData; struct tm * timeData;
char buffer[128]; char buffer[128];
if (local) timeData = localtime(&unixtime);
{
timeData = localtime(&unixtime);
}
else
{
timeData = gmtime(&unixtime);
}
strftime(buffer, 128, dateFormat.c_str(), timeData); strftime(buffer, 128, dateFormat.c_str(), timeData);
return ByteString(buffer); return ByteString(buffer);

View File

@ -11,7 +11,7 @@ namespace format
{ {
ByteString URLEncode(ByteString value); ByteString URLEncode(ByteString value);
ByteString URLDecode(ByteString value); ByteString URLDecode(ByteString value);
ByteString UnixtimeToDate(time_t unixtime, ByteString dateFomat = ByteString("%d %b %Y"), bool local = true); ByteString UnixtimeToDate(time_t unixtime, ByteString dateFomat = ByteString("%d %b %Y"));
ByteString UnixtimeToDateMini(time_t unixtime); ByteString UnixtimeToDateMini(time_t unixtime);
String CleanString(String dirtyString, bool ascii, bool color, bool newlines, bool numeric = false); String CleanString(String dirtyString, bool ascii, bool color, bool newlines, bool numeric = false);
std::vector<char> PixelsToPPM(PlaneAdapter<std::vector<pixel>> const &); std::vector<char> PixelsToPPM(PlaneAdapter<std::vector<pixel>> const &);

View File

@ -1,14 +0,0 @@
#pragma once
#include <variant>
struct FpsLimitVsync
{
};
struct FpsLimitNone
{
};
struct FpsLimitExplicit
{
float value;
};
using FpsLimit = std::variant<FpsLimitVsync, FpsLimitNone, FpsLimitExplicit>;

View File

@ -1,9 +1,75 @@
#include "Misc.h" #include "Misc.h"
#include "common/tpt-minmax.h"
#include "common/String.h" #include "common/String.h"
#include <cstring> #include <cstring>
#include <sys/types.h> #include <sys/types.h>
#include <cmath> #include <cmath>
#include <algorithm>
matrix2d m2d_multiply_m2d(matrix2d m1, matrix2d m2)
{
matrix2d result = {
m1.a*m2.a+m1.b*m2.c, m1.a*m2.b+m1.b*m2.d,
m1.c*m2.a+m1.d*m2.c, m1.c*m2.b+m1.d*m2.d
};
return result;
}
vector2d m2d_multiply_v2d(matrix2d m, vector2d v)
{
vector2d result = {
m.a*v.x+m.b*v.y,
m.c*v.x+m.d*v.y
};
return result;
}
matrix2d m2d_multiply_float(matrix2d m, float s)
{
matrix2d result = {
m.a*s, m.b*s,
m.c*s, m.d*s,
};
return result;
}
vector2d v2d_multiply_float(vector2d v, float s)
{
vector2d result = {
v.x*s,
v.y*s
};
return result;
}
vector2d v2d_add(vector2d v1, vector2d v2)
{
vector2d result = {
v1.x+v2.x,
v1.y+v2.y
};
return result;
}
vector2d v2d_sub(vector2d v1, vector2d v2)
{
vector2d result = {
v1.x-v2.x,
v1.y-v2.y
};
return result;
}
matrix2d m2d_new(float me0, float me1, float me2, float me3)
{
matrix2d result = {me0,me1,me2,me3};
return result;
}
vector2d v2d_new(float x, float y)
{
vector2d result = {x, y};
return result;
}
void HSV_to_RGB(int h,int s,int v,int *r,int *g,int *b)//convert 0-255(0-360 for H) HSV values to 0-255 RGB void HSV_to_RGB(int h,int s,int v,int *r,int *g,int *b)//convert 0-255(0-360 for H) HSV values to 0-255 RGB
{ {
@ -86,6 +152,9 @@ void membwand(void * destv, void * srcv, size_t destsize, size_t srcsize)
} }
} }
vector2d v2d_zero = {0,0};
matrix2d m2d_identity = {1,0,0,1};
bool byteStringEqualsString(const ByteString &str, const char *data, size_t size) bool byteStringEqualsString(const ByteString &str, const char *data, size_t size)
{ {
return str.size() == size && !memcmp(str.data(), data, size); return str.size() == size && !memcmp(str.data(), data, size);

View File

@ -5,25 +5,6 @@
#include <cstddef> #include <cstddef>
#include <vector> #include <vector>
template<class Signed>
inline std::pair<Signed, Signed> floorDiv(Signed a, Signed b)
{
auto quo = a / b;
auto rem = a % b;
if (a < Signed(0) && rem)
{
quo -= Signed(1);
rem += b;
}
return { quo, rem };
}
template<class Signed>
inline std::pair<Signed, Signed> ceilDiv(Signed a, Signed b)
{
return floorDiv(a + b - Signed(1), b);
}
//Linear interpolation //Linear interpolation
template <typename T> inline T LinearInterpolate(T val1, T val2, T lowerCoord, T upperCoord, T coord) template <typename T> inline T LinearInterpolate(T val1, T val2, T lowerCoord, T upperCoord, T coord)
{ {
@ -51,11 +32,6 @@ inline int isign(float i)
return 0; return 0;
} }
inline int iabs(int i)
{
return i * isign(i);
}
inline unsigned clamp_flt(float f, float min, float max) inline unsigned clamp_flt(float f, float min, float max)
{ {
if (f<min) if (f<min)
@ -81,6 +57,45 @@ void HSV_to_RGB(int h,int s,int v,int *r,int *g,int *b);
void RGB_to_HSV(int r,int g,int b,int *h,int *s,int *v); void RGB_to_HSV(int r,int g,int b,int *h,int *s,int *v);
void membwand(void * dest, void * src, size_t destsize, size_t srcsize); void membwand(void * dest, void * src, size_t destsize, size_t srcsize);
// a b
// c d
struct matrix2d {
[[deprecated("Use Mat2")]]
float a,b,c,d;
};
[[deprecated("Use Mat2")]]
typedef struct matrix2d matrix2d;
// column vector
struct vector2d {
[[deprecated("Use Vec2")]]
float x,y;
};
[[deprecated("Use Vec2")]]
typedef struct vector2d vector2d;
//matrix2d m2d_multiply_m2d(matrix2d m1, matrix2d m2);
[[deprecated("Use Mat2::operator*(Vec2)")]]
vector2d m2d_multiply_v2d(matrix2d m, vector2d v);
//matrix2d m2d_multiply_float(matrix2d m, float s);
[[deprecated("Use Vec2<float>::operator*(float)")]]
vector2d v2d_multiply_float(vector2d v, float s);
[[deprecated("Use Vec2::operator+")]]
vector2d v2d_add(vector2d v1, vector2d v2);
[[deprecated("Use Vec2::operator-")]]
vector2d v2d_sub(vector2d v1, vector2d v2);
[[deprecated("Use Mat2")]]
matrix2d m2d_new(float me0, float me1, float me2, float me3);
[[deprecated("Use Vec2")]]
vector2d v2d_new(float x, float y);
[[deprecated("Use Vec2::Zero")]]
extern vector2d v2d_zero;
[[deprecated("Use Mat2::Identity")]]
extern matrix2d m2d_identity;
class ByteString; class ByteString;
bool byteStringEqualsString(const ByteString &str, const char *data, size_t size); bool byteStringEqualsString(const ByteString &str, const char *data, size_t size);

View File

@ -7,22 +7,17 @@
#include "client/SaveFile.h" #include "client/SaveFile.h"
#include "client/SaveInfo.h" #include "client/SaveInfo.h"
#include "client/http/requestmanager/RequestManager.h" #include "client/http/requestmanager/RequestManager.h"
#include "client/http/GetSaveRequest.h"
#include "client/http/GetSaveDataRequest.h"
#include "common/platform/Platform.h" #include "common/platform/Platform.h"
#include "graphics/Graphics.h" #include "graphics/Graphics.h"
#include "simulation/SaveRenderer.h" #include "simulation/SaveRenderer.h"
#include "simulation/SimulationData.h"
#include "common/tpt-rand.h" #include "common/tpt-rand.h"
#include "gui/game/Favorite.h" #include "gui/game/Favorite.h"
#include "gui/Style.h" #include "gui/Style.h"
#include "gui/game/GameController.h" #include "gui/game/GameController.h"
#include "gui/game/GameView.h" #include "gui/game/GameView.h"
#include "gui/game/IntroText.h"
#include "gui/dialogues/ConfirmPrompt.h" #include "gui/dialogues/ConfirmPrompt.h"
#include "gui/dialogues/ErrorMessage.h" #include "gui/dialogues/ErrorMessage.h"
#include "gui/interface/Engine.h" #include "gui/interface/Engine.h"
#include "gui/interface/TextWrapper.h"
#include "Config.h" #include "Config.h"
#include "SimulationConfig.h" #include "SimulationConfig.h"
#include <optional> #include <optional>
@ -30,8 +25,6 @@
#include <iostream> #include <iostream>
#include <csignal> #include <csignal>
#include <SDL.h> #include <SDL.h>
#include <exception>
#include <cstdlib>
void LoadWindowPosition() void LoadWindowPosition()
{ {
@ -85,14 +78,14 @@ void SaveWindowPosition()
void LargeScreenDialog() void LargeScreenDialog()
{ {
StringBuilder message; StringBuilder message;
auto scale = ui::Engine::Ref().windowFrameOps.scale;
message << "Switching to " << scale << "x size mode since your screen was determined to be large enough: "; message << "Switching to " << scale << "x size mode since your screen was determined to be large enough: ";
message << desktopWidth << "x" << desktopHeight << " detected, " << WINDOWW * scale << "x" << WINDOWH * scale << " required"; message << desktopWidth << "x" << desktopHeight << " detected, " << WINDOWW*scale << "x" << WINDOWH*scale << " required";
message << "\nTo undo this, hit Cancel. You can change this in settings at any time."; message << "\nTo undo this, hit Cancel. You can change this in settings at any time.";
new ConfirmPrompt("Large screen detected", message.Build(), { nullptr, []() { if (!ConfirmPrompt::Blocking("Large screen detected", message.Build()))
{
GlobalPrefs::Ref().Set("Scale", 1); GlobalPrefs::Ref().Set("Scale", 1);
ui::Engine::Ref().windowFrameOps.scale = 1; ui::Engine::Ref().SetScale(1);
} }); }
} }
void TickClient() void TickClient()
@ -100,115 +93,50 @@ void TickClient()
Client::Ref().Tick(); Client::Ref().Tick();
} }
static void BlueScreen(String detailMessage, std::optional<std::vector<String>> stackTrace) void BlueScreen(String detailMessage)
{ {
auto &engine = ui::Engine::Ref(); auto &engine = ui::Engine::Ref();
engine.g->BlendFilledRect(engine.g->Size().OriginRect(), 0x1172A9_rgb .WithAlpha(0xD2)); engine.g->BlendFilledRect(engine.g->Size().OriginRect(), 0x1172A9_rgb .WithAlpha(0xD2));
auto crashPrevLogPath = ByteString("crash.prev.log"); String errorTitle = "ERROR";
auto crashLogPath = ByteString("crash.log"); String errorDetails = "Details: " + detailMessage;
Platform::RenameFile(crashLogPath, crashPrevLogPath, true); String errorHelp = String("An unrecoverable fault has occurred, please report the error by visiting the website below\n") + SCHEME + SERVER;
StringBuilder crashInfo; // We use the width of errorHelp to center, but heights of the individual texts for vertical spacing
crashInfo << "ERROR - Details: " << detailMessage << "\n"; auto pos = engine.g->Size() / 2 - Vec2(Graphics::TextSize(errorHelp).X / 2, 100);
crashInfo << "An unrecoverable fault has occurred, please report it by visiting the website below\n\n " << SCHEME << SERVER << "\n\n"; engine.g->BlendText(pos, errorTitle, 0xFFFFFF_rgb .WithAlpha(0xFF));
crashInfo << "An attempt will be made to save all of this information to " << crashLogPath.FromUtf8() << " in your data folder.\n"; pos.Y += 4 + Graphics::TextSize(errorTitle).Y;
crashInfo << "Please attach this file to your report.\n\n"; engine.g->BlendText(pos, errorDetails, 0xFFFFFF_rgb .WithAlpha(0xFF));
crashInfo << "Version: " << VersionInfo().FromUtf8() << "\n"; pos.Y += 4 + Graphics::TextSize(errorDetails).Y;
crashInfo << "Tag: " << VCS_TAG << "\n"; engine.g->BlendText(pos, errorHelp, 0xFFFFFF_rgb .WithAlpha(0xFF));
crashInfo << "Date: " << format::UnixtimeToDate(time(NULL), "%Y-%m-%dT%H:%M:%SZ", false).FromUtf8() << "\n";
if (stackTrace)
{
crashInfo << "Stack trace:\n";
for (auto &item : *stackTrace)
{
crashInfo << " - " << item << "\n";
}
}
else
{
crashInfo << "Stack trace not available\n";
}
String errorText = crashInfo.Build();
constexpr auto width = 440;
ui::TextWrapper tw;
tw.Update(errorText, true, width);
engine.g->BlendText(ui::Point((engine.g->Size().X - width) / 2, 80), tw.WrappedText(), 0xFFFFFF_rgb .WithAlpha(0xFF));
auto crashLogData = errorText.ToUtf8();
std::cerr << crashLogData << std::endl;
Platform::WriteFile(std::vector<char>(crashLogData.begin(), crashLogData.end()), crashLogPath);
//Death loop //Death loop
SDL_Event event; SDL_Event event;
auto running = true; while(true)
while (running)
{ {
while (SDL_PollEvent(&event)) while (SDL_PollEvent(&event))
{ if(event.type == SDL_QUIT)
if (event.type == SDL_QUIT) exit(-1); // Don't use Platform::Exit, we're practically zombies at this point anyway.
{
running = false;
}
}
blit(engine.g->Data()); blit(engine.g->Data());
} }
// Don't use Platform::Exit, we're practically zombies at this point anyway.
#if defined(__MINGW32__) || defined(__APPLE__) || defined(__EMSCRIPTEN__)
// Come on...
exit(-1);
#else
quick_exit(-1);
#endif
} }
static struct void SigHandler(int signal)
{ {
int sig; switch(signal){
const char *message; case SIGSEGV:
} signalMessages[] = { BlueScreen("Memory read/write error");
{ SIGSEGV, "Memory read/write error" }, break;
{ SIGFPE, "Floating point exception" }, case SIGFPE:
{ SIGILL, "Program execution exception" }, BlueScreen("Floating point exception");
{ SIGABRT, "Unexpected program abort" }, break;
{ 0, nullptr }, case SIGILL:
}; BlueScreen("Program execution exception");
break;
static void SigHandler(int signal) case SIGABRT:
{ BlueScreen("Unexpected program abort");
const char *message = "Unknown signal"; break;
for (auto *msg = signalMessages; msg->message; ++msg)
{
if (msg->sig == signal)
{
message = msg->message;
break;
}
} }
BlueScreen(ByteString(message).FromUtf8(), Platform::StackTrace());
}
static void TerminateHandler()
{
ByteString err = "std::terminate called without a current exception";
auto eptr = std::current_exception();
try
{
if (eptr)
{
std::rethrow_exception(eptr);
}
}
catch (const std::exception &e)
{
err = "unhandled exception: " + ByteString(e.what());
}
catch (...)
{
err = "unhandled exception not derived from std::exception, cannot determine reason";
}
BlueScreen(err.FromUtf8(), Platform::StackTrace());
} }
constexpr int SCALE_MAXIMUM = 10; constexpr int SCALE_MAXIMUM = 10;
@ -236,28 +164,17 @@ struct ExplicitSingletons
http::RequestManagerPtr requestManager; http::RequestManagerPtr requestManager;
std::unique_ptr<Client> client; std::unique_ptr<Client> client;
std::unique_ptr<SaveRenderer> saveRenderer; std::unique_ptr<SaveRenderer> saveRenderer;
std::unique_ptr<RNG> rng;
std::unique_ptr<Favorite> favorite; std::unique_ptr<Favorite> favorite;
std::unique_ptr<ui::Engine> engine; std::unique_ptr<ui::Engine> engine;
std::unique_ptr<SimulationData> simulationData;
std::unique_ptr<GameController> gameController; std::unique_ptr<GameController> gameController;
}; };
static std::unique_ptr<ExplicitSingletons> explicitSingletons; static std::unique_ptr<ExplicitSingletons> explicitSingletons;
int main(int argc, char *argv[]) int main(int argc, char * argv[])
{ {
Platform::SetupCrt(); Platform::SetupCrt();
return Platform::InvokeMain(argc, argv);
}
int Main(int argc, char *argv[])
{
Platform::Atexit([]() { Platform::Atexit([]() {
SaveWindowPosition();
// Unregister dodgy error handlers so they don't try to show the blue screen when the window is closed
for (auto *msg = signalMessages; msg->message; ++msg)
{
signal(msg->sig, SIG_DFL);
}
SDLClose(); SDLClose();
explicitSingletons.reset(); explicitSingletons.reset();
}); });
@ -323,38 +240,35 @@ int Main(int argc, char *argv[])
} }
else else
{ {
auto ddir = Platform::DefaultDdir(); char *ddir = SDL_GetPrefPath(NULL, APPDATA);
if (!Platform::FileExists("powder.pref")) if (!Platform::FileExists("powder.pref"))
{ {
if (ddir.size()) if (ddir)
{ {
if (!Platform::ChangeDir(ddir)) if (!Platform::ChangeDir(ddir))
{ {
perror("failed to chdir to default ddir"); perror("failed to chdir to default ddir");
ddir = {}; SDL_free(ddir);
ddir = nullptr;
} }
} }
} }
if (ddir.size()) if (ddir)
{ {
Platform::sharedCwd = ddir; Platform::sharedCwd = ddir;
SDL_free(ddir);
} }
} }
// We're now in the correct directory, time to get prefs. // We're now in the correct directory, time to get prefs.
explicitSingletons->globalPrefs = std::make_unique<GlobalPrefs>(); explicitSingletons->globalPrefs = std::make_unique<GlobalPrefs>();
auto &prefs = GlobalPrefs::Ref(); auto &prefs = GlobalPrefs::Ref();
scale = prefs.Get("Scale", 1);
WindowFrameOps windowFrameOps{ resizable = prefs.Get("Resizable", false);
prefs.Get("Scale", 1), fullscreen = prefs.Get("Fullscreen", false);
prefs.Get("Resizable", false), altFullscreen = prefs.Get("AltFullscreen", false);
prefs.Get("Fullscreen", false), forceIntegerScaling = prefs.Get("ForceIntegerScaling", true);
prefs.Get("AltFullscreen", false),
prefs.Get("ForceIntegerScaling", true),
prefs.Get("BlurryScaling", false),
};
auto graveExitsConsole = prefs.Get("GraveExitsConsole", true);
momentumScroll = prefs.Get("MomentumScroll", true); momentumScroll = prefs.Get("MomentumScroll", true);
showAvatars = prefs.Get("ShowAvatars", true); showAvatars = prefs.Get("ShowAvatars", true);
@ -375,8 +289,8 @@ int Main(int argc, char *argv[])
auto kioskArg = arguments["kiosk"]; auto kioskArg = arguments["kiosk"];
if (kioskArg.has_value()) if (kioskArg.has_value())
{ {
windowFrameOps.fullscreen = true_string(kioskArg.value()); fullscreen = true_string(kioskArg.value());
prefs.Set("Fullscreen", windowFrameOps.fullscreen); prefs.Set("Fullscreen", fullscreen);
} }
if (true_arg(arguments["redirect"])) if (true_arg(arguments["redirect"]))
@ -394,8 +308,8 @@ int Main(int argc, char *argv[])
{ {
try try
{ {
windowFrameOps.scale = scaleArg.value().ToNumber<int>(); scale = scaleArg.value().ToNumber<int>();
prefs.Set("Scale", windowFrameOps.scale); prefs.Set("Scale", scale);
} }
catch (const std::runtime_error &e) catch (const std::runtime_error &e)
{ {
@ -430,45 +344,49 @@ int Main(int argc, char *argv[])
Client::Ref().Initialize(); Client::Ref().Initialize();
explicitSingletons->saveRenderer = std::make_unique<SaveRenderer>(); explicitSingletons->saveRenderer = std::make_unique<SaveRenderer>();
explicitSingletons->rng = std::make_unique<RNG>();
explicitSingletons->favorite = std::make_unique<Favorite>(); explicitSingletons->favorite = std::make_unique<Favorite>();
explicitSingletons->engine = std::make_unique<ui::Engine>(); explicitSingletons->engine = std::make_unique<ui::Engine>();
// TODO: maybe bind the maximum allowed scale to screen size somehow // TODO: maybe bind the maximum allowed scale to screen size somehow
if(windowFrameOps.scale < 1 || windowFrameOps.scale > SCALE_MAXIMUM) if(scale < 1 || scale > SCALE_MAXIMUM)
windowFrameOps.scale = 1; scale = 1;
auto &engine = ui::Engine::Ref();
engine.g = new Graphics();
engine.GraveExitsConsole = graveExitsConsole;
engine.MomentumScroll = momentumScroll;
engine.ShowAvatars = showAvatars;
engine.Begin();
engine.SetFastQuit(prefs.Get("FastQuit", true));
engine.TouchUI = prefs.Get("TouchUI", DEFAULT_TOUCH_UI);
engine.windowFrameOps = windowFrameOps;
SDLOpen(); SDLOpen();
if (Client::Ref().IsFirstRun() && FORCE_WINDOW_FRAME_OPS == forceWindowFrameOpsNone) if (Client::Ref().IsFirstRun())
{ {
auto guessed = GuessBestScale(); scale = GuessBestScale();
if (engine.windowFrameOps.scale != guessed) if (scale > 1)
{ {
engine.windowFrameOps.scale = guessed; prefs.Set("Scale", scale);
prefs.Set("Scale", windowFrameOps.scale); SDL_SetWindowSize(sdl_window, WINDOWW * scale, WINDOWH * scale);
showLargeScreenDialog = true; showLargeScreenDialog = true;
} }
} }
bool enableBluescreen = USE_BLUESCREEN && !true_arg(arguments["disable-bluescreen"]); StopTextInput();
auto &engine = ui::Engine::Ref();
engine.g = new Graphics();
engine.Scale = scale;
engine.SetResizable(resizable);
engine.Fullscreen = fullscreen;
engine.SetAltFullscreen(altFullscreen);
engine.SetForceIntegerScaling(forceIntegerScaling);
engine.MomentumScroll = momentumScroll;
engine.ShowAvatars = showAvatars;
engine.Begin();
engine.SetFastQuit(prefs.Get("FastQuit", true));
bool enableBluescreen = !DEBUG && !true_arg(arguments["disable-bluescreen"]);
if (enableBluescreen) if (enableBluescreen)
{ {
//Get ready to catch any dodgy errors //Get ready to catch any dodgy errors
for (auto *msg = signalMessages; msg->message; ++msg) signal(SIGSEGV, SigHandler);
{ signal(SIGFPE, SigHandler);
signal(msg->sig, SigHandler); signal(SIGILL, SigHandler);
} signal(SIGABRT, SigHandler);
std::set_terminate(TerminateHandler);
} }
if constexpr (X86) if constexpr (X86)
@ -476,97 +394,114 @@ int Main(int argc, char *argv[])
X86KillDenormals(); X86KillDenormals();
} }
explicitSingletons->simulationData = std::make_unique<SimulationData>(); auto wrapWithBluescreen = [&]() {
explicitSingletons->gameController = std::make_unique<GameController>(); explicitSingletons->gameController = std::make_unique<GameController>();
auto *gameController = explicitSingletons->gameController.get(); auto *gameController = explicitSingletons->gameController.get();
engine.ShowWindow(gameController->GetView()); engine.ShowWindow(gameController->GetView());
auto openArg = arguments["open"]; auto openArg = arguments["open"];
if (openArg.has_value()) if (openArg.has_value())
{
if constexpr (DEBUG)
{ {
std::cout << "Loading " << openArg.value() << std::endl; if constexpr (DEBUG)
{
std::cout << "Loading " << openArg.value() << std::endl;
}
if (Platform::FileExists(openArg.value()))
{
try
{
std::vector<char> gameSaveData;
if (!Platform::ReadFile(gameSaveData, openArg.value()))
{
new ErrorMessage("Error", "Could not read file");
}
else
{
SaveFile * newFile = new SaveFile(openArg.value());
GameSave * newSave = new GameSave(std::move(gameSaveData));
newFile->SetGameSave(newSave);
gameController->LoadSaveFile(newFile);
delete newFile;
}
}
catch (std::exception & e)
{
new ErrorMessage("Error", "Could not open save file:\n" + ByteString(e.what()).FromUtf8()) ;
}
}
else
{
new ErrorMessage("Error", "Could not open file");
}
} }
if (Platform::FileExists(openArg.value()))
auto ptsaveArg = arguments["ptsave"];
if (ptsaveArg.has_value())
{ {
engine.g->Clear();
engine.g->DrawRect(RectSized(engine.g->Size() / 2 - Vec2(100, 25), Vec2(200, 50)), 0xB4B4B4_rgb);
String loadingText = "Loading save...";
engine.g->BlendText(engine.g->Size() / 2 - Vec2(Graphics::textwidth(loadingText) / 2, 5), loadingText, style::Colour::InformationTitle);
blit(engine.g->Data());
try try
{ {
std::vector<char> gameSaveData; ByteString saveIdPart;
if (!Platform::ReadFile(gameSaveData, openArg.value())) if (ByteString::Split split = ptsaveArg.value().SplitBy(':'))
{ {
new ErrorMessage("Error", "Could not read file"); if (split.Before() != "ptsave")
throw std::runtime_error("Not a ptsave link");
saveIdPart = split.After().SplitBy('#').Before();
} }
else else
{ throw std::runtime_error("Invalid save link");
auto newFile = std::make_unique<SaveFile>(openArg.value());
auto newSave = std::make_unique<GameSave>(std::move(gameSaveData));
newFile->SetGameSave(std::move(newSave));
gameController->LoadSaveFile(std::move(newFile));
}
if (!saveIdPart.size())
throw std::runtime_error("No Save ID");
if constexpr (DEBUG)
{
std::cout << "Got Ptsave: id: " << saveIdPart << std::endl;
}
int saveId = saveIdPart.ToNumber<int>();
SaveInfo * newSave = Client::Ref().GetSave(saveId, 0);
if (!newSave)
throw std::runtime_error("Could not load save info");
auto saveData = Client::Ref().GetSaveData(saveId, 0);
if (!saveData.size())
throw std::runtime_error(("Could not load save\n" + Client::Ref().GetLastError()).ToUtf8());
GameSave * newGameSave = new GameSave(std::move(saveData));
newSave->SetGameSave(newGameSave);
gameController->LoadSave(newSave);
delete newSave;
} }
catch (std::exception & e) catch (std::exception & e)
{ {
new ErrorMessage("Error", "Could not open save file:\n" + ByteString(e.what()).FromUtf8()) ; new ErrorMessage("Error", ByteString(e.what()).FromUtf8());
} }
} }
else
{
new ErrorMessage("Error", "Could not open file");
}
}
auto ptsaveArg = arguments["ptsave"]; EngineProcess();
if (ptsaveArg.has_value()) SaveWindowPosition();
};
if (enableBluescreen)
{ {
engine.g->Clear();
engine.g->DrawRect(RectSized(engine.g->Size() / 2 - Vec2(100, 25), Vec2(200, 50)), 0xB4B4B4_rgb);
String loadingText = "Loading save...";
engine.g->BlendText(engine.g->Size() / 2 - Vec2((Graphics::TextSize(loadingText).X - 1) / 2, 5), loadingText, style::Colour::InformationTitle);
blit(engine.g->Data());
try try
{ {
ByteString saveIdPart; wrapWithBluescreen();
if (ByteString::Split split = ptsaveArg.value().SplitBy(':'))
{
if (split.Before() != "ptsave")
throw std::runtime_error("Not a ptsave link");
saveIdPart = split.After().SplitBy('#').Before();
}
else
throw std::runtime_error("Invalid save link");
if (!saveIdPart.size())
throw std::runtime_error("No Save ID");
if constexpr (DEBUG)
{
std::cout << "Got Ptsave: id: " << saveIdPart << std::endl;
}
ByteString saveHistoryPart = "0";
if (auto split = saveIdPart.SplitBy('@'))
{
saveHistoryPart = split.After();
saveIdPart = split.Before();
}
int saveId = saveIdPart.ToNumber<int>();
int saveHistory = saveHistoryPart.ToNumber<int>();
gameController->OpenSavePreview(saveId, saveHistory, savePreviewUrl);
} }
catch (std::exception & e) catch (const std::exception &e)
{ {
new ErrorMessage("Error", ByteString(e.what()).FromUtf8()); BlueScreen(ByteString(e.what()).FromUtf8());
Platform::MarkPresentable();
} }
} }
else else
{ {
Platform::MarkPresentable(); wrapWithBluescreen();
} }
MainLoop();
Platform::Exit(0); Platform::Exit(0);
return 0; return 0;
} }

View File

@ -28,6 +28,7 @@ void TickClient()
struct ExplicitSingletons struct ExplicitSingletons
{ {
// These need to be listed in the order they are populated in main. // These need to be listed in the order they are populated in main.
std::unique_ptr<RNG> rng;
std::unique_ptr<ui::Engine> engine; std::unique_ptr<ui::Engine> engine;
}; };
static std::unique_ptr<ExplicitSingletons> explicitSingletons; static std::unique_ptr<ExplicitSingletons> explicitSingletons;
@ -41,31 +42,40 @@ int main(int argc, char * argv[])
}); });
explicitSingletons = std::make_unique<ExplicitSingletons>(); explicitSingletons = std::make_unique<ExplicitSingletons>();
WindowFrameOps windowFrameOps; scale = 1;
if (argc >= 3) if (argc >= 3)
{ {
std::istringstream ss(argv[2]); std::istringstream ss(argv[2]);
int buf; int buf;
if (ss >> buf) if (ss >> buf)
{ {
windowFrameOps.scale = buf; scale = buf;
} }
} }
resizable = false;
fullscreen = false;
altFullscreen = false;
forceIntegerScaling = true;
// TODO: maybe bind the maximum allowed scale to screen size somehow // TODO: maybe bind the maximum allowed scale to screen size somehow
if (windowFrameOps.scale < 1 || windowFrameOps.scale > 10) if(scale < 1 || scale > 10)
{ scale = 1;
windowFrameOps.scale = 1;
}
explicitSingletons->rng = std::make_unique<RNG>();
explicitSingletons->engine = std::make_unique<ui::Engine>(); explicitSingletons->engine = std::make_unique<ui::Engine>();
auto &engine = ui::Engine::Ref();
engine.g = new Graphics();
engine.windowFrameOps = windowFrameOps;
SDLOpen(); SDLOpen();
StopTextInput();
ui::Engine::Ref().g = new Graphics();
ui::Engine::Ref().Scale = scale;
ui::Engine::Ref().SetResizable(resizable);
ui::Engine::Ref().Fullscreen = fullscreen;
ui::Engine::Ref().SetAltFullscreen(altFullscreen);
ui::Engine::Ref().SetForceIntegerScaling(forceIntegerScaling);
auto &engine = ui::Engine::Ref();
engine.Begin(); engine.Begin();
engine.SetFastQuit(true); engine.SetFastQuit(true);
@ -75,14 +85,11 @@ int main(int argc, char * argv[])
} }
else else
{ {
std::cerr << "path to font.bz2 not supplied" << std::endl; std::cerr << "path to font.cpp not supplied" << std::endl;
Platform::Exit(1); Platform::Exit(1);
} }
while (engine.Running()) EngineProcess();
{
EngineProcess();
}
Platform::Exit(0); Platform::Exit(0);
return 0; return 0;
} }

View File

@ -6,7 +6,6 @@
#include "gui/interface/Engine.h" #include "gui/interface/Engine.h"
#include "client/GameSave.h" #include "client/GameSave.h"
#include "simulation/Simulation.h" #include "simulation/Simulation.h"
#include "simulation/SimulationData.h"
#include "common/platform/Platform.h" #include "common/platform/Platform.h"
#include <ctime> #include <ctime>
#include <iostream> #include <iostream>
@ -22,18 +21,16 @@ int main(int argc, char *argv[])
auto inputFilename = ByteString(argv[1]); auto inputFilename = ByteString(argv[1]);
auto outputFilename = ByteString(argv[2]) + ".png"; auto outputFilename = ByteString(argv[2]) + ".png";
auto simulationData = std::make_unique<SimulationData>();
std::vector<char> fileData; std::vector<char> fileData;
if (!Platform::ReadFile(fileData, inputFilename)) if (!Platform::ReadFile(fileData, inputFilename))
{ {
return 1; return 1;
} }
std::unique_ptr<GameSave> gameSave; GameSave * gameSave = NULL;
try try
{ {
gameSave = std::make_unique<GameSave>(fileData, false); gameSave = new GameSave(fileData, false);
} }
catch (ParseException &e) catch (ParseException &e)
{ {
@ -42,12 +39,13 @@ int main(int argc, char *argv[])
throw e; throw e;
} }
auto rng = std::make_unique<RNG>();
Simulation * sim = new Simulation(); Simulation * sim = new Simulation();
Renderer * ren = new Renderer(sim); Renderer * ren = new Renderer(sim);
if (gameSave) if (gameSave)
{ {
sim->Load(gameSave.get(), true, { 0, 0 }); sim->Load(gameSave, true);
//Render save //Render save
ren->decorations_enable = true; ren->decorations_enable = true;
@ -64,10 +62,9 @@ int main(int argc, char *argv[])
} }
else else
{ {
ren->clearScreen(); int w = Graphics::textwidth("Save file invalid")+16, x = (XRES-w)/2, y = (YRES-24)/2;
int w = Graphics::TextSize("Save file invalid").X + 15, x = (XRES-w)/2, y = (YRES-24)/2; ren->drawrect(x, y, w, 24, 192, 192, 192, 255);
ren->DrawRect(RectSized(Vec2{ x, y }, Vec2{ w, 24 }), 0xC0C0C0_rgb); ren->drawtext(x+8, y+8, "Save file invalid", 192, 192, 240, 255);
ren->BlendText({ x+8, y+8 }, "Save file invalid", 0xC0C0F0_rgb .WithAlpha(255));
} }
ren->RenderBegin(); ren->RenderBegin();

View File

@ -5,7 +5,6 @@
#include "gui/interface/Engine.h" #include "gui/interface/Engine.h"
#include "graphics/Graphics.h" #include "graphics/Graphics.h"
#include "common/platform/Platform.h" #include "common/platform/Platform.h"
#include "common/clipboard/Clipboard.h"
#include <iostream> #include <iostream>
int desktopWidth = 1280; int desktopWidth = 1280;
@ -13,8 +12,11 @@ int desktopHeight = 1024;
SDL_Window *sdl_window = NULL; SDL_Window *sdl_window = NULL;
SDL_Renderer *sdl_renderer = NULL; SDL_Renderer *sdl_renderer = NULL;
SDL_Texture *sdl_texture = NULL; SDL_Texture *sdl_texture = NULL;
bool vsyncHint = false; int scale = 1;
WindowFrameOps currentFrameOps; bool fullscreen = false;
bool altFullscreen = false;
bool forceIntegerScaling = true;
bool resizable = false;
bool momentumScroll = true; bool momentumScroll = true;
bool showAvatars = true; bool showAvatars = true;
uint64_t lastTick = 0; uint64_t lastTick = 0;
@ -26,10 +28,6 @@ int mouseButton = 0;
bool mouseDown = false; bool mouseDown = false;
bool calculatedInitialMouse = false; bool calculatedInitialMouse = false;
bool hasMouseMoved = false; bool hasMouseMoved = false;
double correctedFrameTimeAvg = 0;
uint64_t drawingTimer = 0;
uint64_t frameStart = 0;
uint64_t oldFrameStart = 0;
void StartTextInput() void StartTextInput()
{ {
@ -43,24 +41,11 @@ void StopTextInput()
void SetTextInputRect(int x, int y, int w, int h) void SetTextInputRect(int x, int y, int w, int h)
{ {
// Why does SDL_SetTextInputRect not take logical coordinates???
SDL_Rect rect; SDL_Rect rect;
#if SDL_VERSION_ATLEAST(2, 0, 18) rect.x = x;
int wx, wy, wwx, why; rect.y = y;
SDL_RenderLogicalToWindow(sdl_renderer, float(x), float(y), &wx, &wy); rect.w = w;
SDL_RenderLogicalToWindow(sdl_renderer, float(x + w), float(y + h), &wwx, &why); rect.h = h;
rect.x = wx;
rect.y = wy;
rect.w = wwx - wx;
rect.h = why - wy;
#else
// TODO: use SDL_RenderLogicalToWindow when ubuntu deigns to update to sdl 2.0.18
auto scale = ui::Engine::Ref().windowFrameOps.scale;
rect.x = x * scale;
rect.y = y * scale;
rect.w = w * scale;
rect.h = h * scale;
#endif
SDL_SetTextInputRect(&rect); SDL_SetTextInputRect(&rect);
} }
@ -84,7 +69,7 @@ unsigned int GetTicks()
return SDL_GetTicks(); return SDL_GetTicks();
} }
static void CalculateMousePosition(int *x, int *y) void CalculateMousePosition(int *x, int *y)
{ {
int globalMx, globalMy; int globalMx, globalMy;
SDL_GetGlobalMouseState(&globalMx, &globalMy); SDL_GetGlobalMouseState(&globalMx, &globalMy);
@ -92,16 +77,16 @@ static void CalculateMousePosition(int *x, int *y)
SDL_GetWindowPosition(sdl_window, &windowX, &windowY); SDL_GetWindowPosition(sdl_window, &windowX, &windowY);
if (x) if (x)
*x = (globalMx - windowX) / currentFrameOps.scale; *x = (globalMx - windowX) / scale;
if (y) if (y)
*y = (globalMy - windowY) / currentFrameOps.scale; *y = (globalMy - windowY) / scale;
} }
void blit(pixel *vid) void blit(pixel *vid)
{ {
SDL_UpdateTexture(sdl_texture, NULL, vid, WINDOWW * sizeof (Uint32)); SDL_UpdateTexture(sdl_texture, NULL, vid, WINDOWW * sizeof (Uint32));
// need to clear the renderer if there are black edges (fullscreen, or resizable window) // need to clear the renderer if there are black edges (fullscreen, or resizable window)
if (currentFrameOps.fullscreen || currentFrameOps.resizable) if (fullscreen || resizable)
SDL_RenderClear(sdl_renderer); SDL_RenderClear(sdl_renderer);
SDL_RenderCopy(sdl_renderer, sdl_texture, NULL, NULL); SDL_RenderCopy(sdl_renderer, sdl_texture, NULL, NULL);
SDL_RenderPresent(sdl_renderer); SDL_RenderPresent(sdl_renderer);
@ -114,9 +99,12 @@ void SDLOpen()
fprintf(stderr, "Initializing SDL (video subsystem): %s\n", SDL_GetError()); fprintf(stderr, "Initializing SDL (video subsystem): %s\n", SDL_GetError());
Platform::Exit(-1); Platform::Exit(-1);
} }
Clipboard::Init();
SDLSetScreen(); if (!RecreateWindow())
{
fprintf(stderr, "Creating SDL window: %s\n", SDL_GetError());
Platform::Exit(-1);
}
int displayIndex = SDL_GetWindowDisplayIndex(sdl_window); int displayIndex = SDL_GetWindowDisplayIndex(sdl_window);
if (displayIndex >= 0) if (displayIndex >= 0)
@ -129,7 +117,10 @@ void SDLOpen()
} }
} }
StopTextInput(); if constexpr (SET_WINDOW_ICON)
{
WindowIcon(sdl_window);
}
} }
void SDLClose() void SDLClose()
@ -148,150 +139,105 @@ void SDLClose()
SDL_Quit(); SDL_Quit();
} }
void SDLSetScreen() void SDLSetScreen(int scale_, bool resizable_, bool fullscreen_, bool altFullscreen_, bool forceIntegerScaling_)
{ {
auto newFrameOps = ui::Engine::Ref().windowFrameOps; // bool changingScale = scale != scale_;
auto newVsyncHint = std::holds_alternative<FpsLimitVsync>(ui::Engine::Ref().GetFpsLimit()); bool changingFullscreen = fullscreen_ != fullscreen || (altFullscreen_ != altFullscreen && fullscreen);
if (FORCE_WINDOW_FRAME_OPS == forceWindowFrameOpsEmbedded) bool changingResizable = resizable != resizable_;
{ scale = scale_;
newFrameOps.resizable = false; fullscreen = fullscreen_;
newFrameOps.fullscreen = false; altFullscreen = altFullscreen_;
newFrameOps.changeResolution = false; resizable = resizable_;
newFrameOps.forceIntegerScaling = false; forceIntegerScaling = forceIntegerScaling_;
} // Recreate the window when toggling fullscreen, due to occasional issues
if (FORCE_WINDOW_FRAME_OPS == forceWindowFrameOpsHandheld) // Also recreate it when enabling resizable windows, to fix bugs on windows,
{ // see https://github.com/jacob1/The-Powder-Toy/issues/24
newFrameOps.resizable = false; if (changingFullscreen || altFullscreen || (changingResizable && resizable && !fullscreen))
newFrameOps.fullscreen = true;
newFrameOps.changeResolution = false;
newFrameOps.forceIntegerScaling = false;
}
auto currentFrameOpsNorm = currentFrameOps.Normalize();
auto newFrameOpsNorm = newFrameOps.Normalize();
auto recreate = !sdl_window ||
// Recreate the window when toggling fullscreen, due to occasional issues
newFrameOpsNorm.fullscreen != currentFrameOpsNorm.fullscreen ||
// Also recreate it when enabling resizable windows, to fix bugs on windows,
// see https://github.com/jacob1/The-Powder-Toy/issues/24
newFrameOpsNorm.resizable != currentFrameOpsNorm.resizable ||
newFrameOpsNorm.changeResolution != currentFrameOpsNorm.changeResolution ||
newFrameOpsNorm.blurryScaling != currentFrameOpsNorm.blurryScaling ||
newVsyncHint != vsyncHint;
if (!(recreate ||
newFrameOpsNorm.scale != currentFrameOpsNorm.scale ||
newFrameOpsNorm.forceIntegerScaling != currentFrameOpsNorm.forceIntegerScaling))
{ {
RecreateWindow();
return; return;
} }
if (changingResizable)
SDL_RestoreWindow(sdl_window);
auto size = WINDOW * newFrameOpsNorm.scale; SDL_SetWindowSize(sdl_window, WINDOWW * scale, WINDOWH * scale);
if (sdl_window && newFrameOpsNorm.resizable) SDL_RenderSetIntegerScale(sdl_renderer, forceIntegerScaling && fullscreen ? SDL_TRUE : SDL_FALSE);
{ unsigned int flags = 0;
SDL_GetWindowSize(sdl_window, &size.X, &size.Y); if (fullscreen)
} flags = altFullscreen ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP;
SDL_SetWindowFullscreen(sdl_window, flags);
if (recreate) if (fullscreen)
{
if (sdl_texture)
{
SDL_DestroyTexture(sdl_texture);
sdl_texture = NULL;
}
if (sdl_renderer)
{
SDL_DestroyRenderer(sdl_renderer);
sdl_renderer = NULL;
}
if (sdl_window)
{
SaveWindowPosition();
SDL_DestroyWindow(sdl_window);
sdl_window = NULL;
}
unsigned int flags = 0;
unsigned int rendererFlags = 0;
if (newFrameOpsNorm.fullscreen)
{
flags = newFrameOpsNorm.changeResolution ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP;
}
if (newFrameOpsNorm.resizable)
{
flags |= SDL_WINDOW_RESIZABLE;
}
if (vsyncHint)
{
rendererFlags |= SDL_RENDERER_PRESENTVSYNC;
}
sdl_window = SDL_CreateWindow(APPNAME, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, size.X, size.Y, flags);
if (!sdl_window)
{
fprintf(stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError());
Platform::Exit(-1);
}
if constexpr (SET_WINDOW_ICON)
{
WindowIcon(sdl_window);
}
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, newFrameOpsNorm.blurryScaling ? "linear" : "nearest");
sdl_renderer = SDL_CreateRenderer(sdl_window, -1, rendererFlags);
if (!sdl_renderer)
{
fprintf(stderr, "SDL_CreateRenderer failed; available renderers:\n");
int num = SDL_GetNumRenderDrivers();
for (int i = 0; i < num; ++i)
{
SDL_RendererInfo info;
SDL_GetRenderDriverInfo(i, &info);
fprintf(stderr, " - %s\n", info.name);
}
Platform::Exit(-1);
}
SDL_RenderSetLogicalSize(sdl_renderer, WINDOWW, WINDOWH);
sdl_texture = SDL_CreateTexture(sdl_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, WINDOWW, WINDOWH);
if (!sdl_texture)
{
fprintf(stderr, "SDL_CreateTexture failed: %s\n", SDL_GetError());
Platform::Exit(-1);
}
SDL_RaiseWindow(sdl_window); SDL_RaiseWindow(sdl_window);
Clipboard::RecreateWindow(); SDL_SetWindowResizable(sdl_window, resizable ? SDL_TRUE : SDL_FALSE);
}
SDL_RenderSetIntegerScale(sdl_renderer, newFrameOpsNorm.forceIntegerScaling ? SDL_TRUE : SDL_FALSE);
if (!(newFrameOpsNorm.resizable && SDL_GetWindowFlags(sdl_window) & SDL_WINDOW_MAXIMIZED))
{
SDL_SetWindowSize(sdl_window, size.X, size.Y);
LoadWindowPosition();
}
UpdateFpsLimit();
if (newFrameOpsNorm.fullscreen)
{
SDL_RaiseWindow(sdl_window);
}
currentFrameOps = newFrameOps;
vsyncHint = newVsyncHint;
} }
static void EventProcess(const SDL_Event &event) bool RecreateWindow()
{
unsigned int flags = 0;
if (fullscreen)
flags = altFullscreen ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP;
if (resizable && !fullscreen)
flags |= SDL_WINDOW_RESIZABLE;
if (sdl_texture)
SDL_DestroyTexture(sdl_texture);
if (sdl_renderer)
SDL_DestroyRenderer(sdl_renderer);
if (sdl_window)
{
SaveWindowPosition();
SDL_DestroyWindow(sdl_window);
}
sdl_window = SDL_CreateWindow(APPNAME, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WINDOWW * scale, WINDOWH * scale,
flags);
if (!sdl_window)
{
return false;
}
sdl_renderer = SDL_CreateRenderer(sdl_window, -1, 0);
if (!sdl_renderer)
{
fprintf(stderr, "SDL_CreateRenderer failed; available renderers:\n");
int num = SDL_GetNumRenderDrivers();
for (int i = 0; i < num; ++i)
{
SDL_RendererInfo info;
SDL_GetRenderDriverInfo(i, &info);
fprintf(stderr, " - %s\n", info.name);
}
return false;
}
SDL_RenderSetLogicalSize(sdl_renderer, WINDOWW, WINDOWH);
if (forceIntegerScaling && fullscreen)
SDL_RenderSetIntegerScale(sdl_renderer, SDL_TRUE);
sdl_texture = SDL_CreateTexture(sdl_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, WINDOWW, WINDOWH);
SDL_RaiseWindow(sdl_window);
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
//Uncomment this to enable resizing
//SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
//SDL_SetWindowResizable(sdl_window, SDL_TRUE);
LoadWindowPosition();
return true;
}
void EventProcess(const SDL_Event &event)
{ {
auto &engine = ui::Engine::Ref(); auto &engine = ui::Engine::Ref();
switch (event.type) switch (event.type)
{ {
case SDL_QUIT: case SDL_QUIT:
if (ALLOW_QUIT && (engine.GetFastQuit() || engine.CloseWindow())) if (engine.GetFastQuit() || engine.CloseWindow())
{
engine.Exit(); engine.Exit();
}
break; break;
case SDL_KEYDOWN: case SDL_KEYDOWN:
if (SDL_GetModState() & KMOD_GUI) if (SDL_GetModState() & KMOD_GUI)
{ {
break; break;
} }
if (ALLOW_QUIT && !event.key.repeat && event.key.keysym.sym == 'q' && (event.key.keysym.mod&KMOD_CTRL)) if (!event.key.repeat && event.key.keysym.sym == 'q' && (event.key.keysym.mod&KMOD_CTRL))
engine.ConfirmExit(); engine.ConfirmExit();
else else
engine.onKeyPress(event.key.keysym.sym, event.key.keysym.scancode, event.key.repeat, event.key.keysym.mod&KMOD_SHIFT, event.key.keysym.mod&KMOD_CTRL, event.key.keysym.mod&KMOD_ALT); engine.onKeyPress(event.key.keysym.sym, event.key.keysym.scancode, event.key.repeat, event.key.keysym.mod&KMOD_SHIFT, event.key.keysym.mod&KMOD_CTRL, event.key.keysym.mod&KMOD_ALT);
@ -349,7 +295,7 @@ static void EventProcess(const SDL_Event &event)
mousey = event.button.y; mousey = event.button.y;
} }
mouseButton = event.button.button; mouseButton = event.button.button;
engine.onMouseDown(mousex, mousey, mouseButton); engine.onMouseClick(mousex, mousey, mouseButton);
mouseDown = true; mouseDown = true;
if constexpr (!DEBUG) if constexpr (!DEBUG)
@ -365,7 +311,7 @@ static void EventProcess(const SDL_Event &event)
mousey = event.button.y; mousey = event.button.y;
} }
mouseButton = event.button.button; mouseButton = event.button.button;
engine.onMouseUp(mousex, mousey, mouseButton); engine.onMouseUnclick(mousex, mousey, mouseButton);
mouseDown = false; mouseDown = false;
if constexpr (!DEBUG) if constexpr (!DEBUG)
@ -395,52 +341,75 @@ static void EventProcess(const SDL_Event &event)
void EngineProcess() void EngineProcess()
{ {
auto &engine = ui::Engine::Ref(); double correctedFrameTimeAvg = 0;
auto correctedFrameTime = frameStart - oldFrameStart;
drawingTimer += correctedFrameTime;
correctedFrameTimeAvg = correctedFrameTimeAvg + (correctedFrameTime - correctedFrameTimeAvg) * 0.05;
if (correctedFrameTime && frameStart - lastFpsUpdate > UINT64_C(200'000'000))
{
engine.SetFps(1e9f / correctedFrameTimeAvg);
lastFpsUpdate = frameStart;
}
if (frameStart - lastTick > UINT64_C(100'000'000))
{
lastTick = frameStart;
TickClient();
}
if (showLargeScreenDialog)
{
showLargeScreenDialog = false;
LargeScreenDialog();
}
SDL_Event event; SDL_Event event;
while (SDL_PollEvent(&event))
{
EventProcess(event);
}
engine.Tick(); uint64_t drawingTimer = 0;
auto frameStart = uint64_t(SDL_GetTicks()) * UINT64_C(1'000'000);
auto fpsLimit = ui::Engine::Ref().GetFpsLimit(); auto &engine = ui::Engine::Ref();
int drawcap = ui::Engine::Ref().GetDrawingFrequencyLimit(); while(engine.Running())
if (!drawcap || drawingTimer > 1e9f / drawcap)
{ {
engine.Draw(); if(engine.Broken()) { engine.UnBreak(); break; }
drawingTimer = 0; event.type = 0;
SDLSetScreen(); while (SDL_PollEvent(&event))
blit(engine.g->Data()); {
EventProcess(event);
event.type = 0; //Clear last event
}
if(engine.Broken()) { engine.UnBreak(); break; }
engine.Tick();
int drawcap = ui::Engine::Ref().GetDrawingFrequencyLimit();
if (!drawcap || drawingTimer > 1e9f / drawcap)
{
engine.Draw();
drawingTimer = 0;
if (scale != engine.Scale || fullscreen != engine.Fullscreen ||
altFullscreen != engine.GetAltFullscreen() ||
forceIntegerScaling != engine.GetForceIntegerScaling() || resizable != engine.GetResizable())
{
SDLSetScreen(engine.Scale, engine.GetResizable(), engine.Fullscreen, engine.GetAltFullscreen(),
engine.GetForceIntegerScaling());
}
blit(engine.g->Data());
}
auto fpsLimit = ui::Engine::Ref().FpsLimit;
auto now = uint64_t(SDL_GetTicks()) * UINT64_C(1'000'000);
auto oldFrameStart = frameStart;
frameStart = now;
if (fpsLimit > 2)
{
auto timeBlockDuration = uint64_t(UINT64_C(1'000'000'000) / fpsLimit);
auto oldFrameStartTimeBlock = oldFrameStart / timeBlockDuration;
auto frameStartTimeBlock = oldFrameStartTimeBlock + 1U;
frameStart = std::max(frameStart, frameStartTimeBlock * timeBlockDuration);
SDL_Delay((frameStart - now) / UINT64_C(1'000'000));
}
auto correctedFrameTime = frameStart - oldFrameStart;
drawingTimer += correctedFrameTime;
correctedFrameTimeAvg = correctedFrameTimeAvg + (correctedFrameTime - correctedFrameTimeAvg) * 0.05;
if (frameStart - lastFpsUpdate > UINT64_C(200'000'000))
{
engine.SetFps(1e9f / correctedFrameTimeAvg);
lastFpsUpdate = frameStart;
}
if (frameStart - lastTick > UINT64_C(100'000'000))
{
lastTick = frameStart;
TickClient();
}
if (showLargeScreenDialog)
{
showLargeScreenDialog = false;
LargeScreenDialog();
}
} }
auto now = uint64_t(SDL_GetTicks()) * UINT64_C(1'000'000); if constexpr (DEBUG)
oldFrameStart = frameStart;
frameStart = now;
if (auto *fpsLimitExplicit = std::get_if<FpsLimitExplicit>(&fpsLimit))
{ {
auto timeBlockDuration = uint64_t(UINT64_C(1'000'000'000) / fpsLimitExplicit->value); std::cout << "Breaking out of EngineProcess" << std::endl;
auto oldFrameStartTimeBlock = oldFrameStart / timeBlockDuration;
auto frameStartTimeBlock = oldFrameStartTimeBlock + 1U;
frameStart = std::max(frameStart, frameStartTimeBlock * timeBlockDuration);
SDL_Delay((frameStart - now) / UINT64_C(1'000'000));
} }
} }

View File

@ -1,17 +1,19 @@
#pragma once #pragma once
#include "common/String.h" #include "common/String.h"
#include "graphics/Pixel.h" #include "graphics/Pixel.h"
#include "gui/WindowFrameOps.h"
#include "FpsLimit.h"
#include <cstdint> #include <cstdint>
#include <SDL.h> #include <SDL.h>
#include <variant>
extern int desktopWidth; extern int desktopWidth;
extern int desktopHeight; extern int desktopHeight;
extern SDL_Window *sdl_window; extern SDL_Window *sdl_window;
extern SDL_Renderer *sdl_renderer; extern SDL_Renderer *sdl_renderer;
extern SDL_Texture *sdl_texture; extern SDL_Texture *sdl_texture;
extern int scale;
extern bool fullscreen;
extern bool altFullscreen;
extern bool forceIntegerScaling;
extern bool resizable;
extern bool momentumScroll; extern bool momentumScroll;
extern bool showAvatars; extern bool showAvatars;
extern uint64_t lastTick; extern uint64_t lastTick;
@ -24,7 +26,6 @@ extern bool mouseDown;
extern bool calculatedInitialMouse; extern bool calculatedInitialMouse;
extern bool hasMouseMoved; extern bool hasMouseMoved;
void MainLoop();
void EngineProcess(); void EngineProcess();
void StartTextInput(); void StartTextInput();
void StopTextInput(); void StopTextInput();
@ -33,13 +34,14 @@ void ClipboardPush(ByteString text);
ByteString ClipboardPull(); ByteString ClipboardPull();
int GetModifiers(); int GetModifiers();
unsigned int GetTicks(); unsigned int GetTicks();
void CalculateMousePosition(int *x, int *y);
void blit(pixel *vid); void blit(pixel *vid);
void SDLOpen(); void SDLOpen();
void SDLClose(); void SDLClose();
void SDLSetScreen(); void SDLSetScreen(int scale_, bool resizable_, bool fullscreen_, bool altFullscreen_, bool forceIntegerScaling_);
void SetFpsLimit(FpsLimit newFpsLimit); bool RecreateWindow();
void LoadWindowPosition(); void LoadWindowPosition();
void SaveWindowPosition(); void SaveWindowPosition();
void LargeScreenDialog(); void LargeScreenDialog();
void TickClient(); void TickClient();
void UpdateFpsLimit(); void EventProcess(const SDL_Event &event);

View File

@ -1,18 +0,0 @@
#include "PowderToySDL.h"
#include "gui/interface/Engine.h"
void MainLoop()
{
while (ui::Engine::Ref().Running())
{
EngineProcess();
}
}
void SetFpsLimit(FpsLimit newFpsLimit)
{
}
void UpdateFpsLimit()
{
}

View File

@ -1,52 +0,0 @@
#include "PowderToySDL.h"
#include "gui/interface/Engine.h"
#include <emscripten.h>
#include <iostream>
namespace Platform
{
void MaybeTriggerSyncFs();
}
static void MainLoopBody()
{
EngineProcess();
Platform::MaybeTriggerSyncFs();
}
void SetFpsLimit(FpsLimit newFpsLimit)
{
static bool mainLoopSet = false;
if (!mainLoopSet)
{
emscripten_set_main_loop(MainLoopBody, 0, 0);
mainLoopSet = true;
}
if (auto *fpsLimitVsync = std::get_if<FpsLimitVsync>(&newFpsLimit))
{
emscripten_set_main_loop_timing(EM_TIMING_RAF, 1);
std::cerr << "implicit fps limit via vsync" << std::endl;
}
else
{
auto delay = 0;
if (auto *fpsLimitExplicit = std::get_if<FpsLimitExplicit>(&newFpsLimit))
{
delay = int(1000.f / fpsLimitExplicit->value);
}
emscripten_set_main_loop_timing(EM_TIMING_SETTIMEOUT, delay);
std::cerr << "explicit fps limit: " << delay << "ms delays" << std::endl;
}
}
void UpdateFpsLimit()
{
SetFpsLimit(ui::Engine::Ref().GetFpsLimit());
}
// Is actually only called once at startup, the real main loop body is MainLoopBody.
void MainLoop()
{
UpdateFpsLimit();
MainLoopBody();
}

View File

@ -31,7 +31,7 @@ constexpr int MAXSIGNS = 16;
constexpr int ISTP = CELL / 2; constexpr int ISTP = CELL / 2;
constexpr float CFDS = 4.0f / CELL; constexpr float CFDS = 4.0f / CELL;
constexpr float MAX_VELOCITY = 1e4f; constexpr float SIM_MAXVELOCITY = 1e4f;
//Air constants //Air constants
constexpr float AIR_TSTEPP = 0.3f; constexpr float AIR_TSTEPP = 0.3f;
@ -42,13 +42,10 @@ constexpr float AIR_PLOSS = 0.9999f;
constexpr int NGOL = 24; constexpr int NGOL = 24;
enum DefaultBrushes constexpr int CIRCLE_BRUSH = 0;
{ constexpr int SQUARE_BRUSH = 1;
BRUSH_CIRCLE, constexpr int TRI_BRUSH = 2;
BRUSH_SQUARE, constexpr int BRUSH_NUM = 3;
BRUSH_TRIANGLE,
NUM_DEFAULTBRUSHES,
};
//Photon constants //Photon constants
constexpr int SURF_RANGE = 10; constexpr int SURF_RANGE = 10;
@ -64,5 +61,3 @@ constexpr float GLASS_IOR = 1.9f;
constexpr float GLASS_DISP = 0.07f; constexpr float GLASS_DISP = 0.07f;
constexpr float R_TEMP = 22; constexpr float R_TEMP = 22;
constexpr bool LATENTHEAT = false;

View File

@ -1,3 +0,0 @@
#pragma once
constexpr char VCS_TAG[] = "@VCS_TAG@";

View File

@ -19,6 +19,7 @@
*/ */
#pragma once #pragma once
#include "common/tpt-inline.h"
#include <ctime> #include <ctime>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
@ -1182,7 +1183,7 @@ bson_bool_t bson_check_string( bson *b, const char *string,
#define bson_big_endian32(out, in) ( bson_swap_endian32(out, in) ) #define bson_big_endian32(out, in) ( bson_swap_endian32(out, in) )
//#endif //#endif
static inline void bson_swap_endian64( void *outp, const void *inp ) { static TPT_INLINE void bson_swap_endian64( void *outp, const void *inp ) {
const char *in = ( const char * )inp; const char *in = ( const char * )inp;
char *out = ( char * )outp; char *out = ( char * )outp;
@ -1196,7 +1197,7 @@ static inline void bson_swap_endian64( void *outp, const void *inp ) {
out[7] = in[0]; out[7] = in[0];
} }
static inline void bson_swap_endian32( void *outp, const void *inp ) { static TPT_INLINE void bson_swap_endian32( void *outp, const void *inp ) {
const char *in = ( const char * )inp; const char *in = ( const char * )inp;
char *out = ( char * )outp; char *out = ( char * )outp;

View File

@ -1,30 +0,0 @@
#include "Client.h"
#include "prefs/GlobalPrefs.h"
void Client::LoadAuthUser()
{
auto &prefs = GlobalPrefs::Ref();
authUser.UserID = prefs.Get("User.ID", 0);
authUser.Username = prefs.Get("User.Username", ByteString(""));
authUser.SessionID = prefs.Get("User.SessionID", ByteString(""));
authUser.SessionKey = prefs.Get("User.SessionKey", ByteString(""));
authUser.UserElevation = prefs.Get("User.Elevation", User::ElevationNone);
}
void Client::SaveAuthUser()
{
auto &prefs = GlobalPrefs::Ref();
Prefs::DeferWrite dw(prefs);
if (authUser.UserID)
{
prefs.Set("User.ID", authUser.UserID);
prefs.Set("User.SessionID", authUser.SessionID);
prefs.Set("User.SessionKey", authUser.SessionKey);
prefs.Set("User.Username", authUser.Username);
prefs.Set("User.Elevation", authUser.UserElevation);
}
else
{
prefs.Clear("User");
}
}

View File

@ -1,42 +0,0 @@
#include "Client.h"
#include "prefs/GlobalPrefs.h"
#include <emscripten.h>
#include <iostream>
void Client::LoadAuthUser()
{
ByteString newUsername, newSessionKey;
if (EM_ASM_INT({
return (document.querySelector("#PowderSessionInfo [name='Username']") &&
document.querySelector("#PowderSessionInfo [name='SessionKey']")) ? 1 : 0;
}))
{
newUsername = ByteString(std::unique_ptr<char, decltype(&free)>((char *)EM_ASM_PTR({
return stringToNewUTF8(document.querySelector("#PowderSessionInfo [name='Username']").value);
}), free).get());
newSessionKey = ByteString(std::unique_ptr<char, decltype(&free)>((char *)EM_ASM_PTR({
return stringToNewUTF8(document.querySelector("#PowderSessionInfo [name='SessionKey']").value);
}), free).get());
}
else
{
std::cerr << "required #PowderSessionInfo elements not found, can't authenticate" << std::endl;
}
if (newUsername.size() && newSessionKey.size())
{
authUser.UserID = -1; // Not quite valid but evaluates to true and that's all that matters for this codebase.
authUser.Username = newUsername;
authUser.SessionID = "(invalid)";
authUser.SessionKey = newSessionKey;
authUser.UserElevation = User::ElevationNone; // We don't deal with this in the browser.
}
else
{
authUser.UserID = 0;
}
}
void Client::SaveAuthUser()
{
// Nothing is; the cookie headers in the login and logout responses take care of state management.
}

View File

@ -1,8 +1,9 @@
#include "Client.h" #include "Client.h"
#include "prefs/GlobalPrefs.h" #include "prefs/GlobalPrefs.h"
#include "client/http/StartupRequest.h" #include "client/http/Request.h"
#include "ClientListener.h" #include "ClientListener.h"
#include "Format.h" #include "Format.h"
#include "MD5.h"
#include "client/GameSave.h" #include "client/GameSave.h"
#include "client/SaveFile.h" #include "client/SaveFile.h"
#include "client/SaveInfo.h" #include "client/SaveInfo.h"
@ -10,9 +11,9 @@
#include "common/platform/Platform.h" #include "common/platform/Platform.h"
#include "common/String.h" #include "common/String.h"
#include "graphics/Graphics.h" #include "graphics/Graphics.h"
#include "gui/dialogues/ErrorMessage.h"
#include "prefs/Prefs.h" #include "prefs/Prefs.h"
#include "lua/CommandInterface.h" #include "lua/CommandInterface.h"
#include "gui/preview/Comment.h"
#include "Config.h" #include "Config.h"
#include <cstring> #include <cstring>
#include <cstdlib> #include <cstdlib>
@ -28,12 +29,27 @@
Client::Client(): Client::Client():
messageOfTheDay("Fetching the message of the day..."), messageOfTheDay("Fetching the message of the day..."),
versionCheckRequest(nullptr),
alternateVersionCheckRequest(nullptr),
usingAltUpdateServer(false), usingAltUpdateServer(false),
updateAvailable(false), updateAvailable(false),
authUser(0, "") authUser(0, "")
{ {
LoadAuthUser();
auto &prefs = GlobalPrefs::Ref(); auto &prefs = GlobalPrefs::Ref();
authUser.UserID = prefs.Get("User.ID", 0);
authUser.Username = prefs.Get("User.Username", ByteString(""));
authUser.SessionID = prefs.Get("User.SessionID", ByteString(""));
authUser.SessionKey = prefs.Get("User.SessionKey", ByteString(""));
auto elevation = prefs.Get("User.Elevation", ByteString(""));
authUser.UserElevation = User::ElevationNone;
if (elevation == "Admin")
{
authUser.UserElevation = User::ElevationAdmin;
}
if (elevation == "Mod")
{
authUser.UserElevation = User::ElevationModerator;
}
firstRun = !prefs.BackedByFile(); firstRun = !prefs.BackedByFile();
} }
@ -72,14 +88,24 @@ void Client::Initialize()
} }
//Begin version check //Begin version check
versionCheckRequest = std::make_unique<http::StartupRequest>(false); versionCheckRequest = std::make_unique<http::Request>(ByteString::Build(SCHEME, SERVER, "/Startup.json"));
if (authUser.UserID)
{
versionCheckRequest->AuthHeaders(ByteString::Build(authUser.UserID), authUser.SessionID);
}
versionCheckRequest->Start(); versionCheckRequest->Start();
if constexpr (USE_UPDATESERVER) if constexpr (USE_UPDATESERVER)
{ {
// use an alternate update server // use an alternate update server
alternateVersionCheckRequest = std::make_unique<http::StartupRequest>(true); alternateVersionCheckRequest = std::make_unique<http::Request>(ByteString::Build(SCHEME, UPDATESERVER, "/Startup.json"));
alternateVersionCheckRequest->Start();
usingAltUpdateServer = true; usingAltUpdateServer = true;
if (authUser.UserID)
{
alternateVersionCheckRequest->AuthHeaders(authUser.Username, "");
}
alternateVersionCheckRequest->Start();
} }
} }
@ -99,82 +125,203 @@ String Client::GetMessageOfTheDay()
return messageOfTheDay; return messageOfTheDay;
} }
void Client::AddServerNotification(ServerNotification notification) void Client::AddServerNotification(std::pair<String, ByteString> notification)
{ {
serverNotifications.push_back(notification); serverNotifications.push_back(notification);
notifyNewNotification(notification); notifyNewNotification(notification);
} }
std::vector<ServerNotification> Client::GetServerNotifications() std::vector<std::pair<String, ByteString> > Client::GetServerNotifications()
{ {
return serverNotifications; return serverNotifications;
} }
RequestStatus Client::ParseServerReturn(ByteString &result, int status, bool json)
{
lastError = "";
// no server response, return "Malformed Response"
if (status == 200 && !result.size())
{
status = 603;
}
if (status == 302)
return RequestOkay;
if (status != 200)
{
lastError = String::Build("HTTP Error ", status, ": ", http::StatusText(status));
return RequestFailure;
}
if (json)
{
std::istringstream datastream(result);
Json::Value root;
try
{
datastream >> root;
// assume everything is fine if an empty [] is returned
if (root.size() == 0)
{
return RequestOkay;
}
int status = root.get("Status", 1).asInt();
if (status != 1)
{
lastError = ByteString(root.get("Error", "Unspecified Error").asString()).FromUtf8();
return RequestFailure;
}
}
catch (std::exception &e)
{
// sometimes the server returns a 200 with the text "Error: 401"
if (!strncmp(result.c_str(), "Error: ", 7))
{
status = ByteString(result.begin() + 7, result.end()).ToNumber<int>();
lastError = String::Build("HTTP Error ", status, ": ", http::StatusText(status));
return RequestFailure;
}
lastError = "Could not read response: " + ByteString(e.what()).FromUtf8();
return RequestFailure;
}
}
else
{
if (strncmp(result.c_str(), "OK", 2))
{
lastError = result.FromUtf8();
return RequestFailure;
}
}
return RequestOkay;
}
void Client::Tick() void Client::Tick()
{ {
auto applyUpdateInfo = false; CheckUpdate(versionCheckRequest, true);
if (versionCheckRequest && versionCheckRequest->CheckDone()) CheckUpdate(alternateVersionCheckRequest, false);
}
void Client::CheckUpdate(std::unique_ptr<http::Request> &updateRequest, bool checkSession)
{
//Check status on version check request
if (updateRequest && updateRequest->CheckDone())
{ {
if (versionCheckRequest->StatusCode() == 618) auto [ status, data ] = updateRequest->Finish();
if (checkSession && status == 618)
{ {
AddServerNotification({ "Failed to load SSL certificates", ByteString::Build(SCHEME, SERVER, "/FAQ.html") }); AddServerNotification({ "Failed to load SSL certificates", ByteString(SCHEME) + "powdertoy.co.uk/FAQ.html" });
} }
try
if (status != 200)
{ {
auto info = versionCheckRequest->Finish(); //free(data);
if (!info.sessionGood) if (usingAltUpdateServer && !checkSession)
this->messageOfTheDay = String::Build("HTTP Error ", status, " while checking for updates: ", http::StatusText(status));
else
this->messageOfTheDay = String::Build("HTTP Error ", status, " while fetching MotD");
}
else if(data.size())
{
std::istringstream dataStream(data);
try
{ {
SetAuthUser(User(0, "")); Json::Value objDocument;
dataStream >> objDocument;
//Check session
if (checkSession)
{
if (!objDocument["Session"].asBool())
{
SetAuthUser(User(0, ""));
}
}
//Notifications from server
Json::Value notificationsArray = objDocument["Notifications"];
for (Json::UInt j = 0; j < notificationsArray.size(); j++)
{
ByteString notificationLink = notificationsArray[j]["Link"].asString();
String notificationText = ByteString(notificationsArray[j]["Text"].asString()).FromUtf8();
std::pair<String, ByteString> item = std::pair<String, ByteString>(notificationText, notificationLink);
AddServerNotification(item);
}
//MOTD
if (!usingAltUpdateServer || !checkSession)
{
this->messageOfTheDay = ByteString(objDocument["MessageOfTheDay"].asString()).FromUtf8();
notifyMessageOfTheDay();
if constexpr (!IGNORE_UPDATES)
{
//Check for updates
Json::Value versions = objDocument["Updates"];
if constexpr (!SNAPSHOT)
{
Json::Value stableVersion = versions["Stable"];
int stableMajor = stableVersion["Major"].asInt();
int stableMinor = stableVersion["Minor"].asInt();
int stableBuild = stableVersion["Build"].asInt();
ByteString stableFile = stableVersion["File"].asString();
String stableChangelog = ByteString(stableVersion["Changelog"].asString()).FromUtf8();
if (stableBuild > BUILD_NUM)
{
updateAvailable = true;
updateInfo = UpdateInfo(stableMajor, stableMinor, stableBuild, stableFile, stableChangelog, UpdateInfo::Stable);
}
}
if (!updateAvailable)
{
Json::Value betaVersion = versions["Beta"];
int betaMajor = betaVersion["Major"].asInt();
int betaMinor = betaVersion["Minor"].asInt();
int betaBuild = betaVersion["Build"].asInt();
ByteString betaFile = betaVersion["File"].asString();
String betaChangelog = ByteString(betaVersion["Changelog"].asString()).FromUtf8();
if (betaBuild > BUILD_NUM)
{
updateAvailable = true;
updateInfo = UpdateInfo(betaMajor, betaMinor, betaBuild, betaFile, betaChangelog, UpdateInfo::Beta);
}
}
if constexpr (SNAPSHOT || MOD)
{
Json::Value snapshotVersion = versions["Snapshot"];
int snapshotSnapshot = snapshotVersion["Snapshot"].asInt();
ByteString snapshotFile = snapshotVersion["File"].asString();
String snapshotChangelog = ByteString(snapshotVersion["Changelog"].asString()).FromUtf8();
if (snapshotSnapshot > SNAPSHOT_ID)
{
updateAvailable = true;
updateInfo = UpdateInfo(snapshotSnapshot, snapshotFile, snapshotChangelog, UpdateInfo::Snapshot);
}
}
if(updateAvailable)
{
notifyUpdateAvailable();
}
}
}
} }
if (!usingAltUpdateServer) catch (std::exception & e)
{ {
updateInfo = info.updateInfo; //Do nothing
applyUpdateInfo = true;
SetMessageOfTheDay(info.messageOfTheDay);
}
for (auto &notification : info.notifications)
{
AddServerNotification(notification);
} }
} }
catch (const http::RequestError &ex) updateRequest.reset();
{
if (!usingAltUpdateServer)
{
SetMessageOfTheDay(ByteString::Build("Error while fetching MotD: ", ex.what()).FromUtf8());
}
}
versionCheckRequest.reset();
}
if (alternateVersionCheckRequest && alternateVersionCheckRequest->CheckDone())
{
try
{
auto info = alternateVersionCheckRequest->Finish();
updateInfo = info.updateInfo;
applyUpdateInfo = true;
SetMessageOfTheDay(info.messageOfTheDay);
for (auto &notification : info.notifications)
{
AddServerNotification(notification);
}
}
catch (const http::RequestError &ex)
{
SetMessageOfTheDay(ByteString::Build("Error while checking for updates: ", ex.what()).FromUtf8());
}
alternateVersionCheckRequest.reset();
}
if (applyUpdateInfo && !IGNORE_UPDATES)
{
if (updateInfo)
{
notifyUpdateAvailable();
}
} }
} }
std::optional<UpdateInfo> Client::GetUpdateInfo() UpdateInfo Client::GetUpdateInfo()
{ {
return updateInfo; return updateInfo;
} }
@ -203,7 +350,7 @@ void Client::notifyAuthUserChanged()
} }
} }
void Client::notifyNewNotification(ServerNotification notification) void Client::notifyNewNotification(std::pair<String, ByteString> notification)
{ {
for (std::vector<ClientListener*>::iterator iterator = listeners.begin(), end = listeners.end(); iterator != end; ++iterator) for (std::vector<ClientListener*>::iterator iterator = listeners.begin(), end = listeners.end(); iterator != end; ++iterator)
{ {
@ -235,7 +382,31 @@ Client::~Client()
void Client::SetAuthUser(User user) void Client::SetAuthUser(User user)
{ {
authUser = user; authUser = user;
SaveAuthUser(); {
auto &prefs = GlobalPrefs::Ref();
Prefs::DeferWrite dw(prefs);
if (authUser.UserID)
{
prefs.Set("User.ID", authUser.UserID);
prefs.Set("User.SessionID", authUser.SessionID);
prefs.Set("User.SessionKey", authUser.SessionKey);
prefs.Set("User.Username", authUser.Username);
ByteString elevation = "None";
if (authUser.UserElevation == User::ElevationAdmin)
{
elevation = "Admin";
}
if (authUser.UserElevation == User::ElevationModerator)
{
elevation = "Mod";
}
prefs.Set("User.Elevation", elevation);
}
else
{
prefs.Clear("User");
}
}
notifyAuthUserChanged(); notifyAuthUserChanged();
} }
@ -244,6 +415,65 @@ User Client::GetAuthUser()
return authUser; return authUser;
} }
RequestStatus Client::UploadSave(SaveInfo & save)
{
lastError = "";
int dataStatus;
ByteString data;
ByteString userID = ByteString::Build(authUser.UserID);
if (authUser.UserID)
{
if (!save.GetGameSave())
{
lastError = "Empty game save";
return RequestFailure;
}
save.SetID(0);
auto [ fromNewerVersion, gameData ] = save.GetGameSave()->Serialise();
(void)fromNewerVersion;
if (!gameData.size())
{
lastError = "Cannot serialize game save";
return RequestFailure;
}
else if (ALLOW_FAKE_NEWER_VERSION && fromNewerVersion && save.GetPublished())
{
lastError = "Cannot publish save, incompatible with latest release version.";
return RequestFailure;
}
std::tie(dataStatus, data) = http::Request::SimpleAuth(ByteString::Build(SCHEME, SERVER, "/Save.api"), userID, authUser.SessionID, {
{ "Name", save.GetName().ToUtf8() },
{ "Description", save.GetDescription().ToUtf8() },
{ "Data:save.bin", ByteString(gameData.begin(), gameData.end()) },
{ "Publish", save.GetPublished() ? "Public" : "Private" },
{ "Key", authUser.SessionKey }
});
}
else
{
lastError = "Not authenticated";
return RequestFailure;
}
RequestStatus ret = ParseServerReturn(data, dataStatus, false);
if (ret == RequestOkay)
{
int saveID = ByteString(data.begin() + 3, data.end()).ToNumber<int>();
if (!saveID)
{
lastError = "Server did not return Save ID";
ret = RequestFailure;
}
else
save.SetID(saveID);
}
return ret;
}
void Client::MoveStampToFront(ByteString stampID) void Client::MoveStampToFront(ByteString stampID)
{ {
auto it = std::find(stampIDs.begin(), stampIDs.end(), stampID); auto it = std::find(stampIDs.begin(), stampIDs.end(), stampID);
@ -265,10 +495,10 @@ void Client::MoveStampToFront(ByteString stampID)
} }
} }
std::unique_ptr<SaveFile> Client::GetStamp(ByteString stampID) SaveFile * Client::GetStamp(ByteString stampID)
{ {
ByteString stampFile = ByteString(ByteString::Build(STAMPS_DIR, PATH_SEP_CHAR, stampID, ".stm")); ByteString stampFile = ByteString(ByteString::Build(STAMPS_DIR, PATH_SEP_CHAR, stampID, ".stm"));
auto saveFile = LoadSaveFile(stampFile); SaveFile *saveFile = LoadSaveFile(stampFile);
if (!saveFile) if (!saveFile)
saveFile = LoadSaveFile(stampID); saveFile = LoadSaveFile(stampID);
else else
@ -287,28 +517,7 @@ void Client::DeleteStamp(ByteString stampID)
} }
} }
void Client::RenameStamp(ByteString stampID, ByteString newName) ByteString Client::AddStamp(GameSave * saveData)
{
auto oldPath = ByteString::Build(STAMPS_DIR, PATH_SEP_CHAR, stampID, ".stm");
auto newPath = ByteString::Build(STAMPS_DIR, PATH_SEP_CHAR, newName, ".stm");
if (Platform::FileExists(newPath))
{
new ErrorMessage("Error renaming stamp", "A stamp with this name already exists.");
return;
}
if (!Platform::RenameFile(oldPath, newPath, false))
{
new ErrorMessage("Error renaming stamp", "Could not rename the stamp.");
return;
}
std::replace(stampIDs.begin(), stampIDs.end(), stampID, newName);
WriteStamps();
}
ByteString Client::AddStamp(std::unique_ptr<GameSave> saveData)
{ {
auto now = (uint64_t)time(NULL); auto now = (uint64_t)time(NULL);
if (lastStampTime != now) if (lastStampTime != now)
@ -346,8 +555,8 @@ ByteString Client::AddStamp(std::unique_ptr<GameSave> saveData)
} }
saveData->authors = stampInfo; saveData->authors = stampInfo;
std::vector<char> gameData; auto [ fromNewerVersion, gameData ] = saveData->Serialise();
std::tie(std::ignore, gameData) = saveData->Serialise(); (void)fromNewerVersion;
if (!gameData.size()) if (!gameData.size())
return ""; return "";
@ -377,6 +586,7 @@ void Client::RescanStamps()
newStampIDs.push_back(stampID); newStampIDs.push_back(stampID);
} }
} }
auto oldCount = newStampIDs.size();
auto stampIDsSet = std::set<ByteString>(stampIDs.begin(), stampIDs.end()); auto stampIDsSet = std::set<ByteString>(stampIDs.begin(), stampIDs.end());
for (auto &stampID : stampFilesSet) for (auto &stampID : stampFilesSet)
{ {
@ -388,6 +598,8 @@ void Client::RescanStamps()
} }
if (changed) if (changed)
{ {
// Move newly discovered stamps to front.
std::rotate(newStampIDs.begin(), newStampIDs.begin() + oldCount, newStampIDs.end());
stampIDs = newStampIDs; stampIDs = newStampIDs;
WriteStamps(); WriteStamps();
} }
@ -406,19 +618,325 @@ const std::vector<ByteString> &Client::GetStamps() const
return stampIDs; return stampIDs;
} }
std::unique_ptr<SaveFile> Client::LoadSaveFile(ByteString filename) RequestStatus Client::ExecVote(int saveID, int direction)
{
lastError = "";
int dataStatus;
ByteString data;
if (authUser.UserID)
{
ByteString saveIDText = ByteString::Build(saveID);
ByteString userIDText = ByteString::Build(authUser.UserID);
std::tie(dataStatus, data) = http::Request::SimpleAuth(ByteString::Build(SCHEME, SERVER, "/Vote.api"), userIDText, authUser.SessionID, {
{ "ID", saveIDText },
{ "Action", direction ? (direction == 1 ? "Up" : "Down") : "Reset" },
{ "Key", authUser.SessionKey }
});
}
else
{
lastError = "Not authenticated";
return RequestFailure;
}
RequestStatus ret = ParseServerReturn(data, dataStatus, false);
return ret;
}
std::vector<char> Client::GetSaveData(int saveID, int saveDate)
{
lastError = "";
ByteString urlStr;
if (saveDate)
urlStr = ByteString::Build(STATICSCHEME, STATICSERVER, "/", saveID, "_", saveDate, ".cps");
else
urlStr = ByteString::Build(STATICSCHEME, STATICSERVER, "/", saveID, ".cps");
auto [ dataStatus, data ] = http::Request::Simple(urlStr);
// will always return failure
ParseServerReturn(data, dataStatus, false);
if (data.size() && dataStatus == 200)
{
return std::vector<char>(data.begin(), data.end());
}
return {};
}
LoginStatus Client::Login(ByteString username, ByteString password, User & user)
{
lastError = "";
user.UserID = 0;
user.Username = "";
user.SessionID = "";
user.SessionKey = "";
auto [ dataStatus, data ] = http::Request::Simple(ByteString::Build("https://", SERVER, "/Login.json"), {
{ "name", username },
{ "pass", password },
});
RequestStatus ret = ParseServerReturn(data, dataStatus, true);
if (ret == RequestOkay)
{
try
{
std::istringstream dataStream(data);
Json::Value objDocument;
dataStream >> objDocument;
ByteString usernameTemp = objDocument["Username"].asString();
int userIDTemp = objDocument["UserID"].asInt();
ByteString sessionIDTemp = objDocument["SessionID"].asString();
ByteString sessionKeyTemp = objDocument["SessionKey"].asString();
ByteString userElevationTemp = objDocument["Elevation"].asString();
Json::Value notificationsArray = objDocument["Notifications"];
for (Json::UInt j = 0; j < notificationsArray.size(); j++)
{
ByteString notificationLink = notificationsArray[j]["Link"].asString();
String notificationText = ByteString(notificationsArray[j]["Text"].asString()).FromUtf8();
std::pair<String, ByteString> item = std::pair<String, ByteString>(notificationText, notificationLink);
AddServerNotification(item);
}
user.Username = usernameTemp;
user.UserID = userIDTemp;
user.SessionID = sessionIDTemp;
user.SessionKey = sessionKeyTemp;
ByteString userElevation = userElevationTemp;
if(userElevation == "Admin")
user.UserElevation = User::ElevationAdmin;
else if(userElevation == "Mod")
user.UserElevation = User::ElevationModerator;
else
user.UserElevation= User::ElevationNone;
return LoginOkay;
}
catch (std::exception &e)
{
lastError = "Could not read response: " + ByteString(e.what()).FromUtf8();
return LoginError;
}
}
return LoginError;
}
RequestStatus Client::DeleteSave(int saveID)
{
lastError = "";
ByteString data;
ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/Delete.json?ID=", saveID, "&Mode=Delete&Key=", authUser.SessionKey);
int dataStatus;
if(authUser.UserID)
{
ByteString userID = ByteString::Build(authUser.UserID);
std::tie(dataStatus, data) = http::Request::SimpleAuth(url, userID, authUser.SessionID);
}
else
{
lastError = "Not authenticated";
return RequestFailure;
}
RequestStatus ret = ParseServerReturn(data, dataStatus, true);
return ret;
}
RequestStatus Client::AddComment(int saveID, String comment)
{
lastError = "";
ByteString data;
int dataStatus;
ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/Comments.json?ID=", saveID);
if(authUser.UserID)
{
ByteString userID = ByteString::Build(authUser.UserID);
std::tie(dataStatus, data) = http::Request::SimpleAuth(url, userID, authUser.SessionID, {
{ "Comment", comment.ToUtf8() },
{ "Key", authUser.SessionKey }
});
}
else
{
lastError = "Not authenticated";
return RequestFailure;
}
RequestStatus ret = ParseServerReturn(data, dataStatus, true);
return ret;
}
RequestStatus Client::FavouriteSave(int saveID, bool favourite)
{
lastError = "";
ByteStringBuilder urlStream;
ByteString data;
int dataStatus;
urlStream << SCHEME << SERVER << "/Browse/Favourite.json?ID=" << saveID << "&Key=" << authUser.SessionKey;
if(!favourite)
urlStream << "&Mode=Remove";
if(authUser.UserID)
{
ByteString userID = ByteString::Build(authUser.UserID);
std::tie(dataStatus, data) = http::Request::SimpleAuth(urlStream.Build(), userID, authUser.SessionID);
}
else
{
lastError = "Not authenticated";
return RequestFailure;
}
RequestStatus ret = ParseServerReturn(data, dataStatus, true);
return ret;
}
RequestStatus Client::ReportSave(int saveID, String message)
{
lastError = "";
ByteString data;
int dataStatus;
ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/Report.json?ID=", saveID, "&Key=", authUser.SessionKey);
if(authUser.UserID)
{
ByteString userID = ByteString::Build(authUser.UserID);
std::tie(dataStatus, data) = http::Request::SimpleAuth(url, userID, authUser.SessionID, {
{ "Reason", message.ToUtf8() },
});
}
else
{
lastError = "Not authenticated";
return RequestFailure;
}
RequestStatus ret = ParseServerReturn(data, dataStatus, true);
return ret;
}
RequestStatus Client::UnpublishSave(int saveID)
{
lastError = "";
ByteString data;
int dataStatus;
ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/Delete.json?ID=", saveID, "&Mode=Unpublish&Key=", authUser.SessionKey);
if(authUser.UserID)
{
ByteString userID = ByteString::Build(authUser.UserID);
std::tie(dataStatus, data) = http::Request::SimpleAuth(url, userID, authUser.SessionID);
}
else
{
lastError = "Not authenticated";
return RequestFailure;
}
RequestStatus ret = ParseServerReturn(data, dataStatus, true);
return ret;
}
RequestStatus Client::PublishSave(int saveID)
{
lastError = "";
ByteString data;
int dataStatus;
ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/View.json?ID=", saveID, "&Key=", authUser.SessionKey);
if (authUser.UserID)
{
ByteString userID = ByteString::Build(authUser.UserID);
std::tie(dataStatus, data) = http::Request::SimpleAuth(url, userID, authUser.SessionID, {
{ "ActionPublish", "bagels" },
});
}
else
{
lastError = "Not authenticated";
return RequestFailure;
}
RequestStatus ret = ParseServerReturn(data, dataStatus, true);
return ret;
}
SaveInfo * Client::GetSave(int saveID, int saveDate)
{
lastError = "";
ByteStringBuilder urlStream;
urlStream << SCHEME << SERVER << "/Browse/View.json?ID=" << saveID;
if(saveDate)
{
urlStream << "&Date=" << saveDate;
}
ByteString data;
int dataStatus;
if(authUser.UserID)
{
ByteString userID = ByteString::Build(authUser.UserID);
std::tie(dataStatus, data) = http::Request::SimpleAuth(urlStream.Build(), userID, authUser.SessionID);
}
else
{
std::tie(dataStatus, data) = http::Request::Simple(urlStream.Build());
}
if(dataStatus == 200 && data.size())
{
try
{
std::istringstream dataStream(data);
Json::Value objDocument;
dataStream >> objDocument;
int tempID = objDocument["ID"].asInt();
int tempScoreUp = objDocument["ScoreUp"].asInt();
int tempScoreDown = objDocument["ScoreDown"].asInt();
int tempMyScore = objDocument["ScoreMine"].asInt();
ByteString tempUsername = objDocument["Username"].asString();
String tempName = ByteString(objDocument["Name"].asString()).FromUtf8();
String tempDescription = ByteString(objDocument["Description"].asString()).FromUtf8();
int tempCreatedDate = objDocument["DateCreated"].asInt();
int tempUpdatedDate = objDocument["Date"].asInt();
bool tempPublished = objDocument["Published"].asBool();
bool tempFavourite = objDocument["Favourite"].asBool();
int tempComments = objDocument["Comments"].asInt();
int tempViews = objDocument["Views"].asInt();
int tempVersion = objDocument["Version"].asInt();
Json::Value tagsArray = objDocument["Tags"];
std::list<ByteString> tempTags;
for (Json::UInt j = 0; j < tagsArray.size(); j++)
tempTags.push_back(tagsArray[j].asString());
SaveInfo * tempSave = new SaveInfo(tempID, tempCreatedDate, tempUpdatedDate, tempScoreUp,
tempScoreDown, tempMyScore, tempUsername, tempName,
tempDescription, tempPublished, tempTags);
tempSave->Comments = tempComments;
tempSave->Favourite = tempFavourite;
tempSave->Views = tempViews;
tempSave->Version = tempVersion;
return tempSave;
}
catch (std::exception & e)
{
lastError = "Could not read response: " + ByteString(e.what()).FromUtf8();
return NULL;
}
}
else
{
lastError = http::StatusText(dataStatus);
}
return NULL;
}
SaveFile * Client::LoadSaveFile(ByteString filename)
{ {
ByteString err; ByteString err;
std::unique_ptr<SaveFile> file; SaveFile *file = nullptr;
if (Platform::FileExists(filename)) if (Platform::FileExists(filename))
{ {
file = std::make_unique<SaveFile>(filename); file = new SaveFile(filename);
try try
{ {
std::vector<char> data; std::vector<char> data;
if (Platform::ReadFile(data, filename)) if (Platform::ReadFile(data, filename))
{ {
file->SetGameSave(std::make_unique<GameSave>(std::move(data))); file->SetGameSave(new GameSave(std::move(data)));
} }
else else
{ {
@ -441,10 +959,89 @@ std::unique_ptr<SaveFile> Client::LoadSaveFile(ByteString filename)
{ {
file->SetLoadingError(err.FromUtf8()); file->SetLoadingError(err.FromUtf8());
} }
commandInterface->SetLastError(err.FromUtf8());
} }
return file; return file;
} }
std::list<ByteString> * Client::RemoveTag(int saveID, ByteString tag)
{
lastError = "";
std::list<ByteString> * tags = NULL;
ByteString data;
int dataStatus;
ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/EditTag.json?Op=delete&ID=", saveID, "&Tag=", tag, "&Key=", authUser.SessionKey);
if(authUser.UserID)
{
ByteString userID = ByteString::Build(authUser.UserID);
std::tie(dataStatus, data) = http::Request::SimpleAuth(url, userID, authUser.SessionID);
}
else
{
lastError = "Not authenticated";
return NULL;
}
RequestStatus ret = ParseServerReturn(data, dataStatus, true);
if (ret == RequestOkay)
{
try
{
std::istringstream dataStream(data);
Json::Value responseObject;
dataStream >> responseObject;
Json::Value tagsArray = responseObject["Tags"];
tags = new std::list<ByteString>();
for (Json::UInt j = 0; j < tagsArray.size(); j++)
tags->push_back(tagsArray[j].asString());
}
catch (std::exception &e)
{
lastError = "Could not read response: " + ByteString(e.what()).FromUtf8();
}
}
return tags;
}
std::list<ByteString> * Client::AddTag(int saveID, ByteString tag)
{
lastError = "";
std::list<ByteString> * tags = NULL;
ByteString data;
int dataStatus;
ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/EditTag.json?Op=add&ID=", saveID, "&Tag=", tag, "&Key=", authUser.SessionKey);
if(authUser.UserID)
{
ByteString userID = ByteString::Build(authUser.UserID);
std::tie(dataStatus, data) = http::Request::SimpleAuth(url, userID, authUser.SessionID);
}
else
{
lastError = "Not authenticated";
return NULL;
}
RequestStatus ret = ParseServerReturn(data, dataStatus, true);
if (ret == RequestOkay)
{
try
{
std::istringstream dataStream(data);
Json::Value responseObject;
dataStream >> responseObject;
Json::Value tagsArray = responseObject["Tags"];
tags = new std::list<ByteString>();
for (Json::UInt j = 0; j < tagsArray.size(); j++)
tags->push_back(tagsArray[j].asString());
}
catch (std::exception & e)
{
lastError = "Could not read response: " + ByteString(e.what()).FromUtf8();
}
}
return tags;
}
// stamp-specific wrapper for MergeAuthorInfo // stamp-specific wrapper for MergeAuthorInfo
// also used for clipboard and lua stamps // also used for clipboard and lua stamps
void Client::MergeStampAuthorInfo(Json::Value stampAuthors) void Client::MergeStampAuthorInfo(Json::Value stampAuthors)

View File

@ -1,7 +1,6 @@
#pragma once #pragma once
#include "common/String.h" #include "common/String.h"
#include "common/ExplicitSingleton.h" #include "common/ExplicitSingleton.h"
#include "StartupInfo.h"
#include "User.h" #include "User.h"
#include <vector> #include <vector>
#include <cstdint> #include <cstdint>
@ -11,27 +10,53 @@
class SaveInfo; class SaveInfo;
class SaveFile; class SaveFile;
class SaveComment;
class GameSave; class GameSave;
class VideoBuffer; class VideoBuffer;
enum LoginStatus {
LoginOkay, LoginError
};
enum RequestStatus {
RequestOkay, RequestFailure
};
class UpdateInfo
{
public:
enum BuildType { Stable, Beta, Snapshot };
ByteString File;
String Changelog;
int Major;
int Minor;
int Build;
int Time;
BuildType Type;
UpdateInfo() : File(""), Changelog(""), Major(0), Minor(0), Build(0), Time(0), Type(Stable) {}
UpdateInfo(int major, int minor, int build, ByteString file, String changelog, BuildType type) : File(file), Changelog(changelog), Major(major), Minor(minor), Build(build), Time(0), Type(type) {}
UpdateInfo(int time, ByteString file, String changelog, BuildType type) : File(file), Changelog(changelog), Major(0), Minor(0), Build(0), Time(time), Type(type) {}
};
class Prefs; class Prefs;
class RequestListener; class RequestListener;
class ClientListener; class ClientListener;
namespace http namespace http
{ {
class StartupRequest; class Request;
} }
class Client: public ExplicitSingleton<Client> { class Client: public ExplicitSingleton<Client> {
private: private:
String messageOfTheDay; String messageOfTheDay;
std::vector<ServerNotification> serverNotifications; std::vector<std::pair<String, ByteString> > serverNotifications;
std::unique_ptr<http::StartupRequest> versionCheckRequest; std::unique_ptr<http::Request> versionCheckRequest;
std::unique_ptr<http::StartupRequest> alternateVersionCheckRequest; std::unique_ptr<http::Request> alternateVersionCheckRequest;
bool usingAltUpdateServer; bool usingAltUpdateServer;
bool updateAvailable; bool updateAvailable;
std::optional<UpdateInfo> updateInfo; UpdateInfo updateInfo;
String lastError;
bool firstRun; bool firstRun;
std::vector<ByteString> stampIDs; std::vector<ByteString> stampIDs;
@ -44,7 +69,7 @@ private:
void notifyUpdateAvailable(); void notifyUpdateAvailable();
void notifyAuthUserChanged(); void notifyAuthUserChanged();
void notifyMessageOfTheDay(); void notifyMessageOfTheDay();
void notifyNewNotification(ServerNotification notification); void notifyNewNotification(std::pair<String, ByteString> notification);
// Save stealing info // Save stealing info
Json::Value authors; Json::Value authors;
@ -53,9 +78,6 @@ private:
void MigrateStampsDef(); void MigrateStampsDef();
void WriteStamps(); void WriteStamps();
void LoadAuthUser();
void SaveAuthUser();
public: public:
std::vector<ClientListener*> listeners; std::vector<ClientListener*> listeners;
@ -69,7 +91,7 @@ public:
void ClearAuthorInfo() { authors.clear(); } void ClearAuthorInfo() { authors.clear(); }
bool IsAuthorsEmpty() { return authors.size() == 0; } bool IsAuthorsEmpty() { return authors.size() == 0; }
std::optional<UpdateInfo> GetUpdateInfo(); UpdateInfo GetUpdateInfo();
Client(); Client();
~Client(); ~Client();
@ -77,8 +99,8 @@ public:
ByteString FileOpenDialogue(); ByteString FileOpenDialogue();
//std::string FileSaveDialogue(); //std::string FileSaveDialogue();
void AddServerNotification(ServerNotification notification); void AddServerNotification(std::pair<String, ByteString> notification);
std::vector<ServerNotification> GetServerNotifications(); std::vector<std::pair<String, ByteString> > GetServerNotifications();
void SetMessageOfTheDay(String message); void SetMessageOfTheDay(String message);
String GetMessageOfTheDay(); String GetMessageOfTheDay();
@ -89,19 +111,40 @@ public:
void AddListener(ClientListener * listener); void AddListener(ClientListener * listener);
void RemoveListener(ClientListener * listener); void RemoveListener(ClientListener * listener);
std::unique_ptr<SaveFile> GetStamp(ByteString stampID); RequestStatus ExecVote(int saveID, int direction);
RequestStatus UploadSave(SaveInfo & save);
SaveFile * GetStamp(ByteString stampID);
void DeleteStamp(ByteString stampID); void DeleteStamp(ByteString stampID);
void RenameStamp(ByteString stampID, ByteString newName); ByteString AddStamp(GameSave * saveData);
ByteString AddStamp(std::unique_ptr<GameSave> saveData);
void RescanStamps(); void RescanStamps();
const std::vector<ByteString> &GetStamps() const; const std::vector<ByteString> &GetStamps() const;
void MoveStampToFront(ByteString stampID); void MoveStampToFront(ByteString stampID);
std::unique_ptr<SaveFile> LoadSaveFile(ByteString filename); RequestStatus AddComment(int saveID, String comment);
std::vector<char> GetSaveData(int saveID, int saveDate);
LoginStatus Login(ByteString username, ByteString password, User & user);
SaveInfo * GetSave(int saveID, int saveDate);
SaveFile * LoadSaveFile(ByteString filename);
RequestStatus DeleteSave(int saveID);
RequestStatus ReportSave(int saveID, String message);
RequestStatus UnpublishSave(int saveID);
RequestStatus PublishSave(int saveID);
RequestStatus FavouriteSave(int saveID, bool favourite);
void SetAuthUser(User user); void SetAuthUser(User user);
User GetAuthUser(); User GetAuthUser();
std::list<ByteString> * RemoveTag(int saveID, ByteString tag); //TODO RequestStatus
std::list<ByteString> * AddTag(int saveID, ByteString tag);
String GetLastError() {
return lastError;
}
RequestStatus ParseServerReturn(ByteString &result, int status, bool json);
void Tick(); void Tick();
void CheckUpdate(std::unique_ptr<http::Request> &updateRequest, bool checkSession);
String DoMigration(ByteString fromDir, ByteString toDir); String DoMigration(ByteString fromDir, ByteString toDir);
}; };

View File

@ -1,6 +1,5 @@
#pragma once #pragma once
#include "common/String.h" #include "common/String.h"
#include "client/ServerNotification.h"
class Client; class Client;
class ClientListener class ClientListener
@ -12,6 +11,6 @@ public:
virtual void NotifyUpdateAvailable(Client * sender) {} virtual void NotifyUpdateAvailable(Client * sender) {}
virtual void NotifyAuthUserChanged(Client * sender) {} virtual void NotifyAuthUserChanged(Client * sender) {}
virtual void NotifyMessageOfTheDay(Client * sender) {} virtual void NotifyMessageOfTheDay(Client * sender) {}
virtual void NotifyNewNotification(Client * sender, ServerNotification notification) {} virtual void NotifyNewNotification(Client * sender, std::pair<String, ByteString> notification) {}
}; };

View File

@ -1,11 +0,0 @@
#pragma once
#include "User.h"
struct Comment
{
ByteString authorName;
User::Elevation authorElevation;
bool authorIsSelf;
bool authorIsBanned;
String content;
};

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,11 @@
#pragma once #pragma once
#include "common/Plane.h" #include "common/Plane.h"
#include "common/String.h" #include "common/String.h"
#include "common/tpt-rand.h"
#include "common/Version.h"
#include "simulation/Sign.h" #include "simulation/Sign.h"
#include "simulation/Particle.h" #include "simulation/Particle.h"
#include "simulation/MissingElements.h"
#include "Misc.h" #include "Misc.h"
#include "SimulationConfig.h" #include "SimulationConfig.h"
#include <vector> #include <vector>
#include <array>
#include <json/json.h> #include <json/json.h>
struct sign; struct sign;
@ -56,39 +52,58 @@ public:
} }
}; };
template<typename Item>
struct [[deprecated("Use PlaneAdapter<std::vector>")]] Plane: PlaneAdapter<std::vector<Item>>
{
[[deprecated("Use operator[](Vec2)")]]
Item *operator [](int y)
{
return &*PlaneAdapter<std::vector<Item>>::RowIterator(Vec2(0, y));
}
[[deprecated("Use operator[](Vec2)")]]
const Item *operator [](int y) const
{
return &*PlaneAdapter<std::vector<Item>>::RowIterator(Vec2(0, y));
}
[[deprecated("Use PlaneAdapter<std::vector>")]]
Plane() = default;
[[deprecated("Use PlaneAdapter<std::vector>")]]
Plane(int newWidth, int newHeight, Item defaultVal):
PlaneAdapter<std::vector<Item>>(Vec2(newWidth, newHeight), defaultVal)
{
}
};
class GameSave class GameSave
{ {
// number of pixels translated. When translating CELL pixels, shift all CELL grids // number of pixels translated. When translating CELL pixels, shift all CELL grids
vector2d translated = { 0, 0 };
void readOPS(const std::vector<char> &data); void readOPS(const std::vector<char> &data);
void readPSv(const std::vector<char> &data); void readPSv(const std::vector<char> &data);
std::pair<bool, std::vector<char>> serialiseOPS() const; std::pair<bool, std::vector<char>> serialiseOPS() const;
void MapPalette();
public: public:
Vec2<int> blockSize = { 0, 0 }; int blockWidth = 0;
int blockHeight = 0;
bool fromNewerVersion = false; bool fromNewerVersion = false;
Version<2> version{}; int majorVersion = 0;
int minorVersion = 0;
bool hasPressure = false; bool hasPressure = false;
bool hasAmbientHeat = false; bool hasAmbientHeat = false;
bool hasBlockAirMaps = false; // only written by readOPS, never read
bool ensureDeterminism = false; // only taken seriously by serializeOPS; readOPS may set this even if the save does not have everything required for determinism
bool hasRngState = false; // only written by readOPS, never read
RNG::State rngState;
uint64_t frameCount = 0;
//Simulation data //Simulation data
int particlesCount = 0; int particlesCount = 0;
std::vector<Particle> particles; std::vector<Particle> particles;
PlaneAdapter<std::vector<unsigned char>> blockMap; Plane<unsigned char> blockMap;
PlaneAdapter<std::vector<float>> fanVelX; Plane<float> fanVelX;
PlaneAdapter<std::vector<float>> fanVelY; Plane<float> fanVelY;
PlaneAdapter<std::vector<float>> pressure; Plane<float> pressure;
PlaneAdapter<std::vector<float>> velocityX; Plane<float> velocityX;
PlaneAdapter<std::vector<float>> velocityY; Plane<float> velocityY;
PlaneAdapter<std::vector<float>> ambientHeat; Plane<float> ambientHeat;
PlaneAdapter<std::vector<unsigned char>> blockAir;
PlaneAdapter<std::vector<unsigned char>> blockAirh;
//Simulation Options //Simulation Options
bool waterEEnabled = false; bool waterEEnabled = false;
@ -104,8 +119,6 @@ public:
int edgeMode = 0; int edgeMode = 0;
bool wantAuthors = true; bool wantAuthors = true;
MissingElements missingElements;
//Signs //Signs
std::vector<sign> signs; std::vector<sign> signs;
StkmData stkm; StkmData stkm;
@ -119,12 +132,14 @@ public:
int pmapbits = 8; // default to 8 bits for older saves int pmapbits = 8; // default to 8 bits for older saves
GameSave(Vec2<int> newBlockSize); GameSave(int width, int height);
GameSave(const std::vector<char> &data, bool newWantAuthors = true); GameSave(const std::vector<char> &data, bool newWantAuthors = true);
void setSize(Vec2<int> newBlockSize); void setSize(int width, int height);
// return value is [ fakeFromNewerVersion, gameData ] // return value is [ fakeFromNewerVersion, gameData ]
std::pair<bool, std::vector<char>> Serialise() const; std::pair<bool, std::vector<char>> Serialise() const;
void Transform(Mat2<int> transform, Vec2<int> nudge); vector2d Translate(vector2d translate);
void Transform(matrix2d transform, vector2d translate);
void Transform(matrix2d transform, vector2d translate, vector2d translateReal, int newWidth, int newHeight);
void Expand(const std::vector<char> &data); void Expand(const std::vector<char> &data);

View File

@ -1,10 +0,0 @@
#pragma once
#include "User.h"
#include "ServerNotification.h"
#include <vector>
struct LoginInfo
{
User user;
std::vector<ServerNotification> notifications;
};

231
src/client/MD5.cpp Normal file
View File

@ -0,0 +1,231 @@
// based on public-domain code from Colin Plumb (1993)
#include "MD5.h"
#include <cstring>
static unsigned getu32(const unsigned char *addr)
{
return (((((unsigned long)addr[3] << 8) | addr[2]) << 8) | addr[1]) << 8 | addr[0];
}
static void putu32(unsigned data, unsigned char *addr)
{
addr[0] = (unsigned char)data;
addr[1] = (unsigned char)(data >> 8);
addr[2] = (unsigned char)(data >> 16);
addr[3] = (unsigned char)(data >> 24);
}
void md5_init(struct md5_context *ctx)
{
ctx->buf[0] = 0x67452301;
ctx->buf[1] = 0xefcdab89;
ctx->buf[2] = 0x98badcfe;
ctx->buf[3] = 0x10325476;
ctx->bits[0] = 0;
ctx->bits[1] = 0;
}
void md5_update(struct md5_context *ctx, unsigned char const *buf, unsigned len)
{
unsigned t;
// update bit count
t = ctx->bits[0];
if ((ctx->bits[0] = (t + ((unsigned)len << 3)) & 0xffffffff) < t)
ctx->bits[1]++; // carry
ctx->bits[1] += len >> 29;
t = (t >> 3) & 0x3f;
// use leading data to top up the buffer
if (t)
{
unsigned char *p = ctx->in + t;
t = 64-t;
if (len < t)
{
memcpy(p, buf, len);
return;
}
memcpy(p, buf, t);
md5_transform(ctx->buf, ctx->in);
buf += t;
len -= t;
}
// following 64-byte chunks
while (len >= 64)
{
memcpy(ctx->in, buf, 64);
md5_transform(ctx->buf, ctx->in);
buf += 64;
len -= 64;
}
// save rest of bytes for later
memcpy(ctx->in, buf, len);
}
void md5_final(unsigned char digest[16], struct md5_context *ctx)
{
unsigned count;
unsigned char *p;
// #bytes mod64
count = (ctx->bits[0] >> 3) & 0x3F;
// first char of padding = 0x80
p = ctx->in + count;
*p++ = 0x80;
// calculate # of bytes to pad
count = 64 - 1 - count;
// Pad out to 56 mod 64
if (count < 8)
{
// we need to finish a whole block before padding
memset(p, 0, count);
md5_transform(ctx->buf, ctx->in);
memset(ctx->in, 0, 56);
}
else
{
// just pad to 56 bytes
memset(p, 0, count-8);
}
// append length & final transform
putu32(ctx->bits[0], ctx->in + 56);
putu32(ctx->bits[1], ctx->in + 60);
md5_transform(ctx->buf, ctx->in);
putu32(ctx->buf[0], digest);
putu32(ctx->buf[1], digest + 4);
putu32(ctx->buf[2], digest + 8);
putu32(ctx->buf[3], digest + 12);
memset(&ctx, 0, sizeof(ctx));
}
#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))
#define MD5STEP(f, w, x, y, z, data, s) \
( w += f(x, y, z) + data, w &= 0xffffffff, w = w<<s | w>>(32-s), w += x )
void md5_transform(unsigned buf[4], const unsigned char inraw[64])
{
unsigned a, b, c, d;
unsigned in[16];
int i;
for (i = 0; i < 16; ++i)
in[i] = getu32 (inraw + 4 * i);
a = buf[0];
b = buf[1];
c = buf[2];
d = buf[3];
MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7);
MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12);
MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17);
MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22);
MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7);
MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12);
MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17);
MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22);
MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7);
MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12);
MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17);
MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22);
MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7);
MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12);
MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17);
MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22);
MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5);
MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9);
MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14);
MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20);
MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5);
MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9);
MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14);
MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20);
MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5);
MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9);
MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14);
MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20);
MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5);
MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9);
MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14);
MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20);
MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4);
MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11);
MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16);
MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23);
MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4);
MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11);
MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16);
MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23);
MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4);
MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11);
MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16);
MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23);
MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4);
MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11);
MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16);
MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23);
MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6);
MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10);
MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15);
MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21);
MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6);
MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10);
MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15);
MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21);
MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6);
MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10);
MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15);
MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21);
MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6);
MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10);
MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15);
MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21);
buf[0] += a;
buf[1] += b;
buf[2] += c;
buf[3] += d;
}
static char hexChars[] = "0123456789abcdef";
void md5_ascii(char *result, unsigned char const *buf, unsigned len)
{
struct md5_context md5;
unsigned char hash[16];
int i;
if (len==0)
len = strlen((char *)buf);
md5_init(&md5);
md5_update(&md5, buf, len);
md5_final(hash, &md5);
for (i=0; i<16; i++)
{
result[i*2] = hexChars[(hash[i]>>4)&0xF];
result[i*2+1] = hexChars[hash[i]&0x0F];
}
result[32] = 0;
}

15
src/client/MD5.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
struct md5_context
{
unsigned buf[4];
unsigned bits[2];
unsigned char in[64];
};
void md5_init(struct md5_context *context);
void md5_update(struct md5_context *context, unsigned char const *buf, unsigned len);
void md5_final(unsigned char digest[16], struct md5_context *context);
void md5_transform(unsigned buf[4], const unsigned char in[64]);
void md5_ascii(char *result, unsigned char const *buf, unsigned len);

View File

@ -2,7 +2,19 @@
#include "GameSave.h" #include "GameSave.h"
#include "common/platform/Platform.h" #include "common/platform/Platform.h"
SaveFile::SaveFile(SaveFile & save):
gameSave(NULL),
filename(save.filename),
displayName(save.displayName),
loadingError(save.loadingError),
lazyLoad(save.lazyLoad)
{
if (save.gameSave)
gameSave = new GameSave(*save.gameSave);
}
SaveFile::SaveFile(ByteString filename, bool newLazyLoad): SaveFile::SaveFile(ByteString filename, bool newLazyLoad):
gameSave(NULL),
filename(filename), filename(filename),
displayName(filename.FromUtf8()), displayName(filename.FromUtf8()),
loadingError(""), loadingError(""),
@ -11,7 +23,7 @@ SaveFile::SaveFile(ByteString filename, bool newLazyLoad):
} }
const GameSave *SaveFile::LazyGetGameSave() // non-owning GameSave * SaveFile::GetGameSave()
{ {
if (!gameSave && !loadingError.size() && lazyLoad) if (!gameSave && !loadingError.size() && lazyLoad)
{ {
@ -20,7 +32,7 @@ const GameSave *SaveFile::LazyGetGameSave() // non-owning
std::vector<char> data; std::vector<char> data;
if (Platform::ReadFile(data, filename)) if (Platform::ReadFile(data, filename))
{ {
gameSave = std::make_unique<GameSave>(std::move(data)); gameSave = new GameSave(std::move(data));
} }
else else
{ {
@ -32,33 +44,24 @@ const GameSave *SaveFile::LazyGetGameSave() // non-owning
loadingError = ByteString(e.what()).FromUtf8(); loadingError = ByteString(e.what()).FromUtf8();
} }
} }
return gameSave.get(); return gameSave;
}
const GameSave *SaveFile::GetGameSave() const
{
return gameSave.get();
}
std::unique_ptr<GameSave> SaveFile::TakeGameSave()
{
return std::move(gameSave);
} }
void SaveFile::LazyUnload() void SaveFile::LazyUnload()
{ {
if (lazyLoad) if (lazyLoad && gameSave)
{ {
gameSave.reset(); delete gameSave;
gameSave = nullptr;
} }
} }
void SaveFile::SetGameSave(std::unique_ptr<GameSave> newGameSave) void SaveFile::SetGameSave(GameSave * save)
{ {
gameSave = std::move(newGameSave); gameSave = save;
} }
const ByteString &SaveFile::GetName() const ByteString SaveFile::GetName()
{ {
return filename; return filename;
} }
@ -68,7 +71,7 @@ void SaveFile::SetFileName(ByteString fileName)
this->filename = fileName; this->filename = fileName;
} }
const String &SaveFile::GetDisplayName() const String SaveFile::GetDisplayName()
{ {
return displayName; return displayName;
} }
@ -78,7 +81,7 @@ void SaveFile::SetDisplayName(String displayName)
this->displayName = displayName; this->displayName = displayName;
} }
const String &SaveFile::GetError() const String SaveFile::GetError()
{ {
return loadingError; return loadingError;
} }
@ -87,3 +90,10 @@ void SaveFile::SetLoadingError(String error)
{ {
loadingError = error; loadingError = error;
} }
SaveFile::~SaveFile() {
if (gameSave)
{
delete gameSave;
}
}

View File

@ -1,27 +1,27 @@
#pragma once #pragma once
#include "common/String.h" #include "common/String.h"
#include <memory>
class GameSave; class GameSave;
class SaveFile { class SaveFile {
public: public:
SaveFile(SaveFile & save);
SaveFile(ByteString filename, bool newLazyLoad = false); SaveFile(ByteString filename, bool newLazyLoad = false);
const GameSave *LazyGetGameSave(); GameSave * GetGameSave();
const GameSave *GetGameSave() const; void SetGameSave(GameSave * save);
std::unique_ptr<GameSave> TakeGameSave(); String GetDisplayName();
void SetGameSave(std::unique_ptr<GameSave> newSameSave);
const String &GetDisplayName() const;
void SetDisplayName(String displayName); void SetDisplayName(String displayName);
const ByteString &GetName() const; ByteString GetName();
void SetFileName(ByteString fileName); void SetFileName(ByteString fileName);
const String &GetError() const; String GetError();
void SetLoadingError(String error); void SetLoadingError(String error);
void LazyUnload(); void LazyUnload();
virtual ~SaveFile();
private: private:
std::unique_ptr<GameSave> gameSave; GameSave * gameSave;
ByteString filename; ByteString filename;
String displayName; String displayName;
String loadingError; String loadingError;

View File

@ -1,7 +1,31 @@
#include "SaveInfo.h" #include "SaveInfo.h"
#include "GameSave.h" #include "GameSave.h"
SaveInfo::SaveInfo(int _id, time_t _createdDate, time_t _updatedDate, int _votesUp, int _votesDown, ByteString _userName, String _name): SaveInfo::SaveInfo(SaveInfo & save):
id(save.id),
createdDate(save.createdDate),
updatedDate(save.updatedDate),
votesUp(save.votesUp),
votesDown(save.votesDown),
vote(save.vote),
Favourite(false),
Comments(save.Comments),
Views(save.Views),
Version(save.Version),
userName(save.userName),
name(save.name),
Description(save.Description),
Published(save.Published),
gameSave(NULL)
{
std::list<ByteString> tagsSorted = save.tags;
tagsSorted.sort();
tags = tagsSorted;
if (save.gameSave)
gameSave = new GameSave(*save.gameSave);
}
SaveInfo::SaveInfo(int _id, int _createdDate, int _updatedDate, int _votesUp, int _votesDown, ByteString _userName, String _name):
id(_id), id(_id),
createdDate(_createdDate), createdDate(_createdDate),
updatedDate(_updatedDate), updatedDate(_updatedDate),
@ -15,12 +39,14 @@ SaveInfo::SaveInfo(int _id, time_t _createdDate, time_t _updatedDate, int _votes
userName(_userName), userName(_userName),
name(_name), name(_name),
Description(""), Description(""),
Published(false) Published(false),
tags(),
gameSave(NULL)
{ {
} }
SaveInfo::SaveInfo(int _id, time_t _createdDate, time_t _updatedDate, int _votesUp, int _votesDown, int _vote, ByteString _userName, String _name, String description_, bool published_, std::list<ByteString> tags_): SaveInfo::SaveInfo(int _id, int _createdDate, int _updatedDate, int _votesUp, int _votesDown, int _vote, ByteString _userName, String _name, String description_, bool published_, std::list<ByteString> tags_):
id(_id), id(_id),
createdDate(_createdDate), createdDate(_createdDate),
updatedDate(_updatedDate), updatedDate(_updatedDate),
@ -34,18 +60,28 @@ SaveInfo::SaveInfo(int _id, time_t _createdDate, time_t _updatedDate, int _votes
userName(_userName), userName(_userName),
name(_name), name(_name),
Description(description_), Description(description_),
Published(published_) Published(published_),
tags(),
gameSave(NULL)
{ {
std::list<ByteString> tagsSorted = tags_; std::list<ByteString> tagsSorted = tags_;
tagsSorted.sort(); tagsSorted.sort();
tags=tagsSorted; tags=tagsSorted;
} }
SaveInfo::~SaveInfo()
{
if(gameSave)
{
delete gameSave;
}
}
void SaveInfo::SetName(String name) void SaveInfo::SetName(String name)
{ {
this->name = name; this->name = name;
} }
const String &SaveInfo::GetName() const String SaveInfo::GetName()
{ {
return name; return name;
} }
@ -54,7 +90,7 @@ void SaveInfo::SetDescription(String description)
{ {
Description = description; Description = description;
} }
const String &SaveInfo::GetDescription() const String SaveInfo::GetDescription()
{ {
return Description; return Description;
} }
@ -63,7 +99,7 @@ void SaveInfo::SetPublished(bool published)
{ {
Published = published; Published = published;
} }
bool SaveInfo::GetPublished() const bool SaveInfo::GetPublished()
{ {
return Published; return Published;
} }
@ -72,7 +108,7 @@ void SaveInfo::SetVote(int vote)
{ {
this->vote = vote; this->vote = vote;
} }
int SaveInfo::GetVote() const int SaveInfo::GetVote()
{ {
return vote; return vote;
} }
@ -82,7 +118,7 @@ void SaveInfo::SetUserName(ByteString userName)
this->userName = userName; this->userName = userName;
} }
const ByteString &SaveInfo::GetUserName() const ByteString SaveInfo::GetUserName()
{ {
return userName; return userName;
} }
@ -91,7 +127,7 @@ void SaveInfo::SetID(int id)
{ {
this->id = id; this->id = id;
} }
int SaveInfo::GetID() const int SaveInfo::GetID()
{ {
return id; return id;
} }
@ -100,7 +136,7 @@ void SaveInfo::SetVotesUp(int votesUp)
{ {
this->votesUp = votesUp; this->votesUp = votesUp;
} }
int SaveInfo::GetVotesUp() const int SaveInfo::GetVotesUp()
{ {
return votesUp; return votesUp;
} }
@ -109,7 +145,7 @@ void SaveInfo::SetVotesDown(int votesDown)
{ {
this->votesDown = votesDown; this->votesDown = votesDown;
} }
int SaveInfo::GetVotesDown() const int SaveInfo::GetVotesDown()
{ {
return votesDown; return votesDown;
} }
@ -118,7 +154,7 @@ void SaveInfo::SetVersion(int version)
{ {
this->Version = version; this->Version = version;
} }
int SaveInfo::GetVersion() const int SaveInfo::GetVersion()
{ {
return Version; return Version;
} }
@ -130,33 +166,18 @@ void SaveInfo::SetTags(std::list<ByteString> tags)
this->tags=tagsSorted; this->tags=tagsSorted;
} }
std::list<ByteString> SaveInfo::GetTags() const std::list<ByteString> SaveInfo::GetTags()
{ {
return tags; return tags;
} }
const GameSave *SaveInfo::GetGameSave() const GameSave * SaveInfo::GetGameSave()
{ {
return gameSave.get(); return gameSave;
} }
std::unique_ptr<GameSave> SaveInfo::TakeGameSave() void SaveInfo::SetGameSave(GameSave * saveGame)
{ {
return std::move(gameSave); delete gameSave;
} gameSave = saveGame;
void SaveInfo::SetGameSave(std::unique_ptr<GameSave> newGameSave)
{
gameSave = std::move(newGameSave);
}
std::unique_ptr<SaveInfo> SaveInfo::CloneInfo() const
{
auto clone = std::make_unique<SaveInfo>(id, createdDate, updatedDate, votesUp, votesDown, vote, userName, name, Description, Published, tags);
clone->Favourite = false;
clone->Comments = Comments;
clone->Views = Views;
clone->Version = Version;
clone->tags.sort();
return clone;
} }

View File

@ -1,8 +1,10 @@
#pragma once #pragma once
#include "common/String.h" #include "common/String.h"
#include <list> #include <list>
#include <memory>
#include <ctime> #ifdef GetUserName
# undef GetUserName // dammit windows
#endif
class GameSave; class GameSave;
@ -11,8 +13,8 @@ class SaveInfo
private: private:
public: public:
int id; int id;
time_t createdDate; int createdDate;
time_t updatedDate; int updatedDate;
int votesUp, votesDown; int votesUp, votesDown;
int vote; int vote;
bool Favourite; bool Favourite;
@ -27,45 +29,46 @@ public:
bool Published; bool Published;
std::list<ByteString> tags; std::list<ByteString> tags;
std::unique_ptr<GameSave> gameSave; GameSave * gameSave;
SaveInfo(int _id, time_t _createdDate, time_t _updatedDate, int _votesUp, int _votesDown, ByteString _userName, String _name); SaveInfo(SaveInfo & save);
SaveInfo(int _id, time_t _createdDate, time_t _updatedDate, int _votesUp, int _votesDown, int _vote, ByteString _userName, String _name, String description_, bool published_, std::list<ByteString> tags); SaveInfo(int _id, int _createdDate, int _updatedDate, int _votesUp, int _votesDown, ByteString _userName, String _name);
SaveInfo(int _id, int _createdDate, int _updatedDate, int _votesUp, int _votesDown, int _vote, ByteString _userName, String _name, String description_, bool published_, std::list<ByteString> tags);
~SaveInfo();
void SetName(String name); void SetName(String name);
const String &GetName() const; String GetName();
void SetDescription(String description); void SetDescription(String description);
const String &GetDescription() const; String GetDescription();
void SetPublished(bool published); void SetPublished(bool published);
bool GetPublished() const; bool GetPublished();
void SetUserName(ByteString userName); void SetUserName(ByteString userName);
const ByteString &GetUserName() const; ByteString GetUserName();
void SetID(int id); void SetID(int id);
int GetID() const; int GetID();
void SetVote(int vote); void SetVote(int vote);
int GetVote() const; int GetVote();
void SetVotesUp(int votesUp); void SetVotesUp(int votesUp);
int GetVotesUp() const; int GetVotesUp();
void SetVotesDown(int votesDown); void SetVotesDown(int votesDown);
int GetVotesDown() const; int GetVotesDown();
void SetVersion(int version); void SetVersion(int version);
int GetVersion() const; int GetVersion();
void SetTags(std::list<ByteString> tags); void SetTags(std::list<ByteString> tags);
std::list<ByteString> GetTags() const; std::list<ByteString> GetTags();
const GameSave *GetGameSave() const; GameSave * GetGameSave();
std::unique_ptr<GameSave> TakeGameSave(); void SetGameSave(GameSave * gameSave);
void SetGameSave(std::unique_ptr<GameSave> newGameSave);
std::unique_ptr<SaveInfo> CloneInfo() const;
}; };

View File

@ -1,26 +0,0 @@
#pragma once
namespace http
{
enum Category
{
categoryNone,
categoryMyOwn,
categoryFavourites,
};
enum Sort
{
sortByVotes,
sortByDate,
};
enum Period
{
allSaves,
todaySaves,
weekSaves,
monthSaves,
yearSaves,
};
}

View File

@ -1,8 +0,0 @@
#pragma once
#include "common/String.h"
struct ServerNotification
{
String text;
ByteString link;
};

View File

@ -1,29 +0,0 @@
#pragma once
#include "common/String.h"
#include "ServerNotification.h"
#include <vector>
#include <optional>
struct UpdateInfo
{
enum Channel
{
channelStable,
channelBeta,
channelSnapshot,
};
Channel channel;
ByteString file;
String changeLog;
int major = 0;
int minor = 0;
int build = 0;
};
struct StartupInfo
{
bool sessionGood = false;
String messageOfTheDay;
std::vector<ServerNotification> notifications;
std::optional<UpdateInfo> updateInfo;
};

View File

@ -29,7 +29,7 @@ ThumbnailRendererTask::~ThumbnailRendererTask()
bool ThumbnailRendererTask::doWork() bool ThumbnailRendererTask::doWork()
{ {
thumbnail = SaveRenderer::Ref().Render(save.get(), decorations, fire); thumbnail = std::unique_ptr<VideoBuffer>(SaveRenderer::Ref().Render(save.get(), decorations, fire));
if (thumbnail) if (thumbnail)
{ {
thumbnail->ResizeToFit(size, true); thumbnail->ResizeToFit(size, true);

View File

@ -1,32 +0,0 @@
#include "User.h"
static const std::vector<std::pair<User::Elevation, ByteString>> elevationStrings = {
{ User::ElevationAdmin , "Admin" },
{ User::ElevationMod , "Mod" },
{ User::ElevationHalfMod, "HalfMod" },
{ User::ElevationNone , "None" },
};
User::Elevation User::ElevationFromString(ByteString str)
{
auto it = std::find_if(elevationStrings.begin(), elevationStrings.end(), [&str](auto &item) {
return item.second == str;
});
if (it != elevationStrings.end())
{
return it->first;
}
return ElevationNone;
}
ByteString User::ElevationToString(Elevation elevation)
{
auto it = std::find_if(elevationStrings.begin(), elevationStrings.end(), [elevation](auto &item) {
return item.first == elevation;
});
if (it != elevationStrings.end())
{
return it->second;
}
return "None";
}

View File

@ -7,14 +7,8 @@ class User
public: public:
enum Elevation enum Elevation
{ {
ElevationNone, ElevationAdmin, ElevationModerator, ElevationNone
ElevationHalfMod,
ElevationMod,
ElevationAdmin,
}; };
static Elevation ElevationFromString(ByteString str);
static ByteString ElevationToString(Elevation elevation);
int UserID; int UserID;
ByteString Username; ByteString Username;
ByteString SessionID; ByteString SessionID;

View File

@ -1,36 +1,39 @@
#include "APIRequest.h" #include "APIRequest.h"
#include "client/Client.h" #include "client/Client.h"
namespace http namespace http
{ {
APIRequest::APIRequest(ByteString url, AuthMode authMode, bool newCheckStatus) : Request(url), checkStatus(newCheckStatus) APIRequest::APIRequest(ByteString url) : Request(url)
{ {
auto user = Client::Ref().GetAuthUser(); User user = Client::Ref().GetAuthUser();
if (authMode == authRequire && !user.UserID) AuthHeaders(ByteString::Build(user.UserID), user.SessionID);
{
FailEarly("Not authenticated");
return;
}
if (authMode != authOmit && user.UserID)
{
AuthHeaders(ByteString::Build(user.UserID), user.SessionID);
}
} }
Json::Value APIRequest::Finish() APIRequest::~APIRequest()
{ {
auto [ status, data ] = Request::Finish(); }
ParseResponse(data, status, checkStatus ? responseJson : responseData);
Json::Value document; APIRequest::Result APIRequest::Finish()
{
Result result;
try try
{ {
std::istringstream ss(data); ByteString data;
ss >> document; std::tie(result.status, data) = Request::Finish();
Client::Ref().ParseServerReturn(data, result.status, true);
if (result.status == 200 && data.size())
{
std::istringstream dataStream(data);
Json::Value objDocument;
dataStream >> objDocument;
result.document = std::unique_ptr<Json::Value>(new Json::Value(objDocument));
}
} }
catch (const std::exception &ex) catch (std::exception & e)
{ {
throw RequestError("Could not read response: " + ByteString(ex.what()));
} }
return document; return result;
} }
} }

View File

@ -2,22 +2,23 @@
#include "Request.h" #include "Request.h"
#include "common/String.h" #include "common/String.h"
#include <json/json.h> #include <json/json.h>
#include <memory>
#include <map>
namespace http namespace http
{ {
class APIRequest : public Request class APIRequest : public Request
{ {
bool checkStatus;
public: public:
enum AuthMode struct Result
{ {
authRequire, int status;
authUse, std::unique_ptr<Json::Value> document;
authOmit,
}; };
APIRequest(ByteString url, AuthMode authMode, bool newCheckStatus);
Json::Value Finish(); APIRequest(ByteString url);
virtual ~APIRequest();
Result Finish();
}; };
} }

View File

@ -1,21 +0,0 @@
#include "AddCommentRequest.h"
#include "client/Client.h"
#include "Config.h"
namespace http
{
AddCommentRequest::AddCommentRequest(int saveID, String comment) :
APIRequest(ByteString::Build(SCHEME, SERVER, "/Browse/Comments.json?ID=", saveID), authRequire, true)
{
auto user = Client::Ref().GetAuthUser();
AddPostData(FormData{
{ "Comment", comment.ToUtf8() },
{ "Key", user.SessionKey },
});
}
void AddCommentRequest::Finish()
{
APIRequest::Finish();
}
}

View File

@ -1,13 +0,0 @@
#pragma once
#include "APIRequest.h"
namespace http
{
class AddCommentRequest : public APIRequest
{
public:
AddCommentRequest(int saveID, String comment);
void Finish();
};
}

View File

@ -1,29 +0,0 @@
#include "AddTagRequest.h"
#include "client/Client.h"
#include "Config.h"
namespace http
{
AddTagRequest::AddTagRequest(int saveID, ByteString tag) :
APIRequest(ByteString::Build(SCHEME, SERVER, "/Browse/EditTag.json?Op=add&ID=", saveID, "&Tag=", tag, "&Key=", Client::Ref().GetAuthUser().SessionKey), authRequire, true)
{
}
std::list<ByteString> AddTagRequest::Finish()
{
auto result = APIRequest::Finish();
std::list<ByteString> tags;
try
{
for (auto &tag : result["Tags"])
{
tags.push_back(tag.asString());
}
}
catch (const std::exception &ex)
{
throw RequestError("Could not read response: " + ByteString(ex.what()));
}
return tags;
}
}

View File

@ -1,14 +0,0 @@
#pragma once
#include "APIRequest.h"
#include <list>
namespace http
{
class AddTagRequest : public APIRequest
{
public:
AddTagRequest(int saveID, ByteString tag);
std::list<ByteString> Finish();
};
}

View File

@ -1,16 +0,0 @@
#include "DeleteSaveRequest.h"
#include "client/Client.h"
#include "Config.h"
namespace http
{
DeleteSaveRequest::DeleteSaveRequest(int saveID) :
APIRequest(ByteString::Build(SCHEME, SERVER, "/Browse/Delete.json?ID=", saveID, "&Mode=Delete&Key=", Client::Ref().GetAuthUser().SessionKey), authRequire, true)
{
}
void DeleteSaveRequest::Finish()
{
APIRequest::Finish();
}
}

View File

@ -1,13 +0,0 @@
#pragma once
#include "APIRequest.h"
namespace http
{
class DeleteSaveRequest : public APIRequest
{
public:
DeleteSaveRequest(int saveID);
void Finish();
};
}

View File

@ -1,23 +0,0 @@
#include "ExecVoteRequest.h"
#include "client/Client.h"
#include "Config.h"
namespace http
{
ExecVoteRequest::ExecVoteRequest(int saveID, int newDirection) :
APIRequest(ByteString::Build(SCHEME, SERVER, "/Vote.api"), authRequire, false),
direction(newDirection)
{
AddPostData(FormData{
{ "ID", ByteString::Build(saveID) },
{ "Action", direction ? (direction == 1 ? "Up" : "Down") : "Reset" },
{ "Key", Client::Ref().GetAuthUser().SessionKey },
});
}
void ExecVoteRequest::Finish()
{
auto [ status, data ] = Request::Finish();
ParseResponse(data, status, responseOk);
}
}

View File

@ -1,20 +0,0 @@
#pragma once
#include "APIRequest.h"
namespace http
{
class ExecVoteRequest : public APIRequest
{
int direction;
public:
ExecVoteRequest(int saveID, int newDirection);
void Finish();
int Direction() const
{
return direction;
}
};
}

View File

@ -1,28 +0,0 @@
#include "FavouriteSaveRequest.h"
#include "client/Client.h"
#include "Config.h"
namespace http
{
static ByteString Url(int saveID, bool favourite)
{
ByteStringBuilder builder;
builder << SCHEME << SERVER << "/Browse/Favourite.json?ID=" << saveID << "&Key=" << Client::Ref().GetAuthUser().SessionKey;
if (!favourite)
{
builder << "&Mode=Remove";
}
return builder.Build();
}
FavouriteSaveRequest::FavouriteSaveRequest(int saveID, bool newFavourite) :
APIRequest(Url(saveID, newFavourite), authRequire, true),
favourite(newFavourite)
{
}
void FavouriteSaveRequest::Finish()
{
APIRequest::Finish();
}
}

View File

@ -1,20 +0,0 @@
#pragma once
#include "APIRequest.h"
namespace http
{
class FavouriteSaveRequest : public APIRequest
{
bool favourite;
public:
FavouriteSaveRequest(int saveID, bool newFavourite);
void Finish();
bool Favourite() const
{
return favourite;
}
};
}

View File

@ -1,36 +0,0 @@
#include "GetCommentsRequest.h"
#include "client/Client.h"
#include "Config.h"
namespace http
{
GetCommentsRequest::GetCommentsRequest(int saveID, int start, int count) :
APIRequest(ByteString::Build(SCHEME, SERVER, "/Browse/Comments.json?ID=", saveID, "&Start=", start, "&Count=", count), authOmit, false)
{
}
std::vector<Comment> GetCommentsRequest::Finish()
{
auto result = APIRequest::Finish();
std::vector<Comment> comments;
auto user = Client::Ref().GetAuthUser();
try
{
for (auto &comment : result)
{
comments.push_back({
comment["Username"].asString(),
User::ElevationFromString(comment["Elevation"].asString()),
comment["Username"].asString() == user.Username,
comment["IsBanned"].asBool(),
ByteString(comment["Text"].asString()).FromUtf8(),
});
}
}
catch (const std::exception &ex)
{
throw RequestError("Could not read response: " + ByteString(ex.what()));
}
return comments;
}
}

View File

@ -1,14 +0,0 @@
#pragma once
#include "APIRequest.h"
#include "client/Comment.h"
namespace http
{
class GetCommentsRequest : public APIRequest
{
public:
GetCommentsRequest(int saveID, int start, int count);
std::vector<Comment> Finish();
};
}

View File

@ -1,28 +0,0 @@
#include "GetSaveDataRequest.h"
#include "Config.h"
namespace http
{
static ByteString Url(int saveID, int saveDate)
{
ByteStringBuilder builder;
builder << STATICSCHEME << STATICSERVER << "/" << saveID;
if (saveDate)
{
builder << "_" << saveDate;
}
builder << ".cps";
return builder.Build();
}
GetSaveDataRequest::GetSaveDataRequest(int saveID, int saveDate) : Request(Url(saveID, saveDate))
{
}
std::vector<char> GetSaveDataRequest::Finish()
{
auto [ status, data ] = Request::Finish();
ParseResponse(data, status, responseData);
return std::vector<char>(data.begin(), data.end());
}
}

View File

@ -1,13 +0,0 @@
#pragma once
#include "Request.h"
namespace http
{
class GetSaveDataRequest : public Request
{
public:
GetSaveDataRequest(int saveID, int saveDate);
std::vector<char> Finish();
};
}

View File

@ -1,69 +0,0 @@
#include "GetSaveRequest.h"
#include "client/Client.h"
#include "client/SaveInfo.h"
#include "client/GameSave.h"
#include "Config.h"
namespace http
{
static ByteString Url(int saveID, int saveDate)
{
ByteStringBuilder builder;
builder << SCHEME << SERVER << "/Browse/View.json?ID=" << saveID;
if (saveDate)
{
builder << "&Date=" << saveDate;
}
return builder.Build();
}
GetSaveRequest::GetSaveRequest(int saveID, int saveDate) : Request(Url(saveID, saveDate))
{
auto user = Client::Ref().GetAuthUser();
if (user.UserID)
{
// This is needed so we know how we rated this save.
AuthHeaders(ByteString::Build(user.UserID), user.SessionID);
}
}
std::unique_ptr<SaveInfo> GetSaveRequest::Finish()
{
auto [ status, data ] = Request::Finish();
ParseResponse(data, status, responseData);
std::unique_ptr<SaveInfo> saveInfo;
try
{
Json::Value document;
std::istringstream ss(data);
ss >> document;
std::list<ByteString> tags;
for (auto &tag : document["Tags"])
{
tags.push_back(tag.asString());
}
saveInfo = std::make_unique<SaveInfo>(
document["ID"].asInt(),
document["DateCreated"].asInt64(),
document["Date"].asInt64(),
document["ScoreUp"].asInt(),
document["ScoreDown"].asInt(),
document["ScoreMine"].asInt(),
document["Username"].asString(),
ByteString(document["Name"].asString()).FromUtf8(),
ByteString(document["Description"].asString()).FromUtf8(),
document["Published"].asBool(),
tags
);
saveInfo->Comments = document["Comments"].asInt();
saveInfo->Favourite = document["Favourite"].asBool();
saveInfo->Views = document["Views"].asInt();
saveInfo->Version = document["Version"].asInt();
}
catch (const std::exception &ex)
{
throw RequestError("Could not read response: " + ByteString(ex.what()));
}
return saveInfo;
}
}

View File

@ -1,16 +0,0 @@
#pragma once
#include "Request.h"
#include <memory>
class SaveInfo;
namespace http
{
class GetSaveRequest : public Request
{
public:
GetSaveRequest(int saveID, int saveDate);
std::unique_ptr<SaveInfo> Finish();
};
}

View File

@ -5,18 +5,22 @@
namespace http namespace http
{ {
GetUserInfoRequest::GetUserInfoRequest(ByteString username) : GetUserInfoRequest::GetUserInfoRequest(ByteString username) :
APIRequest(ByteString::Build(SCHEME, SERVER, "/User.json?Name=", username), authOmit, false) APIRequest(ByteString::Build(SCHEME, SERVER, "/User.json?Name=", username))
{ {
} }
UserInfo GetUserInfoRequest::Finish() GetUserInfoRequest::~GetUserInfoRequest()
{ {
}
std::unique_ptr<UserInfo> GetUserInfoRequest::Finish()
{
std::unique_ptr<UserInfo> user_info;
auto result = APIRequest::Finish(); auto result = APIRequest::Finish();
UserInfo userInfo; if (result.document)
try
{ {
auto &user = result["User"]; auto &user = (*result.document)["User"];
userInfo = UserInfo( user_info = std::unique_ptr<UserInfo>(new UserInfo(
user["ID"].asInt(), user["ID"].asInt(),
user["Age"].asInt(), user["Age"].asInt(),
user["Username"].asString(), user["Username"].asString(),
@ -29,13 +33,9 @@ namespace http
user["Forum"]["Topics"].asInt(), user["Forum"]["Topics"].asInt(),
user["Forum"]["Replies"].asInt(), user["Forum"]["Replies"].asInt(),
user["Forum"]["Reputation"].asInt() user["Forum"]["Reputation"].asInt()
); ));
} }
catch (const std::exception &ex) return user_info;
{
throw RequestError("Could not read response: " + ByteString(ex.what()));
}
return userInfo;
} }
} }

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "APIRequest.h" #include "APIRequest.h"
#include "client/UserInfo.h"
class UserInfo;
namespace http namespace http
{ {
@ -8,7 +9,8 @@ namespace http
{ {
public: public:
GetUserInfoRequest(ByteString username); GetUserInfoRequest(ByteString username);
virtual ~GetUserInfoRequest();
UserInfo Finish(); std::unique_ptr<UserInfo> Finish();
}; };
} }

View File

@ -1,27 +1,32 @@
#include "ImageRequest.h" #include "ImageRequest.h"
#include "graphics/Graphics.h" #include "graphics/Graphics.h"
#include "client/Client.h"
#include <iostream> #include <iostream>
namespace http namespace http
{ {
ImageRequest::ImageRequest(ByteString url, Vec2<int> newRequestedSize) : Request(url), requestedSize(newRequestedSize) ImageRequest::ImageRequest(ByteString url, Vec2<int> size):
{ Request(std::move(url)),
} size(size)
{}
ImageRequest::~ImageRequest()
{}
std::unique_ptr<VideoBuffer> ImageRequest::Finish() std::unique_ptr<VideoBuffer> ImageRequest::Finish()
{ {
auto [ status, data ] = Request::Finish(); auto [ status, data ] = Request::Finish();
ParseResponse(data, status, responseData); (void)status; // We don't use this for anything, not ideal >_>
auto vb = VideoBuffer::FromPNG(std::vector<char>(data.begin(), data.end())); std::unique_ptr<VideoBuffer> vb;
if (vb) if (data.size())
{ {
vb->Resize(requestedSize, true); vb = VideoBuffer::FromPNG(std::vector<char>(data.begin(), data.end()));
} if (vb)
else vb->Resize(size, true);
{ else
vb = std::make_unique<VideoBuffer>(Vec2(15, 16)); {
vb->BlendChar(Vec2(2, 4), 0xE06E, 0xFFFFFF_rgb .WithAlpha(0xFF)); vb = std::make_unique<VideoBuffer>(Vec2(15, 16));
vb->BlendChar(Vec2(2, 4), 0xE06E, 0xFFFFFF_rgb .WithAlpha(0xFF));
}
} }
return vb; return vb;
} }

View File

@ -2,6 +2,7 @@
#include "common/String.h" #include "common/String.h"
#include "common/Vec2.h" #include "common/Vec2.h"
#include "Request.h" #include "Request.h"
#include <memory> #include <memory>
class VideoBuffer; class VideoBuffer;
@ -10,10 +11,11 @@ namespace http
{ {
class ImageRequest : public Request class ImageRequest : public Request
{ {
Vec2<int> requestedSize; Vec2<int> size;
public: public:
ImageRequest(ByteString url, Vec2<int> newRequestedSize); ImageRequest(ByteString url, Vec2<int> size);
virtual ~ImageRequest();
std::unique_ptr<VideoBuffer> Finish(); std::unique_ptr<VideoBuffer> Finish();
}; };

View File

@ -1,45 +0,0 @@
#include "LoginRequest.h"
#include "Config.h"
#include "client/Client.h"
#include <json/json.h>
namespace http
{
LoginRequest::LoginRequest(ByteString username, ByteString password) : Request(ByteString::Build("https://", SERVER, "/Login.json"))
{
AddPostData(FormData{
{ "name", username },
{ "pass", password },
});
}
LoginInfo LoginRequest::Finish()
{
auto [ status, data ] = Request::Finish();
ParseResponse(data, status, responseJson);
LoginInfo loginInfo = { { 0, "" }, {} };
try
{
Json::Value document;
std::istringstream ss(data);
ss >> document;
loginInfo.user.Username = document["Username"].asString();
loginInfo.user.UserID = document["UserID"].asInt();
loginInfo.user.SessionID = document["SessionID"].asString();
loginInfo.user.SessionKey = document["SessionKey"].asString();
loginInfo.user.UserElevation = User::ElevationFromString(document["Elevation"].asString());
for (auto &item : document["Notifications"])
{
loginInfo.notifications.push_back({
ByteString(item["Text"].asString()).FromUtf8(),
item["Link"].asString(),
});
}
}
catch (const std::exception &ex)
{
throw RequestError("Could not read response: " + ByteString(ex.what()));
}
return loginInfo;
}
}

View File

@ -1,14 +0,0 @@
#pragma once
#include "Request.h"
#include "client/LoginInfo.h"
namespace http
{
class LoginRequest : public Request
{
public:
LoginRequest(ByteString username, ByteString password);
LoginInfo Finish();
};
}

View File

@ -1,16 +0,0 @@
#include "LogoutRequest.h"
#include "client/Client.h"
#include "Config.h"
namespace http
{
LogoutRequest::LogoutRequest() :
APIRequest(ByteString::Build(SCHEME, SERVER, "/Logout.json?Key=" + Client::Ref().GetAuthUser().SessionKey), authRequire, true)
{
}
void LogoutRequest::Finish()
{
APIRequest::Finish();
}
}

View File

@ -1,13 +0,0 @@
#pragma once
#include "APIRequest.h"
namespace http
{
class LogoutRequest : public APIRequest
{
public:
LogoutRequest();
void Finish();
};
}

View File

@ -1,23 +0,0 @@
#pragma once
#include "common/String.h"
#include <vector>
#include <variant>
#include <optional>
namespace http
{
struct Header
{
ByteString name;
ByteString value;
};
struct FormItem
{
ByteString name;
ByteString value;
std::optional<ByteString> filename;
};
using StringData = ByteString;
using FormData = std::vector<FormItem>;
using PostData = std::variant<StringData, FormData>;
};

View File

@ -1,19 +0,0 @@
#include "PublishSaveRequest.h"
#include "client/Client.h"
#include "Config.h"
namespace http
{
PublishSaveRequest::PublishSaveRequest(int saveID) :
APIRequest(ByteString::Build(SCHEME, SERVER, "/Browse/View.json?ID=", saveID, "&Key=", Client::Ref().GetAuthUser().SessionKey), authRequire, true)
{
AddPostData(FormData{
{ "ActionPublish", "bagels" },
});
}
void PublishSaveRequest::Finish()
{
APIRequest::Finish();
}
}

View File

@ -1,13 +0,0 @@
#pragma once
#include "APIRequest.h"
namespace http
{
class PublishSaveRequest : public APIRequest
{
public:
PublishSaveRequest(int saveID);
void Finish();
};
}

View File

@ -1,29 +0,0 @@
#include "RemoveTagRequest.h"
#include "client/Client.h"
#include "Config.h"
namespace http
{
RemoveTagRequest::RemoveTagRequest(int saveID, ByteString tag) :
APIRequest(ByteString::Build(SCHEME, SERVER, "/Browse/EditTag.json?Op=delete&ID=", saveID, "&Tag=", tag, "&Key=", Client::Ref().GetAuthUser().SessionKey), authRequire, true)
{
}
std::list<ByteString> RemoveTagRequest::Finish()
{
auto result = APIRequest::Finish();
std::list<ByteString> tags;
try
{
for (auto &tag : result["Tags"])
{
tags.push_back(tag.asString());
}
}
catch (const std::exception &ex)
{
throw RequestError("Could not read response: " + ByteString(ex.what()));
}
return tags;
}
}

View File

@ -1,14 +0,0 @@
#pragma once
#include "APIRequest.h"
#include <list>
namespace http
{
class RemoveTagRequest : public APIRequest
{
public:
RemoveTagRequest(int saveID, ByteString tag);
std::list<ByteString> Finish();
};
}

View File

@ -1,19 +0,0 @@
#include "ReportSaveRequest.h"
#include "client/Client.h"
#include "Config.h"
namespace http
{
ReportSaveRequest::ReportSaveRequest(int saveID, String message) :
APIRequest(ByteString::Build(SCHEME, SERVER, "/Browse/Report.json?ID=", saveID, "&Key=", Client::Ref().GetAuthUser().SessionKey), authRequire, true)
{
AddPostData(FormData{
{ "Reason", message.ToUtf8() },
});
}
void ReportSaveRequest::Finish()
{
APIRequest::Finish();
}
}

View File

@ -1,13 +0,0 @@
#pragma once
#include "APIRequest.h"
namespace http
{
class ReportSaveRequest : public APIRequest
{
public:
ReportSaveRequest(int saveID, String message);
void Finish();
};
}

View File

@ -1,9 +1,6 @@
#include "Request.h" #include "Request.h"
#include "requestmanager/RequestManager.h" #include "requestmanager/RequestManager.h"
#include <memory> #include <memory>
#include <iostream>
#include <cstring>
#include <json/json.h>
namespace http namespace http
{ {
@ -15,61 +12,45 @@ namespace http
Request::~Request() Request::~Request()
{ {
bool tryUnregister; if (handle->state != RequestHandle::ready)
{ {
std::lock_guard lk(handle->stateMx);
tryUnregister = handle->state == RequestHandle::running;
}
if (tryUnregister)
{
// At this point it may have already finished and been unregistered but that's ok,
// attempting to unregister a request multiple times is allowed. We only do the
// state-checking dance so we don't wake up RequestManager if we don't have to.
// In fact, we could just not unregister requests here at all, they'd just run to
// completion and be unregistered later. All this does is cancel them early.
RequestManager::Ref().UnregisterRequest(*this); RequestManager::Ref().UnregisterRequest(*this);
} }
} }
void Request::FailEarly(ByteString error)
{
assert(handle->state == RequestHandle::ready);
handle->failEarly = error;
}
void Request::Verb(ByteString newVerb) void Request::Verb(ByteString newVerb)
{ {
assert(handle->state == RequestHandle::ready); assert(handle->state == RequestHandle::ready);
handle->verb = newVerb; handle->verb = newVerb;
} }
void Request::AddHeader(Header header) void Request::AddHeader(ByteString header)
{ {
assert(handle->state == RequestHandle::ready); assert(handle->state == RequestHandle::ready);
handle->headers.push_back(header); handle->headers.push_back(header);
} }
void Request::AddPostData(PostData data) void Request::AddPostData(std::map<ByteString, ByteString> data)
{ {
assert(handle->state == RequestHandle::ready); assert(handle->state == RequestHandle::ready);
// Even if the map is empty, calling this function signifies you want to do a POST request // Even if the map is empty, calling this function signifies you want to do a POST request
handle->isPost = true; handle->isPost = true;
handle->postData = data; handle->postData.insert(data.begin(), data.end());
} }
void Request::AuthHeaders(ByteString ID, ByteString session) void Request::AuthHeaders(ByteString ID, ByteString session)
{ {
assert(handle->state == RequestHandle::ready); assert(handle->state == RequestHandle::ready);
if (ID.size() && ID != "-1") // -1 is an emscripten hack, see AuthUserEmscripten.cpp if (ID.size())
{ {
if (session.size()) if (session.size())
{ {
AddHeader({ "X-Auth-User-Id", ID }); AddHeader("X-Auth-User-Id: " + ID);
AddHeader({ "X-Auth-Session-Key", session }); AddHeader("X-Auth-Session-Key: " + session);
} }
else else
{ {
AddHeader({ "X-Auth-User", ID }); AddHeader("X-Auth-User: " + ID);
} }
} }
} }
@ -88,71 +69,52 @@ namespace http
return handle->state == RequestHandle::done; return handle->state == RequestHandle::done;
} }
std::pair<int64_t, int64_t> Request::CheckProgress() const std::pair<int, int> Request::CheckProgress() const
{ {
std::lock_guard lk(handle->stateMx); std::lock_guard lk(handle->stateMx);
assert(handle->state == RequestHandle::running || handle->state == RequestHandle::done); assert(handle->state == RequestHandle::running || handle->state == RequestHandle::done);
return { handle->bytesTotal, handle->bytesDone }; return { handle->bytesTotal, handle->bytesDone };
} }
const std::vector<Header> &Request::ResponseHeaders() const const std::vector<ByteString> &Request::ResponseHeaders() const
{ {
std::lock_guard lk(handle->stateMx); std::lock_guard lk(handle->stateMx);
assert(handle->state == RequestHandle::done); assert(handle->state == RequestHandle::done);
return handle->responseHeaders; return handle->responseHeaders;
} }
void Request::Wait()
{
std::unique_lock lk(handle->stateMx);
assert(handle->state == RequestHandle::running);
handle->stateCv.wait(lk, [this]() {
return handle->state == RequestHandle::done;
});
}
int Request::StatusCode() const
{
{
std::unique_lock lk(handle->stateMx);
assert(handle->state == RequestHandle::done);
}
return handle->statusCode;
}
std::pair<int, ByteString> Request::Finish() std::pair<int, ByteString> Request::Finish()
{ {
std::unique_lock lk(handle->stateMx);
if (handle->state == RequestHandle::running)
{ {
std::unique_lock lk(handle->stateMx); handle->stateCv.wait(lk, [this]() {
assert(handle->state == RequestHandle::done); return handle->state == RequestHandle::done;
});
} }
assert(handle->state == RequestHandle::done);
handle->state = RequestHandle::dead; handle->state = RequestHandle::dead;
if (handle->error) return { handle->statusCode, std::move(handle->responseData) };
{
throw RequestError(*handle->error);
}
return std::pair{ handle->statusCode, std::move(handle->responseData) };
} }
void RequestHandle::MarkDone() std::pair<int, ByteString> Request::Simple(ByteString uri, std::map<ByteString, ByteString> post_data)
{ {
{ return SimpleAuth(uri, "", "", post_data);
std::lock_guard lk(stateMx);
assert(state == RequestHandle::running);
state = RequestHandle::done;
}
stateCv.notify_one();
if (error)
{
std::cerr << *error << std::endl;
}
else if (statusCode >= 400)
{
std::cerr << "status code " << statusCode << " for request to " << uri << std::endl;
}
} }
const char *StatusText(int ret) std::pair<int, ByteString> Request::SimpleAuth(ByteString uri, ByteString ID, ByteString session, std::map<ByteString, ByteString> post_data)
{
auto request = std::make_unique<Request>(uri);
if (!post_data.empty())
{
request->AddPostData(post_data);
}
request->AuthHeaders(ID, session);
request->Start();
return request->Finish();
}
String StatusText(int ret)
{ {
switch (ret) switch (ret)
{ {
@ -234,69 +196,7 @@ namespace http
case 619: return "SSL: Failed to Load CRL File"; case 619: return "SSL: Failed to Load CRL File";
case 620: return "SSL: Issuer Check Failed"; case 620: return "SSL: Issuer Check Failed";
case 621: return "SSL: Pinned Public Key Mismatch"; case 621: return "SSL: Pinned Public Key Mismatch";
} default: return "Unknown Status Code";
return "Unknown Status Code";
}
void Request::ParseResponse(const ByteString &result, int status, ResponseType responseType)
{
// no server response, return "Malformed Response"
if (status == 200 && !result.size())
{
status = 603;
}
if (status == 302)
{
return;
}
if (status != 200)
{
throw RequestError(ByteString::Build("HTTP Error ", status, ": ", http::StatusText(status)));
}
switch (responseType)
{
case responseOk:
if (strncmp(result.c_str(), "OK", 2))
{
throw RequestError(result);
}
break;
case responseJson:
{
std::istringstream ss(result);
Json::Value root;
try
{
ss >> root;
// assume everything is fine if an empty [] is returned
if (root.size() == 0)
{
return;
}
int status = root.get("Status", 1).asInt();
if (status != 1)
{
throw RequestError(ByteString(root.get("Error", "Unspecified Error").asString()));
}
}
catch (const std::exception &ex)
{
// sometimes the server returns a 200 with the text "Error: 401"
if (!strncmp(result.c_str(), "Error: ", 7))
{
status = ByteString(result.begin() + 7, result.end()).ToNumber<int>();
throw RequestError(ByteString::Build("HTTP Error ", status, ": ", http::StatusText(status)));
}
throw RequestError("Could not read response: " + ByteString(ex.what()));
}
}
break;
case responseData:
// no further processing required
break;
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More