Merge branch 'master' into phot-revamp

This commit is contained in:
Ksawi 2023-06-30 19:47:35 +02:00 committed by GitHub
commit b690a8ad75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
676 changed files with 17989 additions and 19098 deletions

70
.github/build.sh vendored
View File

@ -43,6 +43,28 @@ aarch64-android-bionic-static) ;;
*) >&2 echo "configuration $BSH_HOST_ARCH-$BSH_HOST_PLATFORM-$BSH_HOST_LIBC-$BSH_STATIC_DYNAMIC is not supported" && exit 1;;
esac
if [[ -z ${BSH_NO_PACKAGES-} ]]; then
case $BSH_BUILD_PLATFORM in
linux)
sudo apt update
if [[ $BSH_STATIC_DYNAMIC == static ]]; then
sudo apt install libc6-dev libc6-dev-i386
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
if [[ $BSH_STATIC_DYNAMIC != static ]]; then
brew install luajit curl fftw zlib sdl2 bzip2 jsoncpp
fi
;;
esac
fi
function inplace_sed() {
local subst=$1
local path=$2
@ -163,7 +185,6 @@ meson_configure+=$'\t'-Dapp_data=$APP_DATA
meson_configure+=$'\t'-Dapp_vendor=$APP_VENDOR
meson_configure+=$'\t'-Db_strip=false
meson_configure+=$'\t'-Db_staticpic=false
meson_configure+=$'\t'-Dinstall_check=true
meson_configure+=$'\t'-Dmod_id=$MOD_ID
case $BSH_HOST_ARCH-$BSH_HOST_PLATFORM-$BSH_HOST_LIBC-$BSH_DEBUG_RELEASE in
x86_64-linux-gnu-debug) ;&
@ -174,6 +195,12 @@ x86_64-darwin-macos-debug)
meson_configure+=$'\t'-Dbuild_font=true
;;
esac
if [[ $PACKAGE_MODE == nohttp ]]; then
meson_configure+=$'\t'-Dhttp=false
fi
if [[ $PACKAGE_MODE == nolua ]]; then
meson_configure+=$'\t'-Dlua=none
fi
if [[ $BSH_STATIC_DYNAMIC == static ]]; then
meson_configure+=$'\t'-Dstatic=prebuilt
if [[ $BSH_HOST_PLATFORM == windows ]]; then
@ -212,9 +239,15 @@ fi
if [[ $RELEASE_TYPE == stable ]]; then
stable_or_beta=yes
fi
save_version=$(grep -w src/Config.template.h -e "#define SAVE_VERSION" | cut -d ' ' -f 3)
minor_version=$(grep -w src/Config.template.h -e "#define MINOR_VERSION" | cut -d ' ' -f 3)
build_num=$(grep -w src/Config.template.h -e "#define BUILD_NUM" | cut -d ' ' -f 3)
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)
@ -252,9 +285,14 @@ if [[ $BSH_HOST_PLATFORM-$BSH_HOST_ARCH == darwin-aarch64 ]]; then
meson_configure+=$'\t'--cross-file=.github/macaa64-ghactions.ini
fi
if [[ $RELEASE_TYPE == tptlibsdev ]] && ([[ $BSH_HOST_PLATFORM == windows ]] || [[ $BSH_STATIC_DYNAMIC == static ]]); then
if [[ -z "${GITHUB_REPOSITORY_OWNER-}" ]]; then
>&2 echo "GITHUB_REPOSITORY_OWNER not set"
exit 1
if [[ -z ${TPTLIBSREMOTE-} ]]; then
if [[ -z "${GITHUB_REPOSITORY_OWNER-}" ]]; then
>&2 echo "GITHUB_REPOSITORY_OWNER not set"
exit 1
fi
tptlibsremote=https://github.com/$GITHUB_REPOSITORY_OWNER/tpt-libs
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"
@ -262,10 +300,13 @@ if [[ $RELEASE_TYPE == tptlibsdev ]] && ([[ $BSH_HOST_PLATFORM == windows ]] ||
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
fi
if [[ ! -d build-tpt-libs/tpt-libs ]]; then
mkdir -p build-tpt-libs
cd build-tpt-libs
git clone https://github.com/$GITHUB_REPOSITORY_OWNER/tpt-libs --branch $tptlibsbranch --depth 1
git clone $tptlibsremote --branch $tptlibsbranch --depth 1
cd ..
fi
tpt_libs_vtag=v00000000000000
@ -331,7 +372,7 @@ if [[ $BSH_HOST_PLATFORM == android ]]; then
fi
if [[ $PACKAGE_MODE == appimage ]]; then
# so far this can only happen with $BSH_HOST_PLATFORM-$BSH_HOST_LIBC == linux-gnu, but this may change later
meson configure -Dinstall_check=false -Dignore_updates=true -Dbuild_render=false -Dbuild_font=false
meson configure -Dcan_install=no -Dignore_updates=true -Dbuild_render=false -Dbuild_font=false
strip_target=$APP_EXE
fi
if [[ $BSH_BUILD_PLATFORM == windows ]]; then
@ -341,6 +382,14 @@ if [[ $BSH_BUILD_PLATFORM == windows ]]; then
set -e
cat $APP_EXE.exe.rsp
[[ $ninja_code == 0 ]];
echo # rsps don't usually have a newline at the end
if [[ "$BSH_HOST_PLATFORM-$BSH_STATIC_DYNAMIC $BSH_BUILD_PLATFORM" == "windows-dynamic windows" ]]; then
# on windows we provide the dynamic dependencies also; makes sense to check for their presence
# msys ldd works fine but only on windows build machines
if ldd $APP_EXE | grep "not found"; then
exit 1 # ldd | grep will have printed missing deps
fi
fi
else
ninja -v
fi
@ -383,8 +432,7 @@ if [[ $PACKAGE_MODE == dmg ]]; then
mv $appdir dmgroot/$appdir
cp ../LICENSE dmgroot/LICENSE
cp ../README.md dmgroot/README.md
hdiutil create uncompressed.dmg -ov -volname $APP_NAME -fs HFS+ -srcfolder dmgroot
hdiutil convert uncompressed.dmg -format UDZO -o $ASSET_PATH
hdiutil create -format UDZO -volname $APP_NAME -fs HFS+ -srcfolder dmgroot -o $ASSET_PATH
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

28
.github/prepare.py vendored
View File

@ -78,11 +78,13 @@ 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 in [
( 'x86_64', 'linux', 'gnu', 'static', 'linux', 'ubuntu-18.04', '', False, False, None, None, None, 'debug' ),
( 'x86_64', 'linux', 'gnu', 'static', 'linux', 'ubuntu-18.04', '', True, True, '.dbg', None, 'x86_64-lin-gcc-static', 'release' ),
( 'x86_64', 'linux', 'gnu', 'static', 'linux', 'ubuntu-18.04', '', False, True, '.dbg', 'appimage', None, 'release' ),
( 'x86_64', 'linux', 'gnu', 'dynamic', 'linux', 'ubuntu-18.04', '', False, False, None, None, None, 'debug' ),
( 'x86_64', 'linux', 'gnu', 'dynamic', 'linux', 'ubuntu-18.04', '', False, False, None, None, None, 'release' ),
( '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' ),
@ -107,14 +109,14 @@ for arch, platform, libc, statdyn, bplatform, runson, suff
( '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-18.04', '.apk', False, False, None, None, None, 'debug' ),
( 'x86', 'android', 'bionic', 'static', 'linux', 'ubuntu-18.04', '.apk', True, True, '.dbg', None, 'i686-and-gcc-static', 'release' ),
( 'x86_64', 'android', 'bionic', 'static', 'linux', 'ubuntu-18.04', '.apk', False, False, None, None, None, 'debug' ),
( 'x86_64', 'android', 'bionic', 'static', 'linux', 'ubuntu-18.04', '.apk', True, True, '.dbg', None, 'x86_64-and-gcc-static', 'release' ),
( 'arm', 'android', 'bionic', 'static', 'linux', 'ubuntu-18.04', '.apk', False, False, None, None, None, 'debug' ),
( 'arm', 'android', 'bionic', 'static', 'linux', 'ubuntu-18.04', '.apk', True, True, '.dbg', None, 'arm-and-gcc-static', 'release' ),
( 'aarch64', 'android', 'bionic', 'static', 'linux', 'ubuntu-18.04', '.apk', False, False, None, None, None, 'debug' ),
( 'aarch64', 'android', 'bionic', 'static', 'linux', 'ubuntu-18.04', '.apk', True, True, '.dbg', None, 'arm64-and-gcc-static', 'release' ),
( '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 not mode:
mode = 'default'

View File

@ -31,6 +31,7 @@ jobs:
app_vendor: ${{ steps.prepare.outputs.app_vendor }}
do_publish: ${{ steps.prepare.outputs.do_publish }}
steps:
- run: git config --global core.autocrlf false
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
@ -58,21 +59,14 @@ jobs:
fail-fast: false
matrix: ${{ fromJSON(needs.prepare.outputs.build_matrix) }}
steps:
- run: git config --global core.autocrlf false
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- run: python -m pip install meson ninja
- if: matrix.bsh_build_platform == 'darwin'
run: brew install pkg-config coreutils binutils bash
- if: matrix.bsh_build_platform == 'darwin' && matrix.bsh_static_dynamic != 'static'
run: brew install luajit curl fftw zlib sdl2 bzip2 jsoncpp
- if: matrix.bsh_build_platform == 'linux' && matrix.bsh_host_libc == 'mingw'
run: sudo apt update && sudo apt install g++-mingw-w64-x86-64
- if: matrix.bsh_build_platform == 'linux' && matrix.bsh_static_dynamic != 'static'
run: sudo apt update && sudo apt install libluajit-5.1-dev libcurl4-openssl-dev libfftw3-dev zlib1g-dev libsdl2-dev libbz2-dev libjsoncpp-dev
- if: matrix.bsh_build_platform == 'linux' && matrix.bsh_static_dynamic == 'static'
run: sudo apt update && sudo apt install libc6-dev libc6-dev-i386
run: brew install bash coreutils
- run: bash -c './.github/build.sh'
env:
BSH_HOST_ARCH: ${{ matrix.bsh_host_arch }}
@ -130,6 +124,7 @@ jobs:
matrix: ${{ fromJSON(needs.prepare.outputs.publish_matrix) }}
if: needs.prepare.outputs.do_publish == 'yes'
steps:
- run: git config --global core.autocrlf false
- uses: actions/checkout@v3
- uses: actions/download-artifact@v3
with:
@ -147,6 +142,7 @@ jobs:
needs: [build, publish, prepare]
if: needs.prepare.outputs.do_publish == 'yes'
steps:
- run: git config --global core.autocrlf false
- uses: actions/checkout@v3
- run: ./.github/starcatcher-release.sh
env:

View File

@ -1,4 +1,4 @@
The Powder Toy - December 2022
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
* Bryan Hoyle
* Victoria Hoyle
* Nathan Cousins
* jacksonmj
* Felix Wallin
@ -111,15 +111,16 @@ Controls
Command Line
---------------------------------------------------------------------------
| Command | Description | Example |
| --------------------- | ------------------------------------------------ | --------------------------------- |
| `scale:SIZE` | Change window scale factor | `scale:2` |
| `kiosk` | Fullscreen mode | |
| `proxy:SERVER[:PORT]` | Proxy server to use | `proxy:wwwcache.lancs.ac.uk:8080` |
| `open FILE` | Opens the file as a stamp or game save | |
| `ddir DIRECTORY` | Directory used for saving stamps and preferences | |
| `ptsave:SAVEID` | Open online save, used by ptsave: URLs | `ptsave:2198` |
| `disable-network` | Disables internet connections | |
| `redirect` | Redirects output to stdout.txt / stderr.txt | |
| Command | Description | Example |
| --------------------- | ------------------------------------------------ | --------------------------------------------|
| `scale:SIZE` | Change window scale factor | `scale:2` |
| `kiosk` | Fullscreen mode | |
| `proxy:SERVER[:PORT]` | Proxy server to use | `proxy:wwwcache.lancs.ac.uk:8080` |
| `open FILE` | Opens the file as a stamp or game save | |
| `ddir DIRECTORY` | Directory used for saving stamps and preferences | |
| `ptsave:SAVEID` | Open online save, used by ptsave: URLs | `ptsave:2198` |
| `disable-network` | Disables internet connections | |
| `disable-bluescreen` | Disable bluescreen handler | |
| `redirect` | Redirects output to stdout.txt / stderr.txt | |
| `cafile:CAFILE` | Set certificate bundle path | `cafile:/etc/ssl/certs/ca-certificates.crt` |
| `capath:CAPATH` | Set certificate directory path | `capath:/etc/ssl/certs` |
| `capath:CAPATH` | Set certificate directory path | `capath:/etc/ssl/certs` |

View File

@ -37,6 +37,9 @@ host_arch = host_machine.cpu_family()
host_platform = host_machine.system()
# educated guesses follow, PRs welcome
if c_compiler.get_id() in [ 'msvc' ]
if host_platform != 'windows'
error('this seems fishy')
endif
host_libc = 'msvc'
elif c_compiler.get_id() in [ 'gcc' ] and host_platform == 'windows'
host_libc = 'mingw'
@ -49,6 +52,11 @@ elif host_platform in [ 'android' ]
host_platform = 'android'
host_libc = 'bionic'
else
if host_platform != 'linux'
# TODO: maybe use 'default' in place of 'linux', or use something other than host_platform where details such as desktop integration are concerned
warning('host platform is not linux but we will pretend that it is')
host_platform = 'linux'
endif
host_libc = 'gnu'
endif
@ -64,11 +72,7 @@ endif
is_static = static_variant != 'none'
is_debug = get_option('optimization') in [ '0', 'g' ]
enforce_https = get_option('enforce_https')
if not is_debug and not enforce_https
error('refusing to build a release binary with enforce_https=false')
endif
app_exe = get_option('app_exe')
tpt_libs_static = 'none'
if static_variant == 'prebuilt'
@ -81,7 +85,7 @@ 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 = 'v20221223184203'
tpt_libs_vtag = 'v20230205154205'
endif
if tpt_libs_static != 'none'
if tpt_libs_variant not in [
@ -122,10 +126,6 @@ else
endif
endif
conf_data = configuration_data()
conf_data.set('CURL_STATICLIB', false)
conf_data.set('ZLIB_WINAPI', false)
x86_sse_level_str = get_option('x86_sse')
if x86_sse_level_str == 'auto'
x86_sse_level = 20
@ -179,9 +179,7 @@ if host_platform == 'android'
endif
curl_dep = enable_http ? dependency('libcurl', static: is_static) : []
enable_gravfft = get_option('gravfft')
fftw_dep = enable_gravfft ? dependency('fftw3f', 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)
@ -278,13 +276,11 @@ endif
if host_platform == 'windows'
args_ccomp_win = [ '-D_WIN32_WINNT=0x0501' ]
project_c_args += args_ccomp_win
project_cpp_args += args_ccomp_win
windows_mod = import('windows')
if is_static
conf_data.set('CURL_STATICLIB', true)
args_ccomp_win += [ '-DCURL_STATICLIB' ]
if host_arch == 'x86_64'
conf_data.set('ZLIB_WINAPI', true)
args_ccomp_win += [ '-DZLIB_WINAPI' ]
endif
else
foreach input_output_condition : tpt_libs.get_variable('config_dlls')
@ -302,6 +298,8 @@ if host_platform == 'windows'
endif
endforeach
endif
project_c_args += args_ccomp_win
project_cpp_args += args_ccomp_win
endif
project_inc = include_directories([ 'src', 'resources' ])
@ -316,43 +314,6 @@ else
ident_platform = 'UNKNOWN'
endif
app_exe = get_option('app_exe')
app_id = get_option('app_id')
conf_data.set('LIN', host_platform == 'linux')
conf_data.set('AND', host_platform == 'android')
conf_data.set('WIN', host_platform == 'windows')
conf_data.set('MACOSX', host_platform == 'darwin')
conf_data.set('X86', is_x86)
conf_data.set('X86_SSE3', x86_sse_level >= 30)
conf_data.set('X86_SSE2', x86_sse_level >= 20)
conf_data.set('X86_SSE', x86_sse_level >= 10)
conf_data.set('_64BIT', is_64bit)
conf_data.set('BETA', get_option('beta'))
conf_data.set('NO_INSTALL_CHECK', not get_option('install_check'))
conf_data.set('IGNORE_UPDATES', get_option('ignore_updates'))
conf_data.set('MOD_ID', get_option('mod_id'))
conf_data.set('DEBUG', is_debug)
conf_data.set('SNAPSHOT', get_option('snapshot'))
conf_data.set('SNAPSHOT_ID', get_option('snapshot_id'))
conf_data.set('SERVER', '"@0@"'.format(get_option('server')))
conf_data.set('STATICSERVER', '"@0@"'.format(get_option('static_server')))
conf_data.set('IDENT_PLATFORM', '"@0@"'.format(ident_platform))
conf_data.set('IDENT', '"@0@-@1@-@2@"'.format(host_arch, host_platform, host_libc).to_upper())
conf_data.set('ENFORCE_HTTPS', enforce_https)
conf_data.set('APPNAME', get_option('app_name'))
conf_data.set('APPCOMMENT', get_option('app_comment'))
conf_data.set('APPEXE', app_exe)
conf_data.set('APPID', app_id)
conf_data.set('APPDATA', get_option('app_data'))
conf_data.set('APPVENDOR', get_option('app_vendor'))
if get_option('update_server') != ''
conf_data.set('UPDATESERVER', '"@0@"'.format(get_option('update_server')))
else
conf_data.set('UPDATESERVER', false)
endif
data_files = []
subdir('src')
@ -378,7 +339,7 @@ if get_option('build_powder')
powder_sha = shared_library(
app_exe,
sources: powder_files,
include_directories: [ project_inc, powder_inc ],
include_directories: project_inc,
c_args: project_c_args,
cpp_args: project_cpp_args,
link_args: project_link_args,
@ -389,12 +350,13 @@ if get_option('build_powder')
executable(
app_exe,
sources: powder_files,
include_directories: [ project_inc, powder_inc ],
include_directories: project_inc,
c_args: project_c_args,
cpp_args: project_cpp_args,
win_subsystem: is_debug ? 'console' : 'windows',
link_args: project_link_args,
dependencies: powder_deps,
install: true,
)
endif
endif
@ -414,7 +376,7 @@ if get_option('build_render')
executable(
'render',
sources: render_files,
include_directories: [ project_inc, render_inc ],
include_directories: project_inc,
c_args: project_c_args,
cpp_args: project_cpp_args,
link_args: render_link_args,
@ -434,7 +396,7 @@ if get_option('build_font')
executable(
'font',
sources: font_files,
include_directories: [ project_inc, font_inc ],
include_directories: project_inc,
c_args: project_c_args,
cpp_args: project_cpp_args,
link_args: project_link_args,

View File

@ -18,10 +18,11 @@ option(
description: 'Don\'t show notifications about available updates'
)
option(
'install_check',
type: 'boolean',
value: false,
description: 'Do install check on startup'
'can_install',
type: 'combo',
choices: [ 'no', 'yes', 'yes_check', 'auto' ],
value: 'auto',
description: 'Disable (\'no\') or enable (\'yes\') setting up file and URL associations, or even offer to do it at startup (\'yes_check\')'
)
option(
'http',
@ -29,12 +30,6 @@ option(
value: true,
description: 'Enable HTTP via libcurl'
)
option(
'gravfft',
type: 'boolean',
value: true,
description: 'Enable FFT gravity via libfftw3'
)
option(
'snapshot',
type: 'boolean',
@ -207,6 +202,12 @@ 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',

View File

@ -18,7 +18,7 @@
<key>CFBundleIconFile</key>
<string>icon_exe.icns</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2008-2011 Stanislaw K Skowrenek, Copyright © 2011-2022 Simon Robertshaw, Copyright © 2016-2022 jacob1</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>

Binary file not shown.

View File

@ -60,6 +60,7 @@ if host_platform == 'windows'
],
depend_files: [
'resource.h',
'winutf8.xml',
],
)
elif host_platform == 'darwin'

View File

@ -1,4 +1,6 @@
#include "resource.h"
#include <winuser.h>
IDI_ICON ICON DISCARDABLE "icon_exe.ico"
IDI_DOC_ICON ICON DISCARDABLE "icon_cps.ico"
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "winutf8.xml"

View File

@ -1,5 +1,3 @@
#define LUASCRIPT 256
#define IDI_ICON 101
#define IDI_DOC_ICON 102

8
resources/winutf8.xml Normal file
View File

@ -0,0 +1,8 @@
<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 "Config.h"
#include "gui/interface/Window.h"
class Activity

View File

@ -1,158 +1,46 @@
#ifndef CONFIG_H
#define CONFIG_H
#pragma once
#mesondefine CURL_STATICLIB
#mesondefine ZLIB_WINAPI
constexpr bool SET_WINDOW_ICON = @SET_WINDOW_ICON@;
constexpr bool DEBUG = @DEBUG@;
constexpr bool X86 = @X86@;
constexpr bool BETA = @BETA@;
constexpr bool SNAPSHOT = @SNAPSHOT@;
constexpr bool MOD = @MOD@;
constexpr bool NOHTTP = @NOHTTP@;
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 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 char PATH_SEP_CHAR = '@PATH_SEP_CHAR@';
#mesondefine LUACONSOLE
#mesondefine NOHTTP
#mesondefine GRAVFFT
#mesondefine RENDERER
#mesondefine FONTEDITOR
constexpr char SERVER[] = "@SERVER@";
constexpr char STATICSERVER[] = "@STATICSERVER@";
constexpr char UPDATESERVER[] = "@UPDATESERVER@";
constexpr char IDENT_PLATFORM[] = "@IDENT_PLATFORM@";
constexpr char IDENT[] = "@IDENT@";
constexpr char APPNAME[] = "@APPNAME@";
constexpr char APPCOMMENT[] = "@APPCOMMENT@";
constexpr char APPEXE[] = "@APPEXE@";
constexpr char APPID[] = "@APPID@";
constexpr char APPDATA[] = "@APPDATA@";
constexpr char APPVENDOR[] = "@APPVENDOR@";
#mesondefine BETA
#mesondefine DEBUG
#mesondefine IGNORE_UPDATES
#mesondefine LIN
#mesondefine AND
#mesondefine NO_INSTALL_CHECK
#mesondefine SNAPSHOT
#mesondefine WIN
#mesondefine MACOSX
#mesondefine X86
#mesondefine X86_SSE
#mesondefine X86_SSE2
#mesondefine X86_SSE3
#mesondefine _64BIT
#mesondefine SERVER
#mesondefine STATICSERVER
#mesondefine UPDATESERVER
#mesondefine IDENT_PLATFORM
#mesondefine IDENT
#mesondefine ENFORCE_HTTPS
#define APPNAME "@APPNAME@"
#define APPCOMMENT "@APPCOMMENT@"
#define APPEXE "@APPEXE@"
#define APPID "@APPID@"
#define APPDATA "@APPDATA@"
#define APPVENDOR "@APPVENDOR@"
constexpr int SAVE_VERSION = 97;
constexpr int MINOR_VERSION = 0;
constexpr int BUILD_NUM = 352;
constexpr int SNAPSHOT_ID = @SNAPSHOT_ID@;
constexpr int MOD_ID = @MOD_ID@;
constexpr int FUTURE_SAVE_VERSION = 98;
constexpr int FUTURE_MINOR_VERSION = 0;
#ifdef WIN
# define PATH_SEP "\\"
# define PATH_SEP_CHAR '\\'
#else
# define PATH_SEP "/"
# define PATH_SEP_CHAR '/'
#endif
constexpr char IDENT_RELTYPE = SNAPSHOT ? 'S' : (BETA ? 'B' : 'R');
//VersionInfoStart
#define SAVE_VERSION 97
#define MINOR_VERSION 0
#define BUILD_NUM 351
#mesondefine SNAPSHOT_ID
#mesondefine MOD_ID
#define FUTURE_SAVE_VERSION 97
#define FUTURE_MINOR_VERSION 0
#if !(defined(SNAPSHOT) || defined(BETA) || defined(DEBUG) || MOD_ID > 0)
#undef FUTURE_SAVE_VERSION
#undef FUTURE_MINOR_VERSION
#endif
//VersionInfoEnd
#if !(defined(MACOSX) && defined(DEBUG))
#define HIGH_QUALITY_RESAMPLE //High quality image resampling, slower but much higher quality than my terribad linear interpolation
#endif
#if defined(SNAPSHOT)
#define IDENT_RELTYPE "S"
#elif defined(BETA)
#define IDENT_RELTYPE "B"
#else
#define IDENT_RELTYPE "R"
#endif
#if defined(X86_SSE3)
#define IDENT_BUILD "SSE3"
#elif defined(X86_SSE2)
#define IDENT_BUILD "SSE2"
#elif defined(X86_SSE)
#define IDENT_BUILD "SSE"
#else
#define IDENT_BUILD "NO"
#endif
#define MTOS_EXPAND(str) #str
#define MTOS(str) MTOS_EXPAND(str)
#define SCHEME "https://"
#define STATICSCHEME "https://"
#define LOCAL_SAVE_DIR "Saves"
#define STAMPS_DIR "stamps"
#define BRUSH_DIR "Brushes"
#ifndef M_GRAV
#define M_GRAV 6.67300e-1
#endif
#ifdef RENDERER
#define MENUSIZE 0
#define BARSIZE 0
#else
#define MENUSIZE 40
#define BARSIZE 17
#endif
#define XRES 612
#define YRES 384
#define NPART XRES*YRES
#define XCNTR XRES/2
#define YCNTR YRES/2
#define WINDOWW (XRES+BARSIZE)
#define WINDOWH (YRES+MENUSIZE)
#define GRAV_DIFF
#define MAXSIGNS 16
//CELL, the size of the pressure, gravity, and wall maps. Larger than 1 to prevent extreme lag
#define CELL 4
#define ISTP (CELL/2)
#define CFDS (4.0f/CELL)
#define SIM_MAXVELOCITY 1e4f
//Air constants
#define AIR_TSTEPP 0.3f
#define AIR_TSTEPV 0.4f
#define AIR_VADV 0.3f
#define AIR_VLOSS 0.999f
#define AIR_PLOSS 0.9999f
#define NGOL 24
#define CIRCLE_BRUSH 0
#define SQUARE_BRUSH 1
#define TRI_BRUSH 2
#define BRUSH_NUM 3
//Photon constants
#define SURF_RANGE 10
#define NORMAL_MIN_EST 3
#define NORMAL_INTERP 20
#define NORMAL_FRAC 16
#define REFRACT 0x80000000
/* heavy flint glass, for awesome refraction/dispersion
this way you can make roof prisms easily */
#define GLASS_IOR 1.9
#define GLASS_DISP 0.07
#define SDEUT
#define R_TEMP 22
#endif /* CONFIG_H */
constexpr char SCHEME[] = "https://";
constexpr char STATICSCHEME[] = "https://";
constexpr char LOCAL_SAVE_DIR[] = "Saves";
constexpr char STAMPS_DIR[] = "stamps";
constexpr char BRUSH_DIR[] = "Brushes";

View File

@ -1,6 +1,4 @@
#ifndef CONTROLLER_H_
#define CONTROLLER_H_
#pragma once
class Controller
{
private:
@ -9,5 +7,3 @@ private:
virtual void Hide();
virtual ~Controller() = default;
};
#endif /* CONTROLLER_H_ */

View File

@ -1,19 +1,15 @@
#include "Format.h"
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <stdexcept>
#include <iostream>
#include <iterator>
#include <cstring>
#include <zlib.h>
#include <cstdio>
#include <optional>
#include <stdexcept>
#include <png.h>
#include "Format.h"
#include "graphics/Graphics.h"
#ifndef RENDERER
# include "SDLCompat.h"
#endif
ByteString format::UnixtimeToDate(time_t unixtime, ByteString dateFormat)
{
struct tm * timeData;
@ -99,30 +95,187 @@ String format::CleanString(String dirtyString, bool ascii, bool color, bool newl
return dirtyString;
}
std::vector<char> format::VideoBufferToPPM(const VideoBuffer & vidBuf)
std::vector<char> format::PixelsToPPM(PlaneAdapter<std::vector<pixel>> const &input)
{
std::vector<char> data;
char buffer[256];
sprintf(buffer, "P6\n%d %d\n255\n", vidBuf.Width, vidBuf.Height);
data.insert(data.end(), buffer, buffer+strlen(buffer));
sprintf(buffer, "P6\n%d %d\n255\n", input.Size().X, input.Size().Y);
data.insert(data.end(), buffer, buffer + strlen(buffer));
unsigned char * currentRow = new unsigned char[vidBuf.Width*3];
for(int y = 0; y < vidBuf.Height; y++)
data.reserve(data.size() + input.Size().X * input.Size().Y * 3);
for (int i = 0; i < input.Size().X * input.Size().Y; i++)
{
int rowPos = 0;
for(int x = 0; x < vidBuf.Width; x++)
{
currentRow[rowPos++] = PIXR(vidBuf.Buffer[(y*vidBuf.Width)+x]);
currentRow[rowPos++] = PIXG(vidBuf.Buffer[(y*vidBuf.Width)+x]);
currentRow[rowPos++] = PIXB(vidBuf.Buffer[(y*vidBuf.Width)+x]);
}
data.insert(data.end(), currentRow, currentRow+(vidBuf.Width*3));
auto colour = RGB<uint8_t>::Unpack(input.data()[i]);
data.push_back(colour.Red);
data.push_back(colour.Green);
data.push_back(colour.Blue);
}
delete [] currentRow;
return data;
}
static std::unique_ptr<PlaneAdapter<std::vector<uint32_t>>> readPNG(
std::vector<char> const &data,
// If omitted,
// RGB data is returned with A=0xFF
// RGBA data is returned as itself
// If specified
// RGB data is returned with A=0x00
// RGBA data is blended against the background and returned with A=0x00
std::optional<RGB<uint8_t>> background
)
{
png_infop info = nullptr;
auto deleter = [&info](png_struct *png) {
png_destroy_read_struct(&png, &info, NULL);
};
auto png = std::unique_ptr<png_struct, decltype(deleter)>(
png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL,
[](png_structp png, png_const_charp msg) {
fprintf(stderr, "PNG error: %s\n", msg);
},
[](png_structp png, png_const_charp msg) {
fprintf(stderr, "PNG warning: %s\n", msg);
}
), deleter
);
if (!png)
return nullptr;
// libpng might longjmp() here in case of error
// Every time we create an object with a non-trivial destructor we must call setjmp again
if (setjmp(png_jmpbuf(png.get())))
return nullptr;
info = png_create_info_struct(png.get());
if (!info)
return nullptr;
auto it = data.begin();
auto const end = data.end();
auto readFn = [&it, end](png_structp png, png_bytep data, size_t length) {
if (size_t(end - it) < length)
png_error(png, "Tried to read beyond the buffer");
std::copy_n(it, length, data);
it += length;
};
// See above
if (setjmp(png_jmpbuf(png.get())))
return nullptr;
png_set_read_fn(png.get(), static_cast<void *>(&readFn), [](png_structp png, png_bytep data, size_t length) {
(*static_cast<decltype(readFn) *>(png_get_io_ptr(png)))(png, data, length);
});
png_set_user_limits(png.get(), RES.X, RES.Y); // Refuse to parse larger images
png_read_info(png.get(), info);
auto output = std::make_unique<PlaneAdapter<std::vector<uint32_t>>>(
Vec2<int>(png_get_image_width(png.get(), info), png_get_image_height(png.get(), info))
);
std::vector<png_bytep> rowPointers(output->Size().Y);
for (int y = 0; y < output->Size().Y; y++)
rowPointers[y] = reinterpret_cast<png_bytep>(&*output->RowIterator(Vec2(0, y)));
// See above
if (setjmp(png_jmpbuf(png.get())))
return nullptr;
png_set_filler(png.get(), background ? 0x00 : 0xFF, PNG_FILLER_AFTER);
png_set_bgr(png.get());
auto bitDepth = png_get_bit_depth(png.get(), info);
auto colorType = png_get_color_type(png.get(), info);
if (colorType == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb(png.get());
if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8)
png_set_expand_gray_1_2_4_to_8(png.get());
if (bitDepth == 16)
png_set_scale_16(png.get());
if (png_get_valid(png.get(), info, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(png.get());
if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png.get());
if (background)
{
png_color_16 colour;
colour.red = background->Red;
colour.green = background->Green;
colour.blue = background->Blue;
png_set_background(png.get(), &colour, PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
}
png_read_image(png.get(), rowPointers.data());
return output;
}
std::unique_ptr<PlaneAdapter<std::vector<pixel_rgba>>> format::PixelsFromPNG(std::vector<char> const &data)
{
return readPNG(data, std::nullopt);
}
std::unique_ptr<PlaneAdapter<std::vector<pixel>>> format::PixelsFromPNG(std::vector<char> const &data, RGB<uint8_t> background)
{
return readPNG(data, background);
}
std::unique_ptr<std::vector<char>> format::PixelsToPNG(PlaneAdapter<std::vector<pixel>> const &input)
{
png_infop info = nullptr;
auto deleter = [&info](png_struct *png) {
png_destroy_write_struct(&png, &info);
};
auto png = std::unique_ptr<png_struct, decltype(deleter)>(
png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL,
[](png_structp png, png_const_charp msg) {
fprintf(stderr, "PNG error: %s\n", msg);
},
[](png_structp png, png_const_charp msg) {
fprintf(stderr, "PNG warning: %s\n", msg);
}
), deleter
);
if (!png)
return nullptr;
// libpng might longjmp() here in case of error
// Every time we create an object with a non-trivial destructor we must call setjmp again
if (setjmp(png_jmpbuf(png.get())))
return nullptr;
info = png_create_info_struct(png.get());
if (!info)
return nullptr;
std::vector<char> output;
auto writeFn = [&output](png_structp png, png_bytep data, size_t length) {
output.insert(output.end(), data, data + length);
};
std::vector<png_const_bytep> rowPointers(input.Size().Y);
for (int y = 0; y < input.Size().Y; y++)
rowPointers[y] = reinterpret_cast<png_const_bytep>(&*input.RowIterator(Vec2(0, y)));
// See above
if (setjmp(png_jmpbuf(png.get())))
return nullptr;
png_set_write_fn(png.get(), static_cast<void *>(&writeFn), [](png_structp png, png_bytep data, size_t length) {
(*static_cast<decltype(writeFn) *>(png_get_io_ptr(png)))(png, data, length);
}, NULL);
png_set_IHDR(png.get(), info, input.Size().X, input.Size().Y, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_write_info(png.get(), info);
png_set_filler(png.get(), 0x00, PNG_FILLER_AFTER);
png_set_bgr(png.get());
png_write_image(png.get(), const_cast<png_bytepp>(rowPointers.data()));
png_write_end(png.get(), NULL);
return std::make_unique<std::vector<char>>(std::move(output));
}
const static char hex[] = "0123456789ABCDEF";
ByteString format::URLEncode(ByteString source)

View File

@ -1,8 +1,9 @@
#pragma once
#include "Config.h"
#include "common/String.h"
#include <memory>
#include <vector>
#include "common/String.h"
#include "common/Plane.h"
#include "graphics/Pixel.h"
class VideoBuffer;
@ -13,7 +14,10 @@ namespace format
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> VideoBufferToPPM(const VideoBuffer & vidBuf);
std::vector<char> PixelsToPPM(PlaneAdapter<std::vector<pixel>> const &);
std::unique_ptr<std::vector<char>> PixelsToPNG(PlaneAdapter<std::vector<pixel>> const &);
std::unique_ptr<PlaneAdapter<std::vector<pixel_rgba>>> PixelsFromPNG(std::vector<char> const &);
std::unique_ptr<PlaneAdapter<std::vector<pixel>>> PixelsFromPNG(std::vector<char> const &, RGB<uint8_t> background);
void RenderTemperature(StringBuilder &sb, float temp, int scale);
float StringToTemperature(String str, int defaultScale);
}

View File

@ -1,140 +1,10 @@
#include "Misc.h"
#include "Config.h"
#include "common/tpt-minmax.h"
#include "common/String.h"
#include <cstring>
#include <sys/types.h>
#include <cmath>
#include "common/tpt-minmax.h"
#include "common/String.h"
const static char hex[] = "0123456789ABCDEF";
void strcaturl(char *dst, char *src)
{
char *d;
unsigned char *s;
for (d=dst; *d; d++) ;
for (s=(unsigned char *)src; *s; s++)
{
if ((*s>='0' && *s<='9') ||
(*s>='a' && *s<='z') ||
(*s>='A' && *s<='Z'))
*(d++) = *s;
else
{
*(d++) = '%';
*(d++) = hex[*s>>4];
*(d++) = hex[*s&15];
}
}
*d = 0;
}
void strappend(char *dst, const char *src)
{
char *d;
unsigned char *s;
for (d=dst; *d; d++) ;
for (s=(unsigned char *)src; *s; s++)
{
*(d++) = *s;
}
*d = 0;
}
void *file_load(char *fn, int *size)
{
FILE *f = fopen(fn, "rb");
void *s;
if (!f)
return NULL;
fseek(f, 0, SEEK_END);
*size = ftell(f);
fseek(f, 0, SEEK_SET);
s = malloc(*size);
if (!s)
{
fclose(f);
return NULL;
}
int readsize = fread(s, *size, 1, f);
fclose(f);
if (readsize != 1)
{
free(s);
return NULL;
}
return s;
}
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
{
float hh, ss, vv, c, x;
@ -216,9 +86,6 @@ 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

@ -1,12 +1,29 @@
#ifndef UTILS_H
#define UTILS_H
#include "Config.h"
#pragma once
#include <cmath>
#include <cstdio>
#include <cstdlib>
#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)
{
@ -55,52 +72,10 @@ inline float restrict_flt(float f, float min, float max)
return f;
}
void save_presets(int do_update);
void load_presets(void);
void strcaturl(char *dst, const char *src);
void strappend(char *dst, const char *src);
void *file_load(char *fn, int *size);
extern char *clipboard_text;
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 {
float a,b,c,d;
};
typedef struct matrix2d matrix2d;
// column vector
struct vector2d {
float x,y;
};
typedef struct vector2d vector2d;
matrix2d m2d_multiply_m2d(matrix2d m1, matrix2d m2);
vector2d m2d_multiply_v2d(matrix2d m, vector2d v);
matrix2d m2d_multiply_float(matrix2d m, float s);
vector2d v2d_multiply_float(vector2d v, float s);
vector2d v2d_add(vector2d v1, vector2d v2);
vector2d v2d_sub(vector2d v1, vector2d v2);
matrix2d m2d_new(float me0, float me1, float me2, float me3);
vector2d v2d_new(float x, float y);
extern vector2d v2d_zero;
extern matrix2d m2d_identity;
class ByteString;
bool byteStringEqualsString(const ByteString &str, const char *data, size_t size);
@ -110,5 +85,3 @@ bool byteStringEqualsLiteral(const ByteString &str, const char (&lit)[N])
{
return byteStringEqualsString(str, lit, N - 1U);
}
#endif

534
src/PowderToy.cpp Normal file
View File

@ -0,0 +1,534 @@
#include "PowderToySDL.h"
#include "Format.h"
#include "X86KillDenormals.h"
#include "prefs/GlobalPrefs.h"
#include "client/Client.h"
#include "client/GameSave.h"
#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 "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/dialogues/ConfirmPrompt.h"
#include "gui/dialogues/ErrorMessage.h"
#include "gui/interface/Engine.h"
#include "Config.h"
#include "SimulationConfig.h"
#include <optional>
#include <climits>
#include <iostream>
#include <csignal>
#include <SDL.h>
void LoadWindowPosition()
{
if (Client::Ref().IsFirstRun())
{
return;
}
auto &prefs = GlobalPrefs::Ref();
int savedWindowX = prefs.Get("WindowX", INT_MAX);
int savedWindowY = prefs.Get("WindowY", INT_MAX);
int borderTop, borderLeft;
SDL_GetWindowBordersSize(sdl_window, &borderTop, &borderLeft, nullptr, nullptr);
// Sometimes (Windows), the border size may not be reported for 200+ frames
// So just have a default of 5 to ensure the window doesn't get stuck where it can't be moved
if (borderTop == 0)
borderTop = 5;
int numDisplays = SDL_GetNumVideoDisplays();
SDL_Rect displayBounds;
bool ok = false;
for (int i = 0; i < numDisplays; i++)
{
SDL_GetDisplayBounds(i, &displayBounds);
if (savedWindowX + borderTop > displayBounds.x && savedWindowY + borderLeft > displayBounds.y &&
savedWindowX + borderTop < displayBounds.x + displayBounds.w &&
savedWindowY + borderLeft < displayBounds.y + displayBounds.h)
{
ok = true;
break;
}
}
if (ok)
SDL_SetWindowPosition(sdl_window, savedWindowX + borderLeft, savedWindowY + borderTop);
}
void SaveWindowPosition()
{
int x, y;
SDL_GetWindowPosition(sdl_window, &x, &y);
int borderTop, borderLeft;
SDL_GetWindowBordersSize(sdl_window, &borderTop, &borderLeft, nullptr, nullptr);
auto &prefs = GlobalPrefs::Ref();
prefs.Set("WindowX", x - borderLeft);
prefs.Set("WindowY", y - borderTop);
}
void LargeScreenDialog()
{
StringBuilder message;
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 << "\nTo undo this, hit Cancel. You can change this in settings at any time.";
if (!ConfirmPrompt::Blocking("Large screen detected", message.Build()))
{
GlobalPrefs::Ref().Set("Scale", 1);
ui::Engine::Ref().SetScale(1);
}
}
void TickClient()
{
Client::Ref().Tick();
}
void BlueScreen(String detailMessage)
{
auto &engine = ui::Engine::Ref();
engine.g->BlendFilledRect(engine.g->Size().OriginRect(), 0x1172A9_rgb .WithAlpha(0xD2));
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;
// 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;
while(true)
{
while (SDL_PollEvent(&event))
if(event.type == SDL_QUIT)
exit(-1); // Don't use Platform::Exit, we're practically zombies at this point anyway.
blit(engine.g->Data());
}
}
struct
{
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 },
};
void SigHandler(int signal)
{
const char *message = "Unknown signal";
for (auto *msg = signalMessages; msg->message; ++msg)
{
if (msg->sig == signal)
{
message = msg->message;
break;
}
}
BlueScreen(ByteString(message).FromUtf8());
}
constexpr int SCALE_MAXIMUM = 10;
constexpr int SCALE_MARGIN = 30;
int GuessBestScale()
{
const int widthNoMargin = desktopWidth - SCALE_MARGIN;
const int widthGuess = widthNoMargin / WINDOWW;
const int heightNoMargin = desktopHeight - SCALE_MARGIN;
const int heightGuess = heightNoMargin / WINDOWH;
int guess = std::min(widthGuess, heightGuess);
if(guess < 1 || guess > SCALE_MAXIMUM)
guess = 1;
return guess;
}
struct ExplicitSingletons
{
// These need to be listed in the order they are populated in main.
std::unique_ptr<GlobalPrefs> globalPrefs;
http::RequestManagerPtr requestManager;
std::unique_ptr<Client> client;
std::unique_ptr<SaveRenderer> saveRenderer;
std::unique_ptr<Favorite> favorite;
std::unique_ptr<ui::Engine> engine;
std::unique_ptr<GameController> gameController;
};
static std::unique_ptr<ExplicitSingletons> explicitSingletons;
int main(int argc, char * argv[])
{
Platform::SetupCrt();
Platform::Atexit([]() {
// 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();
});
explicitSingletons = std::make_unique<ExplicitSingletons>();
// https://bugzilla.libsdl.org/show_bug.cgi?id=3796
if (SDL_Init(0) < 0)
{
fprintf(stderr, "Initializing SDL: %s\n", SDL_GetError());
return 1;
}
Platform::originalCwd = Platform::GetCwd();
using Argument = std::optional<ByteString>;
std::map<ByteString, Argument> arguments;
for (auto i = 1; i < argc; ++i)
{
auto str = ByteString(argv[i]);
if (str.BeginsWith("file://"))
{
arguments.insert({ "open", format::URLDecode(str.substr(7 /* length of the "file://" prefix */)) });
}
else if (str.BeginsWith("ptsave:"))
{
arguments.insert({ "ptsave", str });
}
else if (auto split = str.SplitBy(':'))
{
arguments.insert({ split.Before(), split.After() });
}
else if (auto split = str.SplitBy('='))
{
arguments.insert({ split.Before(), split.After() });
}
else if (str == "open" || str == "ptsave" || str == "ddir")
{
if (i + 1 < argc)
{
arguments.insert({ str, argv[i + 1] });
i += 1;
}
else
{
std::cerr << "no value provided for command line parameter " << str << std::endl;
}
}
else
{
arguments.insert({ str, "" }); // so .has_value() is true
}
}
auto ddirArg = arguments["ddir"];
if (ddirArg.has_value())
{
if (Platform::ChangeDir(ddirArg.value()))
Platform::sharedCwd = Platform::GetCwd();
else
perror("failed to chdir to requested ddir");
}
else
{
auto ddir = std::unique_ptr<char, decltype(&SDL_free)>(SDL_GetPrefPath(NULL, APPDATA), SDL_free);
if (!Platform::FileExists("powder.pref"))
{
if (ddir)
{
if (!Platform::ChangeDir(ddir.get()))
{
perror("failed to chdir to default ddir");
ddir.reset();
}
}
}
if (ddir)
{
Platform::sharedCwd = ddir.get();
}
}
// We're now in the correct directory, time to get prefs.
explicitSingletons->globalPrefs = std::make_unique<GlobalPrefs>();
auto &prefs = GlobalPrefs::Ref();
scale = prefs.Get("Scale", 1);
auto graveExitsConsole = prefs.Get("GraveExitsConsole", true);
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);
auto true_string = [](ByteString str) {
str = str.ToLower();
return str == "true" ||
str == "t" ||
str == "on" ||
str == "yes" ||
str == "y" ||
str == "1" ||
str == ""; // standalone "redirect" or "disable-bluescreen" or similar arguments
};
auto true_arg = [&true_string](Argument arg) {
return arg.has_value() && true_string(arg.value());
};
auto kioskArg = arguments["kiosk"];
if (kioskArg.has_value())
{
fullscreen = true_string(kioskArg.value());
prefs.Set("Fullscreen", fullscreen);
}
if (true_arg(arguments["redirect"]))
{
FILE *new_stdout = freopen("stdout.log", "w", stdout);
FILE *new_stderr = freopen("stderr.log", "w", stderr);
if (!new_stdout || !new_stderr)
{
Platform::Exit(42);
}
}
auto scaleArg = arguments["scale"];
if (scaleArg.has_value())
{
try
{
scale = scaleArg.value().ToNumber<int>();
prefs.Set("Scale", scale);
}
catch (const std::runtime_error &e)
{
std::cerr << "failed to set scale: " << e.what() << std::endl;
}
}
auto clientConfig = [&prefs](Argument arg, ByteString name, ByteString defaultValue) {
ByteString value;
if (arg.has_value())
{
value = arg.value();
if (value == "")
{
value = defaultValue;
}
prefs.Set(name, value);
}
else
{
value = prefs.Get(name, defaultValue);
}
return value;
};
ByteString proxyString = clientConfig(arguments["proxy"], "Proxy", "");
ByteString cafileString = clientConfig(arguments["cafile"], "CAFile", "");
ByteString capathString = clientConfig(arguments["capath"], "CAPath", "");
bool disableNetwork = true_arg(arguments["disable-network"]);
explicitSingletons->requestManager = http::RequestManager::Create(proxyString, cafileString, capathString, disableNetwork);
explicitSingletons->client = std::make_unique<Client>();
Client::Ref().Initialize();
explicitSingletons->saveRenderer = std::make_unique<SaveRenderer>();
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(scale < 1 || scale > SCALE_MAXIMUM)
scale = 1;
SDLOpen();
if (Client::Ref().IsFirstRun())
{
scale = GuessBestScale();
if (scale > 1)
{
prefs.Set("Scale", scale);
SDL_SetWindowSize(sdl_window, WINDOWW * scale, WINDOWH * scale);
showLargeScreenDialog = true;
}
}
StopTextInput();
auto &engine = ui::Engine::Ref();
engine.g = new Graphics();
engine.Scale = scale;
engine.GraveExitsConsole = graveExitsConsole;
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);
}
}
if constexpr (X86)
{
X86KillDenormals();
}
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)
{
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
{
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));
}
}
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");
}
}
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::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;
}
int saveId = saveIdPart.ToNumber<int>();
auto getSave = std::make_unique<http::GetSaveRequest>(saveId, 0);
getSave->Start();
getSave->Wait();
std::unique_ptr<SaveInfo> newSave;
try
{
newSave = getSave->Finish();
}
catch (const http::RequestError &ex)
{
throw std::runtime_error("Could not load save info\n" + ByteString(ex.what()));
}
auto getSaveData = std::make_unique<http::GetSaveDataRequest>(saveId, 0);
getSaveData->Start();
getSaveData->Wait();
std::unique_ptr<GameSave> saveData;
try
{
saveData = std::make_unique<GameSave>(getSaveData->Finish());
}
catch (const http::RequestError &ex)
{
throw std::runtime_error("Could not load save\n" + ByteString(ex.what()));
}
newSave->SetGameSave(std::move(saveData));
gameController->LoadSave(std::move(newSave));
}
catch (std::exception & e)
{
new ErrorMessage("Error", ByteString(e.what()).FromUtf8());
}
}
EngineProcess();
SaveWindowPosition();
};
if (enableBluescreen)
{
try
{
wrapWithBluescreen();
}
catch (const std::exception &e)
{
BlueScreen(ByteString(e.what()).FromUtf8());
}
}
else
{
wrapWithBluescreen();
}
Platform::Exit(0);
return 0;
}

View File

@ -1,12 +0,0 @@
#pragma once
#include "common/String.h"
void EngineProcess();
void ClipboardPush(ByteString text);
ByteString ClipboardPull();
int GetModifiers();
unsigned int GetTicks();
void StartTextInput();
void StopTextInput();
void SetTextInputRect(int x, int y, int w, int h);

View File

@ -1,442 +1,45 @@
#include "Config.h"
#include <ctime>
#include <climits>
#ifdef WIN
#include <direct.h>
#endif
#include "SDLCompat.h"
#ifdef X86_SSE
#include <xmmintrin.h>
#endif
#ifdef X86_SSE3
#include <pmmintrin.h>
#endif
#include <iostream>
#if defined(LIN)
# include "icon_exe.png.h"
#endif
#include <stdexcept>
#ifndef WIN
#include <unistd.h>
#endif
#ifdef MACOSX
# include "common/macosx.h"
#endif
#include "Format.h"
#include "Misc.h"
#include "PowderToySDL.h"
#include "graphics/Graphics.h"
#include "client/SaveInfo.h"
#include "client/GameSave.h"
#include "client/SaveFile.h"
#include "gui/game/GameController.h"
#include "gui/game/GameView.h"
#include "common/platform/Platform.h"
#include "common/tpt-rand.h"
#include "gui/font/FontEditor.h"
#include "gui/dialogues/ErrorMessage.h"
#include "gui/dialogues/ConfirmPrompt.h"
#include "gui/interface/Keys.h"
#include "gui/Style.h"
#include "gui/interface/Engine.h"
#include "Config.h"
#include "SimulationConfig.h"
#include <iostream>
#include <memory>
#include "SDLCompat.h"
int desktopWidth = 1280, desktopHeight = 1024;
SDL_Window * sdl_window;
SDL_Renderer * sdl_renderer;
SDL_Texture * sdl_texture;
int scale = 1;
bool fullscreen = false;
bool altFullscreen = false;
bool forceIntegerScaling = true;
bool resizable = false;
void StartTextInput()
void LoadWindowPosition()
{
SDL_StartTextInput();
}
void StopTextInput()
void SaveWindowPosition()
{
SDL_StopTextInput();
}
void SetTextInputRect(int x, int y, int w, int h)
void LargeScreenDialog()
{
SDL_Rect rect;
rect.x = x;
rect.y = y;
rect.w = w;
rect.h = h;
SDL_SetTextInputRect(&rect);
}
void ClipboardPush(ByteString text)
void TickClient()
{
SDL_SetClipboardText(text.c_str());
}
ByteString ClipboardPull()
struct ExplicitSingletons
{
return ByteString(SDL_GetClipboardText());
}
int GetModifiers()
{
return SDL_GetModState();
}
void CalculateMousePosition(int *x, int *y)
{
int globalMx, globalMy;
SDL_GetGlobalMouseState(&globalMx, &globalMy);
int windowX, windowY;
SDL_GetWindowPosition(sdl_window, &windowX, &windowY);
if (x)
*x = (globalMx - windowX) / scale;
if (y)
*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 (fullscreen || resizable)
SDL_RenderClear(sdl_renderer);
SDL_RenderCopy(sdl_renderer, sdl_texture, NULL, NULL);
SDL_RenderPresent(sdl_renderer);
}
void RecreateWindow();
int SDLOpen()
{
if (SDL_Init(SDL_INIT_VIDEO) < 0)
{
fprintf(stderr, "Initializing SDL: %s\n", SDL_GetError());
return 1;
}
RecreateWindow();
int displayIndex = SDL_GetWindowDisplayIndex(sdl_window);
if (displayIndex >= 0)
{
SDL_Rect rect;
if (!SDL_GetDisplayUsableBounds(displayIndex, &rect))
{
desktopWidth = rect.w;
desktopHeight = rect.h;
}
}
#ifdef LIN
std::vector<pixel> imageData;
int imgw, imgh;
if (PngDataToPixels(imageData, imgw, imgh, reinterpret_cast<const char *>(icon_exe_png), icon_exe_png_size, false))
{
SDL_Surface *icon = SDL_CreateRGBSurfaceFrom(&imageData[0], imgw, imgh, 32, imgw * sizeof(pixel), 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
SDL_SetWindowIcon(sdl_window, icon);
SDL_FreeSurface(icon);
}
#endif
return 0;
}
void SDLSetScreen(int scale_, bool resizable_, bool fullscreen_, bool altFullscreen_, bool 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 || (changingResizable && resizable && !fullscreen))
{
RecreateWindow();
return;
}
if (changingResizable)
SDL_RestoreWindow(sdl_window);
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);
SDL_SetWindowResizable(sdl_window, resizable ? SDL_TRUE : SDL_FALSE);
}
void 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)
{
SDL_DestroyWindow(sdl_window);
}
sdl_window = SDL_CreateWindow(APPNAME, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WINDOWW * scale, WINDOWH * scale,
flags);
sdl_renderer = SDL_CreateRenderer(sdl_window, -1, 0);
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);
}
unsigned int GetTicks()
{
return SDL_GetTicks();
}
int elapsedTime = 0, currentTime = 0, lastTime = 0, currentFrame = 0;
unsigned int lastTick = 0;
unsigned int lastFpsUpdate = 0;
float fps = 0;
ui::Engine * engine = NULL;
bool showDoubleScreenDialog = false;
float currentWidth, currentHeight;
int mousex = 0, mousey = 0;
int mouseButton = 0;
bool mouseDown = false;
bool calculatedInitialMouse = false, delay = false;
bool hasMouseMoved = false;
void EventProcess(SDL_Event event)
{
switch (event.type)
{
case SDL_QUIT:
if (engine->GetFastQuit() || engine->CloseWindow())
engine->Exit();
break;
case SDL_KEYDOWN:
if (SDL_GetModState() & KMOD_GUI)
{
break;
}
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);
break;
case SDL_KEYUP:
if (SDL_GetModState() & KMOD_GUI)
{
break;
}
engine->onKeyRelease(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);
break;
case SDL_TEXTINPUT:
if (SDL_GetModState() & KMOD_GUI)
{
break;
}
engine->onTextInput(ByteString(event.text.text).FromUtf8());
break;
case SDL_TEXTEDITING:
if (SDL_GetModState() & KMOD_GUI)
{
break;
}
engine->onTextEditing(ByteString(event.edit.text).FromUtf8(), event.edit.start);
break;
case SDL_MOUSEWHEEL:
{
// int x = event.wheel.x;
int y = event.wheel.y;
if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED)
{
// x *= -1;
y *= -1;
}
engine->onMouseWheel(mousex, mousey, y); // TODO: pass x?
break;
}
case SDL_MOUSEMOTION:
mousex = event.motion.x;
mousey = event.motion.y;
engine->onMouseMove(mousex, mousey);
hasMouseMoved = true;
break;
case SDL_DROPFILE:
engine->onFileDrop(event.drop.file);
SDL_free(event.drop.file);
break;
case SDL_MOUSEBUTTONDOWN:
// if mouse hasn't moved yet, sdl will send 0,0. We don't want that
if (hasMouseMoved)
{
mousex = event.motion.x;
mousey = event.motion.y;
}
mouseButton = event.button.button;
engine->onMouseClick(event.motion.x, event.motion.y, mouseButton);
mouseDown = true;
#if !defined(NDEBUG) && !defined(DEBUG)
SDL_CaptureMouse(SDL_TRUE);
#endif
break;
case SDL_MOUSEBUTTONUP:
// if mouse hasn't moved yet, sdl will send 0,0. We don't want that
if (hasMouseMoved)
{
mousex = event.motion.x;
mousey = event.motion.y;
}
mouseButton = event.button.button;
engine->onMouseUnclick(mousex, mousey, mouseButton);
mouseDown = false;
#if !defined(NDEBUG) && !defined(DEBUG)
SDL_CaptureMouse(SDL_FALSE);
#endif
break;
case SDL_WINDOWEVENT:
{
switch (event.window.event)
{
case SDL_WINDOWEVENT_SHOWN:
if (!calculatedInitialMouse)
{
//initial mouse coords, sdl won't tell us this if mouse hasn't moved
CalculateMousePosition(&mousex, &mousey);
engine->onMouseMove(mousex, mousey);
calculatedInitialMouse = true;
}
break;
// This event would be needed in certain glitchy cases of window resizing
// But for all currently tested cases, it isn't needed
/*case SDL_WINDOWEVENT_RESIZED:
{
float width = event.window.data1;
float height = event.window.data2;
currentWidth = width;
currentHeight = height;
// this "* scale" thing doesn't really work properly
// currently there is a bug where input doesn't scale properly after resizing, only when double scale mode is active
inputScaleH = (float)WINDOWW * scale / currentWidth;
inputScaleV = (float)WINDOWH * scale / currentHeight;
std::cout << "Changing input scale to " << inputScaleH << "x" << inputScaleV << std::endl;
break;
}*/
// This would send a mouse up event when focus is lost
// Not even sdl itself will know when the mouse was released if it happens in another window
// So it will ignore the next mouse down (after tpt is re-focused) and not send any events at all
// This is more unintuitive than pretending the mouse is still down when it's not, so this code is commented out
/*case SDL_WINDOWEVENT_FOCUS_LOST:
if (mouseDown)
{
mouseDown = false;
engine->onMouseUnclick(mousex, mousey, mouseButton);
}
break;*/
}
break;
}
}
}
void EngineProcess()
{
double frameTimeAvg = 0.0f, correctedFrameTimeAvg = 0.0f;
SDL_Event event;
while(engine->Running())
{
int frameStart = SDL_GetTicks();
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();
engine->Draw();
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->vid);
int frameTime = SDL_GetTicks() - frameStart;
frameTimeAvg = frameTimeAvg * 0.8 + frameTime * 0.2;
float fpsLimit = ui::Engine::Ref().FpsLimit;
if(fpsLimit > 2)
{
double offset = 1000.0 / fpsLimit - frameTimeAvg;
if(offset > 0)
SDL_Delay(Uint32(offset + 0.5));
}
int correctedFrameTime = SDL_GetTicks() - frameStart;
correctedFrameTimeAvg = correctedFrameTimeAvg * 0.95 + correctedFrameTime * 0.05;
if (frameStart - lastFpsUpdate > 200)
{
engine->SetFps(1000.0 / correctedFrameTimeAvg);
lastFpsUpdate = frameStart;
}
if (frameStart - lastTick > 100)
{
lastTick = frameStart;
}
if (showDoubleScreenDialog)
{
showDoubleScreenDialog = false;
}
}
#ifdef DEBUG
std::cout << "Breaking out of EngineProcess" << std::endl;
#endif
}
// These need to be listed in the order they are populated in main.
std::unique_ptr<ui::Engine> engine;
};
static std::unique_ptr<ExplicitSingletons> explicitSingletons;
int main(int argc, char * argv[])
{
currentWidth = WINDOWW;
currentHeight = WINDOWH;
Platform::SetupCrt();
Platform::Atexit([]() {
SDLClose();
explicitSingletons.reset();
});
explicitSingletons = std::make_unique<ExplicitSingletons>();
scale = 1;
if (argc >= 3)
@ -457,6 +60,8 @@ int main(int argc, char * argv[])
if(scale < 1 || scale > 10)
scale = 1;
explicitSingletons->engine = std::make_unique<ui::Engine>();
SDLOpen();
StopTextInput();
@ -468,38 +73,21 @@ int main(int argc, char * argv[])
ui::Engine::Ref().SetAltFullscreen(altFullscreen);
ui::Engine::Ref().SetForceIntegerScaling(forceIntegerScaling);
engine = &ui::Engine::Ref();
engine->SetMaxSize(desktopWidth, desktopHeight);
engine->Begin(WINDOWW, WINDOWH);
engine->SetFastQuit(true);
GameController * gameController = NULL;
auto &engine = ui::Engine::Ref();
engine.Begin();
engine.SetFastQuit(true);
if (argc >= 2)
{
engine->ShowWindow(new FontEditor(argv[1]));
engine.ShowWindow(new FontEditor(argv[1]));
}
else
{
std::cerr << "path to font.cpp not supplied" << std::endl;
exit(1);
Platform::Exit(1);
}
EngineProcess();
ui::Engine::Ref().CloseWindow();
delete gameController;
delete ui::Engine::Ref().g;
if (SDL_GetWindowFlags(sdl_window) & SDL_WINDOW_OPENGL)
{
// * nvidia-460 egl registers callbacks with x11 that end up being called
// after egl is unloaded unless we grab it here and release it after
// sdl closes the display. this is an nvidia driver weirdness but
// technically an sdl bug. glfw has this fixed:
// https://github.com/glfw/glfw/commit/9e6c0c747be838d1f3dc38c2924a47a42416c081
SDL_GL_LoadLibrary(NULL);
SDL_QuitSubSystem(SDL_INIT_VIDEO);
SDL_GL_UnloadLibrary();
}
SDL_Quit();
Platform::Exit(0);
return 0;
}

View File

@ -1,42 +1,17 @@
#include "Config.h"
#include "graphics/Graphics.h"
#include "graphics/Renderer.h"
#include "common/String.h"
#include "common/tpt-rand.h"
#include "Format.h"
#include "gui/interface/Engine.h"
#include "client/GameSave.h"
#include "simulation/Simulation.h"
#include "common/platform/Platform.h"
#include <ctime>
#include <iostream>
#include <fstream>
#include <vector>
#include "common/String.h"
#include "Format.h"
#include "gui/interface/Engine.h"
#include "client/GameSave.h"
#include "simulation/Simulation.h"
void EngineProcess() {}
void ClipboardPush(ByteString) {}
ByteString ClipboardPull() { return ""; }
int GetModifiers() { return 0; }
void SetCursorEnabled(int enabled) {}
unsigned int GetTicks() { return 0; }
static bool ReadFile(std::vector<char> &fileData, ByteString filename)
{
std::ifstream f(filename, std::ios::binary);
if (f) f.seekg(0, std::ios::end);
if (f) fileData.resize(f.tellg());
if (f) f.seekg(0);
if (f) f.read(&fileData[0], fileData.size());
if (!f)
{
std::cerr << "ReadFile: " << filename << ": " << strerror(errno) << std::endl;
return false;
}
return true;
}
int main(int argc, char *argv[])
{
if (!argv[1] || !argv[2]) {
@ -47,15 +22,15 @@ int main(int argc, char *argv[])
auto outputFilename = ByteString(argv[2]) + ".png";
std::vector<char> fileData;
if (!ReadFile(fileData, inputFilename))
if (!Platform::ReadFile(fileData, inputFilename))
{
return 1;
}
GameSave * gameSave = NULL;
std::unique_ptr<GameSave> gameSave;
try
{
gameSave = new GameSave(fileData);
gameSave = std::make_unique<GameSave>(fileData, false);
}
catch (ParseException &e)
{
@ -65,11 +40,11 @@ int main(int argc, char *argv[])
}
Simulation * sim = new Simulation();
Renderer * ren = new Renderer(new Graphics(), sim);
Renderer * ren = new Renderer(sim);
if (gameSave)
{
sim->Load(gameSave, true);
sim->Load(gameSave.get(), true, { 0, 0 });
//Render save
ren->decorations_enable = true;
@ -81,19 +56,19 @@ int main(int argc, char *argv[])
frame--;
ren->render_parts();
ren->render_fire();
ren->clearScreen(1.0f);
ren->clearScreen();
}
}
else
{
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);
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));
}
ren->RenderBegin();
ren->RenderEnd();
VideoBuffer screenBuffer = ren->DumpFrame();
screenBuffer.WritePNG(outputFilename);
if (auto data = ren->DumpFrame().ToPNG())
Platform::WriteFile(*data, outputFilename);
}

View File

@ -1,66 +1,17 @@
#include "PowderToySDL.h"
#include "SimulationConfig.h"
#include "WindowIcon.h"
#include "Config.h"
#include "common/tpt-minmax.h"
#include <map>
#include <optional>
#include <ctime>
#include <climits>
#ifdef WIN
#include <direct.h>
#endif
#include "SDLCompat.h"
#ifdef X86_SSE
#include <xmmintrin.h>
#endif
#ifdef X86_SSE3
#include <pmmintrin.h>
#endif
#include <iostream>
#if defined(LIN)
# include "icon_exe.png.h"
#endif
#include <csignal>
#include <stdexcept>
#ifndef WIN
# include <unistd.h>
#endif
#ifdef MACOSX
# ifdef DEBUG
# undef DEBUG
# define DEBUG 1
# endif
# include <CoreServices/CoreServices.h>
#endif
#include <sys/stat.h>
#include "Format.h"
#include "Misc.h"
#include "client/Client.h"
#include "client/GameSave.h"
#include "client/SaveFile.h"
#include "client/SaveInfo.h"
#include "common/Platform.h"
#include "graphics/Graphics.h"
#include "gui/Style.h"
#include "gui/game/GameController.h"
#include "gui/game/GameView.h"
#include "gui/dialogues/ConfirmPrompt.h"
#include "gui/dialogues/ErrorMessage.h"
#include "gui/interface/Engine.h"
#include "gui/interface/Keys.h"
#include "graphics/Graphics.h"
#include "common/platform/Platform.h"
#include <iostream>
#include "SDLCompat.h"
int desktopWidth = 1280, desktopHeight = 1024;
SDL_Window * sdl_window;
SDL_Renderer * sdl_renderer;
SDL_Texture * sdl_texture;
int desktopWidth = 1280;
int desktopHeight = 1024;
SDL_Window *sdl_window = NULL;
SDL_Renderer *sdl_renderer = NULL;
SDL_Texture *sdl_texture = NULL;
int scale = 1;
bool fullscreen = false;
bool altFullscreen = false;
@ -68,6 +19,15 @@ bool forceIntegerScaling = true;
bool resizable = false;
bool momentumScroll = true;
bool showAvatars = true;
uint64_t lastTick = 0;
uint64_t lastFpsUpdate = 0;
bool showLargeScreenDialog = false;
int mousex = 0;
int mousey = 0;
int mouseButton = 0;
bool mouseDown = false;
bool calculatedInitialMouse = false;
bool hasMouseMoved = false;
void StartTextInput()
{
@ -104,46 +64,9 @@ int GetModifiers()
return SDL_GetModState();
}
void LoadWindowPosition()
unsigned int GetTicks()
{
int savedWindowX = Client::Ref().GetPrefInteger("WindowX", INT_MAX);
int savedWindowY = Client::Ref().GetPrefInteger("WindowY", INT_MAX);
int borderTop, borderLeft;
SDL_GetWindowBordersSize(sdl_window, &borderTop, &borderLeft, nullptr, nullptr);
// Sometimes (Windows), the border size may not be reported for 200+ frames
// So just have a default of 5 to ensure the window doesn't get stuck where it can't be moved
if (borderTop == 0)
borderTop = 5;
int numDisplays = SDL_GetNumVideoDisplays();
SDL_Rect displayBounds;
bool ok = false;
for (int i = 0; i < numDisplays; i++)
{
SDL_GetDisplayBounds(i, &displayBounds);
if (savedWindowX + borderTop > displayBounds.x && savedWindowY + borderLeft > displayBounds.y &&
savedWindowX + borderTop < displayBounds.x + displayBounds.w &&
savedWindowY + borderLeft < displayBounds.y + displayBounds.h)
{
ok = true;
break;
}
}
if (ok)
SDL_SetWindowPosition(sdl_window, savedWindowX + borderLeft, savedWindowY + borderTop);
}
void SaveWindowPosition()
{
int x, y;
SDL_GetWindowPosition(sdl_window, &x, &y);
int borderTop, borderLeft;
SDL_GetWindowBordersSize(sdl_window, &borderTop, &borderLeft, nullptr, nullptr);
Client::Ref().SetPref("WindowX", x - borderLeft);
Client::Ref().SetPref("WindowY", y - borderTop);
return SDL_GetTicks();
}
void CalculateMousePosition(int *x, int *y)
@ -159,7 +82,7 @@ void CalculateMousePosition(int *x, int *y)
*y = (globalMy - windowY) / scale;
}
void blit(pixel * vid)
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)
@ -169,19 +92,18 @@ void blit(pixel * vid)
SDL_RenderPresent(sdl_renderer);
}
bool RecreateWindow();
void SDLOpen()
{
if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
{
fprintf(stderr, "Initializing SDL (video subsystem): %s\n", SDL_GetError());
exit(-1);
Platform::Exit(-1);
}
if (!RecreateWindow())
{
fprintf(stderr, "Creating SDL window: %s\n", SDL_GetError());
exit(-1);
Platform::Exit(-1);
}
int displayIndex = SDL_GetWindowDisplayIndex(sdl_window);
@ -195,16 +117,26 @@ void SDLOpen()
}
}
#ifdef LIN
std::vector<pixel> imageData;
int imgw, imgh;
if (PngDataToPixels(imageData, imgw, imgh, reinterpret_cast<const char *>(icon_exe_png), icon_exe_png_size, false))
if constexpr (SET_WINDOW_ICON)
{
SDL_Surface *icon = SDL_CreateRGBSurfaceFrom(&imageData[0], imgw, imgh, 32, imgw * sizeof(pixel), 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
SDL_SetWindowIcon(sdl_window, icon);
SDL_FreeSurface(icon);
WindowIcon(sdl_window);
}
#endif
}
void SDLClose()
{
if (SDL_GetWindowFlags(sdl_window) & SDL_WINDOW_OPENGL)
{
// * nvidia-460 egl registers callbacks with x11 that end up being called
// after egl is unloaded unless we grab it here and release it after
// sdl closes the display. this is an nvidia driver weirdness but
// technically an sdl bug. glfw has this fixed:
// https://github.com/glfw/glfw/commit/9e6c0c747be838d1f3dc38c2924a47a42416c081
SDL_GL_LoadLibrary(NULL);
SDL_QuitSubSystem(SDL_INIT_VIDEO);
SDL_GL_UnloadLibrary();
}
SDL_Quit();
}
void SDLSetScreen(int scale_, bool resizable_, bool fullscreen_, bool altFullscreen_, bool forceIntegerScaling_)
@ -286,39 +218,19 @@ bool RecreateWindow()
//SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
//SDL_SetWindowResizable(sdl_window, SDL_TRUE);
if (!Client::Ref().IsFirstRun())
LoadWindowPosition();
LoadWindowPosition();
return true;
}
unsigned int GetTicks()
{
return SDL_GetTicks();
}
int elapsedTime = 0, currentTime = 0, lastTime = 0, currentFrame = 0;
unsigned int lastTick = 0;
unsigned int lastFpsUpdate = 0;
float fps = 0;
ui::Engine * engine = NULL;
bool showLargeScreenDialog = false;
float currentWidth, currentHeight;
int mousex = 0, mousey = 0;
int mouseButton = 0;
bool mouseDown = false;
bool calculatedInitialMouse = false, delay = false;
bool hasMouseMoved = false;
void EventProcess(SDL_Event event)
void EventProcess(const SDL_Event &event)
{
auto &engine = ui::Engine::Ref();
switch (event.type)
{
case SDL_QUIT:
if (engine->GetFastQuit() || engine->CloseWindow())
engine->Exit();
if (engine.GetFastQuit() || engine.CloseWindow())
engine.Exit();
break;
case SDL_KEYDOWN:
if (SDL_GetModState() & KMOD_GUI)
@ -326,30 +238,30 @@ void EventProcess(SDL_Event event)
break;
}
if (!event.key.repeat && event.key.keysym.sym == 'q' && (event.key.keysym.mod&KMOD_CTRL))
engine->ConfirmExit();
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);
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);
break;
case SDL_KEYUP:
if (SDL_GetModState() & KMOD_GUI)
{
break;
}
engine->onKeyRelease(event.key.keysym.sym, event.key.keysym.scancode, event.key.repeat, event.key.keysym.mod&KMOD_SHIFT, event.key.keysym.mod&KMOD_CTRL, event.key.keysym.mod&KMOD_ALT);
engine.onKeyRelease(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);
break;
case SDL_TEXTINPUT:
if (SDL_GetModState() & KMOD_GUI)
{
break;
}
engine->onTextInput(ByteString(event.text.text).FromUtf8());
engine.onTextInput(ByteString(event.text.text).FromUtf8());
break;
case SDL_TEXTEDITING:
if (SDL_GetModState() & KMOD_GUI)
{
break;
}
engine->onTextEditing(ByteString(event.edit.text).FromUtf8(), event.edit.start);
engine.onTextEditing(ByteString(event.edit.text).FromUtf8(), event.edit.start);
break;
case SDL_MOUSEWHEEL:
{
@ -361,18 +273,18 @@ void EventProcess(SDL_Event event)
y *= -1;
}
engine->onMouseWheel(mousex, mousey, y); // TODO: pass x?
engine.onMouseWheel(mousex, mousey, y); // TODO: pass x?
break;
}
case SDL_MOUSEMOTION:
mousex = event.motion.x;
mousey = event.motion.y;
engine->onMouseMove(mousex, mousey);
engine.onMouseMove(mousex, mousey);
hasMouseMoved = true;
break;
case SDL_DROPFILE:
engine->onFileDrop(event.drop.file);
engine.onFileDrop(event.drop.file);
SDL_free(event.drop.file);
break;
case SDL_MOUSEBUTTONDOWN:
@ -383,12 +295,13 @@ void EventProcess(SDL_Event event)
mousey = event.button.y;
}
mouseButton = event.button.button;
engine->onMouseClick(mousex, mousey, mouseButton);
engine.onMouseClick(mousex, mousey, mouseButton);
mouseDown = true;
#if !defined(NDEBUG) && !defined(DEBUG)
SDL_CaptureMouse(SDL_TRUE);
#endif
if constexpr (!DEBUG)
{
SDL_CaptureMouse(SDL_TRUE);
}
break;
case SDL_MOUSEBUTTONUP:
// if mouse hasn't moved yet, sdl will send 0,0. We don't want that
@ -398,12 +311,13 @@ void EventProcess(SDL_Event event)
mousey = event.button.y;
}
mouseButton = event.button.button;
engine->onMouseUnclick(mousex, mousey, mouseButton);
engine.onMouseUnclick(mousex, mousey, mouseButton);
mouseDown = false;
#if !defined(NDEBUG) && !defined(DEBUG)
SDL_CaptureMouse(SDL_FALSE);
#endif
if constexpr (!DEBUG)
{
SDL_CaptureMouse(SDL_FALSE);
}
break;
case SDL_WINDOWEVENT:
{
@ -414,119 +328,79 @@ void EventProcess(SDL_Event event)
{
//initial mouse coords, sdl won't tell us this if mouse hasn't moved
CalculateMousePosition(&mousex, &mousey);
engine->initialMouse(mousex, mousey);
engine->onMouseMove(mousex, mousey);
engine.initialMouse(mousex, mousey);
engine.onMouseMove(mousex, mousey);
calculatedInitialMouse = true;
}
break;
// This event would be needed in certain glitchy cases of window resizing
// But for all currently tested cases, it isn't needed
/*case SDL_WINDOWEVENT_RESIZED:
{
float width = event.window.data1;
float height = event.window.data2;
currentWidth = width;
currentHeight = height;
// this "* scale" thing doesn't really work properly
// currently there is a bug where input doesn't scale properly after resizing, only when double scale mode is active
inputScaleH = (float)WINDOWW * scale / currentWidth;
inputScaleV = (float)WINDOWH * scale / currentHeight;
std::cout << "Changing input scale to " << inputScaleH << "x" << inputScaleV << std::endl;
break;
}*/
// This would send a mouse up event when focus is lost
// Not even sdl itself will know when the mouse was released if it happens in another window
// So it will ignore the next mouse down (after tpt is re-focused) and not send any events at all
// This is more unintuitive than pretending the mouse is still down when it's not, so this code is commented out
/*case SDL_WINDOWEVENT_FOCUS_LOST:
if (mouseDown)
{
mouseDown = false;
engine->onMouseUnclick(mousex, mousey, mouseButton);
}
break;*/
}
break;
}
}
}
void LargeScreenDialog()
{
StringBuilder message;
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 << "\nTo undo this, hit Cancel. You can change this in settings at any time.";
if (!ConfirmPrompt::Blocking("Large screen detected", message.Build()))
{
Client::Ref().SetPref("Scale", 1);
engine->SetScale(1);
}
}
void EngineProcess()
{
double frameTimeAvg = 0.0f, correctedFrameTimeAvg = 0.0f;
double correctedFrameTimeAvg = 0;
SDL_Event event;
int drawingTimer = 0;
int frameStart = 0;
uint64_t drawingTimer = 0;
auto frameStart = uint64_t(SDL_GetTicks()) * UINT64_C(1'000'000);
while(engine->Running())
auto &engine = ui::Engine::Ref();
while(engine.Running())
{
int oldFrameStart = frameStart;
frameStart = SDL_GetTicks();
drawingTimer += frameStart - oldFrameStart;
if(engine->Broken()) { engine->UnBreak(); break; }
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; }
if(engine.Broken()) { engine.UnBreak(); break; }
engine->Tick();
engine.Tick();
int drawcap = ui::Engine::Ref().GetDrawingFrequencyLimit();
if (!drawcap || drawingTimer > 1000.f/drawcap)
if (!drawcap || drawingTimer > 1e9f / drawcap)
{
engine->Draw();
engine.Draw();
drawingTimer = 0;
if (scale != engine->Scale || fullscreen != engine->Fullscreen ||
altFullscreen != engine->GetAltFullscreen() ||
forceIntegerScaling != engine->GetForceIntegerScaling() || resizable != engine->GetResizable())
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());
SDLSetScreen(engine.Scale, engine.GetResizable(), engine.Fullscreen, engine.GetAltFullscreen(),
engine.GetForceIntegerScaling());
}
blit(engine->g->vid);
blit(engine.g->Data());
}
int frameTime = SDL_GetTicks() - frameStart;
frameTimeAvg = frameTimeAvg * 0.8 + frameTime * 0.2;
float fpsLimit = ui::Engine::Ref().FpsLimit;
if(fpsLimit > 2)
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)
{
double offset = 1000.0 / fpsLimit - frameTimeAvg;
if(offset > 0)
SDL_Delay(Uint32(offset + 0.5));
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));
}
int correctedFrameTime = SDL_GetTicks() - frameStart;
correctedFrameTimeAvg = correctedFrameTimeAvg * 0.95 + correctedFrameTime * 0.05;
if (frameStart - lastFpsUpdate > 200)
auto correctedFrameTime = frameStart - oldFrameStart;
drawingTimer += correctedFrameTime;
correctedFrameTimeAvg = correctedFrameTimeAvg + (correctedFrameTime - correctedFrameTimeAvg) * 0.05;
if (frameStart - lastFpsUpdate > UINT64_C(200'000'000))
{
engine->SetFps(1000.0 / correctedFrameTimeAvg);
engine.SetFps(1e9f / correctedFrameTimeAvg);
lastFpsUpdate = frameStart;
}
if (frameStart - lastTick > 100)
if (frameStart - lastTick > UINT64_C(100'000'000))
{
lastTick = frameStart;
Client::Ref().Tick();
TickClient();
}
if (showLargeScreenDialog)
{
@ -534,438 +408,8 @@ void EngineProcess()
LargeScreenDialog();
}
}
#ifdef DEBUG
std::cout << "Breaking out of EngineProcess" << std::endl;
#endif
}
void BlueScreen(String detailMessage)
{
ui::Engine * engine = &ui::Engine::Ref();
engine->g->fillrect(0, 0, engine->GetWidth(), engine->GetHeight(), 17, 114, 169, 210);
String errorTitle = "ERROR";
String errorDetails = "Details: " + detailMessage;
String errorHelp = "An unrecoverable fault has occurred, please report the error by visiting the website below\n"
SCHEME SERVER;
int currentY = 0, width, height;
int errorWidth = 0;
Graphics::textsize(errorHelp, errorWidth, height);
engine->g->drawtext((engine->GetWidth()/2)-(errorWidth/2), ((engine->GetHeight()/2)-100) + currentY, errorTitle.c_str(), 255, 255, 255, 255);
Graphics::textsize(errorTitle, width, height);
currentY += height + 4;
engine->g->drawtext((engine->GetWidth()/2)-(errorWidth/2), ((engine->GetHeight()/2)-100) + currentY, errorDetails.c_str(), 255, 255, 255, 255);
Graphics::textsize(errorTitle, width, height);
currentY += height + 4;
engine->g->drawtext((engine->GetWidth()/2)-(errorWidth/2), ((engine->GetHeight()/2)-100) + currentY, errorHelp.c_str(), 255, 255, 255, 255);
Graphics::textsize(errorTitle, width, height);
currentY += height + 4;
//Death loop
SDL_Event event;
while(true)
if constexpr (DEBUG)
{
while (SDL_PollEvent(&event))
if(event.type == SDL_QUIT)
exit(-1);
blit(engine->g->vid);
std::cout << "Breaking out of EngineProcess" << std::endl;
}
}
void SigHandler(int signal)
{
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;
}
}
constexpr int SCALE_MAXIMUM = 10;
constexpr int SCALE_MARGIN = 30;
int GuessBestScale()
{
const int widthNoMargin = desktopWidth - SCALE_MARGIN;
const int widthGuess = widthNoMargin / WINDOWW;
const int heightNoMargin = desktopHeight - SCALE_MARGIN;
const int heightGuess = heightNoMargin / WINDOWH;
int guess = std::min(widthGuess, heightGuess);
if(guess < 1 || guess > SCALE_MAXIMUM)
guess = 1;
return guess;
}
int main(int argc, char * argv[])
{
#if defined(DEBUG) && defined(_MSC_VER)
_CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG);
#endif
currentWidth = WINDOWW;
currentHeight = WINDOWH;
// https://bugzilla.libsdl.org/show_bug.cgi?id=3796
if (SDL_Init(0) < 0)
{
fprintf(stderr, "Initializing SDL: %s\n", SDL_GetError());
return 1;
}
Platform::originalCwd = Platform::GetCwd();
using Argument = std::optional<ByteString>;
std::map<ByteString, Argument> arguments;
for (auto i = 1; i < argc; ++i)
{
auto str = ByteString(argv[i]);
if (str.BeginsWith("file://"))
{
arguments.insert({ "open", format::URLDecode(str.substr(7 /* length of the "file://" prefix */)) });
}
else if (str.BeginsWith("ptsave:"))
{
arguments.insert({ "ptsave", str });
}
else if (auto split = str.SplitBy(':'))
{
arguments.insert({ split.Before(), split.After() });
}
else if (auto split = str.SplitBy('='))
{
arguments.insert({ split.Before(), split.After() });
}
else if (str == "open" || str == "ptsave" || str == "ddir")
{
if (i + 1 < argc)
{
arguments.insert({ str, argv[i + 1] });
i += 1;
}
else
{
std::cerr << "no value provided for command line parameter " << str << std::endl;
}
}
else
{
arguments.insert({ str, "" }); // so .has_value() is true
}
}
auto ddirArg = arguments["ddir"];
if (ddirArg.has_value())
{
#ifdef WIN
int failure = _chdir(ddirArg.value().c_str());
#else
int failure = chdir(ddirArg.value().c_str());
#endif
if (!failure)
Platform::sharedCwd = Platform::GetCwd();
else
perror("failed to chdir to requested ddir");
}
else
{
char *ddir = SDL_GetPrefPath(NULL, APPDATA);
#ifdef WIN
struct _stat s;
if (_stat("powder.pref", &s) != 0)
#else
struct stat s;
if (stat("powder.pref", &s) != 0)
#endif
{
if (ddir)
{
#ifdef WIN
int failure = _chdir(ddir);
#else
int failure = chdir(ddir);
#endif
if (failure)
{
perror("failed to chdir to default ddir");
SDL_free(ddir);
ddir = nullptr;
}
}
}
if (ddir)
{
Platform::sharedCwd = ddir;
SDL_free(ddir);
}
}
scale = Client::Ref().GetPrefInteger("Scale", 1);
resizable = Client::Ref().GetPrefBool("Resizable", false);
fullscreen = Client::Ref().GetPrefBool("Fullscreen", false);
altFullscreen = Client::Ref().GetPrefBool("AltFullscreen", false);
forceIntegerScaling = Client::Ref().GetPrefBool("ForceIntegerScaling", true);
momentumScroll = Client::Ref().GetPrefBool("MomentumScroll", true);
showAvatars = Client::Ref().GetPrefBool("ShowAvatars", true);
auto true_string = [](ByteString str) {
str = str.ToLower();
return str == "true" ||
str == "t" ||
str == "on" ||
str == "yes" ||
str == "y" ||
str == ""; // standalone "redirect" or "disable-bluescreen" or similar arguments
};
auto true_arg = [&true_string](Argument arg) {
return arg.has_value() && true_string(arg.value());
};
auto kioskArg = arguments["kiosk"];
if (kioskArg.has_value())
{
fullscreen = true_string(kioskArg.value());
Client::Ref().SetPref("Fullscreen", fullscreen);
}
if (true_arg(arguments["redirect"]))
{
FILE *new_stdout = freopen("stdout.log", "w", stdout);
FILE *new_stderr = freopen("stderr.log", "w", stderr);
if (!new_stdout || !new_stderr)
{
exit(42);
}
}
auto scaleArg = arguments["scale"];
if (scaleArg.has_value())
{
try
{
scale = scaleArg.value().ToNumber<int>();
Client::Ref().SetPref("Scale", scale);
}
catch (const std::runtime_error &e)
{
std::cerr << "failed to set scale: " << e.what() << std::endl;
}
}
auto clientConfig = [](Argument arg, ByteString name, ByteString defaultValue) {
ByteString value;
if (arg.has_value())
{
if (value == "")
{
value = defaultValue;
}
Client::Ref().SetPref(name, value);
}
else
{
value = Client::Ref().GetPrefByteString(name, defaultValue);
}
return value;
};
ByteString proxyString = clientConfig(arguments["proxy"], "Proxy", "");
ByteString cafileString = clientConfig(arguments["cafile"], "CAFile", "");
ByteString capathString = clientConfig(arguments["capath"], "CAPath", "");
bool disableNetwork = true_arg(arguments["disable-network"]);
Client::Ref().Initialise(proxyString, cafileString, capathString, disableNetwork);
// TODO: maybe bind the maximum allowed scale to screen size somehow
if(scale < 1 || scale > SCALE_MAXIMUM)
scale = 1;
SDLOpen();
if (Client::Ref().IsFirstRun())
{
scale = GuessBestScale();
if (scale > 1)
{
Client::Ref().SetPref("Scale", scale);
SDL_SetWindowSize(sdl_window, WINDOWW * scale, WINDOWH * scale);
showLargeScreenDialog = true;
}
}
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);
ui::Engine::Ref().MomentumScroll = momentumScroll;
ui::Engine::Ref().ShowAvatars = showAvatars;
engine = &ui::Engine::Ref();
engine->SetMaxSize(desktopWidth, desktopHeight);
engine->Begin(WINDOWW, WINDOWH);
engine->SetFastQuit(Client::Ref().GetPrefBool("FastQuit", true));
#if !defined(DEBUG)
bool enableBluescreen = !true_arg(arguments["disable-bluescreen"]);
if (enableBluescreen)
{
//Get ready to catch any dodgy errors
signal(SIGSEGV, SigHandler);
signal(SIGFPE, SigHandler);
signal(SIGILL, SigHandler);
signal(SIGABRT, SigHandler);
}
#endif
#ifdef X86_SSE
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
#endif
#ifdef X86_SSE3
_MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
#endif
GameController * gameController = NULL;
auto wrapWithBluescreen = [&]() {
gameController = new GameController();
engine->ShowWindow(gameController->GetView());
auto openArg = arguments["open"];
if (openArg.has_value())
{
#ifdef DEBUG
std::cout << "Loading " << openArg.value() << std::endl;
#endif
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");
}
}
auto ptsaveArg = arguments["ptsave"];
if (ptsaveArg.has_value())
{
engine->g->fillrect((engine->GetWidth()/2)-101, (engine->GetHeight()/2)-26, 202, 52, 0, 0, 0, 210);
engine->g->drawrect((engine->GetWidth()/2)-100, (engine->GetHeight()/2)-25, 200, 50, 255, 255, 255, 180);
engine->g->drawtext((engine->GetWidth()/2)-(Graphics::textwidth("Loading save...")/2), (engine->GetHeight()/2)-5, "Loading save...", style::Colour::InformationTitle.Red, style::Colour::InformationTitle.Green, style::Colour::InformationTitle.Blue, 255);
blit(engine->g->vid);
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");
#ifdef DEBUG
std::cout << "Got Ptsave: id: " << saveIdPart << std::endl;
#endif
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", ByteString(e.what()).FromUtf8());
}
}
EngineProcess();
SaveWindowPosition();
};
#if !defined(DEBUG)
if (enableBluescreen)
{
try
{
wrapWithBluescreen();
}
catch (const std::exception &e)
{
BlueScreen(ByteString(e.what()).FromUtf8());
}
}
else
#endif
wrapWithBluescreen(); // the else branch of the if in the #if !defined(DEBUG)
ui::Engine::Ref().CloseWindow();
delete gameController;
delete ui::Engine::Ref().g;
Client::Ref().Shutdown();
if (SDL_GetWindowFlags(sdl_window) & SDL_WINDOW_OPENGL)
{
// * nvidia-460 egl registers callbacks with x11 that end up being called
// after egl is unloaded unless we grab it here and release it after
// sdl closes the display. this is an nvidia driver weirdness but
// technically an sdl bug. glfw has this fixed:
// https://github.com/glfw/glfw/commit/9e6c0c747be838d1f3dc38c2924a47a42416c081
SDL_GL_LoadLibrary(NULL);
SDL_QuitSubSystem(SDL_INIT_VIDEO);
SDL_GL_UnloadLibrary();
}
SDL_Quit();
return 0;
}

47
src/PowderToySDL.h Normal file
View File

@ -0,0 +1,47 @@
#pragma once
#include "common/String.h"
#include "graphics/Pixel.h"
#include <cstdint>
#include <SDL.h>
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;
extern uint64_t lastFpsUpdate;
extern bool showLargeScreenDialog;
extern int mousex;
extern int mousey;
extern int mouseButton;
extern bool mouseDown;
extern bool calculatedInitialMouse;
extern bool hasMouseMoved;
void EngineProcess();
void StartTextInput();
void StopTextInput();
void SetTextInputRect(int x, int y, int w, int h);
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(int scale_, bool resizable_, bool fullscreen_, bool altFullscreen_, bool forceIntegerScaling_);
bool RecreateWindow();
void LoadWindowPosition();
void SaveWindowPosition();
void LargeScreenDialog();
void TickClient();
void EventProcess(const SDL_Event &event);

View File

@ -14,10 +14,9 @@
*/
#include "Probability.h"
#include "common/tpt-rand.h"
#include <numeric>
#include <cstdlib>
#include "common/tpt-rand.h"
namespace Probability
{

View File

@ -13,12 +13,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef tptmath_h
#define tptmath_h
#include "Config.h"
#pragma once
// This file is used for EMP, to simulate many EMP going off at once at the end of the frame
#include <cmath>
namespace Probability
@ -41,5 +37,3 @@ namespace Probability
unsigned int calc(float randFloat);
};
}
#endif

View File

@ -1,7 +0,0 @@
#include "Config.h"
#include <SDL.h>
#ifdef INCLUDE_SYSWM
# if defined(WIN)
# include <SDL_syswm.h>
# endif // WIN
#endif // INCLUDE_SYSWM

63
src/SimulationConfig.h Normal file
View File

@ -0,0 +1,63 @@
#pragma once
#include <cstdint>
#include <common/Vec2.h>
constexpr int MENUSIZE = 40;
constexpr int BARSIZE = 17;
constexpr float M_GRAV = 6.67300e-1f;
//CELL, the size of the pressure, gravity, and wall maps. Larger than 1 to prevent extreme lag
constexpr int CELL = 4;
constexpr Vec2<int> CELLS = Vec2(153, 96);
constexpr Vec2<int> RES = CELLS * CELL;
constexpr int XCELLS = CELLS.X;
constexpr int YCELLS = CELLS.Y;
constexpr int NCELL = XCELLS * YCELLS;
constexpr int XRES = RES.X;
constexpr int YRES = RES.Y;
constexpr int NPART = XRES * YRES;
constexpr int XCNTR = XRES / 2;
constexpr int YCNTR = YRES / 2;
constexpr Vec2<int> WINDOW = RES + Vec2(BARSIZE, MENUSIZE);
constexpr int WINDOWW = WINDOW.X;
constexpr int WINDOWH = WINDOW.Y;
constexpr int MAXSIGNS = 16;
constexpr int ISTP = CELL / 2;
constexpr float CFDS = 4.0f / CELL;
constexpr float SIM_MAXVELOCITY = 1e4f;
//Air constants
constexpr float AIR_TSTEPP = 0.3f;
constexpr float AIR_TSTEPV = 0.4f;
constexpr float AIR_VADV = 0.3f;
constexpr float AIR_VLOSS = 0.999f;
constexpr float AIR_PLOSS = 0.9999f;
constexpr int NGOL = 24;
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;
constexpr int NORMAL_MIN_EST = 3;
constexpr int NORMAL_INTERP = 20;
constexpr int NORMAL_FRAC = 16;
constexpr auto REFRACT = UINT32_C(0x80000000);
/* heavy flint glass, for awesome refraction/dispersion
this way you can make roof prisms easily */
constexpr float GLASS_IOR = 1.9f;
constexpr float GLASS_DISP = 0.07f;
constexpr float R_TEMP = 22;

View File

@ -1,155 +0,0 @@
#include "Update.h"
#include <cstdio>
#include <cstdlib>
#ifndef WIN
#include <sys/param.h>
#endif
#if !defined(MACOSX) && !defined(BSD)
#include <malloc.h>
#endif
#include <cstring>
#include <cstdint>
#ifdef WIN
# ifndef NOMINMAX
# define NOMINMAX
# endif
# include <windows.h>
#else
# include <unistd.h>
# include <sys/stat.h>
#endif
#ifdef MACOSX
# include <mach-o/dyld.h>
# include <errno.h>
#endif
#include "common/Platform.h"
// returns 1 on failure, 0 on success
int update_start(char *data, unsigned int len)
{
ByteString exeName = Platform::ExecutableName(), updName;
FILE *f;
if (!exeName.length())
return 1;
#ifdef WIN
updName = exeName;
ByteString extension = exeName.substr(exeName.length() - 4);
if (extension == ".exe")
updName = exeName.substr(0, exeName.length() - 4);
updName = updName + "_upd.exe";
if (!MoveFile(Platform::WinWiden(exeName).c_str(), Platform::WinWiden(updName).c_str()))
return 1;
f = fopen(exeName.c_str(), "wb");
if (!f)
return 1;
if (fwrite(data, 1, len, f) != len)
{
fclose(f);
Platform::RemoveFile(exeName);
return 1;
}
fclose(f);
if ((uintptr_t)ShellExecute(NULL, L"open", Platform::WinWiden(exeName).c_str(), NULL, NULL, SW_SHOWNORMAL) <= 32)
{
Platform::RemoveFile(exeName);
return 1;
}
return 0;
#else
updName = exeName + "-update";
f = fopen(updName.c_str(), "w");
if (!f)
return 1;
if (fwrite(data, 1, len, f) != len)
{
fclose(f);
unlink(updName.c_str());
return 1;
}
fclose(f);
if (chmod(updName.c_str(), 0755))
{
unlink(updName.c_str());
return 1;
}
if (rename(updName.c_str(), exeName.c_str()))
{
unlink(updName.c_str());
return 1;
}
execl(exeName.c_str(), "powder-update", NULL);
return 0;
#endif
}
// returns 1 on failure, 0 on success
int update_finish()
{
#ifdef WIN
ByteString exeName = Platform::ExecutableName(), updName;
int timeout = 5, err;
#ifdef DEBUG
printf("Update: Current EXE name: %s\n", exeName.c_str());
#endif
updName = exeName;
ByteString extension = exeName.substr(exeName.length() - 4);
if (extension == ".exe")
updName = exeName.substr(0, exeName.length() - 4);
updName = updName + "_upd.exe";
#ifdef DEBUG
printf("Update: Temp EXE name: %s\n", updName.c_str());
#endif
while (!Platform::RemoveFile(updName))
{
err = GetLastError();
if (err == ERROR_FILE_NOT_FOUND)
{
#ifdef DEBUG
printf("Update: Temp file not deleted\n");
#endif
// Old versions of powder toy name their update files with _update.exe, delete that upgrade file here
updName = exeName;
ByteString extension = exeName.substr(exeName.length() - 4);
if (extension == ".exe")
updName = exeName.substr(0, exeName.length() - 4);
updName = updName + "_update.exe";
Platform::RemoveFile(updName);
return 0;
}
Sleep(500);
timeout--;
if (timeout <= 0)
{
#ifdef DEBUG
printf("Update: Delete timeout\n");
#endif
return 1;
}
}
#endif
return 0;
}
void update_cleanup()
{
#ifdef WIN
update_finish();
#endif
}

View File

@ -1,10 +0,0 @@
#ifndef UPDATE_H_
#define UPDATE_H_
#include "Config.h"
//char *exe_name(void);
int update_start(char *data, unsigned int len);
int update_finish();
void update_cleanup();
#endif /* UPDATE_H_ */

15
src/WindowIcon.cpp Normal file
View File

@ -0,0 +1,15 @@
#include "Format.h"
#include "graphics/Graphics.h"
#include "WindowIcon.h"
#include "icon_exe.png.h"
void WindowIcon(SDL_Window *window)
{
if (auto image = format::PixelsFromPNG(std::vector<char>(icon_exe_png, icon_exe_png + icon_exe_png_size)))
{
SDL_Surface *icon = SDL_CreateRGBSurfaceFrom(image->data(), image->Size().X, image->Size().Y, 32, image->Size().Y * sizeof(pixel), 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
SDL_SetWindowIcon(window, icon);
SDL_FreeSurface(icon);
}
}

4
src/WindowIcon.h Normal file
View File

@ -0,0 +1,4 @@
#pragma once
#include <SDL.h>
void WindowIcon(SDL_Window *window);

9
src/X86KillDenormals.cpp Normal file
View File

@ -0,0 +1,9 @@
#include "X86KillDenormals.h"
#include <xmmintrin.h>
#include <pmmintrin.h>
void X86KillDenormals()
{
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
_MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
}

3
src/X86KillDenormals.h Normal file
View File

@ -0,0 +1,3 @@
#pragma once
void X86KillDenormals();

View File

@ -975,7 +975,7 @@ void bson_fatal_msg( int ok , const char *msg ) {
}
bson_errprintf( "error: %s\n" , msg );
exit( -5 );
abort();
}

View File

@ -18,22 +18,18 @@
* limitations under the License.
*/
#ifndef _BSON_H_
#define _BSON_H_
#include "Config.h"
#pragma once
#include "common/tpt-inline.h"
#include <ctime>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cstdarg>
#include <climits>
#include "common/tpt-inline.h"
#include <cstdint>
#define BSON_OK 0
#define BSON_ERROR -1
constexpr int BSON_OK = 0;
constexpr int BSON_ERROR = -1;
static const char bson_numstrs[1000][4] = {
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
@ -1210,5 +1206,3 @@ static TPT_INLINE void bson_swap_endian32( void *outp, const void *inp ) {
out[2] = in[1];
out[3] = in[0];
}
#endif

View File

@ -1,7 +1,5 @@
#include "bz2wrap.h"
#include "bzlib.h"
#include <memory>
#include <functional>
#include <vector>

View File

@ -1,5 +1,4 @@
#pragma once
#include <cstddef>
#include <vector>

File diff suppressed because it is too large Load Diff

View File

@ -1,69 +1,42 @@
#ifndef CLIENT_H
#define CLIENT_H
#include "Config.h"
#include <vector>
#include <list>
#pragma once
#include "common/String.h"
#include "common/Singleton.h"
#include <json/json.h>
#include "common/ExplicitSingleton.h"
#include "StartupInfo.h"
#include "User.h"
#include <vector>
#include <cstdint>
#include <list>
#include <memory>
#include <json/json.h>
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 Request;
class StartupRequest;
}
class Client: public Singleton<Client> {
class Client: public ExplicitSingleton<Client> {
private:
String messageOfTheDay;
std::vector<std::pair<String, ByteString> > serverNotifications;
std::vector<ServerNotification> serverNotifications;
http::Request *versionCheckRequest;
http::Request *alternateVersionCheckRequest;
std::unique_ptr<http::StartupRequest> versionCheckRequest;
std::unique_ptr<http::StartupRequest> alternateVersionCheckRequest;
bool usingAltUpdateServer;
bool updateAvailable;
UpdateInfo updateInfo;
std::optional<UpdateInfo> updateInfo;
String lastError;
bool firstRun;
std::list<ByteString> stampIDs;
unsigned lastStampTime;
int lastStampName;
std::vector<ByteString> stampIDs;
uint64_t lastStampTime = 0;
int lastStampName = 0;
//Auth session
User authUser;
@ -71,16 +44,15 @@ private:
void notifyUpdateAvailable();
void notifyAuthUserChanged();
void notifyMessageOfTheDay();
void notifyNewNotification(std::pair<String, ByteString> notification);
// internal preferences handling
Json::Value preferences;
Json::Value GetPref(Json::Value root, ByteString prop, Json::Value defaultValue = Json::nullValue);
Json::Value SetPrefHelper(Json::Value root, ByteString prop, Json::Value value);
void notifyNewNotification(ServerNotification notification);
// Save stealing info
Json::Value authors;
std::unique_ptr<Prefs> stamps;
void MigrateStampsDef();
void WriteStamps();
public:
std::vector<ClientListener*> listeners;
@ -94,7 +66,7 @@ public:
void ClearAuthorInfo() { authors.clear(); }
bool IsAuthorsEmpty() { return authors.size() == 0; }
UpdateInfo GetUpdateInfo();
std::optional<UpdateInfo> GetUpdateInfo();
Client();
~Client();
@ -102,80 +74,32 @@ public:
ByteString FileOpenDialogue();
//std::string FileSaveDialogue();
bool DoInstallation();
void AddServerNotification(std::pair<String, ByteString> notification);
std::vector<std::pair<String, ByteString> > GetServerNotifications();
void AddServerNotification(ServerNotification notification);
std::vector<ServerNotification> GetServerNotifications();
void SetMessageOfTheDay(String message);
String GetMessageOfTheDay();
void Initialise(ByteString proxy, ByteString cafile, ByteString capath, bool disableNetwork);
void Initialize();
bool IsFirstRun();
void AddListener(ClientListener * listener);
void RemoveListener(ClientListener * listener);
RequestStatus ExecVote(int saveID, int direction);
RequestStatus UploadSave(SaveInfo & save);
SaveFile * GetStamp(ByteString stampID);
std::unique_ptr<SaveFile> GetStamp(ByteString stampID);
void DeleteStamp(ByteString stampID);
ByteString AddStamp(GameSave * saveData);
std::vector<ByteString> GetStamps(int start, int count);
ByteString AddStamp(std::unique_ptr<GameSave> saveData);
void RescanStamps();
int GetStampsCount();
SaveFile * GetFirstStamp();
const std::vector<ByteString> &GetStamps() const;
void MoveStampToFront(ByteString stampID);
void updateStamps();
RequestStatus AddComment(int saveID, String comment);
std::unique_ptr<SaveFile> LoadSaveFile(ByteString filename);
std::vector<char> GetSaveData(int saveID, int saveDate);
LoginStatus Login(ByteString username, ByteString password, User & user);
std::vector<SaveInfo*> * SearchSaves(int start, int count, String query, ByteString sort, ByteString category, int & resultCount);
std::vector<std::pair<ByteString, int> > * GetTags(int start, int count, String query, int & resultCount);
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();
bool CheckUpdate(http::Request *updateRequest, bool checkSession);
void Shutdown();
// preferences functions
void WritePrefs();
ByteString GetPrefByteString(ByteString prop, ByteString defaultValue);
String GetPrefString(ByteString prop, String defaultValue);
double GetPrefNumber(ByteString prop, double defaultValue);
int GetPrefInteger(ByteString prop, int defaultValue);
unsigned int GetPrefUInteger(ByteString prop, unsigned int defaultValue);
bool GetPrefBool(ByteString prop, bool defaultValue);
std::vector<ByteString> GetPrefByteStringArray(ByteString prop);
std::vector<String> GetPrefStringArray(ByteString prop);
std::vector<double> GetPrefNumberArray(ByteString prop);
std::vector<int> GetPrefIntegerArray(ByteString prop);
std::vector<unsigned int> GetPrefUIntegerArray(ByteString prop);
std::vector<bool> GetPrefBoolArray(ByteString prop);
void SetPref(ByteString prop, Json::Value value);
void SetPref(ByteString property, std::vector<Json::Value> value);
void SetPrefUnicode(ByteString prop, String value);
String DoMigration(ByteString fromDir, ByteString toDir);
};
#endif // CLIENT_H
bool AddCustomGol(String ruleString, String nameString, unsigned int highColor, unsigned int lowColor);

View File

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

11
src/client/Comment.h Normal file
View File

@ -0,0 +1,11 @@
#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,12 +1,13 @@
#pragma once
#include "Config.h"
#include <vector>
#include "common/Plane.h"
#include "common/String.h"
#include "common/tpt-rand.h"
#include "simulation/Sign.h"
#include "simulation/Particle.h"
#include "Misc.h"
#include "SimulationConfig.h"
#include <vector>
#include <array>
#include <json/json.h>
struct sign;
@ -53,57 +54,38 @@ public:
}
};
template<class Item>
struct Plane
{
int width = 0;
int height = 0;
std::vector<Item> items;
// invariant: items.size() == width * height
Item *operator [](int y)
{
return &items[y * width];
}
const Item *operator [](int y) const
{
return &items[y * width];
}
Plane() = default;
Plane(int newWidth, int newHeight, Item defaultVal) : width(newWidth), height(newHeight), items(width * height, 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;
public:
int blockWidth = 0;
int blockHeight = 0;
Vec2<int> blockSize = { 0, 0 };
bool fromNewerVersion = false;
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;
Plane<unsigned char> blockMap;
Plane<float> fanVelX;
Plane<float> fanVelY;
Plane<float> pressure;
Plane<float> velocityX;
Plane<float> velocityY;
Plane<float> ambientHeat;
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;
//Simulation Options
bool waterEEnabled = false;
@ -117,6 +99,7 @@ public:
int airMode = 0;
float ambientAirTemp = R_TEMP + 273.15f;
int edgeMode = 0;
bool wantAuthors = true;
//Signs
std::vector<sign> signs;
@ -131,20 +114,15 @@ public:
int pmapbits = 8; // default to 8 bits for older saves
GameSave(int width, int height);
GameSave(const std::vector<char> &data);
void setSize(int width, int height);
GameSave(Vec2<int> newBlockSize);
GameSave(const std::vector<char> &data, bool newWantAuthors = true);
void setSize(Vec2<int> newBlockSize);
// return value is [ fakeFromNewerVersion, gameData ]
std::pair<bool, std::vector<char>> Serialise() const;
vector2d Translate(vector2d translate);
void Transform(matrix2d transform, vector2d translate);
void Transform(matrix2d transform, vector2d translate, vector2d translateReal, int newWidth, int newHeight);
void Transform(Mat2<int> transform, Vec2<int> nudge);
void Expand(const std::vector<char> &data);
static bool TypeInCtype(int type, int ctype);
static bool TypeInTmp(int type);
static bool TypeInTmp2(int type, int tmp2);
static bool PressureInTmp3(int type);
GameSave& operator << (Particle &v);

10
src/client/LoginInfo.h Normal file
View File

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

View File

@ -1,6 +1,6 @@
// based on public-domain code from Colin Plumb (1993)
#include <cstring>
#include "MD5.h"
#include <cstring>
static unsigned getu32(const unsigned char *addr)
{

View File

@ -1,6 +1,4 @@
#ifndef MD5_H
#define MD5_H
#include "Config.h"
#pragma once
struct md5_context
{
@ -15,5 +13,3 @@ 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);
#endif

View File

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

View File

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

View File

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

View File

@ -1,9 +1,8 @@
#ifndef SAVE_H
#define SAVE_H
#include "Config.h"
#include <list>
#pragma once
#include "common/String.h"
#include <list>
#include <memory>
#include <ctime>
#ifdef GetUserName
# undef GetUserName // dammit windows
@ -16,8 +15,8 @@ class SaveInfo
private:
public:
int id;
int createdDate;
int updatedDate;
time_t createdDate;
time_t updatedDate;
int votesUp, votesDown;
int vote;
bool Favourite;
@ -32,48 +31,45 @@ public:
bool Published;
std::list<ByteString> tags;
GameSave * gameSave;
std::unique_ptr<GameSave> gameSave;
SaveInfo(SaveInfo & save);
SaveInfo(int _id, time_t _createdDate, time_t _updatedDate, int _votesUp, int _votesDown, ByteString _userName, String _name);
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();
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);
void SetName(String name);
String GetName();
const String &GetName() const;
void SetDescription(String description);
String GetDescription();
const String &GetDescription() const;
void SetPublished(bool published);
bool GetPublished();
bool GetPublished() const;
void SetUserName(ByteString userName);
ByteString GetUserName();
const ByteString &GetUserName() const;
void SetID(int id);
int GetID();
int GetID() const;
void SetVote(int vote);
int GetVote();
int GetVote() const;
void SetVotesUp(int votesUp);
int GetVotesUp();
int GetVotesUp() const;
void SetVotesDown(int votesDown);
int GetVotesDown();
int GetVotesDown() const;
void SetVersion(int version);
int GetVersion();
int GetVersion() const;
void SetTags(std::list<ByteString> tags);
std::list<ByteString> GetTags();
std::list<ByteString> GetTags() const;
GameSave * GetGameSave();
void SetGameSave(GameSave * gameSave);
const GameSave *GetGameSave() const;
std::unique_ptr<GameSave> TakeGameSave();
void SetGameSave(std::unique_ptr<GameSave> newGameSave);
std::unique_ptr<SaveInfo> CloneInfo() const;
};
#endif // SAVE_H

17
src/client/Search.h Normal file
View File

@ -0,0 +1,17 @@
#pragma once
namespace http
{
enum Category
{
categoryNone,
categoryMyOwn,
categoryFavourites,
};
enum Sort
{
sortByVotes,
sortByDate,
};
}

View File

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

29
src/client/StartupInfo.h Normal file
View File

@ -0,0 +1,29 @@
#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

@ -13,13 +13,11 @@ int ThumbnailRendererTask::QueueSize()
return queueSize;
}
ThumbnailRendererTask::ThumbnailRendererTask(GameSave *save, int width, int height, bool autoRescale, bool decorations, bool fire) :
Save(new GameSave(*save)),
Width(width),
Height(height),
Decorations(decorations),
Fire(fire),
AutoRescale(autoRescale)
ThumbnailRendererTask::ThumbnailRendererTask(GameSave const &save, Vec2<int> size, bool decorations, bool fire):
save(std::make_unique<GameSave>(save)),
size(size),
decorations(decorations),
fire(fire)
{
queueSize += 1;
}
@ -31,33 +29,11 @@ ThumbnailRendererTask::~ThumbnailRendererTask()
bool ThumbnailRendererTask::doWork()
{
thumbnail = std::unique_ptr<VideoBuffer>(SaveRenderer::Ref().Render(Save.get(), Decorations, Fire));
thumbnail = std::unique_ptr<VideoBuffer>(SaveRenderer::Ref().Render(save.get(), decorations, fire));
if (thumbnail)
{
if (AutoRescale)
{
int scaleX = (int)std::ceil((float)thumbnail->Width / Width);
int scaleY = (int)std::ceil((float)thumbnail->Height / Height);
int scale = scaleX > scaleY ? scaleX : scaleY;
int newWidth = thumbnail->Width / scale, newHeight = thumbnail->Height / scale;
thumbnail->Resize(newWidth, newHeight, true);
newWidth = thumbnail->Width;
newHeight = thumbnail->Height;
if (newWidth > Width || newHeight > Height)
{
auto croppedWidth = newWidth > Width ? Width : newWidth;
auto croppedHeight = newHeight > Height ? Height : newHeight;
thumbnail->Crop(croppedWidth, croppedHeight, (newWidth - croppedWidth) / 2, (newHeight - croppedHeight) / 2);
newWidth = thumbnail->Width;
newHeight = thumbnail->Height;
}
Width = newWidth;
Height = newHeight;
}
else
{
thumbnail->Resize(Width, Height, true);
}
thumbnail->ResizeToFit(size, true);
size = thumbnail->Size();
return true;
}
else

View File

@ -1,6 +1,5 @@
#ifndef THUMBNAILRENDERER_H
#define THUMBNAILRENDERER_H
#pragma once
#include "common/Vec2.h"
#include "tasks/AbandonableTask.h"
#include <memory>
@ -9,17 +8,16 @@ class GameSave;
class VideoBuffer;
class ThumbnailRendererTask : public AbandonableTask
{
std::unique_ptr<GameSave> Save;
int Width, Height;
bool Decorations;
bool Fire;
bool AutoRescale;
std::unique_ptr<GameSave> save;
Vec2<int> size;
bool decorations;
bool fire;
std::unique_ptr<VideoBuffer> thumbnail;
static int queueSize;
public:
ThumbnailRendererTask(GameSave *save, int width, int height, bool autoRescale = false, bool decorations = true, bool fire = true);
ThumbnailRendererTask(GameSave const &, Vec2<int> size, bool decorations, bool fire);
virtual ~ThumbnailRendererTask();
virtual bool doWork() override;
@ -27,6 +25,3 @@ public:
static int QueueSize();
};
#endif // THUMBNAILRENDERER_H

32
src/client/User.cpp Normal file
View File

@ -0,0 +1,32 @@
#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

@ -1,6 +1,4 @@
#ifndef USER_H_
#define USER_H_
#pragma once
#include "common/String.h"
@ -9,8 +7,14 @@ class User
public:
enum Elevation
{
ElevationAdmin, ElevationModerator, ElevationNone
ElevationNone,
ElevationHalfMod,
ElevationMod,
ElevationAdmin,
};
static Elevation ElevationFromString(ByteString str);
static ByteString ElevationToString(Elevation elevation);
int UserID;
ByteString Username;
ByteString SessionID;
@ -27,5 +31,3 @@ public:
}
};
#endif /* USER_H_ */

View File

@ -1,6 +1,4 @@
#ifndef USERINFO_H_
#define USERINFO_H_
#pragma once
#include "common/String.h"
class UserInfo
@ -37,6 +35,3 @@ public:
{ }
UserInfo() {}
};
#endif /* USER_H_ */

View File

@ -1,41 +1,36 @@
#include "APIRequest.h"
#include "client/Client.h"
namespace http
{
APIRequest::APIRequest(ByteString url) : Request(url)
APIRequest::APIRequest(ByteString url, AuthMode authMode, bool newCheckStatus) : Request(url), checkStatus(newCheckStatus)
{
User user = Client::Ref().GetAuthUser();
AuthHeaders(ByteString::Build(user.UserID), user.SessionID);
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);
}
}
APIRequest::~APIRequest()
Json::Value APIRequest::Finish()
{
}
APIRequest::Result APIRequest::Finish()
{
Result result;
auto [ status, data ] = Request::Finish();
ParseResponse(data, status, checkStatus ? responseJson : responseData);
Json::Value document;
try
{
ByteString data = Request::Finish(&result.status);
// Note that at this point it's not safe to use any member of the
// APIRequest object as Request::Finish signals RequestManager
// to delete it.
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));
}
std::istringstream ss(data);
ss >> document;
}
catch (std::exception & e)
catch (const std::exception &ex)
{
throw RequestError("Could not read response: " + ByteString(ex.what()));
}
return result;
return document;
}
}

View File

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

View File

@ -0,0 +1,21 @@
#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

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

View File

@ -0,0 +1,29 @@
#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

@ -0,0 +1,14 @@
#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

@ -0,0 +1,16 @@
#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

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

View File

@ -0,0 +1,23 @@
#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

@ -0,0 +1,20 @@
#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

@ -0,0 +1,28 @@
#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

@ -0,0 +1,20 @@
#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

@ -0,0 +1,36 @@
#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()),
ByteString(comment["UserID"].asString()).ToNumber<int>() == user.UserID,
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

@ -0,0 +1,14 @@
#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

@ -0,0 +1,28 @@
#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

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

View File

@ -0,0 +1,69 @@
#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

@ -0,0 +1,16 @@
#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

@ -1,30 +1,22 @@
#include "GetUserInfoRequest.h"
#include "Config.h"
#include "client/UserInfo.h"
#include "Config.h"
namespace http
{
GetUserInfoRequest::GetUserInfoRequest(ByteString username) :
APIRequest(SCHEME SERVER "/User.json?Name=" + username)
APIRequest(ByteString::Build(SCHEME, SERVER, "/User.json?Name=", username), authOmit, false)
{
}
GetUserInfoRequest::~GetUserInfoRequest()
UserInfo GetUserInfoRequest::Finish()
{
}
std::unique_ptr<UserInfo> GetUserInfoRequest::Finish()
{
std::unique_ptr<UserInfo> user_info;
auto result = APIRequest::Finish();
// Note that at this point it's not safe to use any member of the
// GetUserInfoRequest object as Request::Finish signals RequestManager
// to delete it.
if (result.document)
UserInfo userInfo;
try
{
auto &user = (*result.document)["User"];
user_info = std::unique_ptr<UserInfo>(new UserInfo(
auto &user = result["User"];
userInfo = UserInfo(
user["ID"].asInt(),
user["Age"].asInt(),
user["Username"].asString(),
@ -37,9 +29,13 @@ namespace http
user["Forum"]["Topics"].asInt(),
user["Forum"]["Replies"].asInt(),
user["Forum"]["Reputation"].asInt()
));
);
}
return user_info;
catch (const std::exception &ex)
{
throw RequestError("Could not read response: " + ByteString(ex.what()));
}
return userInfo;
}
}

View File

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

View File

@ -1,47 +1,27 @@
#include "ImageRequest.h"
#include "common/Singleton.h"
#include "graphics/Graphics.h"
#include "Config.h"
#include "client/Client.h"
#include <iostream>
namespace http
{
ImageRequest::ImageRequest(ByteString url, int width, int height) :
Request(url),
Width(width),
Height(height)
{
}
ImageRequest::~ImageRequest()
ImageRequest::ImageRequest(ByteString url, Vec2<int> newRequestedSize) : Request(url), requestedSize(newRequestedSize)
{
}
std::unique_ptr<VideoBuffer> ImageRequest::Finish()
{
int width = Width;
int height = Height;
ByteString data = Request::Finish(nullptr);
// Note that at this point it's not safe to use any member of the
// ImageRequest object as Request::Finish signals RequestManager
// to delete it.
std::unique_ptr<VideoBuffer> vb;
if (data.size())
auto [ status, data ] = Request::Finish();
ParseResponse(data, status, responseData);
auto vb = VideoBuffer::FromPNG(std::vector<char>(data.begin(), data.end()));
if (vb)
{
int imgw, imgh;
std::vector<pixel> imageData;
if (PngDataToPixels(imageData, imgw, imgh, data.data(), data.size(), true))
{
vb = std::unique_ptr<VideoBuffer>(new VideoBuffer(imageData.data(), imgw, imgh));
}
else
{
vb = std::unique_ptr<VideoBuffer>(new VideoBuffer(32, 32));
vb->SetCharacter(14, 14, 'x', 255, 255, 255, 255);
}
vb->Resize(width, height, true);
vb->Resize(requestedSize, true);
}
else
{
vb = std::make_unique<VideoBuffer>(Vec2(15, 16));
vb->BlendChar(Vec2(2, 4), 0xE06E, 0xFFFFFF_rgb .WithAlpha(0xFF));
}
return vb;
}

View File

@ -1,9 +1,7 @@
#ifndef IMAGEREQUEST2_H
#define IMAGEREQUEST2_H
#include "Request.h"
#pragma once
#include "common/String.h"
#include "common/Vec2.h"
#include "Request.h"
#include <memory>
class VideoBuffer;
@ -12,15 +10,11 @@ namespace http
{
class ImageRequest : public Request
{
int Width, Height;
Vec2<int> requestedSize;
public:
ImageRequest(ByteString url, int width, int height);
virtual ~ImageRequest();
ImageRequest(ByteString url, Vec2<int> newRequestedSize);
std::unique_ptr<VideoBuffer> Finish();
};
}
#endif // IMAGEREQUEST2_H

View File

@ -0,0 +1,45 @@
#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

@ -0,0 +1,14 @@
#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

@ -0,0 +1,16 @@
#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, false)
{
}
void LogoutRequest::Finish()
{
APIRequest::Finish();
}
}

View File

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

View File

@ -0,0 +1,11 @@
#pragma once
#include "common/String.h"
#include <map>
#include <variant>
namespace http
{
using StringData = ByteString;
using FormData = std::map<ByteString, ByteString>;
using PostData = std::variant<StringData, FormData>;
};

View File

@ -0,0 +1,19 @@
#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

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

View File

@ -0,0 +1,29 @@
#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

@ -0,0 +1,14 @@
#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

@ -0,0 +1,19 @@
#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

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

View File

@ -1,139 +1,65 @@
#include "Request.h"
#include "RequestManager.h"
#ifndef NOHTTP
void SetupCurlEasyCiphers(CURL *easy)
{
#ifdef SECURE_CIPHERS_ONLY
curl_version_info_data *version_info = curl_version_info(CURLVERSION_NOW);
ByteString ssl_type = version_info->ssl_version;
if (ssl_type.Contains("OpenSSL"))
{
curl_easy_setopt(easy, CURLOPT_SSL_CIPHER_LIST, "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");
#ifdef REQUEST_USE_CURL_TLSV13CL
curl_easy_setopt(easy, CURLOPT_TLS13_CIPHERS, "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");
#endif
}
else if (ssl_type.Contains("Schannel"))
{
// TODO: add more cipher algorithms
curl_easy_setopt(easy, CURLOPT_SSL_CIPHER_LIST, "CALG_ECDH_EPHEM");
}
#endif
// TODO: Find out what TLS1.2 is supported on, might need to also allow TLS1.0
curl_easy_setopt(easy, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
#if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 70, 0)
curl_easy_setopt(easy, CURLOPT_SSL_OPTIONS, CURLSSLOPT_REVOKE_BEST_EFFORT);
#elif defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 44, 0)
curl_easy_setopt(easy, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE);
#elif defined(WIN)
# error "That's unfortunate."
#endif
}
#endif
#include "requestmanager/RequestManager.h"
#include <memory>
#include <iostream>
#include <cstring>
#include <json/json.h>
namespace http
{
#ifndef NOHTTP
Request::Request(ByteString uri_):
uri(uri_),
rm_total(0),
rm_done(0),
rm_finished(false),
rm_canceled(false),
rm_started(false),
added_to_multi(false),
status(0),
headers(NULL),
#ifdef REQUEST_USE_CURL_MIMEPOST
post_fields(NULL)
#else
post_fields_first(NULL),
post_fields_last(NULL)
#endif
Request::Request(ByteString newUri)
{
easy = curl_easy_init();
if (!RequestManager::Ref().AddRequest(this))
{
status = 604;
rm_finished = true;
}
handle = RequestHandle::Create();
handle->uri = newUri;
}
#else
Request::Request(ByteString uri_) {}
#endif
Request::~Request()
{
#ifndef NOHTTP
curl_easy_cleanup(easy);
#ifdef REQUEST_USE_CURL_MIMEPOST
curl_mime_free(post_fields);
#else
curl_formfree(post_fields_first);
#endif
curl_slist_free_all(headers);
#endif
bool tryUnregister;
{
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)
{
#ifndef NOHTTP
verb = newVerb;
#endif
assert(handle->state == RequestHandle::ready);
handle->verb = newVerb;
}
void Request::AddHeader(ByteString header)
{
#ifndef NOHTTP
headers = curl_slist_append(headers, header.c_str());
#endif
assert(handle->state == RequestHandle::ready);
handle->headers.push_back(header);
}
// add post data to a request
void Request::AddPostData(std::map<ByteString, ByteString> data)
void Request::AddPostData(PostData data)
{
#ifndef NOHTTP
assert(handle->state == RequestHandle::ready);
// Even if the map is empty, calling this function signifies you want to do a POST request
isPost = true;
if (!data.size())
{
return;
}
if (easy)
{
#ifdef REQUEST_USE_CURL_MIMEPOST
if (!post_fields)
{
post_fields = curl_mime_init(easy);
}
for (auto &field : data)
{
curl_mimepart *part = curl_mime_addpart(post_fields);
curl_mime_data(part, &field.second[0], field.second.size());
if (auto split = field.first.SplitBy(':'))
{
curl_mime_name(part, split.Before().c_str());
curl_mime_filename(part, split.After().c_str());
}
else
{
curl_mime_name(part, field.first.c_str());
}
}
#else
post_fields_map.insert(data.begin(), data.end());
#endif
}
#endif
handle->isPost = true;
handle->postData = data;
}
// add userID and sessionID headers to the request
void Request::AuthHeaders(ByteString ID, ByteString session)
{
assert(handle->state == RequestHandle::ready);
if (ID.size())
{
if (session.size())
@ -148,263 +74,81 @@ namespace http
}
}
#ifndef NOHTTP
size_t Request::HeaderDataHandler(char *ptr, size_t size, size_t count, void *userdata)
{
Request *req = (Request *)userdata;
auto actual_size = size * count;
if (actual_size >= 2 && ptr[actual_size - 2] == '\r' && ptr[actual_size - 1] == '\n')
{
if (actual_size > 2) // don't include header list terminator (but include the status line)
{
req->response_headers.push_back(ByteString(ptr, ptr + actual_size - 2));
}
return actual_size;
}
return 0;
}
size_t Request::WriteDataHandler(char *ptr, size_t size, size_t count, void *userdata)
{
Request *req = (Request *)userdata;
auto actual_size = size * count;
req->response_body.append(ptr, actual_size);
return actual_size;
}
#endif
// start the request thread
void Request::Start()
{
#ifndef NOHTTP
if (CheckStarted() || CheckDone())
{
return;
}
if (easy)
{
#ifdef REQUEST_USE_CURL_MIMEPOST
if (post_fields)
{
curl_easy_setopt(easy, CURLOPT_MIMEPOST, post_fields);
}
#else
if (!post_fields_map.empty())
{
for (auto &field : post_fields_map)
{
if (auto split = field.first.SplitBy(':'))
{
curl_formadd(&post_fields_first, &post_fields_last,
CURLFORM_COPYNAME, split.Before().c_str(),
CURLFORM_BUFFER, split.After().c_str(),
CURLFORM_BUFFERPTR, &field.second[0],
CURLFORM_BUFFERLENGTH, field.second.size(),
CURLFORM_END);
}
else
{
curl_formadd(&post_fields_first, &post_fields_last,
CURLFORM_COPYNAME, field.first.c_str(),
CURLFORM_PTRCONTENTS, &field.second[0],
CURLFORM_CONTENTLEN, field.second.size(),
CURLFORM_END);
}
}
curl_easy_setopt(easy, CURLOPT_HTTPPOST, post_fields_first);
}
#endif
else if (isPost)
{
curl_easy_setopt(easy, CURLOPT_POST, 1L);
curl_easy_setopt(easy, CURLOPT_POSTFIELDS, "");
}
else
{
curl_easy_setopt(easy, CURLOPT_HTTPGET, 1L);
}
if (verb.size())
{
curl_easy_setopt(easy, CURLOPT_CUSTOMREQUEST, verb.c_str());
}
curl_easy_setopt(easy, CURLOPT_FOLLOWLOCATION, 1L);
#if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 85, 0)
# ifdef ENFORCE_HTTPS
curl_easy_setopt(easy, CURLOPT_PROTOCOLS_STR, "https");
curl_easy_setopt(easy, CURLOPT_REDIR_PROTOCOLS_STR, "https");
# else
curl_easy_setopt(easy, CURLOPT_PROTOCOLS_STR, "https,http");
curl_easy_setopt(easy, CURLOPT_REDIR_PROTOCOLS_STR, "https,http");
# endif
#else
# ifdef ENFORCE_HTTPS
curl_easy_setopt(easy, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
curl_easy_setopt(easy, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
# else
curl_easy_setopt(easy, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
curl_easy_setopt(easy, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
# endif
#endif
SetupCurlEasyCiphers(easy);
curl_easy_setopt(easy, CURLOPT_MAXREDIRS, 10L);
curl_easy_setopt(easy, CURLOPT_ERRORBUFFER, error_buffer);
error_buffer[0] = 0;
curl_easy_setopt(easy, CURLOPT_CONNECTTIMEOUT, timeout);
curl_easy_setopt(easy, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(easy, CURLOPT_URL, uri.c_str());
if (proxy.size())
{
curl_easy_setopt(easy, CURLOPT_PROXY, proxy.c_str());
}
if (cafile.size())
{
curl_easy_setopt(easy, CURLOPT_CAINFO, cafile.c_str());
}
if (capath.size())
{
curl_easy_setopt(easy, CURLOPT_CAPATH, capath.c_str());
}
curl_easy_setopt(easy, CURLOPT_PRIVATE, (void *)this);
curl_easy_setopt(easy, CURLOPT_USERAGENT, user_agent.c_str());
curl_easy_setopt(easy, CURLOPT_HEADERDATA, (void *)this);
curl_easy_setopt(easy, CURLOPT_HEADERFUNCTION, Request::HeaderDataHandler);
curl_easy_setopt(easy, CURLOPT_WRITEDATA, (void *)this);
curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, Request::WriteDataHandler);
}
{
std::lock_guard<std::mutex> g(rm_mutex);
rm_started = true;
}
RequestManager::Ref().StartRequest(this);
#endif
assert(handle->state == RequestHandle::ready);
handle->state = RequestHandle::running;
RequestManager::Ref().RegisterRequest(*this);
}
// finish the request (if called before the request is done, this will block)
ByteString Request::Finish(int *status_out, std::vector<ByteString> *headers_out)
bool Request::CheckDone() const
{
std::lock_guard lk(handle->stateMx);
assert(handle->state == RequestHandle::running || handle->state == RequestHandle::done);
return handle->state == RequestHandle::done;
}
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<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
{
#ifndef NOHTTP
if (CheckCanceled())
{
return ""; // shouldn't happen but just in case
std::unique_lock lk(handle->stateMx);
assert(handle->state == RequestHandle::done);
}
ByteString response_out;
return handle->statusCode;
}
std::pair<int, ByteString> Request::Finish()
{
{
std::unique_lock<std::mutex> l(rm_mutex);
done_cv.wait(l, [this]() { return rm_finished; });
rm_started = false;
rm_canceled = true;
if (status_out)
{
*status_out = status;
}
if (headers_out)
{
*headers_out = std::move(response_headers);
}
response_out = std::move(response_body);
std::unique_lock lk(handle->stateMx);
assert(handle->state == RequestHandle::done);
}
RequestManager::Ref().RemoveRequest(this);
return response_out;
#else
if (status_out)
*status_out = 604;
return "";
#endif
}
void Request::CheckProgress(int *total, int *done)
{
#ifndef NOHTTP
std::lock_guard<std::mutex> g(rm_mutex);
if (total)
handle->state = RequestHandle::dead;
if (handle->error)
{
*total = rm_total;
throw RequestError(*handle->error);
}
if (done)
return std::pair{ handle->statusCode, std::move(handle->responseData) };
}
void RequestHandle::MarkDone()
{
{
*done = rm_done;
std::lock_guard lk(stateMx);
assert(state == RequestHandle::running);
state = RequestHandle::done;
}
#endif
}
// returns true if the request has finished
bool Request::CheckDone()
{
#ifndef NOHTTP
std::lock_guard<std::mutex> g(rm_mutex);
return rm_finished;
#else
return true;
#endif
}
// returns true if the request was canceled
bool Request::CheckCanceled()
{
#ifndef NOHTTP
std::lock_guard<std::mutex> g(rm_mutex);
return rm_canceled;
#else
return false;
#endif
}
// returns true if the request is running
bool Request::CheckStarted()
{
#ifndef NOHTTP
std::lock_guard<std::mutex> g(rm_mutex);
return rm_started;
#else
return true;
#endif
}
// cancels the request, the request thread will delete the Request* when it finishes (do not use Request in any way after canceling)
void Request::Cancel()
{
#ifndef NOHTTP
stateCv.notify_one();
if (error)
{
std::lock_guard<std::mutex> g(rm_mutex);
rm_canceled = true;
std::cerr << *error << std::endl;
}
RequestManager::Ref().RemoveRequest(this);
#endif
}
ByteString Request::Simple(ByteString uri, int *status, std::map<ByteString, ByteString> post_data)
{
return SimpleAuth(uri, status, "", "", post_data);
}
ByteString Request::SimpleAuth(ByteString uri, int *status, ByteString ID, ByteString session, std::map<ByteString, ByteString> post_data)
{
Request *request = new Request(uri);
if (!post_data.empty())
{
request->AddPostData(post_data);
}
request->AuthHeaders(ID, session);
request->Start();
return request->Finish(status);
}
String StatusText(int ret)
const char *StatusText(int ret)
{
switch (ret)
{
@ -475,7 +219,6 @@ namespace http
case 607: return "Connection Refused";
case 608: return "Proxy Server Not Found";
case 609: return "SSL: Invalid Certificate Status";
case 610: return "Cancelled by Shutdown";
case 611: return "Too Many Redirects";
case 612: return "SSL: Connect Error";
case 613: return "SSL: Crypto Engine Not Found";
@ -487,8 +230,69 @@ 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";
default: return "Unknown Status Code";
}
return "Unknown Status Code";
}
void Request::ParseResponse(const ByteString &result, int status, ResponseType responseType)
{
// no server response, return "Malformed Response"
if (status == 200 && !result.size())
{
status = 603;
}
if (status == 302)
{
return;
}
if (status != 200)
{
throw RequestError(ByteString::Build("HTTP Error ", status, ": ", http::StatusText(status)));
}
switch (responseType)
{
case responseOk:
if (strncmp(result.c_str(), "OK", 2))
{
throw RequestError(result);
}
break;
case responseJson:
{
std::istringstream ss(result);
Json::Value root;
try
{
ss >> root;
// assume everything is fine if an empty [] is returned
if (root.size() == 0)
{
return;
}
int status = root.get("Status", 1).asInt();
if (status != 1)
{
throw RequestError(ByteString(root.get("Error", "Unspecified Error").asString()));
}
}
catch (const std::exception &ex)
{
// sometimes the server returns a 200 with the text "Error: 401"
if (!strncmp(result.c_str(), "Error: ", 7))
{
status = ByteString(result.begin() + 7, result.end()).ToNumber<int>();
throw RequestError(ByteString::Build("HTTP Error ", status, ": ", http::StatusText(status)));
}
throw RequestError("Could not read response: " + ByteString(ex.what()));
}
}
break;
case responseData:
// no further processing required
break;
}
}
}

View File

@ -1,91 +1,61 @@
#ifndef REQUEST_H
#define REQUEST_H
#include "Config.h"
#ifndef NOHTTP
# include <curl/curl.h>
# include "common/tpt-minmax.h" // for MSVC, ensures windows.h doesn't cause compile errors by defining min/max
# include <mutex>
# include <condition_variable>
# if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 55, 0)
# define REQUEST_USE_CURL_OFFSET_T
# endif
# if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 56, 0)
# define REQUEST_USE_CURL_MIMEPOST
# endif
# if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 61, 0)
# define REQUEST_USE_CURL_TLSV13CL
# endif
#endif
#include <map>
#pragma once
#include "common/String.h"
#include "PostData.h"
#include <map>
#include <utility>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <optional>
namespace http
{
class RequestManager;
struct RequestHandle;
// Thrown by Finish and ParseResponse
struct RequestError : public std::runtime_error
{
using runtime_error::runtime_error;
};
class Request
{
#ifndef NOHTTP
ByteString uri;
std::vector<ByteString> response_headers;
ByteString response_body;
CURL *easy;
char error_buffer[CURL_ERROR_SIZE];
volatile curl_off_t rm_total;
volatile curl_off_t rm_done;
volatile bool rm_finished;
volatile bool rm_canceled;
volatile bool rm_started;
std::mutex rm_mutex;
bool added_to_multi;
int status;
ByteString verb;
struct curl_slist *headers;
bool isPost = false;
#ifdef REQUEST_USE_CURL_MIMEPOST
curl_mime *post_fields;
#else
curl_httppost *post_fields_first, *post_fields_last;
std::map<ByteString, ByteString> post_fields_map;
#endif
std::condition_variable done_cv;
static size_t HeaderDataHandler(char *ptr, size_t size, size_t count, void *userdata);
static size_t WriteDataHandler(char *ptr, size_t size, size_t count, void *userdata);
#endif
std::shared_ptr<RequestHandle> handle;
public:
Request(ByteString uri);
virtual ~Request();
Request(ByteString newUri);
Request(const Request &) = delete;
Request &operator =(const Request &) = delete;
~Request();
void FailEarly(ByteString error);
void Verb(ByteString newVerb);
void AddHeader(ByteString header);
void AddPostData(std::map<ByteString, ByteString> data);
void AddPostData(PostData data);
void AuthHeaders(ByteString ID, ByteString session);
void Start();
ByteString Finish(int *status, std::vector<ByteString> *headers = nullptr);
void Cancel();
bool CheckDone() const;
void CheckProgress(int *total, int *done);
bool CheckDone();
bool CheckCanceled();
bool CheckStarted();
std::pair<int, int> CheckProgress() const; // total, done
const std::vector<ByteString> &ResponseHeaders() const;
void Wait();
int StatusCode() const; // status
std::pair<int, ByteString> Finish(); // status, data
enum ResponseType
{
responseOk,
responseJson,
responseData,
};
static void ParseResponse(const ByteString &result, int status, ResponseType responseType);
friend class RequestManager;
static ByteString Simple(ByteString uri, int *status, std::map<ByteString, ByteString> post_data = std::map<ByteString, ByteString>{});
static ByteString SimpleAuth(ByteString uri, int *status, ByteString ID, ByteString session, std::map<ByteString, ByteString> post_data = std::map<ByteString, ByteString>{});
};
String StatusText(int code);
const char *StatusText(int code);
}
#endif // REQUEST_H

View File

@ -1,272 +0,0 @@
#ifndef NOHTTP
#include "Request.h" // includes curl.h, needs to come first to silence a warning on windows
#include "RequestManager.h"
#include <iostream>
const int curl_multi_wait_timeout_ms = 100;
const long curl_max_host_connections = 1;
const long curl_max_concurrent_streams = 50;
namespace http
{
const long timeout = 15;
ByteString proxy;
ByteString cafile;
ByteString capath;
ByteString user_agent;
void RequestManager::Shutdown()
{
if (rt_shutting_down)
return;
{
std::lock_guard<std::mutex> g(rt_mutex);
rt_shutting_down = true;
}
rt_cv.notify_one();
if (initialized)
{
worker_thread.join();
curl_multi_cleanup(multi);
multi = NULL;
curl_global_cleanup();
}
}
void RequestManager::Initialise(ByteString newProxy, ByteString newCafile, ByteString newCapath)
{
curl_global_init(CURL_GLOBAL_DEFAULT);
multi = curl_multi_init();
if (multi)
{
curl_multi_setopt(multi, CURLMOPT_MAX_HOST_CONNECTIONS, curl_max_host_connections);
#if defined(CURL_AT_LEAST_VERSION) && CURL_AT_LEAST_VERSION(7, 67, 0)
curl_multi_setopt(multi, CURLMOPT_MAX_CONCURRENT_STREAMS, curl_max_concurrent_streams);
#endif
}
proxy = newProxy;
cafile = newCafile;
capath = newCapath;
user_agent =
"PowderToy/" MTOS(SAVE_VERSION) "." MTOS(MINOR_VERSION) " ("
IDENT_PLATFORM
"; " IDENT_BUILD
"; M" MTOS(MOD_ID)
"; " IDENT
") TPTPP/" MTOS(SAVE_VERSION) "." MTOS(MINOR_VERSION) "." MTOS(BUILD_NUM) IDENT_RELTYPE "." MTOS(SNAPSHOT_ID);
worker_thread = std::thread([this]() { Worker(); });
initialized = true;
}
void RequestManager::Worker()
{
bool shutting_down = false;
while (!shutting_down)
{
{
std::unique_lock<std::mutex> l(rt_mutex);
if (!requests_added_to_multi)
{
while (!rt_shutting_down && requests_to_add.empty() && !requests_to_start && !requests_to_remove)
{
rt_cv.wait(l);
}
}
shutting_down = rt_shutting_down;
requests_to_remove = false;
requests_to_start = false;
for (Request *request : requests_to_add)
{
request->status = 0;
requests.insert(request);
}
requests_to_add.clear();
}
if (multi && requests_added_to_multi)
{
int dontcare;
struct CURLMsg *msg;
curl_multi_wait(multi, nullptr, 0, curl_multi_wait_timeout_ms, &dontcare);
curl_multi_perform(multi, &dontcare);
while ((msg = curl_multi_info_read(multi, &dontcare)))
{
if (msg->msg == CURLMSG_DONE)
{
Request *request;
curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &request);
int finish_with = 600;
switch (msg->data.result)
{
case CURLE_OK:
long code;
curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code);
finish_with = (int)code;
break;
case CURLE_UNSUPPORTED_PROTOCOL: finish_with = 601; break;
case CURLE_COULDNT_RESOLVE_HOST: finish_with = 602; break;
case CURLE_OPERATION_TIMEDOUT: finish_with = 605; break;
case CURLE_URL_MALFORMAT: finish_with = 606; break;
case CURLE_COULDNT_CONNECT: finish_with = 607; break;
case CURLE_COULDNT_RESOLVE_PROXY: finish_with = 608; break;
case CURLE_TOO_MANY_REDIRECTS: finish_with = 611; break;
case CURLE_SSL_CONNECT_ERROR: finish_with = 612; break;
case CURLE_SSL_ENGINE_NOTFOUND: finish_with = 613; break;
case CURLE_SSL_ENGINE_SETFAILED: finish_with = 614; break;
case CURLE_SSL_CERTPROBLEM: finish_with = 615; break;
case CURLE_SSL_CIPHER: finish_with = 616; break;
case CURLE_SSL_ENGINE_INITFAILED: finish_with = 617; break;
case CURLE_SSL_CACERT_BADFILE: finish_with = 618; break;
case CURLE_SSL_CRL_BADFILE: finish_with = 619; break;
case CURLE_SSL_ISSUER_ERROR: finish_with = 620; break;
case CURLE_SSL_PINNEDPUBKEYNOTMATCH: finish_with = 621; break;
case CURLE_SSL_INVALIDCERTSTATUS: finish_with = 609; break;
case CURLE_HTTP2:
case CURLE_HTTP2_STREAM:
case CURLE_FAILED_INIT:
case CURLE_NOT_BUILT_IN:
default:
break;
}
if (finish_with >= 600)
{
std::cerr << request->error_buffer << std::endl;
}
request->status = finish_with;
}
};
}
std::set<Request *> requests_to_remove;
for (Request *request : requests)
{
bool signal_done = false;
{
std::lock_guard<std::mutex> g(request->rm_mutex);
if (shutting_down)
{
// In the weird case that a http::Request::Simple* call is
// waiting on this Request, we should fail the request
// instead of cancelling it ourselves.
request->status = 610;
}
if (!request->rm_canceled && request->rm_started && !request->added_to_multi && !request->status)
{
if (multi && request->easy)
{
MultiAdd(request);
}
else
{
request->status = 604;
}
}
if (!request->rm_canceled && request->rm_started && !request->rm_finished)
{
if (multi && request->easy)
{
#ifdef REQUEST_USE_CURL_OFFSET_T
curl_easy_getinfo(request->easy, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &request->rm_total);
curl_easy_getinfo(request->easy, CURLINFO_SIZE_DOWNLOAD_T, &request->rm_done);
#else
double total, done;
curl_easy_getinfo(request->easy, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &total);
curl_easy_getinfo(request->easy, CURLINFO_SIZE_DOWNLOAD, &done);
request->rm_total = (curl_off_t)total;
request->rm_done = (curl_off_t)done;
#endif
}
if (request->status)
{
request->rm_finished = true;
MultiRemove(request);
signal_done = true;
}
}
if (request->rm_canceled)
{
requests_to_remove.insert(request);
}
}
if (signal_done)
{
request->done_cv.notify_one();
}
}
for (Request *request : requests_to_remove)
{
requests.erase(request);
MultiRemove(request);
delete request;
}
}
}
void RequestManager::MultiAdd(Request *request)
{
if (multi && request->easy && !request->added_to_multi)
{
curl_multi_add_handle(multi, request->easy);
request->added_to_multi = true;
++requests_added_to_multi;
}
}
void RequestManager::MultiRemove(Request *request)
{
if (request->added_to_multi)
{
curl_multi_remove_handle(multi, request->easy);
request->added_to_multi = false;
--requests_added_to_multi;
}
}
bool RequestManager::AddRequest(Request *request)
{
if (!initialized)
return false;
{
std::lock_guard<std::mutex> g(rt_mutex);
requests_to_add.insert(request);
}
rt_cv.notify_one();
return true;
}
void RequestManager::StartRequest(Request *request)
{
{
std::lock_guard<std::mutex> g(rt_mutex);
requests_to_start = true;
}
rt_cv.notify_one();
}
void RequestManager::RemoveRequest(Request *request)
{
{
std::lock_guard<std::mutex> g(rt_mutex);
requests_to_remove = true;
}
rt_cv.notify_one();
}
}
#endif

View File

@ -1,61 +0,0 @@
#include "Config.h"
#ifndef NOHTTP
#ifndef REQUESTMANAGER_H
#define REQUESTMANAGER_H
#include "Config.h"
#include "common/tpt-minmax.h" // for MSVC, ensures windows.h doesn't cause compile errors by defining min/max
#include <thread>
#include <mutex>
#include <condition_variable>
#include <set>
#include <curl/curl.h>
#include "common/Singleton.h"
#include "common/String.h"
namespace http
{
class Request;
class RequestManager : public Singleton<RequestManager>
{
std::thread worker_thread;
std::set<Request *> requests;
int requests_added_to_multi = 0;
std::set<Request *> requests_to_add;
bool requests_to_start = false;
bool requests_to_remove = false;
bool initialized = false;
bool rt_shutting_down = false;
std::mutex rt_mutex;
std::condition_variable rt_cv;
CURLM *multi = nullptr;
void Start();
void Worker();
void MultiAdd(Request *request);
void MultiRemove(Request *request);
bool AddRequest(Request *request);
void StartRequest(Request *request);
void RemoveRequest(Request *request);
public:
RequestManager() { }
~RequestManager() { }
void Initialise(ByteString newProxy, ByteString newCafile, ByteString newCapath);
void Shutdown();
friend class Request;
};
extern const long timeout;
extern ByteString proxy;
extern ByteString cafile;
extern ByteString capath;
extern ByteString user_agent;
}
#endif // REQUESTMANAGER_H
#endif

View File

@ -1,56 +0,0 @@
#ifndef REQUESTMONITOR_H
#define REQUESTMONITOR_H
#include <type_traits>
#include <cassert>
namespace http
{
template<class R>
class RequestMonitor
{
R *request;
protected:
RequestMonitor() :
request(nullptr)
{
}
virtual ~RequestMonitor()
{
if (request)
{
request->Cancel();
}
}
void RequestPoll()
{
if (request && request->CheckDone())
{
OnResponse(request->Finish());
request = nullptr;
}
}
template<class... Args>
void RequestSetup(Args&&... args)
{
assert(!request);
request = new R(std::forward<Args>(args)...);
}
void RequestStart()
{
assert(request);
request->Start();
}
virtual void OnResponse(typename std::invoke_result<decltype(&R::Finish), R>::type v) = 0;
};
}
#endif // REQUESTMONITOR_H

View File

@ -1,37 +1,20 @@
#include "SaveUserInfoRequest.h"
#include "Config.h"
#include "client/UserInfo.h"
namespace http
{
SaveUserInfoRequest::SaveUserInfoRequest(UserInfo &info) :
APIRequest(SCHEME SERVER "/Profile.json")
SaveUserInfoRequest::SaveUserInfoRequest(UserInfo info) :
APIRequest(ByteString::Build(SCHEME, SERVER, "/Profile.json"), authRequire, true)
{
AddPostData({
AddPostData(FormData{
{ "Location", info.location.ToUtf8() },
{ "Biography", info.biography.ToUtf8() }
{ "Biography", info.biography.ToUtf8() },
});
}
SaveUserInfoRequest::~SaveUserInfoRequest()
void SaveUserInfoRequest::Finish()
{
}
bool SaveUserInfoRequest::Finish()
{
auto result = APIRequest::Finish();
// Note that at this point it's not safe to use any member of the
// SaveUserInfoRequest object as Request::Finish signals RequestManager
// to delete it.
if (result.document)
{
return (*result.document)["Status"].asInt() == 1;
}
else
{
return false;
}
APIRequest::Finish();
}
}

View File

@ -1,21 +1,14 @@
#ifndef SAVEUSERINFOREQUEST2_H
#define SAVEUSERINFOREQUEST2_H
#pragma once
#include "APIRequest.h"
class UserInfo;
#include "client/UserInfo.h"
namespace http
{
class SaveUserInfoRequest : public APIRequest
{
public:
SaveUserInfoRequest(UserInfo &info);
virtual ~SaveUserInfoRequest();
SaveUserInfoRequest(UserInfo info);
bool Finish();
void Finish();
};
}
#endif // SAVEUSERINFOREQUEST2_H

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