Sort out version info

The idea is to have the following version information included:

 - 1-component save version
   - 2-component under the hood but the minor component shouldn't ever change again
   - see currentVersionMajor in GameSave.cpp
 - 1-component website API version
   - again, currently 2-component because that's what the website code expects
   - see apiVersion in requestmanager/Common.cpp
 - 2-component display version, entirely cosmetic
   - exposed as meson options display_version_major and display_version_minor
   - see APP_VERSION in Config.template.h
 - 1-component business logic version aka build number
   - exposed as meson option build_num
   - see APP_VERSION in Config.template.h
 - variant id aka mod id, tightly coupled with the build number
   - exposed as meson option mod_id
   - see MOD_ID in Config.template.h
 - display and business logic versions repeated for the upstream
   - exposed as meson options upstream_version_major, upstream_version_minor, and upstream_build_num
   - we'll have to update these alongside display_version_major, display_version_minor, and build_num, but mod owners can just merge our changes
   - see UPSTREAM_VERSION in Config.template.h
 - update channel, makes sense in the context of the variant (and yes, this would later enable mod snapshots)
   - currently not exposed as a meson option but derived from meson options snapshot and mod_id
   - see IDENT_RELTYPE in Config.template.h
 - vcs tag aka git commit hash
   - set by build.sh in ghactions workflows
   - see VCS_TAG in VcsTag.tempalte.h

Rather importantly, the save and website API versions are now allowed to change independently of the display version.

These changes also allowed me to remove the ugly sed hacks in build.sh used to provision some manifest files; they are now provisioned by meson.

Also add version info for windows and android.
This commit is contained in:
Tamás Bálint Misius 2023-10-19 10:48:10 +02:00
parent 7f0f9ad132
commit 7fc3fb4c15
No known key found for this signature in database
GPG Key ID: 5B472A12F6ECA9F2
21 changed files with 229 additions and 111 deletions

36
.github/build.sh vendored
View File

@ -87,18 +87,6 @@ function inplace_sed() {
fi fi
} }
function subst_version() {
local path=$1
if [[ $BSH_HOST_PLATFORM == darwin ]]; then
inplace_sed "s|SUBST_MACOS_MIN_VER|$macos_min_ver|g" $path
else
inplace_sed "s|SUBST_DATE|$(date --iso-8601)|g" $path
fi
inplace_sed "s|SUBST_SAVE_VERSION|$save_version|g" $path
inplace_sed "s|SUBST_MINOR_VERSION|$minor_version|g" $path
inplace_sed "s|SUBST_BUILD_NUM|$build_num|g" $path
}
if [[ $BSH_HOST_PLATFORM == android ]]; then if [[ $BSH_HOST_PLATFORM == android ]]; then
android_platform=android-30 android_platform=android-30
if [[ -z "${JAVA_HOME_8_X64-}" ]]; then if [[ -z "${JAVA_HOME_8_X64-}" ]]; then
@ -189,6 +177,11 @@ meson_configure=meson$'\t'setup
if [[ $BSH_DEBUG_RELEASE == release ]]; then if [[ $BSH_DEBUG_RELEASE == release ]]; then
meson_configure+=$'\t'-Dbuildtype=debugoptimized meson_configure+=$'\t'-Dbuildtype=debugoptimized
fi fi
if [[ $BSH_HOST_PLATFORM == darwin ]]; then
meson_configure+=$'\t'-Dmanifest_macos_min_ver=$macos_min_ver
else
meson_configure+=$'\t'-Dmanifest_date=$(date --iso-8601)
fi
meson_configure+=$'\t'-Dapp_name=$APP_NAME meson_configure+=$'\t'-Dapp_name=$APP_NAME
meson_configure+=$'\t'-Dapp_comment=$APP_COMMENT meson_configure+=$'\t'-Dapp_comment=$APP_COMMENT
meson_configure+=$'\t'-Dapp_exe=$APP_EXE meson_configure+=$'\t'-Dapp_exe=$APP_EXE
@ -251,19 +244,10 @@ fi
if [[ $RELEASE_TYPE == stable ]]; then if [[ $RELEASE_TYPE == stable ]]; then
stable_or_beta=yes stable_or_beta=yes
fi fi
set +e if [[ $stable_or_beta == yes ]]; then
save_version=$(cat src/Config.template.h | sed -n 's/constexpr int SAVE_VERSION * = \([^;]*\);/\1/p') meson_configure+=$'\t'-Ddisplay_version_major=$(echo $RELEASE_NAME | cut -d '.' -f 1)
minor_version=$(cat src/Config.template.h | sed -n 's/constexpr int MINOR_VERSION * = \([^;]*\);/\1/p') meson_configure+=$'\t'-Ddisplay_version_minor=$(echo $RELEASE_NAME | cut -d '.' -f 2)
build_num=$(cat src/Config.template.h | sed -n 's/constexpr int BUILD_NUM * = \([^;]*\);/\1/p') meson_configure+=$'\t'-Dbuild_num=$(echo $RELEASE_NAME | cut -d '.' -f 3)
if [[ -z ${save_version-} ]] || [[ -z ${minor_version-} ]] || [[ -z ${build_num-} ]]; then
>&2 echo "failed to extract version from Config.template.h"
exit 1
fi
set -e
if [[ $stable_or_beta == yes ]] && [[ $MOD_ID != 0 ]]; then
save_version=$(echo $RELEASE_NAME | cut -d '.' -f 1)
minor_version=$(echo $RELEASE_NAME | cut -d '.' -f 2)
build_num=$(echo $RELEASE_NAME | cut -d '.' -f 3)
fi fi
if [[ $RELEASE_TYPE == snapshot ]]; then if [[ $RELEASE_TYPE == snapshot ]]; then
meson_configure+=$'\t'-Dsnapshot=true meson_configure+=$'\t'-Dsnapshot=true
@ -426,7 +410,6 @@ if [[ $PACKAGE_MODE == dmg ]]; then
mkdir $appdir mkdir $appdir
mkdir $appdir/Contents mkdir $appdir/Contents
cp resources/Info.plist $appdir/Contents/Info.plist cp resources/Info.plist $appdir/Contents/Info.plist
subst_version $appdir/Contents/Info.plist
mkdir $appdir/Contents/MacOS mkdir $appdir/Contents/MacOS
cp $APP_EXE $appdir/Contents/MacOS/$APP_EXE cp $APP_EXE $appdir/Contents/MacOS/$APP_EXE
mkdir $appdir/Contents/Resources mkdir $appdir/Contents/Resources
@ -471,7 +454,6 @@ elif [[ $PACKAGE_MODE == appimage ]]; then
cp ../resources/icon_exe.svg $appdir/$APP_VENDOR-$APP_EXE.svg cp ../resources/icon_exe.svg $appdir/$APP_VENDOR-$APP_EXE.svg
cp resources/powder.desktop $appdir/$APP_ID.desktop cp resources/powder.desktop $appdir/$APP_ID.desktop
cp resources/appdata.xml $appdir/usr/share/metainfo/$APP_ID.appdata.xml cp resources/appdata.xml $appdir/usr/share/metainfo/$APP_ID.appdata.xml
subst_version $appdir/usr/share/metainfo/$APP_ID.appdata.xml
cp $appdir/$APP_VENDOR-$APP_EXE.svg $appdir/usr/share/icons/$APP_VENDOR-$APP_EXE.svg cp $appdir/$APP_VENDOR-$APP_EXE.svg $appdir/usr/share/icons/$APP_VENDOR-$APP_EXE.svg
cp $appdir/$APP_ID.desktop $appdir/usr/share/applications/$APP_ID.desktop cp $appdir/$APP_ID.desktop $appdir/usr/share/applications/$APP_ID.desktop
./appimagetool $appdir $ASSET_PATH ./appimagetool $appdir $ASSET_PATH

View File

@ -2,8 +2,8 @@
<manifest <manifest
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
package="@APPID@" package="@APPID@"
android:versionCode="1" android:versionCode="@BUILD_NUM@"
android:versionName="1.0" android:versionName="@DISPLAY_VERSION_MAJOR@.@DISPLAY_VERSION_MINOR@.@BUILD_NUM@"
android:installLocation="auto" android:installLocation="auto"
> >
<uses-sdk <uses-sdk

View File

@ -43,6 +43,48 @@ option(
value: 0, value: 0,
description: 'Snapshot ID, only relevant if \'snapshot\' is true' description: 'Snapshot ID, only relevant if \'snapshot\' is true'
) )
option(
'display_version_major',
type: 'integer',
min: 0,
value: 97,
description: 'Major component of the display version, should more or less map to the MINOR version in semantic versioning'
)
option(
'display_version_minor',
type: 'integer',
min: 0,
value: 0,
description: 'Minor component of the display version, should more or less map to the PATCH version in semantic versioning'
)
option(
'build_num',
type: 'integer',
min: 0,
value: 352,
description: 'Build number, should be strictly monotonously increasing across public releases'
)
option(
'upstream_version_major',
type: 'integer',
min: 0,
value: 97,
description: 'Major component of the upstream display version, mod owners should not change this but merge upstream changes to it'
)
option(
'upstream_version_minor',
type: 'integer',
min: 0,
value: 0,
description: 'Minor component of the upstream display version, mod owners should not change this but merge upstream changes to it'
)
option(
'upstream_build_num',
type: 'integer',
min: 0,
value: 352,
description: 'Upstream build number, mod owners should not change this but merge upstream changes to it'
)
option( option(
'mod_id', 'mod_id',
type: 'integer', type: 'integer',
@ -227,3 +269,21 @@ option(
value: 'static_release_only', value: 'static_release_only',
description: 'Enable VCS tag resolution, introduces an always-stale custom target' description: 'Enable VCS tag resolution, introduces an always-stale custom target'
) )
option(
'manifest_copyright',
type: 'string',
value: 'Copyright © 2008-2011 Stanislaw K Skowrenek, Copyright © 2011-2023 Simon Robertshaw, Copyright © 2016-2023 jacob1',
description: 'Copyright string, don\'t take too seriously, subject to change'
)
option(
'manifest_macos_min_ver',
type: 'string',
value: '',
description: 'MacOS minimum allowed platform version string, used by ghactions workflows, not useful otherwise'
)
option(
'manifest_date',
type: 'string',
value: '',
description: 'Build date string, used by ghactions workflows, not useful otherwise'
)

View File

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

View File

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

View File

@ -52,8 +52,18 @@ if host_platform == 'windows'
command: command, command: command,
) } ) }
endforeach endforeach
rc_conf_data = configuration_data()
rc_conf_data.merge_from(conf_data)
rc_conf_data.set('RESOUCE_H', join_paths(meson.current_source_dir(), 'resource.h'))
rc_conf_data.set('WINUTF8_XML', join_paths(meson.current_source_dir(), 'winutf8.xml'))
rc_conf_data.set('ICON_EXE_ICO', join_paths(meson.current_build_dir(), 'icon_exe.ico'))
rc_conf_data.set('ICON_CPS_ICO', join_paths(meson.current_build_dir(), 'icon_cps.ico'))
powder_files += windows_mod.compile_resources( powder_files += windows_mod.compile_resources(
'powder-res.rc', configure_file(
input: 'powder-res.template.rc',
output: 'powder-res.rc',
configuration: rc_conf_data,
),
depends: [ depends: [
generated_win_icos['icon_exe'], generated_win_icos['icon_exe'],
generated_win_icos['icon_cps'], generated_win_icos['icon_cps'],

View File

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

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

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "VcsTag.h" #include "VcsTag.h"
#include "common/Version.h"
constexpr bool SET_WINDOW_ICON = @SET_WINDOW_ICON@; constexpr bool SET_WINDOW_ICON = @SET_WINDOW_ICON@;
constexpr bool DEBUG = @DEBUG@; constexpr bool DEBUG = @DEBUG@;
@ -44,13 +45,18 @@ constexpr char APPID[] = "@APPID@";
constexpr char APPDATA[] = "@APPDATA@"; constexpr char APPDATA[] = "@APPDATA@";
constexpr char APPVENDOR[] = "@APPVENDOR@"; constexpr char APPVENDOR[] = "@APPVENDOR@";
constexpr int SAVE_VERSION = 97;
constexpr int MINOR_VERSION = 0;
constexpr int BUILD_NUM = 352;
constexpr int SNAPSHOT_ID = @SNAPSHOT_ID@; constexpr int SNAPSHOT_ID = @SNAPSHOT_ID@;
constexpr int MOD_ID = @MOD_ID@; constexpr int MOD_ID = @MOD_ID@;
constexpr int FUTURE_SAVE_VERSION = 98;
constexpr int FUTURE_MINOR_VERSION = 0; struct DisplayVersionWithBuild
{
Version<2> displayVersion;
size_t build;
};
constexpr DisplayVersionWithBuild APP_VERSION = { { @DISPLAY_VERSION_MAJOR@, @DISPLAY_VERSION_MINOR@ }, @BUILD_NUM@ };
constexpr DisplayVersionWithBuild UPSTREAM_VERSION = { { @UPSTREAM_VERSION_MAJOR@, @UPSTREAM_VERSION_MINOR@ }, @UPSTREAM_BUILD_NUM@ };
constexpr auto DISPLAY_VERSION = APP_VERSION.displayVersion;
constexpr char IDENT_RELTYPE = SNAPSHOT ? 'S' : (BETA ? 'B' : 'R'); constexpr char IDENT_RELTYPE = SNAPSHOT ? 'S' : (BETA ? 'B' : 'R');

View File

@ -325,8 +325,8 @@ ByteString Client::AddStamp(std::unique_ptr<GameSave> saveData)
} }
saveData->authors = stampInfo; saveData->authors = stampInfo;
auto [ fromNewerVersion, gameData ] = saveData->Serialise(); std::vector<char> gameData;
(void)fromNewerVersion; std::tie(std::ignore, gameData) = saveData->Serialise();
if (!gameData.size()) if (!gameData.size())
return ""; return "";

View File

@ -4,7 +4,6 @@
#include "simulation/Simulation.h" #include "simulation/Simulation.h"
#include "simulation/ElementClasses.h" #include "simulation/ElementClasses.h"
#include "common/tpt-compat.h" #include "common/tpt-compat.h"
#include "common/Version.h"
#include "bson/BSON.h" #include "bson/BSON.h"
#include "graphics/Renderer.h" #include "graphics/Renderer.h"
#include "Config.h" #include "Config.h"
@ -16,6 +15,9 @@
#include <cmath> #include <cmath>
#include <algorithm> #include <algorithm>
constexpr auto currentVersionMajor = 97;
constexpr auto nextVersionMajor = currentVersionMajor + 1;
static void ConvertJsonToBson(bson *b, Json::Value j, int depth = 0); static void ConvertJsonToBson(bson *b, Json::Value j, int depth = 0);
static void ConvertBsonToJson(bson_iterator *b, Json::Value *j, int depth = 0); static void ConvertBsonToJson(bson_iterator *b, Json::Value *j, int depth = 0);
static void CheckBsonFieldUser(bson_iterator iter, const char *field, unsigned char **data, unsigned int *fieldLen); static void CheckBsonFieldUser(bson_iterator iter, const char *field, unsigned char **data, unsigned int *fieldLen);
@ -326,6 +328,9 @@ static void CheckBsonFieldFloat(bson_iterator iter, const char *field, float *se
void GameSave::readOPS(const std::vector<char> &data) void GameSave::readOPS(const std::vector<char> &data)
{ {
constexpr auto currentVersion = Version(currentVersionMajor, 0);
constexpr auto nextVersion = Version(nextVersionMajor, 0);
Renderer::PopulateTables(); Renderer::PopulateTables();
unsigned char *inputData = (unsigned char*)&data[0], *partsData = NULL, *partsPosData = NULL, *fanData = NULL, *wallData = NULL, *soapLinkData = NULL; unsigned char *inputData = (unsigned char*)&data[0], *partsData = NULL, *partsPosData = NULL, *fanData = NULL, *wallData = NULL, *soapLinkData = NULL;
@ -333,9 +338,8 @@ void GameSave::readOPS(const std::vector<char> &data)
unsigned int inputDataLen = data.size(), bsonDataLen = 0, partsDataLen, partsPosDataLen, fanDataLen, wallDataLen, soapLinkDataLen; unsigned int inputDataLen = data.size(), bsonDataLen = 0, partsDataLen, partsPosDataLen, fanDataLen, wallDataLen, soapLinkDataLen;
unsigned int pressDataLen, vxDataLen, vyDataLen, ambientDataLen, blockAirDataLen; unsigned int pressDataLen, vxDataLen, vyDataLen, ambientDataLen, blockAirDataLen;
unsigned partsCount = 0; unsigned partsCount = 0;
int savedVersion = inputData[4]; unsigned int savedVersion = inputData[4];
majorVersion = savedVersion; version = { savedVersion, 0 };
minorVersion = 0;
bool fakeNewerVersion = false; // used for development builds only bool fakeNewerVersion = false; // used for development builds only
bson b; bson b;
@ -354,7 +358,7 @@ void GameSave::readOPS(const std::vector<char> &data)
auto partS = blockS * CELL; auto partS = blockS * CELL;
//From newer version //From newer version
if (savedVersion > SAVE_VERSION) if (savedVersion > currentVersion[0])
{ {
fromNewerVersion = true; fromNewerVersion = true;
//throw ParseException(ParseException::WrongVersion, "Save from newer version"); //throw ParseException(ParseException::WrongVersion, "Save from newer version");
@ -470,7 +474,7 @@ void GameSave::readOPS(const std::vector<char> &data)
if (!strcmp(bson_iterator_key(&signiter), "text") && bson_iterator_type(&signiter) == BSON_STRING) if (!strcmp(bson_iterator_key(&signiter), "text") && bson_iterator_type(&signiter) == BSON_STRING)
{ {
tempSign.text = format::CleanString(ByteString(bson_iterator_string(&signiter)).FromUtf8(), true, true, true).Substr(0, 45); tempSign.text = format::CleanString(ByteString(bson_iterator_string(&signiter)).FromUtf8(), true, true, true).Substr(0, 45);
if (majorVersion < 94 || (majorVersion == 94 && minorVersion < 2)) if (version < Version(94, 2))
{ {
if (tempSign.text == "{t}") if (tempSign.text == "{t}")
{ {
@ -582,7 +586,7 @@ void GameSave::readOPS(const std::vector<char> &data)
{ {
if (!strcmp(bson_iterator_key(&subiter), "minorVersion")) if (!strcmp(bson_iterator_key(&subiter), "minorVersion"))
{ {
minorVersion = bson_iterator_int(&subiter); version[1] = bson_iterator_int(&subiter);
} }
} }
} }
@ -596,27 +600,30 @@ void GameSave::readOPS(const std::vector<char> &data)
{ {
if (bson_iterator_type(&iter) == BSON_OBJECT) if (bson_iterator_type(&iter) == BSON_OBJECT)
{ {
int major = INT_MAX, minor = INT_MAX; Version<2> version;
bson_iterator subiter;
bson_iterator_subiterator(&iter, &subiter);
while (bson_iterator_next(&subiter))
{ {
if (bson_iterator_type(&subiter) == BSON_INT) int major = INT_MAX, minor = INT_MAX;
bson_iterator subiter;
bson_iterator_subiterator(&iter, &subiter);
while (bson_iterator_next(&subiter))
{ {
if (!strcmp(bson_iterator_key(&subiter), "major")) if (bson_iterator_type(&subiter) == BSON_INT)
major = bson_iterator_int(&subiter); {
else if (!strcmp(bson_iterator_key(&subiter), "minor")) if (!strcmp(bson_iterator_key(&subiter), "major"))
minor = bson_iterator_int(&subiter); major = bson_iterator_int(&subiter);
else if (!strcmp(bson_iterator_key(&subiter), "minor"))
minor = bson_iterator_int(&subiter);
}
} }
version = Version(major, minor);
} }
auto majorToCheck = ALLOW_FAKE_NEWER_VERSION ? FUTURE_SAVE_VERSION : SAVE_VERSION; auto versionToCheck = ALLOW_FAKE_NEWER_VERSION ? nextVersion : currentVersion;
auto minorToCheck = ALLOW_FAKE_NEWER_VERSION ? FUTURE_MINOR_VERSION : MINOR_VERSION; if (versionToCheck < version)
if (major > majorToCheck || (major == majorToCheck && minor > minorToCheck))
{ {
String errorMessage = String::Build("Save from a newer version: Requires version ", major, ".", minor); String errorMessage = String::Build("Save from a newer version: Requires version ", version[0], ".", version[1]);
throw ParseException(ParseException::WrongVersion, errorMessage); throw ParseException(ParseException::WrongVersion, errorMessage);
} }
else if (ALLOW_FAKE_NEWER_VERSION && (major > SAVE_VERSION || (major == SAVE_VERSION && minor > MINOR_VERSION))) else if (ALLOW_FAKE_NEWER_VERSION && currentVersion < version)
{ {
fakeNewerVersion = true; fakeNewerVersion = true;
} }
@ -642,8 +649,8 @@ void GameSave::readOPS(const std::vector<char> &data)
} }
} }
auto paletteRemap = [this, saveVersion = Version(majorVersion, minorVersion)](auto maxVersion, ByteString from, ByteString to) { auto paletteRemap = [this](auto maxVersion, ByteString from, ByteString to) {
if (saveVersion <= maxVersion) if (version <= maxVersion)
{ {
auto it = std::find_if(palette.begin(), palette.end(), [&from](auto &item) { auto it = std::find_if(palette.begin(), palette.end(), [&from](auto &item) {
return item.first == from; return item.first == from;
@ -1182,7 +1189,7 @@ void GameSave::readPSv(const std::vector<char> &dataVec)
unsigned char * saveData = (unsigned char *)&dataVec[0]; unsigned char * saveData = (unsigned char *)&dataVec[0];
auto dataLength = int(dataVec.size()); auto dataLength = int(dataVec.size());
int q,p=0, ver, pty, ty, legacy_beta=0; int q,p=0, pty, ty, legacy_beta=0;
Vec2<int> blockP = { 0, 0 }; Vec2<int> blockP = { 0, 0 };
int new_format = 0, ttv = 0; int new_format = 0, ttv = 0;
@ -1202,11 +1209,10 @@ void GameSave::readPSv(const std::vector<char> &dataVec)
if (saveData[2]==0x76 && saveData[1]==0x53 && saveData[0]==0x50) { if (saveData[2]==0x76 && saveData[1]==0x53 && saveData[0]==0x50) {
new_format = 1; new_format = 1;
} }
if (saveData[4]>SAVE_VERSION) if (saveData[4]>97) // this used to respect currentVersion but no valid PSv will ever have a version > 97 so it's ok to hardcode
throw ParseException(ParseException::WrongVersion, "Save from newer version"); throw ParseException(ParseException::WrongVersion, "Save from newer version");
ver = saveData[4]; version = { saveData[4], 0 };
majorVersion = saveData[4]; auto ver = version[0];
minorVersion = 0;
if (ver<34) if (ver<34)
{ {
@ -1818,16 +1824,18 @@ void GameSave::readPSv(const std::vector<char> &dataVec)
std::pair<bool, std::vector<char>> GameSave::serialiseOPS() const std::pair<bool, std::vector<char>> GameSave::serialiseOPS() const
{ {
constexpr auto currentVersion = Version(currentVersionMajor, 0);
// minimum version this save is compatible with // minimum version this save is compatible with
// when building, this number may be increased depending on what elements are used // when building, this number may be increased depending on what elements are used
// or what properties are detected // or what properties are detected
int minimumMajorVersion = 90, minimumMinorVersion = 2; auto minimumVersion = Version(90, 2);
auto RESTRICTVERSION = [&minimumMajorVersion, &minimumMinorVersion](int major, int minor) { auto RESTRICTVERSION = [&minimumVersion](auto major, auto minor = 0) {
// restrict the minimum version this save can be opened with // restrict the minimum version this save can be opened with
if (major > minimumMajorVersion || ((major == minimumMajorVersion && minor > minimumMinorVersion))) auto version = Version(major, minor);
if (minimumVersion < version)
{ {
minimumMajorVersion = major; minimumVersion = version;
minimumMinorVersion = minor;
} }
}; };
@ -2295,7 +2303,7 @@ std::pair<bool, std::vector<char>> GameSave::serialiseOPS() const
} }
// Mark save as incompatible with latest release // Mark save as incompatible with latest release
bool fakeFromNewerVersion = ALLOW_FAKE_NEWER_VERSION && (minimumMajorVersion > SAVE_VERSION || (minimumMajorVersion == SAVE_VERSION && minimumMinorVersion > MINOR_VERSION)); bool fakeFromNewerVersion = ALLOW_FAKE_NEWER_VERSION && currentVersion < minimumVersion;
bson b; bson b;
b.data = NULL; b.data = NULL;
@ -2306,9 +2314,9 @@ std::pair<bool, std::vector<char>> GameSave::serialiseOPS() const
set_bson_err_handler([](const char* err) { throw BuildException("BSON error when parsing save: " + ByteString(err).FromUtf8()); }); set_bson_err_handler([](const char* err) { throw BuildException("BSON error when parsing save: " + ByteString(err).FromUtf8()); });
bson_init(&b); bson_init(&b);
bson_append_start_object(&b, "origin"); bson_append_start_object(&b, "origin");
bson_append_int(&b, "majorVersion", SAVE_VERSION); bson_append_int(&b, "majorVersion", int(currentVersion[0]));
bson_append_int(&b, "minorVersion", MINOR_VERSION); bson_append_int(&b, "minorVersion", int(currentVersion[1]));
bson_append_int(&b, "buildNum", BUILD_NUM); bson_append_int(&b, "buildNum", APP_VERSION.build);
bson_append_int(&b, "snapshotId", SNAPSHOT_ID); bson_append_int(&b, "snapshotId", SNAPSHOT_ID);
bson_append_int(&b, "modId", MOD_ID); bson_append_int(&b, "modId", MOD_ID);
bson_append_string(&b, "releaseType", ByteString(1, IDENT_RELTYPE).c_str()); bson_append_string(&b, "releaseType", ByteString(1, IDENT_RELTYPE).c_str());
@ -2322,8 +2330,8 @@ std::pair<bool, std::vector<char>> GameSave::serialiseOPS() const
RESTRICTVERSION(97, 0); RESTRICTVERSION(97, 0);
} }
bson_append_start_object(&b, "minimumVersion"); bson_append_start_object(&b, "minimumVersion");
bson_append_int(&b, "major", minimumMajorVersion); bson_append_int(&b, "major", int(minimumVersion[0]));
bson_append_int(&b, "minor", minimumMinorVersion); bson_append_int(&b, "minor", int(minimumVersion[1]));
bson_append_finish_object(&b); bson_append_finish_object(&b);
@ -2467,7 +2475,7 @@ std::pair<bool, std::vector<char>> GameSave::serialiseOPS() const
header[1] = 'P'; header[1] = 'P';
header[2] = 'S'; header[2] = 'S';
header[3] = '1'; header[3] = '1';
header[4] = SAVE_VERSION; header[4] = currentVersion[0];
header[5] = CELL; header[5] = CELL;
header[6] = blockS.X; header[6] = blockS.X;
header[7] = blockS.Y; header[7] = blockS.Y;

View File

@ -2,6 +2,7 @@
#include "common/Plane.h" #include "common/Plane.h"
#include "common/String.h" #include "common/String.h"
#include "common/tpt-rand.h" #include "common/tpt-rand.h"
#include "common/Version.h"
#include "simulation/Sign.h" #include "simulation/Sign.h"
#include "simulation/Particle.h" #include "simulation/Particle.h"
#include "Misc.h" #include "Misc.h"
@ -64,8 +65,7 @@ class GameSave
public: public:
Vec2<int> blockSize = { 0, 0 }; Vec2<int> blockSize = { 0, 0 };
bool fromNewerVersion = false; bool fromNewerVersion = false;
int majorVersion = 0; Version<2> version{};
int minorVersion = 0;
bool hasPressure = false; bool hasPressure = false;
bool hasAmbientHeat = false; bool hasAmbientHeat = false;
bool hasBlockAirMaps = false; // only written by readOPS, never read bool hasBlockAirMaps = false; // only written by readOPS, never read

View File

@ -82,12 +82,12 @@ namespace http
else else
{ {
parseUpdate("Stable", UpdateInfo::channelStable, [](int build) -> bool { parseUpdate("Stable", UpdateInfo::channelStable, [](int build) -> bool {
return build > BUILD_NUM; return size_t(build) > APP_VERSION.build;
}); });
if (!startupInfo.updateInfo.has_value()) if (!startupInfo.updateInfo.has_value())
{ {
parseUpdate("Beta", UpdateInfo::channelBeta, [](int build) -> bool { parseUpdate("Beta", UpdateInfo::channelBeta, [](int build) -> bool {
return build > BUILD_NUM; return size_t(build) > APP_VERSION.build;
}); });
} }
} }

View File

@ -10,13 +10,14 @@ namespace http
capath(newCapath), capath(newCapath),
disableNetwork(newDisableNetwork) disableNetwork(newDisableNetwork)
{ {
auto apiVersion = Version(97, 0);
userAgent = ByteString::Build( userAgent = ByteString::Build(
"PowderToy/", SAVE_VERSION, ".", MINOR_VERSION, "PowderToy/", DISPLAY_VERSION[0], ".", DISPLAY_VERSION[1],
" (", IDENT_PLATFORM, " (", IDENT_PLATFORM,
"; NO", // Unused, used to be SSE level. "; NO", // Unused, used to be SSE level.
"; M", MOD_ID, "; M", MOD_ID,
"; ", IDENT, "; ", IDENT,
") TPTPP/", SAVE_VERSION, ".", MINOR_VERSION, ".", BUILD_NUM, IDENT_RELTYPE, ".", SNAPSHOT_ID ") TPTPP/", apiVersion[0], ".", apiVersion[1], ".", APP_VERSION.build, IDENT_RELTYPE, ".", SNAPSHOT_ID
); );
} }

View File

@ -26,6 +26,16 @@ struct Version
{ {
return *this < other || *this == other; return *this < other || *this == other;
} }
constexpr size_t operator [](size_t index) const
{
return components[index];
}
size_t &operator [](size_t index)
{
return components[index];
}
}; };
template<class ...Args> template<class ...Args>

View File

@ -1204,10 +1204,10 @@ void GameController::OpenLocalSaveWindow(bool asCurrent)
gameSave->authors = localSaveInfo; gameSave->authors = localSaveInfo;
Platform::MakeDirectory(LOCAL_SAVE_DIR); Platform::MakeDirectory(LOCAL_SAVE_DIR);
auto [ fromNewerVersion, saveData ] = gameSave->Serialise(); std::vector<char> saveData;
std::tie(std::ignore, saveData) = gameSave->Serialise();
tempSave->SetGameSave(std::move(gameSave)); tempSave->SetGameSave(std::move(gameSave));
gameModel->SetSaveFile(std::move(tempSave), gameView->ShiftBehaviour()); gameModel->SetSaveFile(std::move(tempSave), gameView->ShiftBehaviour());
(void)fromNewerVersion;
if (saveData.size() == 0) if (saveData.size() == 0)
new ErrorMessage("Error", "Unable to serialize game data."); new ErrorMessage("Error", "Unable to serialize game data.");
else if (!Platform::WriteFile(saveData, gameModel->GetSaveFile()->GetName())) else if (!Platform::WriteFile(saveData, gameModel->GetSaveFile()->GetName()))
@ -1611,11 +1611,11 @@ void GameController::NotifyUpdateAvailable(Client * sender)
} }
else if constexpr (BETA) else if constexpr (BETA)
{ {
updateMessage << SAVE_VERSION << "." << MINOR_VERSION << " Beta, Build " << BUILD_NUM; updateMessage << DISPLAY_VERSION[0] << "." << DISPLAY_VERSION[1] << " Beta, Build " << APP_VERSION.build;
} }
else else
{ {
updateMessage << SAVE_VERSION << "." << MINOR_VERSION << " Stable, Build " << BUILD_NUM; updateMessage << DISPLAY_VERSION[0] << "." << DISPLAY_VERSION[1] << " Stable, Build " << APP_VERSION.build;
} }
updateMessage << "\nNew version:\n "; updateMessage << "\nNew version:\n ";

View File

@ -5,7 +5,7 @@
inline ByteString VersionInfo() inline ByteString VersionInfo()
{ {
ByteStringBuilder sb; ByteStringBuilder sb;
sb << SAVE_VERSION << "." << MINOR_VERSION << "." << BUILD_NUM << " " << IDENT; sb << DISPLAY_VERSION[0] << "." << DISPLAY_VERSION[1] << "." << APP_VERSION.build << " " << IDENT;
if constexpr (SNAPSHOT) if constexpr (SNAPSHOT)
{ {
sb << " SNAPSHOT " << SNAPSHOT_ID; sb << " SNAPSHOT " << SNAPSHOT_ID;
@ -39,7 +39,7 @@ inline ByteString VersionInfo()
inline ByteString IntroText() inline ByteString IntroText()
{ {
ByteStringBuilder sb; ByteStringBuilder sb;
sb << "\bl\bU" << APPNAME << "\bU - Version " << SAVE_VERSION << "." << MINOR_VERSION << " - https://powdertoy.co.uk, irc.libera.chat #powder, https://tpt.io/discord\n" sb << "\bl\bU" << APPNAME << "\bU - Version " << DISPLAY_VERSION[0] << "." << DISPLAY_VERSION[1] << " - https://powdertoy.co.uk, irc.libera.chat #powder, https://tpt.io/discord\n"
"\n" "\n"
"\n" "\n"
"\bgControl+C/V/X are Copy, Paste and cut respectively.\n" "\bgControl+C/V/X are Copy, Paste and cut respectively.\n"

View File

@ -115,8 +115,8 @@ void LocalSaveActivity::saveWrite(ByteString finalFilename)
gameSave->authors = localSaveInfo; gameSave->authors = localSaveInfo;
save->SetGameSave(std::move(gameSave)); save->SetGameSave(std::move(gameSave));
} }
auto [ fromNewerVersion, saveData ] = save->GetGameSave()->Serialise(); std::vector<char> saveData;
(void)fromNewerVersion; std::tie(std::ignore, saveData) = save->GetGameSave()->Serialise();
if (saveData.size() == 0) if (saveData.size() == 0)
new ErrorMessage("Error", "Unable to serialize game data."); new ErrorMessage("Error", "Unable to serialize game data.");
else if (!Platform::WriteFile(saveData, finalFilename)) else if (!Platform::WriteFile(saveData, finalFilename))

View File

@ -403,12 +403,18 @@ LuaScriptInterface::LuaScriptInterface(GameController * c, GameModel * m):
lua_newtable(l); lua_newtable(l);
tptPropertiesVersion = lua_gettop(l); tptPropertiesVersion = lua_gettop(l);
lua_pushinteger(l, SAVE_VERSION); lua_pushinteger(l, DISPLAY_VERSION[0]);
lua_setfield(l, tptPropertiesVersion, "major"); lua_setfield(l, tptPropertiesVersion, "major");
lua_pushinteger(l, MINOR_VERSION); lua_pushinteger(l, DISPLAY_VERSION[1]);
lua_setfield(l, tptPropertiesVersion, "minor"); lua_setfield(l, tptPropertiesVersion, "minor");
lua_pushinteger(l, BUILD_NUM); lua_pushinteger(l, APP_VERSION.build);
lua_setfield(l, tptPropertiesVersion, "build"); lua_setfield(l, tptPropertiesVersion, "build");
lua_pushinteger(l, UPSTREAM_VERSION.displayVersion[0]);
lua_setfield(l, tptPropertiesVersion, "upstream_major");
lua_pushinteger(l, UPSTREAM_VERSION.displayVersion[1]);
lua_setfield(l, tptPropertiesVersion, "upstream_minor");
lua_pushinteger(l, UPSTREAM_VERSION.build);
lua_setfield(l, tptPropertiesVersion, "upstream_build");
if constexpr (SNAPSHOT || MOD) if constexpr (SNAPSHOT || MOD)
{ {
lua_pushinteger(l, SNAPSHOT_ID); lua_pushinteger(l, SNAPSHOT_ID);

View File

@ -12,6 +12,15 @@ conf_data.set('DEBUG', is_debug.to_string())
conf_data.set('MOD', is_mod.to_string()) conf_data.set('MOD', is_mod.to_string())
conf_data.set('SNAPSHOT', is_snapshot.to_string()) conf_data.set('SNAPSHOT', is_snapshot.to_string())
conf_data.set('SNAPSHOT_ID', get_option('snapshot_id')) conf_data.set('SNAPSHOT_ID', get_option('snapshot_id'))
conf_data.set('DISPLAY_VERSION_MAJOR', get_option('display_version_major'))
conf_data.set('DISPLAY_VERSION_MINOR', get_option('display_version_minor'))
conf_data.set('BUILD_NUM', get_option('build_num'))
conf_data.set('UPSTREAM_VERSION_MAJOR', get_option('upstream_version_major'))
conf_data.set('UPSTREAM_VERSION_MINOR', get_option('upstream_version_minor'))
conf_data.set('UPSTREAM_BUILD_NUM', get_option('upstream_build_num'))
conf_data.set('MANIFEST_COPYRIGHT', get_option('manifest_copyright'))
conf_data.set('MANIFEST_MACOS_MIN_VER', get_option('manifest_macos_min_ver'))
conf_data.set('MANIFEST_DATE', get_option('manifest_date'))
conf_data.set('ALLOW_FAKE_NEWER_VERSION', (is_snapshot or is_beta or is_debug or is_mod).to_string()) conf_data.set('ALLOW_FAKE_NEWER_VERSION', (is_snapshot or is_beta or is_debug or is_mod).to_string())
conf_data.set('IDENT_PLATFORM', ident_platform) conf_data.set('IDENT_PLATFORM', ident_platform)
conf_data.set('IDENT', '@0@-@1@-@2@'.format(host_arch, host_platform, host_libc).to_upper()) conf_data.set('IDENT', '@0@-@1@-@2@'.format(host_arch, host_platform, host_libc).to_upper())

View File

@ -151,8 +151,8 @@ std::vector<ByteString> Simulation::Load(const GameSave *save, bool includePress
player.spwn = 1; player.spwn = 1;
player.elem = PT_DUST; player.elem = PT_DUST;
if ((save->majorVersion < 93 && parts[i].ctype == SPC_AIR) || if ((save->version < Version(93, 0) && parts[i].ctype == SPC_AIR) ||
(save->majorVersion < 88 && parts[i].ctype == OLD_SPC_AIR)) (save->version < Version(88, 0) && parts[i].ctype == OLD_SPC_AIR))
{ {
player.fan = true; player.fan = true;
} }
@ -165,8 +165,8 @@ std::vector<ByteString> Simulation::Load(const GameSave *save, bool includePress
Element_STKM_init_legs(this, &player2, i); Element_STKM_init_legs(this, &player2, i);
player2.spwn = 1; player2.spwn = 1;
player2.elem = PT_DUST; player2.elem = PT_DUST;
if ((save->majorVersion < 93 && parts[i].ctype == SPC_AIR) || if ((save->version < Version(93, 0) && parts[i].ctype == SPC_AIR) ||
(save->majorVersion < 88 && parts[i].ctype == OLD_SPC_AIR)) (save->version < Version(88, 0) && parts[i].ctype == OLD_SPC_AIR))
{ {
player2.fan = true; player2.fan = true;
} }
@ -189,8 +189,8 @@ std::vector<ByteString> Simulation::Load(const GameSave *save, bool includePress
if (parts[i].tmp >= 0) if (parts[i].tmp >= 0)
{ {
bool fan = false; bool fan = false;
if ((save->majorVersion < 93 && parts[i].ctype == SPC_AIR) if ((save->version < Version(93, 0) && parts[i].ctype == SPC_AIR)
|| (save->majorVersion < 88 && parts[i].ctype == OLD_SPC_AIR)) || (save->version < Version(88, 0) && parts[i].ctype == OLD_SPC_AIR))
{ {
fan = true; fan = true;
parts[i].ctype = 0; parts[i].ctype = 0;