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) ;;
arm-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;;
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
case $BSH_HOST_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
;;
case $BSH_BUILD_PLATFORM in
linux)
sudo apt update
if [[ $BSH_STATIC_DYNAMIC == static ]]; then
@ -86,6 +52,9 @@ if [[ -z ${BSH_NO_PACKAGES-} ]]; then
else
sudo apt install libluajit-5.1-dev libcurl4-openssl-dev libfftw3-dev zlib1g-dev libsdl2-dev libbz2-dev libjsoncpp-dev
fi
if [[ $BSH_HOST_PLATFORM-$BSH_HOST_LIBC == windows-mingw ]]; then
sudo apt install g++-mingw-w64-x86-64
fi
;;
darwin)
brew install pkg-config binutils
@ -93,14 +62,6 @@ if [[ -z ${BSH_NO_PACKAGES-} ]]; then
brew install luajit curl fftw zlib sdl2 bzip2 jsoncpp
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
fi
@ -114,6 +75,34 @@ function inplace_sed() {
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
case $BSH_HOST_ARCH in
x86_64) vs_env_arch=x64;;
@ -142,9 +131,9 @@ elif [[ $BSH_HOST_PLATFORM == darwin ]]; then
elif [[ $BSH_HOST_PLATFORM == android ]]; then
case $BSH_HOST_ARCH in
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 ;;
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
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
@ -188,18 +177,13 @@ meson_configure=meson$'\t'setup
if [[ $BSH_DEBUG_RELEASE == release ]]; then
meson_configure+=$'\t'-Dbuildtype=debugoptimized
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_comment=$APP_COMMENT
meson_configure+=$'\t'-Dapp_exe=$APP_EXE
meson_configure+=$'\t'-Dapp_id=$APP_ID
meson_configure+=$'\t'-Dapp_data=$APP_DATA
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'-Dmod_id=$MOD_ID
case $BSH_HOST_ARCH-$BSH_HOST_PLATFORM-$BSH_HOST_LIBC-$BSH_DEBUG_RELEASE in
@ -217,12 +201,6 @@ fi
if [[ $PACKAGE_MODE == nolua ]]; then
meson_configure+=$'\t'-Dlua=none
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
meson_configure+=$'\t'-Dstatic=prebuilt
if [[ $BSH_HOST_PLATFORM == windows ]]; then
@ -238,11 +216,6 @@ if [[ $BSH_STATIC_DYNAMIC == static ]]; then
c_link_args+=\'-static-libstdc++\',
fi
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
meson_configure+=$'\t'-Dworkaround_elusive_bzip2=true
fi
@ -266,54 +239,52 @@ fi
if [[ $RELEASE_TYPE == stable ]]; then
stable_or_beta=yes
fi
if [[ $stable_or_beta == yes ]]; then
xyz=$(echo $RELEASE_NAME | cut -d 'v' -f 2 | cut -d 'b' -f 1) # $RELEASE_NAME is vX.Y.Z or vX.Y.Zb
display_version_major=$(echo $xyz | cut -d '.' -f 1)
display_version_minor=$(echo $xyz | cut -d '.' -f 2)
build_num=$(echo $xyz | cut -d '.' -f 3)
if [[ $MOD_ID != 0 ]]; then
meson_configure+=$'\t'-Ddisplay_version_major=$display_version_major
meson_configure+=$'\t'-Ddisplay_version_minor=$display_version_minor
meson_configure+=$'\t'-Dbuild_num=$build_num
fi
set +e
save_version=$(cat src/Config.template.h | sed -n 's/constexpr int SAVE_VERSION * = \([^;]*\);/\1/p')
minor_version=$(cat src/Config.template.h | sed -n 's/constexpr int MINOR_VERSION * = \([^;]*\);/\1/p')
build_num=$(cat src/Config.template.h | sed -n 's/constexpr int BUILD_NUM * = \([^;]*\);/\1/p')
if [[ -z ${save_version-} ]] || [[ -z ${minor_version-} ]] || [[ -z ${build_num-} ]]; then
>&2 echo "failed to extract version from Config.template.h"
exit 1
fi
set -e
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
if [[ $RELEASE_TYPE == snapshot ]]; then
build_num=$(echo $RELEASE_NAME | cut -d '-' -f 2) # $RELEASE_NAME is snapshot-X
meson_configure+=$'\t'-Dsnapshot=true
if [[ $MOD_ID != 0 ]]; then
meson_configure+=$'\t'-Dbuild_num=$build_num
fi
meson_configure+=$'\t'-Dsnapshot_id=$(echo $RELEASE_NAME | cut -d '-' -f 2) # $RELEASE_NAME is snapshot-X
fi
if [[ $RELEASE_TYPE == snapshot ]] && [[ $MOD_ID != 0 ]]; then
>&2 echo "mods and snapshots do not mix"
exit 1
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
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
if [[ $RELEASE_TYPE != dev ]]; then
meson_configure+=$'\t'-Dignore_updates=false
fi
if [[ "$BSH_HOST_PLATFORM-$BSH_HOST_LIBC" == "windows-mingw" ]]; then
meson_configure+=$'\t'--cross-file=.github/mingw-ghactions.ini
# there is some mingw bug that only ever manifests on ghactions which makes MakeIco.exe use tons of memory and fail
# TODO: remove this hack once we figure out how to fix that
meson_configure+=$'\t'-Dwindows_icons=false
if [[ $BSH_HOST_PLATFORM-$BSH_HOST_LIBC == windows-mingw ]]; then
if [[ $BSH_BUILD_PLATFORM == linux ]]; then
meson_configure+=$'\t'--cross-file=.github/mingw-ghactions.ini
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
fi
if [[ $BSH_HOST_PLATFORM-$BSH_HOST_ARCH == darwin-aarch64 ]]; then
meson_configure+=$'\t'--cross-file=.github/macaa64-ghactions.ini
fi
if [[ $BSH_HOST_PLATFORM == emscripten ]]; 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 [[ $RELEASE_TYPE == tptlibsdev ]] && ([[ $BSH_HOST_PLATFORM == windows ]] || [[ $BSH_STATIC_DYNAMIC == static ]]); then
if [[ -z ${TPTLIBSREMOTE-} ]]; then
if [[ -z "${GITHUB_REPOSITORY_OWNER-}" ]]; then
>&2 echo "GITHUB_REPOSITORY_OWNER not set"
@ -323,6 +294,11 @@ if [[ $RELEASE_TYPE == tptlibsdev ]] && ([[ $BSH_HOST_PLATFORM-$BSH_HOST_LIBC ==
else
tptlibsremote=$TPTLIBSREMOTE
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
if [[ -d build-tpt-libs ]] && [[ ${TPTLIBSRESET-} == yes ]]; then
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
fi
if [[ $BSH_HOST_PLATFORM == android ]]; then
android_platform_jar=$ANDROID_SDK_ROOT/platforms/$android_platform/android.jar
if ! [[ -f $android_platform_jar ]]; then
>&2 echo "$android_platform_jar not found"
exit 1
fi
android_platform=android-30
meson_configure+=$'\t'--cross-file=android/cross/$BSH_HOST_ARCH.ini
cat << ANDROID_INI > .github/android-ghactions.ini
[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 = android_ndk_toolchain_prefix
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'
[binaries]
@ -382,6 +354,7 @@ zipalign = andriod_sdk_build_tools / 'zipalign'
apksigner = andriod_sdk_build_tools / 'apksigner'
ANDROID_INI
meson_configure+=$'\t'--cross-file=.github/android-ghactions.ini
meson_configure+=$'\t'-Dhttp=false
fi
meson_configure+=$'\t'-Dc_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 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
objcopy=objcopy
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
strip_target=$APP_EXE
fi
meson_compile=meson$'\t'compile
meson_compile+=$'\t'-v
if [[ $BSH_BUILD_PLATFORM == windows ]] && [[ $PACKAGE_MODE != backendvs ]]; then
if [[ $BSH_BUILD_PLATFORM == windows ]]; then
set +e
meson_compile+=$'\t'--ninja-args='["-d","keeprsp"]'
$meson_compile
ninja -v -d keeprsp
ninja_code=$?
set -e
cat $APP_EXE.exe.rsp
@ -444,7 +391,7 @@ if [[ $BSH_BUILD_PLATFORM == windows ]] && [[ $PACKAGE_MODE != backendvs ]]; the
fi
fi
else
$meson_compile
ninja -v
fi
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/Contents
cp resources/Info.plist $appdir/Contents/Info.plist
subst_version $appdir/Contents/Info.plist
mkdir $appdir/Contents/MacOS
cp $APP_EXE $appdir/Contents/MacOS/$APP_EXE
mkdir $appdir/Contents/Resources
@ -485,8 +433,6 @@ if [[ $PACKAGE_MODE == dmg ]]; then
cp ../LICENSE dmgroot/LICENSE
cp ../README.md dmgroot/README.md
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
# 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
@ -509,6 +455,7 @@ elif [[ $PACKAGE_MODE == appimage ]]; then
cp ../resources/icon_exe.svg $appdir/$APP_VENDOR-$APP_EXE.svg
cp resources/powder.desktop $appdir/$APP_ID.desktop
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_ID.desktop $appdir/usr/share/applications/$APP_ID.desktop
./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]
c = [ 'clang', '-arch', 'arm64' ]
cpp = [ 'clang++', '-arch', 'arm64' ]
objcpp = [ 'clang++', '-arch', 'arm64' ]
strip = 'strip'
[host_machine]

View File

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

123
.github/prepare.py vendored
View File

@ -6,7 +6,6 @@ import subprocess
import sys
ref = os.getenv('GITHUB_REF')
event_name = os.getenv('GITHUB_EVENT_NAME')
publish_hostport = os.getenv('PUBLISH_HOSTPORT')
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_snapshot = re.fullmatch(r'refs/tags/snapshot-([0-9]+)', ref)
match_tptlibsdev = re.fullmatch(r'refs/heads/tptlibsdev-(.*)', ref)
match_alljobs = re.fullmatch(r'refs/heads/(.*)-alljobs', ref)
do_release = False
do_priority = 10
if event_name == 'pull_request':
do_priority = 0
if match_stable:
release_type = 'stable'
release_name = 'v%s.%s.%s' % (match_stable.group(1), match_stable.group(2), match_stable.group(3))
do_release = True
do_priority = 0
elif match_beta:
release_type = 'beta'
release_name = 'v%s.%s.%sb' % (match_beta.group(1), match_beta.group(2), match_beta.group(3))
do_release = True
do_priority = 0
elif match_snapshot:
release_type = 'snapshot'
release_name = 'snapshot-%s' % match_snapshot.group(1)
do_release = True
do_priority = 0
elif match_tptlibsdev:
release_type = 'tptlibsdev'
release_name = 'tptlibsdev-%s' % match_tptlibsdev.group(1)
do_priority = 0
else:
release_type = 'dev'
release_name = 'dev'
if match_alljobs:
do_priority = 0
do_publish = publish_hostport and do_release
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()
if int(build_options['mod_id']) == 0:
if release_type == 'stable':
pass
elif release_type == 'beta':
if release_type == 'beta':
build_options['app_name' ] += ' Beta'
build_options['app_comment'] += ' - Beta'
build_options['app_exe' ] += 'beta'
build_options['app_id' ] += 'beta'
elif release_type == 'snapshot':
if release_type == 'snapshot':
build_options['app_name' ] += ' Snapshot'
build_options['app_comment'] += ' - Snapshot'
build_options['app_exe' ] += '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('app_name' , build_options['app_name' ])
@ -95,61 +77,47 @@ app_name_slug = re.sub('[^A-Za-z0-9]', '_', app_name)
build_matrix = []
publish_matrix = []
# consider disabling line wrapping to edit this monstrosity
for arch, platform, libc, statdyn, bplatform, runson, suffix, publish, artifact, dbgsuffix, mode, starcatcher, dbgrel, priority 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', '', True, True, '.dbg', None, 'x86_64-lin-gcc-static', 'release', 10 ),
( 'x86_64', 'linux', 'gnu', 'static', 'linux', 'ubuntu-20.04', '', False, True, '.dbg', 'appimage', None, 'release', 0 ), # priority = 0: appimage 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, 'nohttp', None, 'debug', 10 ),
( '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, None, None, 'release', 10 ),
# ( '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, 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', '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, 'release', 10 ), # ubuntu-20.04 doesn't have ucrt64-capable mingw >_>
( '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, True, '.dbg', None, None, 'release', 10 ),
( '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, 'release', 10 ),
( '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', True, True, '.pdb', None,'x86_64-win-msvc-static', 'release', 10 ),
( '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, 'backendvs', None, 'debug', 0 ), # priority = 0: backend=vs build
( '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', 0 ), # priority = 0: static debug build
( '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', 10 ),
( 'x86', 'windows', 'msvc', 'dynamic', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'release', 10 ),
( '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-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-12', '.dmg', False, False, None, 'dmg', None, 'debug', 10 ),
( 'x86_64', 'darwin', 'macos', 'dynamic', 'darwin', 'macos-12', '.dmg', False, False, None, 'dmg', None, 'release', 10 ),
( '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-12', '.dmg', True, True, None, 'dmg', 'arm64-mac-gcc-static', 'release', 10 ),
# ( '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-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', 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', 10 ),
( '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', 10 ),
( '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', 10 ),
( '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', 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
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' ),
( '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' ),
( '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' ),
( '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' ),
# ( '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' ), # 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' ),
( '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' ),
( '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' ),
( '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' ),
( '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' ),
( 'x86_64', 'windows', 'msvc', 'dynamic', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'release' ),
( 'x86', 'windows', 'msvc', 'static', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'debug' ),
( 'x86', 'windows', 'msvc', 'static', 'windows', 'windows-2019', '.exe', True, True, '.pdb', None, 'i686-win-msvc-static', 'release' ),
( '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, 'release' ),
( 'x86_64', 'darwin', 'macos', 'static', 'darwin', 'macos-11.0', '.dmg', False, False, None, 'dmg', None, 'debug' ),
( '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', 'dynamic', 'darwin', 'macos-11.0', '.dmg', False, False, None, 'dmg', None, 'debug' ),
( 'x86_64', 'darwin', 'macos', 'dynamic', 'darwin', 'macos-11.0', '.dmg', False, False, None, 'dmg', None, 'release' ),
( 'aarch64', 'darwin', 'macos', 'static', 'darwin', 'macos-11.0', '.dmg', False, False, None, 'dmg', None, 'debug' ),
( 'aarch64', 'darwin', 'macos', 'static', 'darwin', 'macos-11.0', '.dmg', True, True, None, 'dmg', 'arm64-mac-gcc-static', 'release' ),
# ( '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-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
( 'x86', 'android', 'bionic', 'static', 'linux', 'ubuntu-20.04', '.apk', False, False, None, None, None, 'debug' ),
( 'x86', 'android', 'bionic', 'static', 'linux', 'ubuntu-20.04', '.apk', True, True, '.dbg', None, 'i686-and-gcc-static', 'release' ),
( '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', True, True, '.dbg', None, 'x86_64-and-gcc-static', 'release' ),
( 'arm', 'android', 'bionic', 'static', 'linux', 'ubuntu-20.04', '.apk', False, False, None, None, None, 'debug' ),
( 'arm', 'android', 'bionic', 'static', 'linux', 'ubuntu-20.04', '.apk', True, True, '.dbg', None, 'arm-and-gcc-static', 'release' ),
( 'aarch64', 'android', 'bionic', 'static', 'linux', 'ubuntu-20.04', '.apk', False, False, None, None, None, 'debug' ),
( 'aarch64', 'android', 'bionic', 'static', 'linux', 'ubuntu-20.04', '.apk', True, True, '.dbg', None, 'arm64-and-gcc-static', 'release' ),
]:
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:
mode = 'default'
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_name = f'{app_name_slug}-{arch}.AppImage.dbg'
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({
'bsh_build_platform': bplatform, # 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_debug_release': dbgrel, # part of the unique portion of the matrix
'runs_on': runson,
'force_msys2_bash': msys2_bash and 'yes' or 'no',
'package_suffix': suffix,
'package_mode': mode,
'publish': publish and 'yes' or 'no',
@ -195,8 +158,6 @@ for arch, platform, libc, statdyn, bplatform, runso
'asset_name': asset_name,
'debug_asset_path': debug_asset_path,
'debug_asset_name': debug_asset_name,
'job_name': job_name,
'shell': shell,
})
if publish:
publish_matrix.append({

View File

@ -3,18 +3,4 @@
set -euo pipefail
IFS=$'\n\t'
cat << NETRC > ~/.netrc
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
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"

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
set +e
export "$i" 2>/dev/null
echo $i | grep ERROR
set -e
done
cl

View File

@ -16,6 +16,7 @@ jobs:
prepare:
runs-on: ubuntu-latest
outputs:
release_upload_url: ${{ steps.create_release.outputs.upload_url }}
do_release: ${{ steps.prepare.outputs.do_release }}
build_matrix: ${{ steps.prepare.outputs.build_matrix }}
publish_matrix: ${{ steps.prepare.outputs.publish_matrix }}
@ -31,76 +32,39 @@ jobs:
do_publish: ${{ steps.prepare.outputs.do_publish }}
steps:
- run: git config --global core.autocrlf false
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
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
run: python ./.github/prepare.py
env:
PUBLISH_HOSTPORT: ${{ secrets.STARCATCHER_PUBLISH_HOSTPORT }}
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'
id: create_release
uses: LBPHacker/create-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_NAME: ${{ steps.prepare.outputs.release_name }}
run: bash -c './.github/create-release.sh'
with:
tag_name: ${{ github.ref }}
release_name: ${{ steps.prepare.outputs.release_name }}
draft: true
prerelease: false
build:
runs-on: ${{ matrix.runs_on }}
name: ${{ matrix.job_name }}
needs: [prepare]
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.prepare.outputs.build_matrix) }}
defaults:
run:
shell: ${{ matrix.shell }}
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
- uses: actions/checkout@v4
- if: matrix.force_msys2_bash != 'yes'
uses: actions/setup-python@v5
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- if: matrix.force_msys2_bash != 'yes'
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
- if: matrix.bsh_build_platform == 'darwin'
run: brew install bash coreutils
- run: bash -c './.github/build.sh'
@ -124,24 +88,30 @@ jobs:
PACKAGE_MODE: ${{ matrix.package_mode }}
ASSET_PATH: ${{ matrix.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:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ASSET_PATH: build/${{ matrix.asset_path }}
ASSET_NAME: ${{ matrix.asset_name }}
run: bash -c './.github/upload-release-asset.sh'
- if: needs.prepare.outputs.do_release == 'yes' && matrix.publish == 'yes' && matrix.separate_debug == 'yes'
with:
upload_url: ${{ needs.prepare.outputs.release_upload_url }}
asset_path: build/${{ matrix.asset_path }}
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:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ASSET_PATH: build/${{ matrix.debug_asset_path }}
ASSET_NAME: ${{ matrix.debug_asset_name }}
run: bash -c './.github/upload-release-asset.sh'
- uses: actions/upload-artifact@v4
with:
upload_url: ${{ needs.prepare.outputs.release_upload_url }}
asset_path: build/${{ matrix.debug_asset_path }}
asset_name: ${{ matrix.debug_asset_name }}
asset_content_type: application/zip
- uses: actions/upload-artifact@v3
if: matrix.artifact == 'yes'
with:
path: build/${{ matrix.asset_path }}
name: ${{ matrix.asset_name }}
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
if: matrix.artifact == 'yes' && matrix.separate_debug == 'yes'
with:
path: build/${{ matrix.debug_asset_path }}
@ -155,12 +125,12 @@ jobs:
if: needs.prepare.outputs.do_publish == 'yes'
steps:
- run: git config --global core.autocrlf false
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
- uses: actions/checkout@v3
- uses: actions/download-artifact@v3
with:
name: ${{ matrix.asset_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:
PUBLISH_HOSTPORT: ${{ secrets.STARCATCHER_PUBLISH_HOSTPORT }}
PUBLISH_USERNAME: ${{ secrets.STARCATCHER_PUBLISH_USERNAME }}
@ -173,7 +143,7 @@ jobs:
if: needs.prepare.outputs.do_publish == 'yes'
steps:
- run: git config --global core.autocrlf false
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- run: ./.github/starcatcher-release.sh
env:
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).
@ -25,7 +25,7 @@ Thanks
* Skresanov Savely
* cracker64
* Catelite
* Victoria Hoyle
* Bryan Hoyle
* Nathan Cousins
* jacksonmj
* Felix Wallin
@ -82,7 +82,6 @@ Controls
| I | Invert Pressure and Velocity map |
| W | Cycle gravity modes (use with Ctrl when STK2 is out) |
| Y | Cycle air modes |
| Ctrl + E | Cycle edge modes |
| B | Enter decoration editor menu |
| Ctrl + B | Toggle decorations on/off |
| N | Toggle Newtonian Gravity on/off |
@ -108,7 +107,6 @@ Controls
| Shift + R | Horizontal 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 |
| F11 | Toggle fullscreen |
Command Line
---------------------------------------------------------------------------

View File

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

View File

@ -1,42 +1,7 @@
package @APPID@;
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 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]
android_ndk_toolchain_prefix = 'armv7a-linux-androideabi21-'
android_ndk_toolchain_prefix = 'armv7a-linux-androideabi19-'
[host_machine]
system = 'android'

View File

@ -1,5 +1,5 @@
[constants]
android_ndk_toolchain_prefix = 'i686-linux-android21-'
android_ndk_toolchain_prefix = 'i686-linux-android19-'
[host_machine]
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,
]).returncode:
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]
c = [ 'clang', '-arch', 'arm64' ]
cpp = [ 'clang++', '-arch', 'arm64' ]
objcpp = [ 'clang++', '-arch', 'arm64' ]
strip = 'strip'
[host_machine]

View File

@ -10,7 +10,7 @@ project(
'build.cpp_std=c++17', # 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')
@ -18,7 +18,6 @@ if get_option('prepare')
subdir_done()
endif
fs = import('fs')
to_array = generator(
executable('toarray', sources: 'resources/ToArray.cpp', native: true),
output: [ '@PLAINNAME@.cpp', '@PLAINNAME@.h' ],
@ -44,11 +43,11 @@ if c_compiler.get_id() in [ 'msvc' ]
host_libc = 'msvc'
elif c_compiler.get_id() in [ 'gcc' ] and host_platform == 'windows'
host_libc = 'mingw'
if get_option('b_lto')
warning('mingw does not like static + lto')
endif
elif host_platform in [ 'darwin' ]
host_libc = 'macos'
elif host_platform in [ 'emscripten' ]
host_platform = 'emscripten'
host_libc = 'emscripten'
elif host_platform in [ 'android' ]
host_platform = 'android'
host_libc = 'bionic'
@ -66,8 +65,8 @@ if static_variant != 'prebuilt' and host_platform == 'android'
warning('only prebuilt libs are supported for android')
static_variant = 'prebuilt'
endif
if static_variant == 'system' and host_platform == 'windows' and host_libc == 'msvc'
warning('no way to find system libs for msvc on windows')
if static_variant == 'system' and host_platform == 'windows'
warning('no way to find system libs on windows')
static_variant = 'prebuilt'
endif
@ -79,19 +78,20 @@ tpt_libs_static = 'none'
if static_variant == 'prebuilt'
tpt_libs_static = 'static'
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'
endif
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_vtag = get_option('tpt_libs_vtag')
if tpt_libs_vtag == ''
tpt_libs_vtag = 'v20240112165024'
tpt_libs_vtag = 'v20230205154205'
endif
if tpt_libs_static != 'none'
if tpt_libs_variant not in [
'x86_64-linux-gnu-static',
'x86_64-windows-mingw-static',
'x86_64-windows-mingw-dynamic',
'x86_64-windows-msvc-static',
'x86_64-windows-msvc-dynamic',
'x86-windows-msvc-static',
@ -102,7 +102,6 @@ if tpt_libs_static != 'none'
'x86_64-android-bionic-static',
'arm-android-bionic-static',
'aarch64-android-bionic-static',
'wasm32-emscripten-emscripten-static',
]
error('no prebuilt @0@ libraries are currently provided'.format(tpt_libs_variant))
endif
@ -145,14 +144,7 @@ endif
lua_variant = get_option('lua')
if lua_variant == 'auto'
if host_platform == 'emscripten'
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')
lua_variant = 'luajit'
endif
if lua_variant == 'none'
lua_dep = []
@ -169,6 +161,9 @@ elif lua_variant == 'luajit'
endif
enable_http = get_option('http')
if host_platform == 'android'
enable_http = false
endif
if host_platform == 'android'
android_ndk_toolchain_prefix = meson.get_external_property('android_ndk_toolchain_prefix')
android_platform = meson.get_external_property('android_platform')
@ -182,57 +177,19 @@ if host_platform == 'android'
error('tpt-libs android platform mismatch')
endif
endif
curl_dep = []
if enable_http and host_platform != 'emscripten'
curl_dep = dependency('libcurl', static: is_static)
endif
curl_dep = enable_http ? dependency('libcurl', static: is_static) : []
fftw_dep = dependency('fftw3f', static: is_static)
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_c_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 x86_sse_level >= 30
warning('SSE3 configured to be enabled but unavailable in msvc')
@ -318,15 +275,14 @@ else
endif
if host_platform == 'windows'
args_ccomp_win = [ '-D_WIN32_WINNT=0x0501', '-DNOMINMAX' ]
args_ccomp_win = [ '-D_WIN32_WINNT=0x0501' ]
windows_mod = import('windows')
if is_static
args_ccomp_win += [ '-DCURL_STATICLIB' ]
if host_arch == 'x86_64'
args_ccomp_win += [ '-DZLIB_WINAPI' ]
endif
endif
if tpt_libs_static == 'dynamic'
else
foreach input_output_condition : tpt_libs.get_variable('config_dlls')
dll_input = input_output_condition[0]
dll_output = input_output_condition[1]
@ -338,7 +294,7 @@ if host_platform == 'windows'
do_copy = true
endif
if do_copy
fs.copyfile(dll_input, dll_output)
configure_file(input: dll_input, output: dll_output, copy: true)
endif
endforeach
endif
@ -358,11 +314,7 @@ else
ident_platform = 'UNKNOWN'
endif
project_deps = []
data_files = []
powder_deps = []
project_export_dynamic = false
subdir('src')
subdir('resources')
@ -371,30 +323,8 @@ powder_files += data_files
render_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')
powder_deps += project_deps + [
powder_deps = [
threads_dep,
zlib_dep,
png_dep,
@ -426,17 +356,12 @@ if get_option('build_powder')
win_subsystem: is_debug ? 'console' : 'windows',
link_args: project_link_args,
dependencies: powder_deps,
export_dynamic: project_export_dynamic,
install: true,
)
endif
endif
if get_option('build_render')
if host_platform == 'emscripten'
error('render does not target emscripten')
endif
render_deps = project_deps + [
render_deps = [
threads_dep,
zlib_dep,
bzip2_dep,
@ -455,15 +380,11 @@ if get_option('build_render')
cpp_args: project_cpp_args,
link_args: render_link_args,
dependencies: render_deps,
export_dynamic: project_export_dynamic,
)
endif
if get_option('build_font')
if host_platform == 'emscripten'
error('font does not target emscripten')
endif
font_deps = project_deps + [
font_deps = [
threads_dep,
zlib_dep,
png_dep,
@ -479,6 +400,5 @@ if get_option('build_font')
cpp_args: project_cpp_args,
link_args: project_link_args,
dependencies: font_deps,
export_dynamic: project_export_dynamic,
)
endif

View File

@ -37,46 +37,11 @@ option(
description: 'Snapshot build'
)
option(
'display_version_major',
'snapshot_id',
type: 'integer',
min: 0,
value: 98,
description: 'Major component of the display version, should more or less map to the MINOR version in semantic versioning'
)
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'
value: 0,
description: 'Snapshot ID, only relevant if \'snapshot\' is true'
)
option(
'mod_id',
@ -133,7 +98,7 @@ option(
'update_server',
type: 'string',
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(
'workaround_noncpp_lua',
@ -237,12 +202,6 @@ option(
value: true,
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(
'prepare',
type: 'boolean',
@ -255,59 +214,3 @@ option(
value: 'disabled',
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>
<string>icon_exe.icns</string>
<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>
<string>English</string>
<key>CFBundleDocumentTypes</key>
@ -57,13 +57,13 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>@DISPLAY_VERSION_MAJOR@.@DISPLAY_VERSION_MINOR@</string>
<string>SUBST_SAVE_VERSION.SUBST_MINOR_VERSION</string>
<key>CFBundleSignature</key>
<string>????</string>
<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>
<string>@MANIFEST_MACOS_MIN_VER@</string>
<string>SUBST_MACOS_MIN_VER</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>

View File

@ -1,137 +1,67 @@
#include <cstdint>
#include <cstring>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
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);
}
#include <cstdint>
int main(int argc, char *argv[])
{
if (argc < 3)
{
std::cerr << "usage: " << argv[0] << " OUTPUT INPUT..." << std::endl;
exit(1);
return 1;
}
auto *outputIcoPath = argv[1];
std::ofstream outputIco(outputIcoPath, std::ios::binary);
if (!outputIco)
{
return 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;
exit(1);
auto *inputAnyPath = argv[i + 2];
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];
std::ofstream output(outputPath, std::ios::binary);
auto outputFailure = [&outputPath](std::string action) {
std::cerr << "failed to " << action << " " << outputPath << ": " << strerror(errno) << std::endl;
exit(1);
};
if (!output)
outputIco.write(&header[0], header.size());
outputIco.write(&allData[0], allData.size());
if (!outputIco)
{
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;
}

View File

@ -29,6 +29,6 @@
<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>
<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>
</component>

Binary file not shown.

View File

@ -32,65 +32,35 @@ else
endif
if host_platform == 'windows'
windows_icons = get_option('windows_icons')
windows_utf8cp = get_option('windows_utf8cp')
rc_conf_depends = []
rc_conf_depend_files = [
'resource.h',
]
icon_exe_ico_path = ''
icon_cps_ico_path = ''
winutf8_xml_path = ''
if windows_icons
make_ico = executable('makeico', sources: 'MakeIco.cpp', native: true)
generated_win_icos = {}
win_icos = {
'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,
) }
make_ico = executable('makeico', sources: 'MakeIco.cpp', native: true)
generated_win_icos = {}
win_icos = {
'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
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_cps'],
]
icon_exe_ico_path = join_paths(meson.current_build_dir(), 'icon_exe.ico')
icon_cps_ico_path = join_paths(meson.current_build_dir(), 'icon_cps.ico')
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,
],
depend_files: [
'resource.h',
],
)
elif host_platform == 'darwin'
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
#include "VcsTag.h"
#include "common/Version.h"
constexpr bool SET_WINDOW_ICON = @SET_WINDOW_ICON@;
constexpr bool DEBUG = @DEBUG@;
@ -13,27 +11,11 @@ constexpr bool LUACONSOLE = @LUACONSOLE@;
constexpr bool ALLOW_FAKE_NEWER_VERSION = @ALLOW_FAKE_NEWER_VERSION@;
constexpr bool USE_UPDATESERVER = @USE_UPDATESERVER@;
constexpr bool CAN_INSTALL = @CAN_INSTALL@;
constexpr bool USE_BLUESCREEN = @USE_BLUESCREEN@;
constexpr bool INSTALL_CHECK = @INSTALL_CHECK@;
constexpr bool IGNORE_UPDATES = @IGNORE_UPDATES@;
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@';
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 STATICSERVER[] = "@STATICSERVER@";
constexpr char UPDATESERVER[] = "@UPDATESERVER@";
@ -46,17 +28,13 @@ constexpr char APPID[] = "@APPID@";
constexpr char APPDATA[] = "@APPDATA@";
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@;
struct DisplayVersionWithBuild
{
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 int FUTURE_SAVE_VERSION = 97;
constexpr int FUTURE_MINOR_VERSION = 0;
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 STAMPS_DIR[] = "stamps";
constexpr char BRUSH_DIR[] = "Brushes";
constexpr int httpMaxConcurrentStreams = 50;
constexpr int httpConnectTimeoutS = 15;

View File

@ -10,19 +10,12 @@
#include "Format.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;
char buffer[128];
if (local)
{
timeData = localtime(&unixtime);
}
else
{
timeData = gmtime(&unixtime);
}
timeData = localtime(&unixtime);
strftime(buffer, 128, dateFormat.c_str(), timeData);
return ByteString(buffer);

View File

@ -11,7 +11,7 @@ namespace format
{
ByteString URLEncode(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);
String CleanString(String dirtyString, bool ascii, bool color, bool newlines, bool numeric = false);
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 "common/tpt-minmax.h"
#include "common/String.h"
#include <cstring>
#include <sys/types.h>
#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
{
@ -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)
{
return str.size() == size && !memcmp(str.data(), data, size);

View File

@ -5,25 +5,6 @@
#include <cstddef>
#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
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;
}
inline int iabs(int i)
{
return i * isign(i);
}
inline unsigned clamp_flt(float f, float min, float max)
{
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 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;
bool byteStringEqualsString(const ByteString &str, const char *data, size_t size);

View File

@ -7,22 +7,17 @@
#include "client/SaveFile.h"
#include "client/SaveInfo.h"
#include "client/http/requestmanager/RequestManager.h"
#include "client/http/GetSaveRequest.h"
#include "client/http/GetSaveDataRequest.h"
#include "common/platform/Platform.h"
#include "graphics/Graphics.h"
#include "simulation/SaveRenderer.h"
#include "simulation/SimulationData.h"
#include "common/tpt-rand.h"
#include "gui/game/Favorite.h"
#include "gui/Style.h"
#include "gui/game/GameController.h"
#include "gui/game/GameView.h"
#include "gui/game/IntroText.h"
#include "gui/dialogues/ConfirmPrompt.h"
#include "gui/dialogues/ErrorMessage.h"
#include "gui/interface/Engine.h"
#include "gui/interface/TextWrapper.h"
#include "Config.h"
#include "SimulationConfig.h"
#include <optional>
@ -30,8 +25,6 @@
#include <iostream>
#include <csignal>
#include <SDL.h>
#include <exception>
#include <cstdlib>
void LoadWindowPosition()
{
@ -85,14 +78,14 @@ void SaveWindowPosition()
void LargeScreenDialog()
{
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 << 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.";
new ConfirmPrompt("Large screen detected", message.Build(), { nullptr, []() {
if (!ConfirmPrompt::Blocking("Large screen detected", message.Build()))
{
GlobalPrefs::Ref().Set("Scale", 1);
ui::Engine::Ref().windowFrameOps.scale = 1;
} });
ui::Engine::Ref().SetScale(1);
}
}
void TickClient()
@ -100,115 +93,50 @@ void TickClient()
Client::Ref().Tick();
}
static void BlueScreen(String detailMessage, std::optional<std::vector<String>> stackTrace)
void BlueScreen(String detailMessage)
{
auto &engine = ui::Engine::Ref();
engine.g->BlendFilledRect(engine.g->Size().OriginRect(), 0x1172A9_rgb .WithAlpha(0xD2));
auto crashPrevLogPath = ByteString("crash.prev.log");
auto crashLogPath = ByteString("crash.log");
Platform::RenameFile(crashLogPath, crashPrevLogPath, true);
String errorTitle = "ERROR";
String errorDetails = "Details: " + detailMessage;
String errorHelp = String("An unrecoverable fault has occurred, please report the error by visiting the website below\n") + SCHEME + SERVER;
StringBuilder crashInfo;
crashInfo << "ERROR - Details: " << detailMessage << "\n";
crashInfo << "An unrecoverable fault has occurred, please report it by visiting the website below\n\n " << SCHEME << SERVER << "\n\n";
crashInfo << "An attempt will be made to save all of this information to " << crashLogPath.FromUtf8() << " in your data folder.\n";
crashInfo << "Please attach this file to your report.\n\n";
crashInfo << "Version: " << VersionInfo().FromUtf8() << "\n";
crashInfo << "Tag: " << VCS_TAG << "\n";
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);
// We use the width of errorHelp to center, but heights of the individual texts for vertical spacing
auto pos = engine.g->Size() / 2 - Vec2(Graphics::TextSize(errorHelp).X / 2, 100);
engine.g->BlendText(pos, errorTitle, 0xFFFFFF_rgb .WithAlpha(0xFF));
pos.Y += 4 + Graphics::TextSize(errorTitle).Y;
engine.g->BlendText(pos, errorDetails, 0xFFFFFF_rgb .WithAlpha(0xFF));
pos.Y += 4 + Graphics::TextSize(errorDetails).Y;
engine.g->BlendText(pos, errorHelp, 0xFFFFFF_rgb .WithAlpha(0xFF));
//Death loop
SDL_Event event;
auto running = true;
while (running)
while(true)
{
while (SDL_PollEvent(&event))
{
if (event.type == SDL_QUIT)
{
running = false;
}
}
if(event.type == SDL_QUIT)
exit(-1); // Don't use Platform::Exit, we're practically zombies at this point anyway.
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;
const char *message;
} signalMessages[] = {
{ SIGSEGV, "Memory read/write error" },
{ SIGFPE, "Floating point exception" },
{ SIGILL, "Program execution exception" },
{ SIGABRT, "Unexpected program abort" },
{ 0, nullptr },
};
static void SigHandler(int signal)
{
const char *message = "Unknown signal";
for (auto *msg = signalMessages; msg->message; ++msg)
{
if (msg->sig == signal)
{
message = msg->message;
break;
}
switch(signal){
case SIGSEGV:
BlueScreen("Memory read/write error");
break;
case SIGFPE:
BlueScreen("Floating point exception");
break;
case SIGILL:
BlueScreen("Program execution exception");
break;
case SIGABRT:
BlueScreen("Unexpected program abort");
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;
@ -236,28 +164,17 @@ struct ExplicitSingletons
http::RequestManagerPtr requestManager;
std::unique_ptr<Client> client;
std::unique_ptr<SaveRenderer> saveRenderer;
std::unique_ptr<RNG> rng;
std::unique_ptr<Favorite> favorite;
std::unique_ptr<ui::Engine> engine;
std::unique_ptr<SimulationData> simulationData;
std::unique_ptr<GameController> gameController;
};
static std::unique_ptr<ExplicitSingletons> explicitSingletons;
int main(int argc, char *argv[])
int main(int argc, char * argv[])
{
Platform::SetupCrt();
return Platform::InvokeMain(argc, argv);
}
int Main(int argc, char *argv[])
{
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();
explicitSingletons.reset();
});
@ -323,38 +240,35 @@ int Main(int argc, char *argv[])
}
else
{
auto ddir = Platform::DefaultDdir();
char *ddir = SDL_GetPrefPath(NULL, APPDATA);
if (!Platform::FileExists("powder.pref"))
{
if (ddir.size())
if (ddir)
{
if (!Platform::ChangeDir(ddir))
{
perror("failed to chdir to default ddir");
ddir = {};
SDL_free(ddir);
ddir = nullptr;
}
}
}
if (ddir.size())
if (ddir)
{
Platform::sharedCwd = ddir;
SDL_free(ddir);
}
}
// We're now in the correct directory, time to get prefs.
explicitSingletons->globalPrefs = std::make_unique<GlobalPrefs>();
auto &prefs = GlobalPrefs::Ref();
WindowFrameOps windowFrameOps{
prefs.Get("Scale", 1),
prefs.Get("Resizable", false),
prefs.Get("Fullscreen", false),
prefs.Get("AltFullscreen", false),
prefs.Get("ForceIntegerScaling", true),
prefs.Get("BlurryScaling", false),
};
auto graveExitsConsole = prefs.Get("GraveExitsConsole", true);
scale = prefs.Get("Scale", 1);
resizable = prefs.Get("Resizable", false);
fullscreen = prefs.Get("Fullscreen", false);
altFullscreen = prefs.Get("AltFullscreen", false);
forceIntegerScaling = prefs.Get("ForceIntegerScaling", true);
momentumScroll = prefs.Get("MomentumScroll", true);
showAvatars = prefs.Get("ShowAvatars", true);
@ -375,8 +289,8 @@ int Main(int argc, char *argv[])
auto kioskArg = arguments["kiosk"];
if (kioskArg.has_value())
{
windowFrameOps.fullscreen = true_string(kioskArg.value());
prefs.Set("Fullscreen", windowFrameOps.fullscreen);
fullscreen = true_string(kioskArg.value());
prefs.Set("Fullscreen", fullscreen);
}
if (true_arg(arguments["redirect"]))
@ -394,8 +308,8 @@ int Main(int argc, char *argv[])
{
try
{
windowFrameOps.scale = scaleArg.value().ToNumber<int>();
prefs.Set("Scale", windowFrameOps.scale);
scale = scaleArg.value().ToNumber<int>();
prefs.Set("Scale", scale);
}
catch (const std::runtime_error &e)
{
@ -430,45 +344,49 @@ int Main(int argc, char *argv[])
Client::Ref().Initialize();
explicitSingletons->saveRenderer = std::make_unique<SaveRenderer>();
explicitSingletons->rng = std::make_unique<RNG>();
explicitSingletons->favorite = std::make_unique<Favorite>();
explicitSingletons->engine = std::make_unique<ui::Engine>();
// TODO: maybe bind the maximum allowed scale to screen size somehow
if(windowFrameOps.scale < 1 || windowFrameOps.scale > SCALE_MAXIMUM)
windowFrameOps.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;
if(scale < 1 || scale > SCALE_MAXIMUM)
scale = 1;
SDLOpen();
if (Client::Ref().IsFirstRun() && FORCE_WINDOW_FRAME_OPS == forceWindowFrameOpsNone)
if (Client::Ref().IsFirstRun())
{
auto guessed = GuessBestScale();
if (engine.windowFrameOps.scale != guessed)
scale = GuessBestScale();
if (scale > 1)
{
engine.windowFrameOps.scale = guessed;
prefs.Set("Scale", windowFrameOps.scale);
prefs.Set("Scale", scale);
SDL_SetWindowSize(sdl_window, WINDOWW * scale, WINDOWH * scale);
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)
{
//Get ready to catch any dodgy errors
for (auto *msg = signalMessages; msg->message; ++msg)
{
signal(msg->sig, SigHandler);
}
std::set_terminate(TerminateHandler);
signal(SIGSEGV, SigHandler);
signal(SIGFPE, SigHandler);
signal(SIGILL, SigHandler);
signal(SIGABRT, SigHandler);
}
if constexpr (X86)
@ -476,97 +394,114 @@ int Main(int argc, char *argv[])
X86KillDenormals();
}
explicitSingletons->simulationData = std::make_unique<SimulationData>();
explicitSingletons->gameController = std::make_unique<GameController>();
auto *gameController = explicitSingletons->gameController.get();
engine.ShowWindow(gameController->GetView());
auto wrapWithBluescreen = [&]() {
explicitSingletons->gameController = std::make_unique<GameController>();
auto *gameController = explicitSingletons->gameController.get();
engine.ShowWindow(gameController->GetView());
auto openArg = arguments["open"];
if (openArg.has_value())
{
if constexpr (DEBUG)
auto openArg = arguments["open"];
if (openArg.has_value())
{
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
{
std::vector<char> gameSaveData;
if (!Platform::ReadFile(gameSaveData, openArg.value()))
ByteString saveIdPart;
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
{
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));
}
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;
}
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)
{
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"];
if (ptsaveArg.has_value())
EngineProcess();
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
{
ByteString saveIdPart;
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);
wrapWithBluescreen();
}
catch (std::exception & e)
catch (const std::exception &e)
{
new ErrorMessage("Error", ByteString(e.what()).FromUtf8());
Platform::MarkPresentable();
BlueScreen(ByteString(e.what()).FromUtf8());
}
}
else
{
Platform::MarkPresentable();
wrapWithBluescreen();
}
MainLoop();
Platform::Exit(0);
return 0;
}

View File

@ -28,6 +28,7 @@ void TickClient()
struct ExplicitSingletons
{
// These need to be listed in the order they are populated in main.
std::unique_ptr<RNG> rng;
std::unique_ptr<ui::Engine> engine;
};
static std::unique_ptr<ExplicitSingletons> explicitSingletons;
@ -41,31 +42,40 @@ int main(int argc, char * argv[])
});
explicitSingletons = std::make_unique<ExplicitSingletons>();
WindowFrameOps windowFrameOps;
scale = 1;
if (argc >= 3)
{
std::istringstream ss(argv[2]);
int 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
if (windowFrameOps.scale < 1 || windowFrameOps.scale > 10)
{
windowFrameOps.scale = 1;
}
if(scale < 1 || scale > 10)
scale = 1;
explicitSingletons->rng = std::make_unique<RNG>();
explicitSingletons->engine = std::make_unique<ui::Engine>();
auto &engine = ui::Engine::Ref();
engine.g = new Graphics();
engine.windowFrameOps = windowFrameOps;
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.SetFastQuit(true);
@ -75,14 +85,11 @@ int main(int argc, char * argv[])
}
else
{
std::cerr << "path to font.bz2 not supplied" << std::endl;
std::cerr << "path to font.cpp not supplied" << std::endl;
Platform::Exit(1);
}
while (engine.Running())
{
EngineProcess();
}
EngineProcess();
Platform::Exit(0);
return 0;
}

View File

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

View File

@ -5,7 +5,6 @@
#include "gui/interface/Engine.h"
#include "graphics/Graphics.h"
#include "common/platform/Platform.h"
#include "common/clipboard/Clipboard.h"
#include <iostream>
int desktopWidth = 1280;
@ -13,8 +12,11 @@ int desktopHeight = 1024;
SDL_Window *sdl_window = NULL;
SDL_Renderer *sdl_renderer = NULL;
SDL_Texture *sdl_texture = NULL;
bool vsyncHint = false;
WindowFrameOps currentFrameOps;
int scale = 1;
bool fullscreen = false;
bool altFullscreen = false;
bool forceIntegerScaling = true;
bool resizable = false;
bool momentumScroll = true;
bool showAvatars = true;
uint64_t lastTick = 0;
@ -26,10 +28,6 @@ int mouseButton = 0;
bool mouseDown = false;
bool calculatedInitialMouse = false;
bool hasMouseMoved = false;
double correctedFrameTimeAvg = 0;
uint64_t drawingTimer = 0;
uint64_t frameStart = 0;
uint64_t oldFrameStart = 0;
void StartTextInput()
{
@ -43,24 +41,11 @@ void StopTextInput()
void SetTextInputRect(int x, int y, int w, int h)
{
// Why does SDL_SetTextInputRect not take logical coordinates???
SDL_Rect rect;
#if SDL_VERSION_ATLEAST(2, 0, 18)
int wx, wy, wwx, why;
SDL_RenderLogicalToWindow(sdl_renderer, float(x), float(y), &wx, &wy);
SDL_RenderLogicalToWindow(sdl_renderer, float(x + w), float(y + h), &wwx, &why);
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
rect.x = x;
rect.y = y;
rect.w = w;
rect.h = h;
SDL_SetTextInputRect(&rect);
}
@ -84,7 +69,7 @@ unsigned int GetTicks()
return SDL_GetTicks();
}
static void CalculateMousePosition(int *x, int *y)
void CalculateMousePosition(int *x, int *y)
{
int globalMx, globalMy;
SDL_GetGlobalMouseState(&globalMx, &globalMy);
@ -92,16 +77,16 @@ static void CalculateMousePosition(int *x, int *y)
SDL_GetWindowPosition(sdl_window, &windowX, &windowY);
if (x)
*x = (globalMx - windowX) / currentFrameOps.scale;
*x = (globalMx - windowX) / scale;
if (y)
*y = (globalMy - windowY) / currentFrameOps.scale;
*y = (globalMy - windowY) / scale;
}
void blit(pixel *vid)
{
SDL_UpdateTexture(sdl_texture, NULL, vid, WINDOWW * sizeof (Uint32));
// 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_RenderCopy(sdl_renderer, sdl_texture, NULL, NULL);
SDL_RenderPresent(sdl_renderer);
@ -114,9 +99,12 @@ void SDLOpen()
fprintf(stderr, "Initializing SDL (video subsystem): %s\n", SDL_GetError());
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);
if (displayIndex >= 0)
@ -129,7 +117,10 @@ void SDLOpen()
}
}
StopTextInput();
if constexpr (SET_WINDOW_ICON)
{
WindowIcon(sdl_window);
}
}
void SDLClose()
@ -148,150 +139,105 @@ void SDLClose()
SDL_Quit();
}
void SDLSetScreen()
void SDLSetScreen(int scale_, bool resizable_, bool fullscreen_, bool altFullscreen_, bool forceIntegerScaling_)
{
auto newFrameOps = ui::Engine::Ref().windowFrameOps;
auto newVsyncHint = std::holds_alternative<FpsLimitVsync>(ui::Engine::Ref().GetFpsLimit());
if (FORCE_WINDOW_FRAME_OPS == forceWindowFrameOpsEmbedded)
{
newFrameOps.resizable = false;
newFrameOps.fullscreen = false;
newFrameOps.changeResolution = false;
newFrameOps.forceIntegerScaling = false;
}
if (FORCE_WINDOW_FRAME_OPS == forceWindowFrameOpsHandheld)
{
newFrameOps.resizable = false;
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))
// bool changingScale = scale != scale_;
bool changingFullscreen = fullscreen_ != fullscreen || (altFullscreen_ != altFullscreen && fullscreen);
bool changingResizable = resizable != resizable_;
scale = scale_;
fullscreen = fullscreen_;
altFullscreen = altFullscreen_;
resizable = resizable_;
forceIntegerScaling = forceIntegerScaling_;
// Recreate the window when toggling fullscreen, due to occasional issues
// Also recreate it when enabling resizable windows, to fix bugs on windows,
// see https://github.com/jacob1/The-Powder-Toy/issues/24
if (changingFullscreen || altFullscreen || (changingResizable && resizable && !fullscreen))
{
RecreateWindow();
return;
}
if (changingResizable)
SDL_RestoreWindow(sdl_window);
auto size = WINDOW * newFrameOpsNorm.scale;
if (sdl_window && newFrameOpsNorm.resizable)
{
SDL_GetWindowSize(sdl_window, &size.X, &size.Y);
}
if (recreate)
{
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_SetWindowSize(sdl_window, WINDOWW * scale, WINDOWH * scale);
SDL_RenderSetIntegerScale(sdl_renderer, forceIntegerScaling && fullscreen ? SDL_TRUE : SDL_FALSE);
unsigned int flags = 0;
if (fullscreen)
flags = altFullscreen ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP;
SDL_SetWindowFullscreen(sdl_window, flags);
if (fullscreen)
SDL_RaiseWindow(sdl_window);
Clipboard::RecreateWindow();
}
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;
SDL_SetWindowResizable(sdl_window, resizable ? SDL_TRUE : SDL_FALSE);
}
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();
switch (event.type)
{
case SDL_QUIT:
if (ALLOW_QUIT && (engine.GetFastQuit() || engine.CloseWindow()))
{
if (engine.GetFastQuit() || engine.CloseWindow())
engine.Exit();
}
break;
case SDL_KEYDOWN:
if (SDL_GetModState() & KMOD_GUI)
{
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();
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);
@ -349,7 +295,7 @@ static void EventProcess(const SDL_Event &event)
mousey = event.button.y;
}
mouseButton = event.button.button;
engine.onMouseDown(mousex, mousey, mouseButton);
engine.onMouseClick(mousex, mousey, mouseButton);
mouseDown = true;
if constexpr (!DEBUG)
@ -365,7 +311,7 @@ static void EventProcess(const SDL_Event &event)
mousey = event.button.y;
}
mouseButton = event.button.button;
engine.onMouseUp(mousex, mousey, mouseButton);
engine.onMouseUnclick(mousex, mousey, mouseButton);
mouseDown = false;
if constexpr (!DEBUG)
@ -395,52 +341,75 @@ static void EventProcess(const SDL_Event &event)
void EngineProcess()
{
auto &engine = ui::Engine::Ref();
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();
}
double correctedFrameTimeAvg = 0;
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();
int drawcap = ui::Engine::Ref().GetDrawingFrequencyLimit();
if (!drawcap || drawingTimer > 1e9f / drawcap)
auto &engine = ui::Engine::Ref();
while(engine.Running())
{
engine.Draw();
drawingTimer = 0;
SDLSetScreen();
blit(engine.g->Data());
if(engine.Broken()) { engine.UnBreak(); break; }
event.type = 0;
while (SDL_PollEvent(&event))
{
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);
oldFrameStart = frameStart;
frameStart = now;
if (auto *fpsLimitExplicit = std::get_if<FpsLimitExplicit>(&fpsLimit))
if constexpr (DEBUG)
{
auto timeBlockDuration = uint64_t(UINT64_C(1'000'000'000) / fpsLimitExplicit->value);
auto oldFrameStartTimeBlock = oldFrameStart / timeBlockDuration;
auto frameStartTimeBlock = oldFrameStartTimeBlock + 1U;
frameStart = std::max(frameStart, frameStartTimeBlock * timeBlockDuration);
SDL_Delay((frameStart - now) / UINT64_C(1'000'000));
std::cout << "Breaking out of EngineProcess" << std::endl;
}
}

View File

@ -1,17 +1,19 @@
#pragma once
#include "common/String.h"
#include "graphics/Pixel.h"
#include "gui/WindowFrameOps.h"
#include "FpsLimit.h"
#include <cstdint>
#include <SDL.h>
#include <variant>
extern int desktopWidth;
extern int desktopHeight;
extern SDL_Window *sdl_window;
extern SDL_Renderer *sdl_renderer;
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 showAvatars;
extern uint64_t lastTick;
@ -24,7 +26,6 @@ extern bool mouseDown;
extern bool calculatedInitialMouse;
extern bool hasMouseMoved;
void MainLoop();
void EngineProcess();
void StartTextInput();
void StopTextInput();
@ -33,13 +34,14 @@ void ClipboardPush(ByteString text);
ByteString ClipboardPull();
int GetModifiers();
unsigned int GetTicks();
void CalculateMousePosition(int *x, int *y);
void blit(pixel *vid);
void SDLOpen();
void SDLClose();
void SDLSetScreen();
void SetFpsLimit(FpsLimit newFpsLimit);
void SDLSetScreen(int scale_, bool resizable_, bool fullscreen_, bool altFullscreen_, bool forceIntegerScaling_);
bool RecreateWindow();
void LoadWindowPosition();
void SaveWindowPosition();
void LargeScreenDialog();
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 float CFDS = 4.0f / CELL;
constexpr float MAX_VELOCITY = 1e4f;
constexpr float SIM_MAXVELOCITY = 1e4f;
//Air constants
constexpr float AIR_TSTEPP = 0.3f;
@ -42,13 +42,10 @@ constexpr float AIR_PLOSS = 0.9999f;
constexpr int NGOL = 24;
enum DefaultBrushes
{
BRUSH_CIRCLE,
BRUSH_SQUARE,
BRUSH_TRIANGLE,
NUM_DEFAULTBRUSHES,
};
constexpr int CIRCLE_BRUSH = 0;
constexpr int SQUARE_BRUSH = 1;
constexpr int TRI_BRUSH = 2;
constexpr int BRUSH_NUM = 3;
//Photon constants
constexpr int SURF_RANGE = 10;
@ -64,5 +61,3 @@ constexpr float GLASS_IOR = 1.9f;
constexpr float GLASS_DISP = 0.07f;
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
#include "common/tpt-inline.h"
#include <ctime>
#include <cstdlib>
#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) )
//#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;
char *out = ( char * )outp;
@ -1196,7 +1197,7 @@ static inline void bson_swap_endian64( void *outp, const void *inp ) {
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;
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 "prefs/GlobalPrefs.h"
#include "client/http/StartupRequest.h"
#include "client/http/Request.h"
#include "ClientListener.h"
#include "Format.h"
#include "MD5.h"
#include "client/GameSave.h"
#include "client/SaveFile.h"
#include "client/SaveInfo.h"
@ -10,9 +11,9 @@
#include "common/platform/Platform.h"
#include "common/String.h"
#include "graphics/Graphics.h"
#include "gui/dialogues/ErrorMessage.h"
#include "prefs/Prefs.h"
#include "lua/CommandInterface.h"
#include "gui/preview/Comment.h"
#include "Config.h"
#include <cstring>
#include <cstdlib>
@ -28,12 +29,27 @@
Client::Client():
messageOfTheDay("Fetching the message of the day..."),
versionCheckRequest(nullptr),
alternateVersionCheckRequest(nullptr),
usingAltUpdateServer(false),
updateAvailable(false),
authUser(0, "")
{
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(""));
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();
}
@ -72,14 +88,24 @@ void Client::Initialize()
}
//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();
if constexpr (USE_UPDATESERVER)
{
// use an alternate update server
alternateVersionCheckRequest = std::make_unique<http::StartupRequest>(true);
alternateVersionCheckRequest->Start();
alternateVersionCheckRequest = std::make_unique<http::Request>(ByteString::Build(SCHEME, UPDATESERVER, "/Startup.json"));
usingAltUpdateServer = true;
if (authUser.UserID)
{
alternateVersionCheckRequest->AuthHeaders(authUser.Username, "");
}
alternateVersionCheckRequest->Start();
}
}
@ -99,82 +125,203 @@ String Client::GetMessageOfTheDay()
return messageOfTheDay;
}
void Client::AddServerNotification(ServerNotification notification)
void Client::AddServerNotification(std::pair<String, ByteString> notification)
{
serverNotifications.push_back(notification);
notifyNewNotification(notification);
}
std::vector<ServerNotification> Client::GetServerNotifications()
std::vector<std::pair<String, ByteString> > Client::GetServerNotifications()
{
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()
{
auto applyUpdateInfo = false;
if (versionCheckRequest && versionCheckRequest->CheckDone())
CheckUpdate(versionCheckRequest, true);
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();
if (!info.sessionGood)
//free(data);
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;
applyUpdateInfo = true;
SetMessageOfTheDay(info.messageOfTheDay);
}
for (auto &notification : info.notifications)
{
AddServerNotification(notification);
//Do nothing
}
}
catch (const http::RequestError &ex)
{
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();
}
updateRequest.reset();
}
}
std::optional<UpdateInfo> Client::GetUpdateInfo()
UpdateInfo Client::GetUpdateInfo()
{
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)
{
@ -235,7 +382,31 @@ Client::~Client()
void Client::SetAuthUser(User 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();
}
@ -244,6 +415,65 @@ User Client::GetAuthUser()
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)
{
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"));
auto saveFile = LoadSaveFile(stampFile);
SaveFile *saveFile = LoadSaveFile(stampFile);
if (!saveFile)
saveFile = LoadSaveFile(stampID);
else
@ -287,28 +517,7 @@ void Client::DeleteStamp(ByteString stampID)
}
}
void Client::RenameStamp(ByteString stampID, ByteString newName)
{
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)
ByteString Client::AddStamp(GameSave * saveData)
{
auto now = (uint64_t)time(NULL);
if (lastStampTime != now)
@ -346,8 +555,8 @@ ByteString Client::AddStamp(std::unique_ptr<GameSave> saveData)
}
saveData->authors = stampInfo;
std::vector<char> gameData;
std::tie(std::ignore, gameData) = saveData->Serialise();
auto [ fromNewerVersion, gameData ] = saveData->Serialise();
(void)fromNewerVersion;
if (!gameData.size())
return "";
@ -377,6 +586,7 @@ void Client::RescanStamps()
newStampIDs.push_back(stampID);
}
}
auto oldCount = newStampIDs.size();
auto stampIDsSet = std::set<ByteString>(stampIDs.begin(), stampIDs.end());
for (auto &stampID : stampFilesSet)
{
@ -388,6 +598,8 @@ void Client::RescanStamps()
}
if (changed)
{
// Move newly discovered stamps to front.
std::rotate(newStampIDs.begin(), newStampIDs.begin() + oldCount, newStampIDs.end());
stampIDs = newStampIDs;
WriteStamps();
}
@ -406,19 +618,325 @@ const std::vector<ByteString> &Client::GetStamps() const
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;
std::unique_ptr<SaveFile> file;
SaveFile *file = nullptr;
if (Platform::FileExists(filename))
{
file = std::make_unique<SaveFile>(filename);
file = new SaveFile(filename);
try
{
std::vector<char> data;
if (Platform::ReadFile(data, filename))
{
file->SetGameSave(std::make_unique<GameSave>(std::move(data)));
file->SetGameSave(new GameSave(std::move(data)));
}
else
{
@ -441,10 +959,89 @@ std::unique_ptr<SaveFile> Client::LoadSaveFile(ByteString filename)
{
file->SetLoadingError(err.FromUtf8());
}
commandInterface->SetLastError(err.FromUtf8());
}
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
// also used for clipboard and lua stamps
void Client::MergeStampAuthorInfo(Json::Value stampAuthors)

View File

@ -1,7 +1,6 @@
#pragma once
#include "common/String.h"
#include "common/ExplicitSingleton.h"
#include "StartupInfo.h"
#include "User.h"
#include <vector>
#include <cstdint>
@ -11,27 +10,53 @@
class SaveInfo;
class SaveFile;
class SaveComment;
class GameSave;
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 RequestListener;
class ClientListener;
namespace http
{
class StartupRequest;
class Request;
}
class Client: public ExplicitSingleton<Client> {
private:
String messageOfTheDay;
std::vector<ServerNotification> serverNotifications;
std::vector<std::pair<String, ByteString> > serverNotifications;
std::unique_ptr<http::StartupRequest> versionCheckRequest;
std::unique_ptr<http::StartupRequest> alternateVersionCheckRequest;
std::unique_ptr<http::Request> versionCheckRequest;
std::unique_ptr<http::Request> alternateVersionCheckRequest;
bool usingAltUpdateServer;
bool updateAvailable;
std::optional<UpdateInfo> updateInfo;
UpdateInfo updateInfo;
String lastError;
bool firstRun;
std::vector<ByteString> stampIDs;
@ -44,7 +69,7 @@ private:
void notifyUpdateAvailable();
void notifyAuthUserChanged();
void notifyMessageOfTheDay();
void notifyNewNotification(ServerNotification notification);
void notifyNewNotification(std::pair<String, ByteString> notification);
// Save stealing info
Json::Value authors;
@ -53,9 +78,6 @@ private:
void MigrateStampsDef();
void WriteStamps();
void LoadAuthUser();
void SaveAuthUser();
public:
std::vector<ClientListener*> listeners;
@ -69,7 +91,7 @@ public:
void ClearAuthorInfo() { authors.clear(); }
bool IsAuthorsEmpty() { return authors.size() == 0; }
std::optional<UpdateInfo> GetUpdateInfo();
UpdateInfo GetUpdateInfo();
Client();
~Client();
@ -77,8 +99,8 @@ public:
ByteString FileOpenDialogue();
//std::string FileSaveDialogue();
void AddServerNotification(ServerNotification notification);
std::vector<ServerNotification> GetServerNotifications();
void AddServerNotification(std::pair<String, ByteString> notification);
std::vector<std::pair<String, ByteString> > GetServerNotifications();
void SetMessageOfTheDay(String message);
String GetMessageOfTheDay();
@ -89,19 +111,40 @@ public:
void AddListener(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 RenameStamp(ByteString stampID, ByteString newName);
ByteString AddStamp(std::unique_ptr<GameSave> saveData);
ByteString AddStamp(GameSave * saveData);
void RescanStamps();
const std::vector<ByteString> &GetStamps() const;
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);
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 CheckUpdate(std::unique_ptr<http::Request> &updateRequest, bool checkSession);
String DoMigration(ByteString fromDir, ByteString toDir);
};

View File

@ -1,6 +1,5 @@
#pragma once
#include "common/String.h"
#include "client/ServerNotification.h"
class Client;
class ClientListener
@ -12,6 +11,6 @@ public:
virtual void NotifyUpdateAvailable(Client * sender) {}
virtual void NotifyAuthUserChanged(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
#include "common/Plane.h"
#include "common/String.h"
#include "common/tpt-rand.h"
#include "common/Version.h"
#include "simulation/Sign.h"
#include "simulation/Particle.h"
#include "simulation/MissingElements.h"
#include "Misc.h"
#include "SimulationConfig.h"
#include <vector>
#include <array>
#include <json/json.h>
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
{
// number of pixels translated. When translating CELL pixels, shift all CELL grids
vector2d translated = { 0, 0 };
void readOPS(const std::vector<char> &data);
void readPSv(const std::vector<char> &data);
std::pair<bool, std::vector<char>> serialiseOPS() const;
void MapPalette();
public:
Vec2<int> blockSize = { 0, 0 };
int blockWidth = 0;
int blockHeight = 0;
bool fromNewerVersion = false;
Version<2> version{};
int majorVersion = 0;
int minorVersion = 0;
bool hasPressure = 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
int particlesCount = 0;
std::vector<Particle> particles;
PlaneAdapter<std::vector<unsigned char>> blockMap;
PlaneAdapter<std::vector<float>> fanVelX;
PlaneAdapter<std::vector<float>> fanVelY;
PlaneAdapter<std::vector<float>> pressure;
PlaneAdapter<std::vector<float>> velocityX;
PlaneAdapter<std::vector<float>> velocityY;
PlaneAdapter<std::vector<float>> ambientHeat;
PlaneAdapter<std::vector<unsigned char>> blockAir;
PlaneAdapter<std::vector<unsigned char>> blockAirh;
Plane<unsigned char> blockMap;
Plane<float> fanVelX;
Plane<float> fanVelY;
Plane<float> pressure;
Plane<float> velocityX;
Plane<float> velocityY;
Plane<float> ambientHeat;
//Simulation Options
bool waterEEnabled = false;
@ -104,8 +119,6 @@ public:
int edgeMode = 0;
bool wantAuthors = true;
MissingElements missingElements;
//Signs
std::vector<sign> signs;
StkmData stkm;
@ -119,12 +132,14 @@ public:
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);
void setSize(Vec2<int> newBlockSize);
void setSize(int width, int height);
// return value is [ fakeFromNewerVersion, gameData ]
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);

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 "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):
gameSave(NULL),
filename(filename),
displayName(filename.FromUtf8()),
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)
{
@ -20,7 +32,7 @@ const GameSave *SaveFile::LazyGetGameSave() // non-owning
std::vector<char> data;
if (Platform::ReadFile(data, filename))
{
gameSave = std::make_unique<GameSave>(std::move(data));
gameSave = new GameSave(std::move(data));
}
else
{
@ -32,33 +44,24 @@ const GameSave *SaveFile::LazyGetGameSave() // non-owning
loadingError = ByteString(e.what()).FromUtf8();
}
}
return gameSave.get();
}
const GameSave *SaveFile::GetGameSave() const
{
return gameSave.get();
}
std::unique_ptr<GameSave> SaveFile::TakeGameSave()
{
return std::move(gameSave);
return gameSave;
}
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;
}
@ -68,7 +71,7 @@ void SaveFile::SetFileName(ByteString fileName)
this->filename = fileName;
}
const String &SaveFile::GetDisplayName() const
String SaveFile::GetDisplayName()
{
return displayName;
}
@ -78,7 +81,7 @@ void SaveFile::SetDisplayName(String displayName)
this->displayName = displayName;
}
const String &SaveFile::GetError() const
String SaveFile::GetError()
{
return loadingError;
}
@ -87,3 +90,10 @@ void SaveFile::SetLoadingError(String error)
{
loadingError = error;
}
SaveFile::~SaveFile() {
if (gameSave)
{
delete gameSave;
}
}

View File

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

View File

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

View File

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

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()
{
thumbnail = SaveRenderer::Ref().Render(save.get(), decorations, fire);
thumbnail = std::unique_ptr<VideoBuffer>(SaveRenderer::Ref().Render(save.get(), decorations, fire));
if (thumbnail)
{
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:
enum Elevation
{
ElevationNone,
ElevationHalfMod,
ElevationMod,
ElevationAdmin,
ElevationAdmin, ElevationModerator, ElevationNone
};
static Elevation ElevationFromString(ByteString str);
static ByteString ElevationToString(Elevation elevation);
int UserID;
ByteString Username;
ByteString SessionID;

View File

@ -1,36 +1,39 @@
#include "APIRequest.h"
#include "client/Client.h"
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();
if (authMode == authRequire && !user.UserID)
{
FailEarly("Not authenticated");
return;
}
if (authMode != authOmit && user.UserID)
{
AuthHeaders(ByteString::Build(user.UserID), user.SessionID);
}
User user = Client::Ref().GetAuthUser();
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
{
std::istringstream ss(data);
ss >> document;
ByteString data;
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 "common/String.h"
#include <json/json.h>
#include <memory>
#include <map>
namespace http
{
class APIRequest : public Request
{
bool checkStatus;
public:
enum AuthMode
struct Result
{
authRequire,
authUse,
authOmit,
int status;
std::unique_ptr<Json::Value> document;
};
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
{
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();
UserInfo userInfo;
try
if (result.document)
{
auto &user = result["User"];
userInfo = UserInfo(
auto &user = (*result.document)["User"];
user_info = std::unique_ptr<UserInfo>(new UserInfo(
user["ID"].asInt(),
user["Age"].asInt(),
user["Username"].asString(),
@ -29,13 +33,9 @@ namespace http
user["Forum"]["Topics"].asInt(),
user["Forum"]["Replies"].asInt(),
user["Forum"]["Reputation"].asInt()
);
));
}
catch (const std::exception &ex)
{
throw RequestError("Could not read response: " + ByteString(ex.what()));
}
return userInfo;
return user_info;
}
}

View File

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

View File

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

View File

@ -2,6 +2,7 @@
#include "common/String.h"
#include "common/Vec2.h"
#include "Request.h"
#include <memory>
class VideoBuffer;
@ -10,10 +11,11 @@ namespace http
{
class ImageRequest : public Request
{
Vec2<int> requestedSize;
Vec2<int> size;
public:
ImageRequest(ByteString url, Vec2<int> newRequestedSize);
ImageRequest(ByteString url, Vec2<int> size);
virtual ~ImageRequest();
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 "requestmanager/RequestManager.h"
#include <memory>
#include <iostream>
#include <cstring>
#include <json/json.h>
namespace http
{
@ -15,61 +12,45 @@ namespace http
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);
}
}
void Request::FailEarly(ByteString error)
{
assert(handle->state == RequestHandle::ready);
handle->failEarly = error;
}
void Request::Verb(ByteString newVerb)
{
assert(handle->state == RequestHandle::ready);
handle->verb = newVerb;
}
void Request::AddHeader(Header header)
void Request::AddHeader(ByteString header)
{
assert(handle->state == RequestHandle::ready);
handle->headers.push_back(header);
}
void Request::AddPostData(PostData data)
void Request::AddPostData(std::map<ByteString, ByteString> data)
{
assert(handle->state == RequestHandle::ready);
// Even if the map is empty, calling this function signifies you want to do a POST request
handle->isPost = true;
handle->postData = data;
handle->postData.insert(data.begin(), data.end());
}
void Request::AuthHeaders(ByteString ID, ByteString session)
{
assert(handle->state == RequestHandle::ready);
if (ID.size() && ID != "-1") // -1 is an emscripten hack, see AuthUserEmscripten.cpp
if (ID.size())
{
if (session.size())
{
AddHeader({ "X-Auth-User-Id", ID });
AddHeader({ "X-Auth-Session-Key", session });
AddHeader("X-Auth-User-Id: " + ID);
AddHeader("X-Auth-Session-Key: " + session);
}
else
{
AddHeader({ "X-Auth-User", ID });
AddHeader("X-Auth-User: " + ID);
}
}
}
@ -88,71 +69,52 @@ namespace http
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);
assert(handle->state == RequestHandle::running || handle->state == RequestHandle::done);
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);
assert(handle->state == RequestHandle::done);
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::unique_lock lk(handle->stateMx);
if (handle->state == RequestHandle::running)
{
std::unique_lock lk(handle->stateMx);
assert(handle->state == RequestHandle::done);
handle->stateCv.wait(lk, [this]() {
return handle->state == RequestHandle::done;
});
}
assert(handle->state == RequestHandle::done);
handle->state = RequestHandle::dead;
if (handle->error)
{
throw RequestError(*handle->error);
}
return std::pair{ handle->statusCode, std::move(handle->responseData) };
return { handle->statusCode, std::move(handle->responseData) };
}
void RequestHandle::MarkDone()
std::pair<int, ByteString> Request::Simple(ByteString uri, std::map<ByteString, ByteString> 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;
}
return SimpleAuth(uri, "", "", post_data);
}
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)
{
@ -234,69 +196,7 @@ namespace http
case 619: return "SSL: Failed to Load CRL File";
case 620: return "SSL: Issuer Check Failed";
case 621: return "SSL: Pinned Public Key Mismatch";
}
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;
default: return "Unknown Status Code";
}
}
}

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