From abc060525d868a23aa5d6dfdafc731b825f02d7b Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Mon, 28 Aug 2023 13:02:52 +0200 Subject: [PATCH 01/15] Add: calendar date for Survey results This means no heuristics is possible on around which date people play the game. --- src/network/network_survey.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/network/network_survey.cpp b/src/network/network_survey.cpp index 211f48e809..55cb7b5972 100644 --- a/src/network/network_survey.cpp +++ b/src/network/network_survey.cpp @@ -248,6 +248,21 @@ static void SurveyCompanies(nlohmann::json &survey) } } +/** + * Convert timer information to JSON. + * + * @param survey The JSON object. + */ +static void SurveyTimers(nlohmann::json &survey) +{ + survey["ticks"] = TimerGameTick::counter; + survey["seconds"] = std::chrono::duration_cast(std::chrono::steady_clock::now() - _switch_mode_time).count(); + + TimerGameCalendar::YearMonthDay ymd; + TimerGameCalendar::ConvertDateToYMD(TimerGameCalendar::date, &ymd); + survey["calendar"] = fmt::format("{:04}-{:02}-{:02} ({})", ymd.year, ymd.month + 1, ymd.day, TimerGameCalendar::date_fract); +} + /** * Convert GRF information to JSON. * @@ -356,8 +371,7 @@ std::string NetworkSurveyHandler::CreatePayload(Reason reason, bool for_preview) { auto &game = survey["game"]; - game["ticks"] = TimerGameTick::counter; - game["time"] = std::chrono::duration_cast(std::chrono::steady_clock::now() - _switch_mode_time).count(); + SurveyTimers(game["timers"]); SurveyCompanies(game["companies"]); SurveySettings(game["settings"]); SurveyGrfs(game["grfs"]); From 110dd0e6c1769992e3cc6584fc46e987e4545dc1 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Mon, 28 Aug 2023 16:24:07 +0200 Subject: [PATCH 02/15] Fix: [CI] Allow release-flow to run in forks (while skipping survey-key) (#11241) --- .github/workflows/release-source.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release-source.yml b/.github/workflows/release-source.yml index 9cf9c779a1..9308e0063d 100644 --- a/.github/workflows/release-source.yml +++ b/.github/workflows/release-source.yml @@ -152,13 +152,18 @@ jobs: - name: Generate survey key id: survey_key run: | - PAYLOAD='{"version":"${{ steps.metadata.outputs.version }}","type":"${{ vars.SURVEY_TYPE }}"}' + if [ -z "${{ vars.SURVEY_TYPE }}" ]; then + echo "SURVEY_TYPE variable not found; most likely running in a fork. Skipping step." + SURVEY_KEY="" + else + PAYLOAD='{"version":"${{ steps.metadata.outputs.version }}","type":"${{ vars.SURVEY_TYPE }}"}' - echo "${{ secrets.SURVEY_SIGNING_KEY }}" > survey_signing_key.pem - SIGNATURE=$(echo -n "${PAYLOAD}" | openssl dgst -sha256 -sign survey_signing_key.pem | base64 -w0) - rm -f survey_signing_key.pem + echo "${{ secrets.SURVEY_SIGNING_KEY }}" > survey_signing_key.pem + SIGNATURE=$(echo -n "${PAYLOAD}" | openssl dgst -sha256 -sign survey_signing_key.pem | base64 -w0) + rm -f survey_signing_key.pem - SURVEY_KEY=$(curl -f -s -X POST -d "${PAYLOAD}" -H "Content-Type: application/json" -H "X-Signature: ${SIGNATURE}" https://survey-participate.openttd.org/create-survey-key/${{ vars.SURVEY_TYPE }}) + SURVEY_KEY=$(curl -f -s -X POST -d "${PAYLOAD}" -H "Content-Type: application/json" -H "X-Signature: ${SIGNATURE}" https://survey-participate.openttd.org/create-survey-key/${{ vars.SURVEY_TYPE }}) + fi echo "survey_key=${SURVEY_KEY}" >> $GITHUB_OUTPUT From 13b76b0243627cebea736f2bb15a4481f4a72a98 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Mon, 28 Aug 2023 16:49:14 +0200 Subject: [PATCH 03/15] Fix: [CI] don't install breakpad on arm64-windows-static, as it is not supported (yet) (#11242) --- .github/workflows/release-windows.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml index 612abc4f72..ea1d4c8ef2 100644 --- a/.github/workflows/release-windows.yml +++ b/.github/workflows/release-windows.yml @@ -65,7 +65,6 @@ jobs: shell: bash run: | vcpkg install --triplet=${{ matrix.arch }}-windows-static \ - breakpad \ liblzma \ libpng \ lzo \ @@ -73,6 +72,13 @@ jobs: zlib \ # EOF + # arm64-windows-static is not (yet) supported for breakpad. + if [ "${{ matrix.arch }}" != "arm64" ]; then + vcpkg install --triplet=${{ matrix.arch }}-windows-static \ + breakpad \ + # EOF + fi + - name: Install MSVC problem matcher uses: ammaraskar/msvc-problem-matcher@master From a3d631ffed4d86309ec132a7e6a5b38a100df7d4 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Mon, 28 Aug 2023 19:04:36 +0200 Subject: [PATCH 04/15] Change: make nlohmann a mandatory library to build OpenTTD (#11235) --- .github/workflows/ci-build.yml | 9 +++++---- CMakeLists.txt | 5 +++-- COMPILING.md | 2 +- src/network/network_survey.cpp | 10 ---------- src/network/network_survey.h | 5 ----- src/os/macosx/survey_osx.cpp | 4 ---- src/os/unix/survey_unix.cpp | 4 ---- src/os/windows/survey_win.cpp | 4 ---- 8 files changed, 9 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index f7cd10c17e..3faeafe6c3 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -77,20 +77,20 @@ jobs: - name: Clang compiler: clang cxxcompiler: clang++ - libraries: libsdl2-dev nlohmann-json3-dev + libraries: libsdl2-dev - name: GCC - SDL2 compiler: gcc cxxcompiler: g++ - libraries: libsdl2-dev nlohmann-json3-dev + libraries: libsdl2-dev - name: GCC - SDL1.2 compiler: gcc cxxcompiler: g++ - libraries: libsdl1.2-dev nlohmann-json3-dev + libraries: libsdl1.2-dev - name: GCC - Dedicated compiler: gcc cxxcompiler: g++ extra-cmake-parameters: -DOPTION_DEDICATED=ON -DCMAKE_CXX_FLAGS_INIT="-DRANDOM_DEBUG" -DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON - # Compile without SDL / SDL2 / nlohmann-json, as that should compile fine too. + # Compile without SDL / SDL2, as that should compile fine too. name: Linux (${{ matrix.name }}) @@ -118,6 +118,7 @@ jobs: libicu-dev \ liblzma-dev \ liblzo2-dev \ + nlohmann-json3-dev \ ${{ matrix.libraries }} \ zlib1g-dev \ # EOF diff --git a/CMakeLists.txt b/CMakeLists.txt index e699c4d56d..400c145e08 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -119,12 +119,13 @@ endif() set(CMAKE_THREAD_PREFER_PTHREAD YES) # Make sure we have Threads available. find_package(Threads REQUIRED) +# nlohmann is used for all our JSON needs. +find_package(nlohmann_json REQUIRED) find_package(ZLIB) find_package(LibLZMA) find_package(LZO) find_package(PNG) -find_package(nlohmann_json) if(WIN32 OR EMSCRIPTEN) # Windows uses WinHttp for HTTP requests. @@ -309,7 +310,7 @@ link_package(PNG TARGET PNG::PNG ENCOURAGED) link_package(ZLIB TARGET ZLIB::ZLIB ENCOURAGED) link_package(LIBLZMA TARGET LibLZMA::LibLZMA ENCOURAGED) link_package(LZO) -link_package(nlohmann_json ENCOURAGED) +link_package(nlohmann_json) if(NOT WIN32 AND NOT EMSCRIPTEN) link_package(CURL ENCOURAGED) diff --git a/COMPILING.md b/COMPILING.md index ef9589366d..5e7883aefc 100644 --- a/COMPILING.md +++ b/COMPILING.md @@ -4,7 +4,7 @@ OpenTTD makes use of the following external libraries: -- (encouraged) nlohmann-json: JSON handling +- (required) nlohmann-json: JSON handling - (encouraged) breakpad: creates minidumps on crash - (encouraged) zlib: (de)compressing of old (0.3.0-1.0.5) savegames, content downloads, heightmaps diff --git a/src/network/network_survey.cpp b/src/network/network_survey.cpp index 55cb7b5972..afe52d6999 100644 --- a/src/network/network_survey.cpp +++ b/src/network/network_survey.cpp @@ -31,9 +31,7 @@ #include "../base_media_base.h" #include "../blitter/factory.hpp" -#ifdef WITH_NLOHMANN_JSON #include -#endif /* WITH_NLOHMANN_JSON */ #include "../safeguards.h" @@ -41,8 +39,6 @@ extern std::string _savegame_id; NetworkSurveyHandler _survey = {}; -#ifdef WITH_NLOHMANN_JSON - NLOHMANN_JSON_SERIALIZE_ENUM(NetworkSurveyHandler::Reason, { {NetworkSurveyHandler::Reason::PREVIEW, "preview"}, {NetworkSurveyHandler::Reason::LEAVE, "leave"}, @@ -334,8 +330,6 @@ std::string SurveyMemoryToText(uint64_t memory) return fmt::format("{} MiB", Ceil(memory, 4)); } -#endif /* WITH_NLOHMANN_JSON */ - /** * Create the payload for the survey. * @@ -345,9 +339,6 @@ std::string SurveyMemoryToText(uint64_t memory) */ std::string NetworkSurveyHandler::CreatePayload(Reason reason, bool for_preview) { -#ifndef WITH_NLOHMANN_JSON - return ""; -#else nlohmann::json survey; survey["schema"] = NETWORK_SURVEY_VERSION; @@ -381,7 +372,6 @@ std::string NetworkSurveyHandler::CreatePayload(Reason reason, bool for_preview) /* For preview, we indent with 4 whitespaces to make things more readable. */ int indent = for_preview ? 4 : -1; return survey.dump(indent); -#endif /* WITH_NLOHMANN_JSON */ } /** diff --git a/src/network/network_survey.h b/src/network/network_survey.h index c957108ecf..fbad97bfab 100644 --- a/src/network/network_survey.h +++ b/src/network/network_survey.h @@ -36,12 +36,7 @@ public: constexpr static bool IsSurveyPossible() { -#ifndef WITH_NLOHMANN_JSON - /* Without JSON library, we cannot send a payload; so we disable the survey. */ - return false; -#else return true; -#endif /* WITH_NLOHMANN_JSON */ } private: diff --git a/src/os/macosx/survey_osx.cpp b/src/os/macosx/survey_osx.cpp index 14260fbd68..c8483b20ff 100644 --- a/src/os/macosx/survey_osx.cpp +++ b/src/os/macosx/survey_osx.cpp @@ -7,8 +7,6 @@ /** @file survey_osx.cpp OSX implementation of OS-specific survey information. */ -#ifdef WITH_NLOHMANN_JSON - #include "../../stdafx.h" #include "../../3rdparty/fmt/format.h" @@ -38,5 +36,3 @@ void SurveyOS(nlohmann::json &json) json["memory"] = SurveyMemoryToText(MacOSGetPhysicalMemory()); json["hardware_concurrency"] = std::thread::hardware_concurrency(); } - -#endif /* WITH_NLOHMANN_JSON */ diff --git a/src/os/unix/survey_unix.cpp b/src/os/unix/survey_unix.cpp index d831a78390..fea37d89da 100644 --- a/src/os/unix/survey_unix.cpp +++ b/src/os/unix/survey_unix.cpp @@ -7,8 +7,6 @@ /** @file survey_unix.cpp Unix implementation of OS-specific survey information. */ -#ifdef WITH_NLOHMANN_JSON - #include "../../stdafx.h" #include @@ -38,5 +36,3 @@ void SurveyOS(nlohmann::json &json) json["memory"] = SurveyMemoryToText(pages * page_size); json["hardware_concurrency"] = std::thread::hardware_concurrency(); } - -#endif /* WITH_NLOHMANN_JSON */ diff --git a/src/os/windows/survey_win.cpp b/src/os/windows/survey_win.cpp index 1af48a39e7..9044f8c22f 100644 --- a/src/os/windows/survey_win.cpp +++ b/src/os/windows/survey_win.cpp @@ -7,8 +7,6 @@ /** @file survey_win.cpp Windows implementation of OS-specific survey information. */ -#ifdef WITH_NLOHMANN_JSON - #include "../../stdafx.h" #include "../../3rdparty/fmt/format.h" @@ -37,5 +35,3 @@ void SurveyOS(nlohmann::json &json) json["memory"] = SurveyMemoryToText(status.ullTotalPhys); json["hardware_concurrency"] = std::thread::hardware_concurrency(); } - -#endif /* WITH_NLOHMANN_JSON */ From 5e75afb628c9a0b42bafdb54063dda42a6424d6f Mon Sep 17 00:00:00 2001 From: translators Date: Mon, 28 Aug 2023 18:38:07 +0000 Subject: [PATCH 05/15] Update: Translations from eints catalan: 2 changes by J0anJosep --- src/lang/catalan.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lang/catalan.txt b/src/lang/catalan.txt index e2de39349c..733dbd40df 100644 --- a/src/lang/catalan.txt +++ b/src/lang/catalan.txt @@ -1187,7 +1187,7 @@ STR_TERRAIN_TYPE_CUSTOM :Alçada persona STR_TERRAIN_TYPE_CUSTOM_VALUE :Alçada personalitzada ({NUM}) ###length 4 -STR_CITY_APPROVAL_LENIENT :Tolerant +STR_CITY_APPROVAL_LENIENT :Indulgent STR_CITY_APPROVAL_TOLERANT :Tolerant STR_CITY_APPROVAL_HOSTILE :Hostil STR_CITY_APPROVAL_PERMISSIVE :Permissiva (les accions de les companyies no l'afecten) @@ -2651,6 +2651,7 @@ STR_TRANSPARENT_BUILDINGS_TOOLTIP :{BLACK}Commuta STR_TRANSPARENT_BRIDGES_TOOLTIP :{BLACK}Commuta la transparència dels ponts. Ctrl+Clic per bloquejar STR_TRANSPARENT_STRUCTURES_TOOLTIP :{BLACK}Commuta la transparència de les estructures com ara fars i antenes. Ctrl+Clic per bloquejar STR_TRANSPARENT_CATENARY_TOOLTIP :{BLACK}Commuta la transparència de la catenària. CTRL+clic per bloquejar +STR_TRANSPARENT_TEXT_TOOLTIP :{BLACK}Commuta la transparència dels indicadors de càrrega i el text de despesa/ingrés. Ctrl+Clic per a blocar. STR_TRANSPARENT_INVISIBLE_TOOLTIP :{BLACK}Alternar entre transparència i invisibilitat dels objectes # Linkgraph legend window From ecb4bb5161c740254a1dd0d9eb7cdb90bc4cae22 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Wed, 30 Aug 2023 12:14:32 +0200 Subject: [PATCH 06/15] Change: for surveys capture more information about the OpenTTD version (#11244) --- src/network/network_survey.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/network/network_survey.cpp b/src/network/network_survey.cpp index afe52d6999..8b16c4fb14 100644 --- a/src/network/network_survey.cpp +++ b/src/network/network_survey.cpp @@ -137,8 +137,12 @@ static void SurveySettings(nlohmann::json &survey) */ static void SurveyOpenTTD(nlohmann::json &survey) { - survey["version"] = std::string(_openttd_revision); - survey["newgrf_version"] = _openttd_newgrf_version; + survey["version"]["revision"] = std::string(_openttd_revision); + survey["version"]["modified"] = _openttd_revision_modified; + survey["version"]["tagged"] = _openttd_revision_tagged; + survey["version"]["hash"] = std::string(_openttd_revision_hash); + survey["version"]["newgrf"] = fmt::format("{:X}", _openttd_newgrf_version); + survey["version"]["content"] = std::string(_openttd_content_version); survey["build_date"] = std::string(_openttd_build_date); survey["bits"] = #ifdef POINTER_IS_64BIT From 2f96ccc18a8303d7f2dfd7cc14aeea146c3092ae Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Wed, 30 Aug 2023 20:27:15 +0200 Subject: [PATCH 07/15] Add: [CI] Create and store breakpad symbols for releases (#11247) Additionally, also store PDB and exe files on the Symbol Server for easier debugging with MSVC. --- .github/workflows/release-linux.yml | 29 +++++++++++++++++++ .github/workflows/release-macos.yml | 31 +++++++++++++++++++++ .github/workflows/release-windows.yml | 36 +++++++++++++++++++++--- .github/workflows/upload-cdn.yml | 40 +++++++++++++++++++++++++-- docs/symbol_server.md | 34 +++++++++++++++++++++++ 5 files changed, 163 insertions(+), 7 deletions(-) create mode 100644 docs/symbol_server.md diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml index 247cd0b0c3..fad44dec9a 100644 --- a/.github/workflows/release-linux.yml +++ b/.github/workflows/release-linux.yml @@ -28,6 +28,19 @@ jobs: run: | tar -xf source.tar.gz --strip-components=1 + # dtolnay/rust-toolchain uses a curl argument (--retry-connrefused) that the curl shipped with our container doesn't support. + # So we need to do one part ourselves; the action takes over the rest and prepares the setup further. + - name: Prepare Rust toolchain + run: | + curl --proto '=https' --tlsv1.2 --retry 10 --location --silent --show-error --fail "https://sh.rustup.rs" | sh -s -- --default-toolchain none -y + echo "${CARGO_HOME:-$HOME/.cargo}/bin" >> $GITHUB_PATH + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Enable Rust cache + uses: Swatinem/rust-cache@v2 + - name: Enable vcpkg cache uses: actions/cache@v3 with: @@ -124,6 +137,10 @@ jobs: ) echo "::endgroup::" + echo "::group::Install breakpad dependencies" + cargo install dump_syms + echo "::endgroup::" + - name: Install GCC problem matcher uses: ammaraskar/gcc-problem-matcher@master @@ -146,6 +163,11 @@ jobs: cmake --build . -j $(nproc) --target openttd echo "::endgroup::" + - name: Create breakpad symbols + run: | + cd build + dump_syms ./openttd --inlines --store symbols + - name: Create bundles run: | cd ${GITHUB_WORKSPACE}/build @@ -165,3 +187,10 @@ jobs: name: openttd-linux-generic path: build/bundles retention-days: 5 + + - name: Store symbols + uses: actions/upload-artifact@v3 + with: + name: symbols-linux-generic + path: build/symbols + retention-days: 5 diff --git a/.github/workflows/release-macos.yml b/.github/workflows/release-macos.yml index 7741064bf7..165bb6b6ab 100644 --- a/.github/workflows/release-macos.yml +++ b/.github/workflows/release-macos.yml @@ -26,15 +26,27 @@ jobs: run: | tar -xf source.tar.gz --strip-components=1 + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Enable Rust cache + uses: Swatinem/rust-cache@v2 + - name: Install dependencies env: HOMEBREW_NO_AUTO_UPDATE: 1 HOMEBREW_NO_INSTALL_CLEANUP: 1 run: | + echo "::group::Install brew dependencies" brew install \ pandoc \ pkg-config \ # EOF + echo "::endgroup::" + + echo "::group::Install breakpad dependencies" + cargo install dump_syms + echo "::endgroup::" - name: Prepare cache key id: key @@ -144,6 +156,18 @@ jobs: cmake --build . -j $(sysctl -n hw.logicalcpu) --target openttd echo "::endgroup::" + - name: Create breakpad symbols + run: | + cd build-x64 + mkdir dSYM + dsymutil ./openttd -o dSYM/openttd + dump_syms ./dSYM/openttd --inlines --store symbols + + cd ../build-arm64 + mkdir dSYM + dsymutil ./openttd -o dSYM/openttd + dump_syms ./dSYM/openttd --inlines --store ../build-x64/symbols + - name: Create bundles run: | cd build-x64 @@ -209,3 +233,10 @@ jobs: name: openttd-macos-universal path: build-x64/bundles retention-days: 5 + + - name: Store symbols + uses: actions/upload-artifact@v3 + with: + name: symbols-macos-universal + path: build-x64/symbols + retention-days: 5 diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml index ea1d4c8ef2..7122a57f0b 100644 --- a/.github/workflows/release-windows.yml +++ b/.github/workflows/release-windows.yml @@ -39,10 +39,22 @@ jobs: run: | tar -xf source.tar.gz --strip-components=1 + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Enable Rust cache + uses: Swatinem/rust-cache@v2 + - name: Install dependencies shell: bash run: | + echo "::group::Install choco dependencies" choco install pandoc + echo "::endgroup::" + + echo "::group::Install breakpad dependencies" + cargo install dump_syms + echo "::endgroup::" - name: Prepare cache key id: key @@ -176,6 +188,12 @@ jobs: env: WINDOWS_CERTIFICATE_COMMON_NAME: ${{ secrets.WINDOWS_CERTIFICATE_COMMON_NAME }} + - name: Create breakpad symbols + shell: bash + run: | + cd build + dump_syms openttd.pdb --inlines --store symbols + - name: Create bundles shell: bash run: | @@ -184,10 +202,13 @@ jobs: cpack echo "::endgroup::" - echo "::group::Prepare PDB to be bundled" - PDB=$(ls bundles/*.zip | cut -d/ -f2 | sed 's/.zip$/.pdb/') - cp openttd.pdb bundles/${PDB} - xz -9 bundles/${PDB} + echo "::group::Move PDB and exe to symbols" + PDB_FOLDER=$(find symbols -mindepth 2 -type d) + cp openttd.pdb ${PDB_FOLDER}/ + + EXE_FOLDER=symbols/openttd.exe/$(grep "INFO CODE_ID" symbols/*/*/openttd.sym | cut -d\ -f3) + mkdir -p ${EXE_FOLDER} + cp openttd.exe ${EXE_FOLDER}/ echo "::endgroup::" echo "::group::Cleanup" @@ -213,3 +234,10 @@ jobs: name: openttd-windows-${{ matrix.arch }} path: build/bundles retention-days: 5 + + - name: Store symbols + uses: actions/upload-artifact@v3 + with: + name: symbols-windows-${{ matrix.arch }} + path: build/symbols + retention-days: 5 diff --git a/.github/workflows/upload-cdn.yml b/.github/workflows/upload-cdn.yml index f7f925222c..79cce0adde 100644 --- a/.github/workflows/upload-cdn.yml +++ b/.github/workflows/upload-cdn.yml @@ -54,6 +54,21 @@ jobs: done fi + - name: Merge symbols + run: | + mkdir symbols + cp -R symbols-*/* symbols/ + + # Compress all files as gzip, to reduce cost of storage on the CDN. + for i in $(find symbols -mindepth 2 -type f); do + gzip ${i} + done + + # Leave a mark in each folder what version actually created the symbol file. + for i in $(find symbols -mindepth 2 -type d); do + touch ${i}/.${{ inputs.version }}.txt + done + - name: Store bundles uses: actions/upload-artifact@v3 with: @@ -61,11 +76,18 @@ jobs: path: bundles/* retention-days: 5 - publish: + - name: Store breakpad symbols + uses: actions/upload-artifact@v3 + with: + name: cdn-symbols + path: symbols/* + retention-days: 5 + + publish-bundles: needs: - prepare - name: Publish + name: Publish bundles uses: OpenTTD/actions/.github/workflows/rw-cdn-upload.yml@v4 secrets: CDN_SIGNING_KEY: ${{ secrets.CDN_SIGNING_KEY }} @@ -76,10 +98,22 @@ jobs: folder: ${{ inputs.folder }} version: ${{ inputs.version }} + publish-symbols: + needs: + - prepare + + name: Publish symbols + uses: OpenTTD/actions/.github/workflows/rw-symbols-upload.yml@v4 + secrets: + SYMBOLS_SIGNING_KEY: ${{ secrets.SYMBOLS_SIGNING_KEY }} + with: + artifact-name: cdn-symbols + repository: OpenTTD + docs: if: ${{ inputs.trigger_type == 'new-master' }} needs: - - publish + - publish-bundles name: Publish docs diff --git a/docs/symbol_server.md b/docs/symbol_server.md new file mode 100644 index 0000000000..c1c522a1b6 --- /dev/null +++ b/docs/symbol_server.md @@ -0,0 +1,34 @@ +# OpenTTD's Symbol Server + +For all official releases, OpenTTD collects the Breakpad Symbols (SYM-files) and Microsoft's Symbols (PDB-files), and publishes them on our own Symbol Server (https://symbols.openttd.org). + +These symbol files are needed to analyze `crash.dmp` files as attached to issues by users. +A `crash.dmp` is created on Windows, Linux, and MacOS when a crash happens. +This combined with the `crash.log` should give a pretty good indication what was going on at the moment the game crashed. + +## Analyzing a crash.dmp + +### MSVC + +In MSVC you can add the above URL as Symbol Server (and please enable MSVC's for all other libraries), allowing you to analyze `crash.dmp`. + +Now simply open up the `crash.dmp`, and start debugging. + +### All other platforms + +The best tool to use is `minidump-stackwalk` as published in the Rust's cargo index: + +```bash +cargo install minidump-stackwalk +``` + +For how to install Rust, please see [here](https://doc.rust-lang.org/cargo/getting-started/installation.html). + +Now run the tool like: + +```bash +minidump-stackwalk --symbols-url https://symbols.openttd.org +``` + +For convenience, the above Symbol Server also check with Mozilla's Symbol Server in case any other library but OpenTTD is requested. +This means files like `libc`, `kernel32.dll`, etc are all available on the above mentioned Symbol Server. From 502414b567b45e34393a4ba9a95484923160f6b4 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Thu, 31 Aug 2023 11:10:11 +0200 Subject: [PATCH 08/15] Fix: [Emscripten] config not saved on exit game (#11248) When changing a Game Option and pressing Exit Game, the changes were not actually stored. This because the post-mainloop code was never executed for Emscripten. --- src/openttd.cpp | 29 +++++++++++++++++------------ src/video/sdl2_v.cpp | 3 +++ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/openttd.cpp b/src/openttd.cpp index 3ee2182d8e..5356f38fbd 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -459,6 +459,22 @@ struct AfterNewGRFScan : NewGRFScanCallback { } }; +void PostMainLoop() +{ + WaitTillSaved(); + + /* only save config if we have to */ + if (_save_config) { + SaveToConfig(); + SaveHotkeysToConfig(); + WindowDesc::SaveToConfig(); + SaveToHighScore(); + } + + /* Reset windowing system, stop drivers, free used memory, ... */ + ShutdownGame(); +} + #if defined(UNIX) extern void DedicatedFork(); #endif @@ -785,18 +801,7 @@ int openttd_main(int argc, char *argv[]) VideoDriver::GetInstance()->MainLoop(); - WaitTillSaved(); - - /* only save config if we have to */ - if (_save_config) { - SaveToConfig(); - SaveHotkeysToConfig(); - WindowDesc::SaveToConfig(); - SaveToHighScore(); - } - - /* Reset windowing system, stop drivers, free used memory, ... */ - ShutdownGame(); + PostMainLoop(); return ret; } diff --git a/src/video/sdl2_v.cpp b/src/video/sdl2_v.cpp index bad581eef3..eb201f0774 100644 --- a/src/video/sdl2_v.cpp +++ b/src/video/sdl2_v.cpp @@ -607,6 +607,9 @@ void VideoDriver_SDL_Base::LoopOnce() * normally done at the end of the main loop for non-Emscripten. * After that, Emscripten just halts, and the HTML shows a nice * "bye, see you next time" message. */ + extern void PostMainLoop(); + PostMainLoop(); + emscripten_cancel_main_loop(); emscripten_exit_pointerlock(); /* In effect, the game ends here. As emscripten_set_main_loop() caused From 75d3dc0a1918b6e7eb3e3c8a48daa612b0371657 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Thu, 31 Aug 2023 12:04:22 +0200 Subject: [PATCH 09/15] Codechange: [Emscripten] remove "relative_mode" parameter from -vsdl as it doesn't exist (#11249) --- os/emscripten/pre.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/os/emscripten/pre.js b/os/emscripten/pre.js index 31858b1894..c242e13fd0 100644 --- a/os/emscripten/pre.js +++ b/os/emscripten/pre.js @@ -1,4 +1,4 @@ -Module.arguments.push('-mnull', '-snull', '-vsdl:relative_mode'); +Module.arguments.push('-mnull', '-snull', '-vsdl'); Module['websocket'] = { url: function(host, port, proto) { /* openttd.org hosts a WebSocket proxy for the content service. */ if (host == "content.openttd.org" && port == 3978 && proto == "tcp") { From 3d1c4a8589ed31728da29c2a8830f4acd96e94eb Mon Sep 17 00:00:00 2001 From: glx22 Date: Thu, 6 Jul 2023 18:18:03 +0200 Subject: [PATCH 10/15] Codechange: [Emscripten] Improve syncfs synchronisation on exit/abort --- os/emscripten/pre.js | 24 +++++++++++++----------- src/openttd.cpp | 1 - src/video/sdl2_v.cpp | 1 - 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/os/emscripten/pre.js b/os/emscripten/pre.js index c242e13fd0..68e76678aa 100644 --- a/os/emscripten/pre.js +++ b/os/emscripten/pre.js @@ -52,24 +52,26 @@ Module.preRun.push(function() { }); window.openttd_syncfs_shown_warning = false; - window.openttd_syncfs = function() { + window.openttd_syncfs = function(callback) { /* Copy the virtual FS to the persistent storage. */ - FS.syncfs(false, function (err) { }); - - /* On first time, warn the user about the volatile behaviour of - * persistent storage. */ - if (!window.openttd_syncfs_shown_warning) { - window.openttd_syncfs_shown_warning = true; - Module.onWarningFs(); - } + FS.syncfs(false, function (err) { + /* On first time, warn the user about the volatile behaviour of + * persistent storage. */ + if (!window.openttd_syncfs_shown_warning) { + window.openttd_syncfs_shown_warning = true; + Module.onWarningFs(); + } + + if (callback) callback(); + }); } window.openttd_exit = function() { - Module.onExit(); + window.openttd_syncfs(Module.onExit); } window.openttd_abort = function() { - Module.onAbort(); + window.openttd_syncfs(Module.onAbort); } window.openttd_server_list = function() { diff --git a/src/openttd.cpp b/src/openttd.cpp index 5356f38fbd..6fb3b4a34f 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -118,7 +118,6 @@ void UserErrorI(const std::string &str) /* In effect, the game ends here. As emscripten_set_main_loop() caused * the stack to be unwound, the code after MainLoop() in * openttd_main() is never executed. */ - EM_ASM(if (window["openttd_syncfs"]) openttd_syncfs()); EM_ASM(if (window["openttd_abort"]) openttd_abort()); #endif diff --git a/src/video/sdl2_v.cpp b/src/video/sdl2_v.cpp index eb201f0774..1f18d143fd 100644 --- a/src/video/sdl2_v.cpp +++ b/src/video/sdl2_v.cpp @@ -615,7 +615,6 @@ void VideoDriver_SDL_Base::LoopOnce() /* In effect, the game ends here. As emscripten_set_main_loop() caused * the stack to be unwound, the code after MainLoop() in * openttd_main() is never executed. */ - EM_ASM(if (window["openttd_syncfs"]) openttd_syncfs()); EM_ASM(if (window["openttd_exit"]) openttd_exit()); #endif return; From 4eddec9e79244d606bd174aaa0c3ffae8a03e256 Mon Sep 17 00:00:00 2001 From: glx22 Date: Thu, 6 Jul 2023 18:20:33 +0200 Subject: [PATCH 11/15] Add: [Emscripten] Support for bootstrapping --- os/emscripten/pre.js | 43 +++++++++-------------- os/emscripten/shell.html | 24 ++++++++++++- src/bootstrap_gui.cpp | 76 +++++++++++++++++++++++++++++++++++++++- src/video/sdl2_v.cpp | 6 +++- 4 files changed, 120 insertions(+), 29 deletions(-) diff --git a/os/emscripten/pre.js b/os/emscripten/pre.js index 68e76678aa..8e46ae3460 100644 --- a/os/emscripten/pre.js +++ b/os/emscripten/pre.js @@ -30,24 +30,6 @@ Module.preRun.push(function() { Module.addRunDependency('syncfs'); FS.syncfs(true, function (err) { - /* FS.mkdir() tends to fail if parent folders do not exist. */ - if (!FS.analyzePath(content_download_dir).exists) { - FS.mkdir(content_download_dir); - } - if (!FS.analyzePath(content_download_dir + '/baseset').exists) { - FS.mkdir(content_download_dir + '/baseset'); - } - - /* Check if the OpenGFX baseset is already downloaded. */ - if (!FS.analyzePath(content_download_dir + '/baseset/opengfx-0.6.0.tar').exists) { - window.openttd_downloaded_opengfx = true; - FS.createPreloadedFile(content_download_dir + '/baseset', 'opengfx-0.6.0.tar', 'https://binaries.openttd.org/installer/emscripten/opengfx-0.6.0.tar', true, true); - } else { - /* Fake dependency increase, so the counter is stable. */ - Module.addRunDependency('opengfx'); - Module.removeRunDependency('opengfx'); - } - Module.removeRunDependency('syncfs'); }); @@ -74,6 +56,23 @@ Module.preRun.push(function() { window.openttd_syncfs(Module.onAbort); } + window.openttd_bootstrap = function(current, total) { + Module.onBootstrap(current, total); + } + + window.openttd_bootstrap_failed = function() { + Module.onBootstrapFailed(); + } + + window.openttd_bootstrap_reload = function() { + window.openttd_syncfs(function() { + Module.onBootstrapReload(); + setTimeout(function() { + location.reload(); + }, 1000); + }); + } + window.openttd_server_list = function() { add_server = Module.cwrap("em_openttd_add_server", null, ["string"]); @@ -125,11 +124,3 @@ Module.preRun.push(function() { return ret; } }); - -Module.postRun.push(function() { - /* Check if we downloaded OpenGFX; if so, sync the virtual FS back to the - * IDBFS so OpenGFX is stored persistent. */ - if (window['openttd_downloaded_opengfx']) { - FS.syncfs(false, function (err) { }); - } -}); diff --git a/os/emscripten/shell.html b/os/emscripten/shell.html index af031c6df8..21f720e7e4 100644 --- a/os/emscripten/shell.html +++ b/os/emscripten/shell.html @@ -75,7 +75,6 @@ } #message { color: #101010; - height: 54px; padding: 4px 4px; } @@ -144,6 +143,8 @@ })(), setStatus: function(text) { + if (document.getElementById("canvas").style.display == "none") return; + var m = text.match(/^([^(]+)\((\d+(\.\d+)?)\/(\d+)\)$/); if (m) { @@ -171,6 +172,27 @@ document.getElementById("message").innerHTML = "Preparing game ..."; }, + onBootstrap: function(current, total) { + document.getElementById("canvas").style.display = "none"; + + document.getElementById("title").innerHTML = "Missing base graphics"; + document.getElementById("message").innerHTML = "OpenTTD is downloading base graphics.

" + current + " / " + total + " bytes downloaded."; + }, + + onBootstrapFailed: function(current, total) { + document.getElementById("canvas").style.display = "none"; + + document.getElementById("title").innerHTML = "Missing base graphics"; + document.getElementById("message").innerHTML = "Failed to download base graphics.
The game cannot start without base graphics.

Please check your Internet connection and/or the console log.
Reload your browser to try again."; + }, + + onBootstrapReload: function() { + document.getElementById("canvas").style.display = "none"; + + document.getElementById("title").innerHTML = "Missing base graphics"; + document.getElementById("message").innerHTML = "Downloading base graphics done.

Your browser will reload to start the game."; + }, + onExit: function() { document.getElementById("canvas").style.display = "none"; diff --git a/src/bootstrap_gui.cpp b/src/bootstrap_gui.cpp index 57d7e96fd2..063e908a32 100644 --- a/src/bootstrap_gui.cpp +++ b/src/bootstrap_gui.cpp @@ -286,6 +286,76 @@ public: #endif /* defined(WITH_FREETYPE) */ +#if defined(__EMSCRIPTEN__) +# include +# include "network/network.h" +# include "network/network_content.h" +# include "openttd.h" +# include "video/video_driver.hpp" + +class BootstrapEmscripten : public ContentCallback { + bool downloading{false}; + uint total_files{0}; + uint total_bytes{0}; + uint downloaded_bytes{0}; + +public: + BootstrapEmscripten() + { + _network_content_client.AddCallback(this); + _network_content_client.Connect(); + } + + ~BootstrapEmscripten() + { + _network_content_client.RemoveCallback(this); + } + + void OnConnect(bool success) override + { + if (!success) { + EM_ASM({ if (window["openttd_bootstrap_failed"]) openttd_bootstrap_failed(); }); + return; + } + + /* Once connected, request the metadata. */ + _network_content_client.RequestContentList(CONTENT_TYPE_BASE_GRAPHICS); + } + + void OnReceiveContentInfo(const ContentInfo *ci) override + { + if (this->downloading) return; + + /* And once the metadata is received, start downloading it. */ + _network_content_client.Select(ci->id); + _network_content_client.DownloadSelectedContent(this->total_files, this->total_bytes); + this->downloading = true; + + EM_ASM({ if (window["openttd_bootstrap"]) openttd_bootstrap($0, $1); }, this->downloaded_bytes, this->total_bytes); + } + + void OnDownloadProgress(const ContentInfo *ci, int bytes) override + { + /* A negative value means we are resetting; for example, when retrying or using a fallback. */ + if (bytes < 0) { + this->downloaded_bytes = 0; + } else { + this->downloaded_bytes += bytes; + } + + EM_ASM({ if (window["openttd_bootstrap"]) openttd_bootstrap($0, $1); }, this->downloaded_bytes, this->total_bytes); + } + + void OnDownloadComplete(ContentID cid) override + { + /* _exit_game is used to break out of the outer video driver's MainLoop. */ + _exit_game = true; + + delete this; + } +}; +#endif /* __EMSCRIPTEN__ */ + /** * Handle all procedures for bootstrapping OpenTTD without a base graphics set. * This requires all kinds of trickery that is needed to avoid the use of @@ -300,12 +370,15 @@ bool HandleBootstrap() if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 0) goto failure; /* If there is no network or no non-sprite font, then there is nothing we can do. Go straight to failure. */ -#if (defined(_WIN32) && defined(WITH_UNISCRIBE)) || (defined(WITH_FREETYPE) && (defined(WITH_FONTCONFIG) || defined(__APPLE__))) || defined(WITH_COCOA) +#if defined(__EMSCRIPTEN__) || (defined(_WIN32) && defined(WITH_UNISCRIBE)) || (defined(WITH_FREETYPE) && (defined(WITH_FONTCONFIG) || defined(__APPLE__))) || defined(WITH_COCOA) if (!_network_available) goto failure; /* First tell the game we're bootstrapping. */ _game_mode = GM_BOOTSTRAP; +#if defined(__EMSCRIPTEN__) + new BootstrapEmscripten(); +#else /* Initialise the font cache. */ InitializeUnicodeGlyphMap(); /* Next "force" finding a suitable non-sprite font as the local font is missing. */ @@ -324,6 +397,7 @@ bool HandleBootstrap() /* Finally ask the question. */ new BootstrapBackground(); new BootstrapAskForDownloadWindow(); +#endif /* __EMSCRIPTEN__ */ /* Process the user events. */ VideoDriver::GetInstance()->MainLoop(); diff --git a/src/video/sdl2_v.cpp b/src/video/sdl2_v.cpp index 1f18d143fd..183b8354c1 100644 --- a/src/video/sdl2_v.cpp +++ b/src/video/sdl2_v.cpp @@ -615,7 +615,11 @@ void VideoDriver_SDL_Base::LoopOnce() /* In effect, the game ends here. As emscripten_set_main_loop() caused * the stack to be unwound, the code after MainLoop() in * openttd_main() is never executed. */ - EM_ASM(if (window["openttd_exit"]) openttd_exit()); + if (_game_mode == GM_BOOTSTRAP) { + EM_ASM(if (window["openttd_bootstrap_reload"]) openttd_bootstrap_reload()); + } else { + EM_ASM(if (window["openttd_exit"]) openttd_exit()); + } #endif return; } From 993e7be707e04992f3366cdf8b09953e29b10f81 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Thu, 31 Aug 2023 18:52:52 +0200 Subject: [PATCH 12/15] Fix: [Script] GSAdmin.Send() could generate invalid JSON (#11250) --- src/script/api/script_admin.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/script/api/script_admin.cpp b/src/script/api/script_admin.cpp index 42615d2aed..9f55e1b958 100644 --- a/src/script/api/script_admin.cpp +++ b/src/script/api/script_admin.cpp @@ -129,7 +129,10 @@ } std::string json; - ScriptAdmin::MakeJSON(vm, -1, SQUIRREL_MAX_DEPTH, json); + if (!ScriptAdmin::MakeJSON(vm, -1, SQUIRREL_MAX_DEPTH, json)) { + sq_pushinteger(vm, 0); + return 1; + } if (json.length() > NETWORK_GAMESCRIPT_JSON_LENGTH) { ScriptLog::Error("You are trying to send a table that is too large to the AdminPort. No data sent."); From 7afd686541afe6ee8c85c2c5bc867e436111ee26 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Thu, 31 Aug 2023 21:38:15 +0200 Subject: [PATCH 13/15] Codechange: add tests for GS <-> AdminPort JSON conversion (#11252) While at it, fix a bug where booleans were made integers. --- src/script/api/script_admin.hpp | 2 +- src/script/api/script_event_types.cpp | 4 +- src/script/api/script_object.hpp | 1 + src/tests/CMakeLists.txt | 1 + src/tests/test_script_admin.cpp | 175 ++++++++++++++++++++++++++ 5 files changed, 180 insertions(+), 3 deletions(-) create mode 100644 src/tests/test_script_admin.cpp diff --git a/src/script/api/script_admin.hpp b/src/script/api/script_admin.hpp index b96b58abf2..03723784c5 100644 --- a/src/script/api/script_admin.hpp +++ b/src/script/api/script_admin.hpp @@ -36,7 +36,7 @@ public: static bool Send(void *table); #endif /* DOXYGEN_API */ -private: +protected: /** * Convert a Squirrel structure into a JSON string. * @param vm The VM to operate on. diff --git a/src/script/api/script_event_types.cpp b/src/script/api/script_event_types.cpp index ffe4020983..cea70f8e61 100644 --- a/src/script/api/script_event_types.cpp +++ b/src/script/api/script_event_types.cpp @@ -222,11 +222,11 @@ const char *ScriptEventAdminPort::ReadValue(HSQUIRRELVM vm, const char *p) SKIP_EMPTY(p); if (strncmp(p, "false", 5) == 0) { - sq_pushinteger(vm, 0); + sq_pushbool(vm, 0); return p + 5; } if (strncmp(p, "true", 4) == 0) { - sq_pushinteger(vm, 1); + sq_pushbool(vm, 1); return p + 4; } if (strncmp(p, "null", 4) == 0) { diff --git a/src/script/api/script_object.hpp b/src/script/api/script_object.hpp index b2f1f392f9..28cac81d59 100644 --- a/src/script/api/script_object.hpp +++ b/src/script/api/script_object.hpp @@ -44,6 +44,7 @@ typedef bool (ScriptAsyncModeProc)(); class ScriptObject : public SimpleCountedObject { friend class ScriptInstance; friend class ScriptController; +friend class TestScriptController; protected: /** * A class that handles the current active instance. By instantiating it at diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index ef617a82b6..ce33de8609 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -4,4 +4,5 @@ add_test_files( string_func.cpp strings_func.cpp test_main.cpp + test_script_admin.cpp ) diff --git a/src/tests/test_script_admin.cpp b/src/tests/test_script_admin.cpp new file mode 100644 index 0000000000..b679498921 --- /dev/null +++ b/src/tests/test_script_admin.cpp @@ -0,0 +1,175 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file script_admin_json.cpp Tests for the Squirrel -> JSON conversion in ScriptAdmin. */ + +#include "../stdafx.h" + +#include "../3rdparty/catch2/catch.hpp" + +#include "../game/game_instance.hpp" +#include "../script/api/script_admin.hpp" +#include "../script/api/script_event_types.hpp" +#include "../script/script_instance.hpp" +#include "../script/squirrel.hpp" + +#include "../3rdparty/fmt/format.h" + +#include + +/** + * A controller to start enough so we can use Squirrel for testing. + * + * To run Squirrel, we need an Allocator, so malloc/free works. + * For functions that log, we need an ActiveInstance, so the logger knows where + * to send the logs to. + * + * By instantiating this class, both are set correctly. After that you can + * use Squirrel without issues. + */ +class TestScriptController { +public: + GameInstance game{}; + ScriptObject::ActiveInstance active{&game}; + + Squirrel engine{"test"}; + ScriptAllocatorScope scope{&engine}; +}; + +/** + * Small wrapper around ScriptAdmin. + * + * MakeJSON is protected; so for tests, we make a public function with + * which we call into the protected one. This prevents accidental use + * by the rest of the code, while still being able to test it. + */ +class TestScriptAdmin : public ScriptAdmin { +public: + static std::optional MakeJSON(std::string_view squirrel) + { + auto vm = sq_open(1024); + /* sq_compile creates a closure with our snipper, which is a table. + * Add "return " to get the table on the stack. */ + std::string buffer = fmt::format("return {}", squirrel); + + /* Insert an (empty) class for testing. */ + sq_pushroottable(vm); + sq_pushstring(vm, "DummyClass", -1); + sq_newclass(vm, SQFalse); + sq_newslot(vm, -3, SQFalse); + sq_pop(vm, 1); + + /* Compile the snippet. */ + REQUIRE(sq_compilebuffer(vm, buffer.c_str(), buffer.size(), "test", SQTrue) == SQ_OK); + /* Execute the snippet, capturing the return value. */ + sq_pushroottable(vm); + REQUIRE(sq_call(vm, 1, SQTrue, SQTrue) == SQ_OK); + /* Ensure the snippet pushed a table on the stack. */ + REQUIRE(sq_gettype(vm, -1) == OT_TABLE); + + /* Feed the snippet into the MakeJSON function. */ + std::string json; + if (!ScriptAdmin::MakeJSON(vm, -1, SQUIRREL_MAX_DEPTH, json)) { + sq_close(vm); + return std::nullopt; + } + + sq_close(vm); + return json; + } + + /** + * Validate ScriptEventAdminPort can convert JSON to Squirrel. + * + * This function is not actually part of ScriptAdmin, but we will use MakeJSON, + * and as such need to be inside this class. + * + * The easiest way to do validate, is to first use ScriptEventAdminPort (the function + * we are testing) to convert the JSON to a Squirrel table. Then to use MakeJSON + * to convert it back to JSON. + * + * Sadly, Squirrel has no way to easily compare if two tables are identical, so we + * use the JSON -> Squirrel -> JSON method to validate the conversion. But mind you, + * a failure in the final JSON might also mean a bug in MakeJSON. + * + * @param json The JSON-string to convert to Squirrel + * @return The Squirrel table converted to a JSON-string. + */ + static std::optional TestScriptEventAdminPort(const std::string &json) + { + auto vm = sq_open(1024); + + /* Run the conversion JSON -> Squirrel (this will now be on top of the stack). */ + ScriptEventAdminPort(json).GetObject(vm); + if (sq_gettype(vm, -1) == OT_NULL) { + sq_close(vm); + return std::nullopt; + } + REQUIRE(sq_gettype(vm, -1) == OT_TABLE); + + std::string squirrel_json; + REQUIRE(ScriptAdmin::MakeJSON(vm, -1, SQUIRREL_MAX_DEPTH, squirrel_json) == true); + + sq_close(vm); + return squirrel_json; + } + +}; + +TEST_CASE("Squirrel -> JSON conversion") +{ + TestScriptController controller; + + CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = null })sq") == R"json({ "test": null })json"); + CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = 1 })sq") == R"json({ "test": 1 })json"); + CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = -1 })sq") == R"json({ "test": -1 })json"); + CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = true })sq") == R"json({ "test": true })json"); + CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = "a" })sq") == R"json({ "test": "a" })json"); + CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = [ ] })sq") == R"json({ "test": [ ] })json"); + CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = [ 1 ] })sq") == R"json({ "test": [ 1 ] })json"); + CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = [ 1, "a", true, { test = 1 }, [], null ] })sq") == R"json({ "test": [ 1, "a", true, { "test": 1 }, [ ], null ] })json"); + CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = { } })sq") == R"json({ "test": { } })json"); + CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = { test = 1 } })sq") == R"json({ "test": { "test": 1 } })json"); + CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = { test = 1, test = 2 } })sq") == R"json({ "test": { "test": 2 } })json"); + CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = { test = 1, test2 = [ 2 ] } })sq") == R"json({ "test": { "test": 1, "test2": [ 2 ] } })json"); + + /* Cases that should fail, as we cannot convert a class to JSON. */ + CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = DummyClass })sq") == std::nullopt); + CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = [ 1, DummyClass ] })sq") == std::nullopt); + CHECK(TestScriptAdmin::MakeJSON(R"sq({ test = { test = 1, test2 = DummyClass } })sq") == std::nullopt); +} + +TEST_CASE("JSON -> Squirrel conversion") +{ + TestScriptController controller; + + CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": null })json") == R"json({ "test": null })json"); + CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": 1 })json") == R"json({ "test": 1 })json"); + CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": -1 })json") == R"json({ "test": -1 })json"); + CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": true })json") == R"json({ "test": true })json"); + CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": "a" })json") == R"json({ "test": "a" })json"); + CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": [] })json") == R"json({ "test": [ ] })json"); + CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": [ 1 ] })json") == R"json({ "test": [ 1 ] })json"); + CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": [ 1, "a", true, { "test": 1 }, [], null ] })json") == R"json({ "test": [ 1, "a", true, { "test": 1 }, [ ], null ] })json"); + // BUG -- This should work, but doesn't. + // CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": {} })json") == R"json({ "test": { } })json"); + CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": { "test": 1 } })json") == R"json({ "test": { "test": 1 } })json"); + CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": { "test": 2 } })json") == R"json({ "test": { "test": 2 } })json"); + CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": { "test": 1, "test2": [ 2 ] } })json") == R"json({ "test": { "test": 1, "test2": [ 2 ] } })json"); + + /* Check if spaces are properly ignored. */ + CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({"test":1})json") == R"json({ "test": 1 })json"); + CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({"test": 1})json") == R"json({ "test": 1 })json"); + + /* Cases that should fail, as it is invalid JSON. */ + CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({"test":test})json") == std::nullopt); + CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": 1 )json") == std::nullopt); // Missing closing } + CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json( "test": 1})json") == std::nullopt); // Missing opening { + CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test" = 1})json") == std::nullopt); + CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": [ 1 })json") == std::nullopt); // Missing closing ] + CHECK(TestScriptAdmin::TestScriptEventAdminPort(R"json({ "test": 1 ] })json") == std::nullopt); // Missing opening [ +} From 6ab169fbe0f10cd5725015f59e95d9b818155933 Mon Sep 17 00:00:00 2001 From: translators Date: Fri, 1 Sep 2023 18:38:57 +0000 Subject: [PATCH 14/15] Update: Translations from eints dutch: 1 change by Afoklala --- src/lang/dutch.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lang/dutch.txt b/src/lang/dutch.txt index 80f03de4a3..e23d49287b 100644 --- a/src/lang/dutch.txt +++ b/src/lang/dutch.txt @@ -2650,6 +2650,7 @@ STR_TRANSPARENT_BUILDINGS_TOOLTIP :{BLACK}Transpar STR_TRANSPARENT_BRIDGES_TOOLTIP :{BLACK}Transparantie voor bruggen aan-uit. Ctrl+klik om vast te zetten. STR_TRANSPARENT_STRUCTURES_TOOLTIP :{BLACK}Transparantie voor gebouwen zoals vuurtorens en zendmasten aan-uit. Ctrl+klik om vast te zetten. STR_TRANSPARENT_CATENARY_TOOLTIP :{BLACK}Transparantie voor bovenleiding aan-uit. Ctrl+klik om vast te zetten. +STR_TRANSPARENT_TEXT_TOOLTIP :{BLACK}Transparantie omschakelen voor teksten bij laden en kosten/inkomsten. Ctrl+klik om vast te zetten STR_TRANSPARENT_INVISIBLE_TOOLTIP :{BLACK}Maak objecten onzichtbaar in plaats van transparant # Linkgraph legend window From 236ec41fa29e1bdd92d2beefbc314cc67de24f29 Mon Sep 17 00:00:00 2001 From: frosch Date: Sat, 2 Sep 2023 12:45:19 +0200 Subject: [PATCH 15/15] Fix #11230: Group list widget layout failed, if dropdown widgets were higher than the font height. (#11231) --- src/group_gui.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/group_gui.cpp b/src/group_gui.cpp index da3b8eb522..cb6959d379 100644 --- a/src/group_gui.cpp +++ b/src/group_gui.cpp @@ -75,8 +75,8 @@ static const NWidgetPart _nested_group_widgets[] = { NWidget(NWID_VERTICAL), NWidget(NWID_HORIZONTAL), NWidget(NWID_VERTICAL), - NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GL_GROUP_BY_ORDER), SetFill(1, 0), SetMinimalSize(0, 12), SetDataTip(STR_STATION_VIEW_GROUP, STR_TOOLTIP_GROUP_ORDER), - NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GL_SORT_BY_ORDER), SetFill(1, 0), SetMinimalSize(0, 12), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER), + NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GL_GROUP_BY_ORDER), SetFill(1, 1), SetMinimalSize(0, 12), SetDataTip(STR_STATION_VIEW_GROUP, STR_TOOLTIP_GROUP_ORDER), + NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GL_SORT_BY_ORDER), SetFill(1, 1), SetMinimalSize(0, 12), SetDataTip(STR_BUTTON_SORT_BY, STR_TOOLTIP_SORT_ORDER), EndContainer(), NWidget(NWID_VERTICAL), NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GL_GROUP_BY_DROPDOWN), SetFill(1, 0), SetMinimalSize(0, 12), SetDataTip(0x0, STR_TOOLTIP_GROUP_ORDER),