From 1002e0a6d5880e16cc628ea10cd68ff572766b6c Mon Sep 17 00:00:00 2001 From: TheTank20 <57580668+thepwrtank18@users.noreply.github.com> Date: Fri, 4 Jul 2025 14:29:23 +0000 Subject: [PATCH] Windows on ARM support + icon fixes (#129) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Support for Windows on ARM (uses MSVC, but it's Windows 11 exclusive anyway so `¯\_(ツ)_/¯`) * Adds the icon back for x86/x64 TDM-GCC builds * Makes Linux compilation on ARM much faster * added tests to every workflow --- .github/workflows/dos-djgpp.yml | 97 ++++++++++++++++++++++++++++- .github/workflows/freebsd.yml | 5 +- .github/workflows/linux.yml | 104 ++++++++++++++++++++++++++++++-- .github/workflows/macos.yml | 5 +- .github/workflows/windows.yml | 94 +++++++++++++++++++++++++++-- CMakeLists.txt | 36 ++++++++++- src/libumskt/confid/confid.cpp | 10 +++ src/windows/umskt.rc | Bin 4976 -> 2398 bytes 8 files changed, 338 insertions(+), 13 deletions(-) diff --git a/.github/workflows/dos-djgpp.yml b/.github/workflows/dos-djgpp.yml index a1335d9..cae1021 100644 --- a/.github/workflows/dos-djgpp.yml +++ b/.github/workflows/dos-djgpp.yml @@ -42,7 +42,7 @@ jobs: - name: Setup build environment run: | sudo apt -y update - sudo apt -y install build-essential cmake wget 7zip git flex libfl-dev nasm libslang2-dev pkg-config libslang2-modules gcc-multilib + sudo apt -y install build-essential cmake wget 7zip git flex libfl-dev nasm libslang2-dev pkg-config libslang2-modules gcc-multilib dosbox - name: Download and Setup DJGPP Toolchain run: | @@ -59,7 +59,16 @@ jobs: make -f djgpp.mak ln -s ${WATT_ROOT}/lib/libwatt.a ${{ github.workspace }}/djgpp/lib + - name: Cache OpenSSL 3.1.2 + uses: actions/cache/restore@v4 + id: cache-openssl + with: + path: | + ${{ github.workspace }}/djgpp + key: openssl-3.1.2-${{ hashFiles('**/CMakeLists.txt') }} + - name: Checkout and Cross Compile OpenSSL 3.1.2 + if: steps.cache-openssl.outputs.cache-hit != 'true' run: | git clone https://github.com/UMSKT/openssl.git openssl pushd openssl @@ -68,6 +77,14 @@ jobs: make && make install_sw popd + - name: Save OpenSSL 3.1.2 + if: steps.cache-openssl.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + with: + path: | + ${{ github.workspace }}/djgpp + key: openssl-3.1.2-${{ hashFiles('**/CMakeLists.txt') }} + - name: Build run: | source ${{ github.workspace }}/djgpp/setenv @@ -75,6 +92,84 @@ jobs: cmake ../ -D DJGPP_WATT32=${WATT_ROOT}/lib/libwatt.a -D CMAKE_FIND_ROOT_PATH=${CMAKE_FIND_ROOT_PATH} make + - name: Setup DOSBox test environment + run: | + mkdir -p dosbox_test + cp build/umskt.exe dosbox_test/ + # Download DPMI server directly + wget https://github.com/UMSKT/winactiontest/raw/refs/heads/main/CWSDPMI.EXE -O dosbox_test/CWSDPMI.EXE + # Create test batch file + cat > dosbox_test/test.bat << EOL + @echo off + echo Running test 1... + umskt.exe -b 2C -c 365 -s 069420 > TEST1.TXT + if errorlevel 1 goto error + echo Running test 2... + umskt.exe -i 253286028742154311079061239762245184619981623171292574 > TEST2.TXT + if errorlevel 1 goto error + echo Tests completed > DONE.TXT + goto end + :error + echo Test failed > ERROR.TXT + :end + exit + EOL + # Create DOSBox configuration + cat > dosbox_test/dosbox.conf << EOL + [sdl] + nosound=true + [cpu] + core=dynamic + cycles=max + [autoexec] + mount c . + c: + test.bat + exit + EOL + + - name: Run tests in DOSBox + run: | + cd dosbox_test + timeout 30s dosbox -conf dosbox.conf -nogui -exit + # Check if the test completed successfully + if [ ! -f DONE.TXT ]; then + echo "Tests did not complete successfully" + if [ -f ERROR.TXT ]; then + echo "Test execution failed" + fi + if [ -f TEST1.TXT ]; then + echo "Test 1 output:" + cat TEST1.TXT + fi + if [ -f TEST2.TXT ]; then + echo "Test 2 output:" + cat TEST2.TXT + fi + exit 1 + fi + # Verify test outputs + if [ ! -f TEST1.TXT ] || [ ! -f TEST2.TXT ]; then + echo "Test output files missing" + exit 1 + fi + # Check test results - looking for key format patterns + if ! grep -qE '[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}' TEST1.TXT || \ + ! grep -qE '[0-9]{6}-[0-9]{6}-[0-9]{6}-[0-9]{6}-[0-9]{6}-[0-9]{6}-[0-9]{6}' TEST2.TXT; then + echo "Tests failed - unexpected output format" + echo "Test 1 output:" + cat TEST1.TXT + echo "Test 2 output:" + cat TEST2.TXT + exit 1 + else + echo "All tests passed successfully" + echo "Test 1 output:" + cat TEST1.TXT + echo "Test 2 output:" + cat TEST2.TXT + fi + - name: Move executable to upload directory run: | mkdir build/actions_upload diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index 406ac82..ef38927 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -51,7 +51,10 @@ cd build cmake .. make - ./umskt # Execute the test here + echo Test 1 - generating key + ./umskt -b 2C -c 365 -s 069420 -v + echo Test 2 - generating confid + ./umskt -i 253286028742154311079061239762245184619981623171292574 - name: Move files to correct directory run: | diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 4a8590f..85326da 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -28,18 +28,25 @@ on: jobs: build: - runs-on: ubuntu-latest strategy: matrix: include: - arch: x86 + runner: ubuntu-latest + use_alpine: true - arch: x86_64 + runner: ubuntu-latest + use_alpine: true - arch: aarch64 + runner: ubuntu-24.04-arm + use_alpine: false + runs-on: ${{ matrix.runner }} steps: - name: Checkout Source Tree uses: actions/checkout@v4 - - name: Setup latest Alpine Linux for ${{ matrix.arch }} + - name: Setup latest Alpine Linux + if: ${{ matrix.use_alpine }} uses: jirutka/setup-alpine@v1 with: packages: > @@ -54,13 +61,46 @@ jobs: arch: ${{ matrix.arch }} shell-name: alpine-target.sh - - name: Configure and build UMSKT + - name: Install Dependencies (Ubuntu ARM64) + if: ${{ !matrix.use_alpine }} + run: | + sudo apt-get update + sudo apt-get install -y build-essential cmake git libssl-dev zlib1g-dev + + - name: Configure and build UMSKT (Alpine) + if: ${{ matrix.use_alpine }} uses: threeal/cmake-action@7ef2eb8da6e5ec0a6de6b1ddc96987080bed06e8 with: options: MUSL_STATIC=ON run-build: true shell: alpine-target.sh {0} + - name: Configure and build UMSKT (Ubuntu) + if: ${{ !matrix.use_alpine }} + uses: threeal/cmake-action@7ef2eb8da6e5ec0a6de6b1ddc96987080bed06e8 + with: + options: MUSL_STATIC=ON + run-build: true + + - name: Test UMSKT (Alpine) + if: ${{ matrix.use_alpine }} + run: | + cd build + echo Test 1 - generating key + ./umskt -b 2C -c 365 -s 069420 -v + echo Test 2 - generating confid + ./umskt -i 253286028742154311079061239762245184619981623171292574 + shell: alpine-target.sh {0} + + - name: Test UMSKT (Ubuntu) + if: ${{ !matrix.use_alpine }} + run: | + cd build + echo Test 1 - generating key + ./umskt -b 2C -c 365 -s 069420 -v + echo Test 2 - generating confid + ./umskt -i 253286028742154311079061239762245184619981623171292574 + - name: Move files to correct directory run: | mkdir -p build/actions_upload @@ -72,16 +112,70 @@ jobs: name: UMSKT-linux-${{ matrix.arch }}-static path: build/actions_upload - - name: Configure and build static internal deps UMSKT + - name: Configure and build static internal deps UMSKT (Alpine) + if: ${{ matrix.use_alpine }} uses: threeal/cmake-action@7ef2eb8da6e5ec0a6de6b1ddc96987080bed06e8 with: options: MUSL_STATIC=OFF BUILD_SHARED_LIBS=OFF run-build: true shell: alpine-target.sh {0} - - name: Configure and build shared deps UMSKT + - name: Configure and build static internal deps UMSKT (Ubuntu) + if: ${{ !matrix.use_alpine }} + uses: threeal/cmake-action@7ef2eb8da6e5ec0a6de6b1ddc96987080bed06e8 + with: + options: MUSL_STATIC=OFF BUILD_SHARED_LIBS=OFF + run-build: true + + - name: Test static internal deps UMSKT (Alpine) + if: ${{ matrix.use_alpine }} + run: | + cd build + echo Test 1 - generating key + ./umskt -b 2C -c 365 -s 069420 -v + echo Test 2 - generating confid + ./umskt -i 253286028742154311079061239762245184619981623171292574 + shell: alpine-target.sh {0} + + - name: Test static internal deps UMSKT (Ubuntu) + if: ${{ !matrix.use_alpine }} + run: | + cd build + echo Test 1 - generating key + ./umskt -b 2C -c 365 -s 069420 -v + echo Test 2 - generating confid + ./umskt -i 253286028742154311079061239762245184619981623171292574 + + - name: Configure and build shared deps UMSKT (Alpine) + if: ${{ matrix.use_alpine }} uses: threeal/cmake-action@7ef2eb8da6e5ec0a6de6b1ddc96987080bed06e8 with: options: MUSL_STATIC=OFF BUILD_SHARED_LIBS=ON run-build: true shell: alpine-target.sh {0} + + - name: Configure and build shared deps UMSKT (Ubuntu) + if: ${{ !matrix.use_alpine }} + uses: threeal/cmake-action@7ef2eb8da6e5ec0a6de6b1ddc96987080bed06e8 + with: + options: MUSL_STATIC=OFF BUILD_SHARED_LIBS=ON + run-build: true + + - name: Test shared deps UMSKT (Alpine) + if: ${{ matrix.use_alpine }} + run: | + cd build + echo Test 1 - generating key + ./umskt -b 2C -c 365 -s 069420 -v + echo Test 2 - generating confid + ./umskt -i 253286028742154311079061239762245184619981623171292574 + shell: alpine-target.sh {0} + + - name: Test shared deps UMSKT (Ubuntu) + if: ${{ !matrix.use_alpine }} + run: | + cd build + echo Test 1 - generating key + ./umskt -b 2C -c 365 -s 069420 -v + echo Test 2 - generating confid + ./umskt -i 253286028742154311079061239762245184619981623171292574 diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 6b1108e..8d73d0e 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -85,7 +85,10 @@ jobs: - name: Run tests run: | cd build/actions_upload - ./umskt + echo Test 1 - generating key + ./umskt -b 2C -c 365 -s 069420 -v + echo Test 2 - generating confid + ./umskt -i 253286028742154311079061239762245184619981623171292574 - name: Upload platform-specific build uses: actions/upload-artifact@v4.6.2 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 29e33ca..612b29f 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -27,13 +27,14 @@ on: workflow_dispatch: jobs: - build-tdm: + build: runs-on: windows-latest strategy: matrix: - arch: [x64, x86] + arch: [x64, x86, arm64] steps: - name: Setup TDM-GCC + if: matrix.arch != 'arm64' run: | Write-Host Downloading TDM-GCC v10.3.0... Invoke-WebRequest -Uri 'https://github.com/jmeubank/tdm-gcc/releases/download/v10.3.0-tdm64-2/tdm64-gcc-10.3.0-2.exe' -OutFile 'C:\Windows\temp\TDM-GCC-64.exe' @@ -49,10 +50,17 @@ jobs: $env:PATH = 'C:\TDM-GCC-64\bin;' + $env:PATH [Environment]::SetEnvironmentVariable('PATH', $env:PATH, [EnvironmentVariableTarget]::Machine) + - name: Setup MSVC for ARM64 + if: matrix.arch == 'arm64' + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: arm64 + - name: Checkout Source Tree uses: actions/checkout@v4 - name: Download OpenSSL-TDM release asset + if: matrix.arch != 'arm64' shell: pwsh run: | if ('${{ matrix.arch }}' -eq 'x64') { @@ -74,7 +82,39 @@ jobs: echo "OPENSSL_LIBDIR=$libdir" | Out-File -FilePath $env:GITHUB_ENV -Append echo "CMAKE_FLAGS=$cmake_flags" | Out-File -FilePath $env:GITHUB_ENV -Append + - name: Setup vcpkg for ARM64 + if: matrix.arch == 'arm64' + shell: pwsh + run: | + git clone https://github.com/Microsoft/vcpkg.git + cd vcpkg + .\bootstrap-vcpkg.bat + echo "VCPKG_ROOT=$env:GITHUB_WORKSPACE/vcpkg" | Out-File -FilePath $env:GITHUB_ENV -Append + echo "VCPKG_DEFAULT_BINARY_CACHE=$env:GITHUB_WORKSPACE/vcpkg/bincache" | Out-File -FilePath $env:GITHUB_ENV -Append + echo "VCPKG_BINARY_SOURCES=clear;default,readwrite" | Out-File -FilePath $env:GITHUB_ENV -Append + + - name: Cache vcpkg packages + if: matrix.arch == 'arm64' + uses: actions/cache@v4 + with: + path: | + ${{ github.workspace }}/vcpkg/bincache + ${{ github.workspace }}/vcpkg/installed + ${{ github.workspace }}/vcpkg/packages + key: vcpkg-arm64-${{ hashFiles('**/CMakeLists.txt') }}-${{ hashFiles('**/*.cmake') }} + restore-keys: | + vcpkg-arm64-${{ hashFiles('**/CMakeLists.txt') }}- + vcpkg-arm64- + + - name: Install OpenSSL for ARM64 + if: matrix.arch == 'arm64' + shell: pwsh + run: | + New-Item -ItemType Directory -Force -Path $env:VCPKG_DEFAULT_BINARY_CACHE + & "$env:VCPKG_ROOT\vcpkg.exe" install openssl:arm64-windows-static --clean-after-build + - name: Configure UMSKT (TDM-GCC ${{ matrix.arch }}) + if: matrix.arch != 'arm64' shell: pwsh run: | $env:PATH = 'C:\TDM-GCC-64\bin;' + $env:PATH @@ -92,14 +132,60 @@ jobs: -DCMAKE_CXX_FLAGS="$env:CMAKE_FLAGS" ` . + - name: Configure UMSKT (MSVC ARM64) + if: matrix.arch == 'arm64' + shell: pwsh + run: | + cmake -G "Visual Studio 17 2022" ` + -A ARM64 ` + -DWINDOWS_ARM=ON ` + -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" ` + -DVCPKG_TARGET_TRIPLET=arm64-windows-static ` + . + - name: Build UMSKT (TDM-GCC ${{ matrix.arch }}) + if: matrix.arch != 'arm64' shell: pwsh run: | $env:PATH = 'C:\TDM-GCC-64\bin;' + $env:PATH mingw32-make + - name: Build UMSKT (MSVC ARM64) + if: matrix.arch == 'arm64' + shell: pwsh + run: | + cmake --build . --config Release + + - name: Run tests (x86/x64) + if: matrix.arch != 'arm64' + shell: pwsh + run: | + Write-Host Test 1 - generating key + .\umskt.exe -b 2C -c 365 -s 069420 -v + Write-Host Test 2 - generatng confid + .\umskt.exe -i 253286028742154311079061239762245184619981623171292574 + - name: Upload build artifact uses: actions/upload-artifact@v4.6.2 with: - name: UMSKT-TDM${{ matrix.arch }} - path: umskt.exe + name: UMSKT-${{ matrix.arch }} + path: ${{ matrix.arch == 'arm64' && 'Release/umskt.exe' || 'umskt.exe' }} + + test-arm64: + needs: build + if: success() + runs-on: windows-11-arm + steps: + - name: Download ARM64 artifact + uses: actions/download-artifact@v4 + with: + name: UMSKT-arm64 + path: . + + - name: Run tests (ARM64) + shell: pwsh + run: | + Write-Host Test 1 - generating key + .\umskt.exe -b 2C -c 365 -s 069420 -v + Write-Host Test 2 - generatng confid + .\umskt.exe -i 253286028742154311079061239762245184619981623171292574 diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b6d82d..10d6710 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,24 @@ if (WIN32 AND NOT MSVC) set(CMAKE_C_COMPILER "C:/TDM-GCC-64/bin/gcc.exe" CACHE FILEPATH "C Compiler" FORCE) set(CMAKE_CXX_COMPILER "C:/TDM-GCC-64/bin/g++.exe" CACHE FILEPATH "C++ Compiler" FORCE) message(STATUS "[UMSKT] Forcing use of TDM-GCC in C:/TDM-GCC-64") + + # Configure windres for resource compilation + set(CMAKE_RC_COMPILER "C:/TDM-GCC-64/bin/windres.exe") + set(CMAKE_RC_COMPILER_INIT windres) + enable_language(RC) + set(CMAKE_RC_FLAGS "--use-temp-file -c65001") + + # Match resource architecture with target architecture + if(CMAKE_SIZEOF_VOID_P EQUAL 4) + set(CMAKE_RC_FLAGS "${CMAKE_RC_FLAGS} -F pe-i386") + else() + set(CMAKE_RC_FLAGS "${CMAKE_RC_FLAGS} -F pe-x86-64") + endif() + + set(CMAKE_RC_COMPILE_OBJECT " -O coff -I${CMAKE_CURRENT_SOURCE_DIR}/src/windows -i -o ") + + # Set the Windows resource file for GCC builds + set(UMSKT_EXE_WINDOWS_EXTRA src/windows/umskt.rc) endif() SET(CMAKE_CXX_STANDARD 17) @@ -36,6 +54,7 @@ OPTION(UMSKT_USE_SHARED_OPENSSL "Force linking against the system-wide OpenSSL l OPTION(MUSL_STATIC "Enable fully static builds in a muslc environment (i.e. Alpine Linux)" OFF) OPTION(DJGPP_WATT32 "Enable compilation and linking with DJGPP/WATT32/OpenSSL" OFF) OPTION(MSVC_MSDOS_STUB "Specify a custom MS-DOS stub for a 32-bit MSVC compilation" OFF) +OPTION(WINDOWS_ARM "Enable compilation for Windows on ARM (requires appropriate toolchain)" OFF) SET(UMSKT_LINK_LIBS ${UMSKT_LINK_LIBS}) SET(UMSKT_LINK_DIRS ${UMSKT_LINK_DIRS}) @@ -60,7 +79,22 @@ ELSE() MESSAGE(STATUS "[UMSKT] Requesting static version of OpenSSL") ENDIF() - +# Configure ARM-specific settings if enabled +if (WINDOWS_ARM) + if (MSVC) + # MSVC ARM64 settings + set(CMAKE_SYSTEM_PROCESSOR ARM64) + set(CMAKE_VS_PLATFORM_TOOLSET_HOST_ARCHITECTURE ARM64) + else() + # MinGW/GCC ARM settings + set(CMAKE_SYSTEM_PROCESSOR arm) + if (CMAKE_CROSSCOMPILING) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv8-a") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=armv8-a") + endif() + endif() + message(STATUS "[UMSKT] Configuring for Windows on ARM") +endif() IF(DJGPP_WATT32) SET(CMAKE_SYSTEM_NAME MSDOS) diff --git a/src/libumskt/confid/confid.cpp b/src/libumskt/confid/confid.cpp index 0b7a4d2..3949e0c 100644 --- a/src/libumskt/confid/confid.cpp +++ b/src/libumskt/confid/confid.cpp @@ -29,6 +29,10 @@ #include "confid.h" +#if defined(_MSC_VER) +#include +#endif + QWORD MOD = 0; QWORD NON_RESIDUE = 0; QWORD f[6] = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; @@ -75,6 +79,12 @@ inline QWORD ConfirmationID::__umul128(QWORD a, QWORD b, QWORD* hi) #else #define __umul128 _umul128 #endif +#elif defined(_M_ARM64) // Microsoft implementation of ARM64 +inline QWORD ConfirmationID::__umul128(QWORD a, QWORD b, QWORD* hi) +{ + *hi = __umulh(a, b); + return a * b; +} #elif defined(__i386__) || defined(_M_IX86) || defined(__arm__) || defined(__EMSCRIPTEN__) inline QWORD ConfirmationID::__umul128(QWORD multiplier, QWORD multiplicand, QWORD *product_hi) { // multiplier = ab = a * 2^32 + b diff --git a/src/windows/umskt.rc b/src/windows/umskt.rc index df98e3002919733ef2d48bb3488224acb0265b71..e4ef4e12aa8b628082228dcb57c0a43f680a96f7 100644 GIT binary patch literal 2398 zcmb_dU60y06n)RHxXZi@Q#P4lq^;Urtu_+^6Ey^skG4o7Ay49fSHX^K2ZYpr)`$H` z`xo{)0m@YE&I@?}e;%KE?(y}%U;lA#ZY;Qgbj=i$tfGMb1ZpBRh!XUgLN-l4q%fD_ zZ<=ddYnFSW-dD8LaP9SBczgHm*WvB({;<>&Tub)cXdx9`Q*D#V?jJU`=hRZHB-xmw zN=YdwQR*F$^gZlF13BR!X~9$nf{Zi}UN}O+Mii{v8v;1i@Pf*gp{8=9j&vtc25wnW zDPTf5m861s^IWkULY7mmC=dm;8BuFmz;g>{;v5?)$$<)t1>zIU1RnrpI4@yKrNTbk zACEc!4nRo5>6&O0fCN#SeSKuThl(J(<5t@;>%Q0ETc`yv-bS<*HN^%3VxT)#Rq#xq zQM7ETfq_7A;Zu-4&axETXaS$xICi6S@jEWoYk{+LOFJmGsVjzoqQH`Hy+`r}Vd}@; zBW`gYf-p!I7y^ugH1d-K#X^%2{*MY5mjQgfG9>}7GtP_>iAE=={wZ{Y$zdh@(j}<4 zcE_JFIms-GJ%2e0!#;fZf=)Nt>n2k-o-F++>sz54O)_`lgK6N+XC6tOtJ*L>Az8X3?kHp5;d)%NqW4@i%ApPX6dU z$tT|Hz)leNKZW?w>MP2z_!~M%{d7H!`j~5Y!O!S{#{{|Htu%*rS86J;qe??Cf-R{U z3Ux(tb9I=+Q7r%qOM_L9)L3})_6Y&j0il^TSUnV0IpBX#ro|-*_ZR3xr9_JdpZs-dNX8hB-_vO7krsuLJb;)_9Xx*P~Zdweq*{nxgA$@jT^W`QwS$F6K>8M6W7$okCUy31^8UAwUvvkB*c z*_A__*rB!UCAWhe12M7R+3#_lFsCtCmD8!+Iqreu+G6h+H+St@+p!<*8=vjkckC50 z>*kmnH*j=eb7m9f;&lsZGpoSeh~H-`sOEA_oxgYdcI}$GN!lB5NZJgn;_KFB?b}c6 z-LW@k?6Egy6zrwnEOHZdCG806(zczy0F{YD55SY_VF--?Rzdc`m}_(9>uZ;+tmnG5 z&`7X7I2<_CgjJo{*jJKY7MP=-5&ZR7HGLFB28?)<#->Y{%x!qd-bIPOWZUO1t!}pO zX(rfn*NY_RBF&K1*uDm`&ph6jTz@!k+2TC7UCVNnc@zd&HrLgb<4<-FPWe!xDBqFB z?-)l88TDDFTXB3j&JTISD*H-HLn!68x^hmW9my*#2tvQNj3c+lh3kFp`X8Y85jx8G zh@KqmjWvj$7j}e&JKW#H)~Aebn7!iDy2H-T3*4e7uN1Am>$u3*5U-m;X~>zlj@~Sv zR|H8@vOvzoq1B*Kg?^;7XZ!Hebe_b8>>+!L#}=?{KCO?qC0^xq;Z^EW9Qd}##9}0-hRXKu{u;;%kul^XUZV~ z59xzb+L2Fw51t+SfnQ~K<@F+_L${5*QZ|ZtEMZ7++e@p7`#`l(Zd08}**e&3_A;c2 zIk0M9@dc#Hb7SIBexZAFFi2YIN!W&-Q=*56j;L=sPdzjNETR6 zNfUPgdnc|ZfYuaV>>R1f}lxnOmaC!f%$za8|s8Z!9#KC;)*Z_8Ke z8T75ryl2#un0;lxs4>w{UU>?QRT+Q7D{wjAY}gt3=M0aI)N?K$<4EtBr*QOd>1+~F z$}ur!B4066Kg>4|y)D^jJ+n*7qs1}cDeB0^%V;Ueke?R&DD_sio_W+iH}D(1Q`9@m z+12|iLv6^wJcpUY+W=)g!5c5Of##*lp{DnBFCN36%MYmF^lCRWH56;bBKKD5Bo?CAjy&6wfx6yjK>pVu$mXf7Rgs z(=+~hdtUM_jmi}RWY!CQbv=I=U+XPhIj)NTSx8m;<2yU$yZbf0+}R~vuD4De&!_RF K^>I~Pvi<`I4wDN2