diff --git a/.cirrus.yml b/.cirrus.yml index 06d46491..5c6b0a24 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,22 +1,10 @@ -task: - name: freebsd-11-amd64 - freebsd_instance: - image_family: freebsd-11-4 - setup_script: - - pkg update - - pkg install -y git sdl2 python - - git submodule update --init --recursive - test_script: - - ./scripts/cirrus/build_freebsd.sh dedicated - - ./scripts/cirrus/build_freebsd.sh full - task: name: freebsd-12-amd64 freebsd_instance: - image_family: freebsd-12-2 + image_family: freebsd-12-3 setup_script: - pkg update - - pkg install -y git sdl2 python + - pkg install -y git sdl2 python fontconfig - git submodule update --init --recursive test_script: - ./scripts/cirrus/build_freebsd.sh dedicated @@ -25,10 +13,22 @@ task: task: name: freebsd-13-amd64 freebsd_instance: - image_family: freebsd-13-0-snap + image_family: freebsd-13-1 setup_script: - pkg update - - pkg install -y git sdl2 python + - pkg install -y git sdl2 python fontconfig + - git submodule update --init --recursive + test_script: + - ./scripts/cirrus/build_freebsd.sh dedicated + - ./scripts/cirrus/build_freebsd.sh full + +task: + name: freebsd-14-amd64 + freebsd_instance: + image_family: freebsd-14-0-snap + setup_script: + - pkg update + - pkg install -y git sdl2 python fontconfig - git submodule update --init --recursive test_script: - ./scripts/cirrus/build_freebsd.sh dedicated diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index a9101e80..2c401f6e 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -1,6 +1,11 @@ name: Build & Deploy Engine on: [push, pull_request] jobs: +# cleanup: +# runs-on: self-hosted +# steps: +# - name: Cleanup +# run: rm -rf .* || true build: runs-on: ${{ matrix.os }} continue-on-error: true @@ -14,6 +19,9 @@ jobs: - os: ubuntu-18.04 targetos: linux targetarch: i386 +# - os: ubuntu-aarch64-20.04 +# targetos: linux +# targetarch: aarch64 # - os: ubuntu-18.04 # targetos: android @@ -38,7 +46,6 @@ jobs: GH_CPU_ARCH: ${{ matrix.targetarch }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ANDROID_SDK_TOOLS_VER: 4333796 - ANDROID_NDK_VER: r10e UPLOADTOOL_ISPRERELEASE: true steps: - name: Checkout @@ -57,15 +64,10 @@ jobs: - name: Build engine run: bash scripts/gha/build_${{ matrix.targetos }}.sh - - name: Upload engine (android) - if: matrix.targetos == 'android' - run: bash scripts/continious_upload.sh xashdroid-${{ matrix.targetarch }}.apk - - name: Upload engine (motomagx) - if: matrix.targetos == 'motomagx' - run: bash scripts/continious_upload.sh xash3d-fwgs-magx.7z - - name: Upload engine (linux) - if: matrix.targetos == 'linux' - run: bash scripts/continious_upload.sh xash3d-fwgs-${{ matrix.targetarch }}.AppImage xashds-linux-${{ matrix.targetarch }} - - name: Upload engine (windows) - if: matrix.targetos == 'win32' - run: bash scripts/continious_upload.sh xash3d-fwgs-win32-${{ matrix.targetarch }}.7z + - name: Upload engine (prereleases) + run: bash scripts/continious_upload.sh artifacts/* + - name: Upload engine (artifacts) + uses: actions/upload-artifact@v2 + with: + name: artifact-${{ matrix.targetos }}-${{ matrix.targetarch }} + path: artifacts/* diff --git a/.gitmodules b/.gitmodules index 56bb02a6..4fdce5cb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -8,9 +8,9 @@ [submodule "ref_gl/gl-wes-v2"] path = ref_gl/gl-wes-v2 url = https://github.com/FWGS/gl-wes-v2 -[submodule "vgui-dev"] - path = vgui-dev - url = https://github.com/FWGS/vgui-dev [submodule "ref_gl/gl4es"] path = ref_gl/gl4es url = https://github.com/ptitSeb/gl4es +[submodule "vgui_support"] + path = vgui_support + url = https://github.com/FWGS/vgui_support diff --git a/Documentation/not-supported-mod-list-and-reasons-why.md b/Documentation/not-supported-mod-list-and-reasons-why.md index 89f834cc..f2ec68a2 100644 --- a/Documentation/not-supported-mod-list-and-reasons-why.md +++ b/Documentation/not-supported-mod-list-and-reasons-why.md @@ -7,6 +7,5 @@ |Counter Strike: Condition Zero |The latest steam release |Uses vgui2 library which xash3d does not support |Some work on vgui2 support was made here: https://github.com/FWGS/xash3d/tree/vinterface |Counter Strike: Condition Zero - Deleted scenes |The latest steam release |Uses vgui2 library which xash3d does not support |Some work on vgui2 support was made here: https://github.com/FWGS/xash3d/tree/vinterface |Day of Defeat |The latest steam release |Uses vgui2 library which xash3d does not support |Some work on vgui2 support was made here: https://github.com/FWGS/xash3d/tree/vinterface -|Sven-Coop |5.0+ |Uses filesystem_stdio library which xash3d does not support |filesystem_stdio replacement already was made: https://github.com/FWGS/filesystem_stdio_xash -|XDM |3.0.4.0 | | +|Sven-Coop |5.0+ |Uses custom GoldSrc engine | diff --git a/Documentation/opensource-mods.md b/Documentation/opensource-mods.md index be6ee7dd..88676a33 100644 --- a/Documentation/opensource-mods.md +++ b/Documentation/opensource-mods.md @@ -17,7 +17,9 @@ Mirrored on github - https://github.com/JoelTroch/am_src_rebirth Mirrored on github - https://github.com/nekonomicon/BattleGrounds ## Bubblemod -Download page on official site - http://www.bubblemod.org/dl_default.php +Download page on official site - http://www.bubblemod.org/dl_default.php (dead link!) + +Mirrored on github - https://github.com/HLSources/BubbleMod ## Chicken Fortress Official github repository - https://github.com/CKFDevPowered/CKF3Alpha @@ -25,6 +27,11 @@ Official github repository - https://github.com/CKFDevPowered/CKF3Alpha ## Cold Ice Version 1.9 is available on ModDB - https://www.moddb.com/mods/cold-ice/downloads/cold-ice-sdk +Version 1.9 mirrored on github - https://github.com/solidi/hl-mods/tree/master/ci + +## Cold Ice Ressurection +Mirrored on github - https://github.com/solidi/hl-mods/tree/master/cir + ## Cthulhu Uploaded to github by Oleg Cherkasky - https://github.com/gunrunners-paradise/Cthulhu-HLmod-SDK @@ -38,25 +45,36 @@ Official github repository - https://github.com/adslbarxatov/xash3d-for-ESHQ Available on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/Half-Life_2D_(Flat-Life) ## Gang Wars -Mirrored on github - https://github.com/hammermaps/gw1.45src +Mirrored on github - https://github.com/nekonomicon/gw1.45src ## Go-mod Versions 2.0 and 3.0, available in mod archives on ModDB - https://www.moddb.com/mods/go-mod/downloads +Version 3.0, mirrored on github - https://github.com/nekonomicon/Go-mod30 + ## GT mod Available on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/GT_mod_(Polnie_ishodniki) +## Half-Life: Advanced Deathmatch +Mirrored on github - https://github.com/solidi/hl-mods/tree/master/hla + +## Half-Life: Decay +Mirrored on github(PC Port) - https://github.com/hoaxer/Half-Life-Decay + ## Half-Life: Echoes -Download link on dropbox - https://www.dropbox.com/s/s6j8gtegn10wgvj/dlls.zip?dl=1 +Available on ModDB - https://www.moddb.com/mods/half-life-echoes ## Half-Life: Expanded Arsenal Available on ModDB - https://www.moddb.com/mods/half-life-expanded-arsenal ## Half-Life: Gravgun mod -Branch **gravgun** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/gravgun +Branch **gravgun** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/gravgun ## Half-Life: Invasion -official github repository - https://github.com/jlecorre/hlinvasion +Official github repository - https://github.com/jlecorre/hlinvasion + +## Half-Life: Pong +Mirrored on github - https://github.com/solidi/hl-mods/tree/master/pong ## Half-Life: Quest Mode Available on cs-mapping.com.ua - https://old.cs-mapping.com.ua/forum/showthread.php?t=38030 @@ -92,7 +110,7 @@ Official github repository - https://github.com/desukuran/half-screwed Version 1.3 on ModDB - https://www.moddb.com/mods/headcrab-frenzy/downloads/headcrab-frenzy-13-beta-source-code ## Heart of Evil -Available on ModDB - https://www.moddb.com/mods/heart-of-evil/downloads/heart-of-evil-dlls-source-code +Mirrored on github - https://github.com/nekonomicon/HeartOfEvil ## Ingram Chillin' Mod Official SourceForge repository - https://sourceforge.net/projects/icm-hl/ @@ -155,7 +173,7 @@ Mirrored on github - https://github.com/ZXCmod/ZXCmod # Reimplementation ## Absolute Redemption -Branch **redempt** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/redempt +Branch **redempt** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/redempt ## Adrenaline Gamer OpenAG by YaLTeR - https://github.com/YaLTeR/OpenAG @@ -163,34 +181,34 @@ OpenAG by YaLTeR - https://github.com/YaLTeR/OpenAG ## Afraid of Monsters malortie's recreation - https://github.com/malortie/hl-aom -Branch **aom** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/aom +Branch **aom** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aom ## Afraid of Monsters: Director's cut -Reverse-engineered code, branch **aomdc** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/aomdc +Reverse-engineered code, branch **aomdc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aomdc ## Azure Sheep malortie's recreation - https://github.com/malortie/hl-asheep -Reverse-engineered code, branch **asheep** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/asheep +Reverse-engineered code, branch **asheep** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/asheep ## Big Lolly malortie's recreation - https://github.com/malortie/hl-biglolly -Branch **biglolly** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/biglolly +Branch **biglolly** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/biglolly ## Black Ops malortie's recreation - https://github.com/malortie/hl-blackops -Branch **blackops** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/blackops +Branch **blackops** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/blackops ## Bloody Pizza: Vendetta -Branch **caseclosed** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/caseclosed +Branch **caseclosed** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/caseclosed ## Case Closed -Branch **caseclosed** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/caseclosed +Branch **caseclosed** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/caseclosed ## Cleaner's Adventures -Branch **CAd** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/CAd +Branch **CAd** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/CAd ## Counter Strike Reverse-engineered code of client part by a1batross - https://github.com/FWGS/cs16-client/tree/v1.32 @@ -208,54 +226,60 @@ Recreation by lostgamer aka nillerusr - https://github.com/LostGamerHL/crack_lif ## Escape from the Darkness malortie's recreation - https://github.com/malortie/hl-eftd -Reverse-engineered code, branch **eftd** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/eftd +Reverse-engineered code, branch **eftd** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/eftd ## Half-Life: Blue Shift Unkle Mike's recreation - https://hlfx.ru/forum/showthread.php?s=&threadid=5253 -Reverse-engineered code, branch **bshift** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/bshift +Reverse-engineered code, branch **bshift** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/bshift ## Half-Life: Induction -Branch **induction** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/induction +Branch **induction** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/induction ## Half-Life: Opposing Force Recreation by lostgamer aka nillerusr - https://github.com/LostGamerHL/hlsdk-xash3d -Reverse-engineered code, clean branch **opfor** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/opfor +Reverse-engineered code, clean branch **opfor** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/opfor Spirit of Half Life: Opposing-Force Edition - https://github.com/Hammermaps-DEV/SOHL-V1.9-Opposing-Force-Edition ## Half-Life: Rebellion -Reverse-engineered code, branch **rebellion** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/rebellion +Reverse-engineered code, branch **rebellion** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/rebellion + +## Half-Life: Urbicide +Branch **hl_urbicide** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/hl_urbicide ## Half-Life: Visitors malortie's recreation - https://github.com/malortie/hl-visitors -Reverse-engineered code, branch **visitors** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/visitors +Reverse-engineered code, branch **visitors** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/visitors ## Half-Secret -Branch **half-secret** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/half-secret +Branch **half-secret** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/half-secret ## Night at the Office malortie's recreation - https://github.com/malortie/hl-nato -Branch **noffice** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/noffice +Branch **noffice** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/noffice ## Poke 646 malortie's recreation - https://github.com/malortie/hl-poke646 -Reverse-engineered code, branch **poke646** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/poke646 +Reverse-engineered code, branch **poke646** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646 ## Poke 646: Vendetta malortie's recreation - https://github.com/malortie/hl-poke646-vendetta -Reverse-engineered code, branch **poke646_vendetta** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/poke646_vendetta +Reverse-engineered code, branch **poke646_vendetta** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646_vendetta ## Residual Life -Reverse-engineered code, branch **residual_point** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/residual_point +Reverse-engineered code, branch **residual_point** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/residual_point ## Residual Point -Reverse-engineered code, branch **residual_point** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/residual_point +Reverse-engineered code, branch **residual_point** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/residual_point + +## Sewer Beta +Branch **sewer_beta** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sewer_beta ## Team Fortress Classic Reverse-engineered code by Velaron - https://github.com/Velaron/tf15-client @@ -263,59 +287,64 @@ Reverse-engineered code by Velaron - https://github.com/Velaron/tf15-client ## The Gate malortie's recreation - https://github.com/malortie/hl-thegate -Reverse-engineered code, branch **thegate** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/thegate +Reverse-engineered code, branch **thegate** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/thegate ## They Hunger malortie's recreation - https://github.com/malortie/hl-theyhunger -Reverse-engineered code, branch **theyhunger** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/theyhunger +Reverse-engineered code, branch **theyhunger** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/theyhunger They Hunger: Tactical - https://www.moddb.com/mods/they-hunger-tactical/downloads/tht-source-code-documentation ## Times of Troubles malortie's recreation - https://github.com/malortie/hl-tot -Branch **tot** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/tot +Branch **tot** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/tot # Derived work ## Adrenaline Gamer -Branch **aghl** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/aghl +Branch **aghl** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aghl ## Bubblemod -Branch **bubblemod** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/bubblemod +Branch **bubblemod** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/bubblemod ## Deathmatch Classic Deathmatch Classic: Adrenaline Gamer Edition - https://github.com/martinwebrant/agmod/tree/master/src/dmc Deathmatch Quaked - https://www.moddb.com/games/deathmatch-classic/downloads/dmq2 -Branch **dmc** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/dmc +Branch **dmc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/dmc ## Half-Life: Echoes -Branch **echoes** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/echoes +Branch **echoes** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/echoes ## Half-Life: Invasion Port to HLSDK 2.4 by malortie - https://github.com/malortie/hl-invasion Port to Linux by fmoraw - https://github.com/fmoraw/hlinvasion +Branch **invasion** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/invasion + ## Half-Life: Top-Down -Branch **hltopdown** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/hltopdown +Branch **hltopdown** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/hltopdown ## Half-Screwed -Branch **half-screwed** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/half-screwed +Branch **half-screwed** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/half-screwed ## Natural Selection Port to Linux - https://github.com/fmoraw/NS +## Spirit of Half-Life +Version 1.2, branch **sohl1.2** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sohl1.2 + ## Swiss Cheese Halloween 2002 Just more playable version by malortie - https://github.com/malortie/hl-shall -Branch **halloween** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/halloween +Branch **halloween** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/halloween ## Threewave CTF -Branch **dmc** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/dmc +Branch **dmc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/dmc ## Zombie-X -Branch **zombie-x** in hlsdk-xash3d - https://github.com/FWGS/hlsdk-xash3d/tree/zombie-x +Branch **zombie-x** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/zombie-x diff --git a/Documentation/ports.md b/Documentation/ports.md index 27dc4fe5..fa072aba 100644 --- a/Documentation/ports.md +++ b/Documentation/ports.md @@ -15,24 +15,24 @@ Status: Table is sorted by status. -| Platform | Status | Maintainer | Note -| -------- | ------ | ---------- | ---- -| Windows | Supported | @a1batross | -| *BSD | Supported | @nekonomicon | -| GNU/Linux(x86, amd64, arm) | Supported | @a1batross, @mittorn | -| Android | Supported | @a1batross, @mittorn | -| MotoMAGX | Supported | @a1batross | -| GNU/Linux(elbrus) | Supported | @a1batross, @mittorn | Rare and eventual access to e2k machine -| Haiku | Supported | not maintained | Was added by #478 and #483 -| DOS4GW | Supported | @mittorn | -| GNU/Linux(mipsel) | Supported | @mittorn | -| Switch | In progress | @fgsfdsfgs | [Github Repository](https://github.com/fgsfdsfgs/xash3d-fwgs/tree/switch_new) -| Wii | In progress | Collaborative effort | [Github Repository](https://github.com/saucesaft/xash3d-wii) -| PSP | In progress | Collaborative effort | No sources available at this moment -| Emscripten | Old Engine | not maintained | -| Oculus Quest | Old Engine fork | @DrBeef | [GitHub Repository](https://github.com/DrBeef/Lambda1VR) -| PSVita | Old Engine fork | not maintained | [GitHub Repository](https://github.com/fgsfdsfgs/vitaXash3D) -| Switch | Old Engine fork | not maintained | [GitHub Repository](https://github.com/switchports/xash3d-switch) -| 3DS | Old Engine fork | not maintained | [GitHub Repository](https://github.com/masterfeizz/Xash3DS) -| macOS | Deprecated | not maintained | See GitHub issue #61 -| iOS | Deprecated | not maintained | See GitHub issue #61 +| Platform | Status | Maintainer | Note +| -------- | ------ | ---------- | ---- +| Windows | Supported | @a1batross, @SNMetamorph | +| *BSD | Supported | @nekonomicon | +| GNU/Linux(x86, amd64, arm) | Supported | @a1batross, @mittorn | +| Android | Supported | @a1batross, @mittorn | +| MotoMAGX | Supported | @a1batross | +| GNU/Linux(elbrus) | Supported | @a1batross, @mittorn | Rare and eventual access to e2k machine +| Haiku | Supported | not maintained | Was added by #478 and #483 +| DOS4GW | Supported | @mittorn | +| GNU/Linux(mipsel) | Supported | @mittorn | +| Switch | In progress | @fgsfdsfgs | [GitHub Repository](https://github.com/fgsfdsfgs/xash3d-fwgs/tree/switch_new) +| Wii | In progress | Collaborative effort | [GitHub Repository](https://github.com/saucesaft/xash3d-wii) +| PSP | In progress | @Crow_bar, @Velaron | [GitHub Repository](https://github.com/Crow-bar/xash3d-fwgs) +| Emscripten | Old Engine | not maintained | +| Oculus Quest | Old Engine fork | @DrBeef | [GitHub Repository](https://github.com/DrBeef/Lambda1VR) +| PSVita | Old Engine fork | not maintained | [GitHub Repository](https://github.com/fgsfdsfgs/vitaXash3D) +| Switch | Old Engine fork | not maintained | [GitHub Repository](https://github.com/switchports/xash3d-switch) +| 3DS | Old Engine fork | not maintained | [GitHub Repository](https://github.com/masterfeizz/Xash3DS) +| macOS | Deprecated | not maintained | See GitHub issue #61 +| iOS | Deprecated | not maintained | See GitHub issue #61 diff --git a/Documentation/supported-mod-list.md b/Documentation/supported-mod-list.md index a155f7ba..54f35a3f 100644 --- a/Documentation/supported-mod-list.md +++ b/Documentation/supported-mod-list.md @@ -46,7 +46,7 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 16. [Betrayal](http://www.moddb.com/mods/betrayal) (this mod has inner bugs; set **gl_allow_mirrors** to **0** to prevent drops of game's perfomance in some areas) 17. [Between Two Worlds](https://www.runthinkshootlive.com/posts/between-two-worlds/) 18. [Black Mesa Energy Testing Chamber](http://twhl.info/competitions.php?results=17) -19. [Black Mesa Sideline](http://www.isolated-design.de/half-life-mods/black-mesa-sideline/) (set **gl_allow_mirrors** to **0** to prevent drops of game's perfomance in some areas) +19. [Black Mesa Sideline](https://www.moddb.com/mods/black-mesa-sideline) (set **gl_allow_mirrors** to **0** to prevent drops of game's perfomance in some areas) 20. [BlackMesa 2007( Black Mesa Missions 2007 )](http://www.moddb.com/addons/blackmesa-2007) 21. [Blood and Bones](https://www.runthinkshootlive.com/posts/blood-and-bones/) 22. Boom (Huknenn) v1.0 & [HL Boom: Gold Edition v1.1](http://www.moddb.com/mods/boom) @@ -103,8 +103,8 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 72. [Freeman](https://www.runthinkshootlive.com/posts/freeman/) 73. [Freeman's Escape](http://www.moddb.com/mods/freeman-escape-french) (2 maps by *JiggZ*) 74. [Freeman's Fight](https://www.runthinkshootlive.com/posts/freemans-fight/) -75. [Freeman's Return](http://jpmaps.wz.cz/epizody.htm) -76. [Freeman's Return 2](http://jpmaps.wz.cz/epizody.htm) +75. [Freeman's Return](https://www.moddb.com/games/half-life/addons/freemans-return-v12) +76. [Freeman's Return 2](https://www.moddb.com/games/half-life/addons/freemans-return-2-v11) 77. [Freeman's Revenge](https://www.runthinkshootlive.com/posts/freemans-revenge/) 78. [Freeman's Tomb( The Crypt )](http://twhl.info/vault.php?map=3787) 79. [G-Invasion v2.5](http://www.moddb.com/mods/g-man-invasion) aka G-Man Invasion (there is an inner crash bug on final titles: some text lines are too long and cannot be properly displayed) @@ -129,7 +129,7 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 96. [Haunted](https://www.runthinkshootlive.com/posts/haunted/) (set **sv_validate_changelevel** to **0** to avoid a problem with level change between maps **pagan7** and **pagan8**) 97. [Hazardous Materials: Episode 1 & 2](http://www.moddb.com/mods/half-life-hazardous-materials) 98. [Hazardous-Course 2](http://www.moddb.com/mods/hazardous-course-2) -99. [Help Wanted](http://maps.mrsucko.org/work/) +99. [Help Wanted](https://www.moddb.com/games/half-life/addons/help-wanted) 100. [Hidden Evil v1.01](https://www.runthinkshootlive.com/posts/hidden-evil/) 101. [High Speed](http://www.moddb.com/games/half-life/addons/high-speed-english-version) 102. [hlife_hotdog_compo26](http://twhl.info/vault.php?map=5181) @@ -155,7 +155,7 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 122. [Mario Keys](http://www.moddb.com/mods/mario-keys) 123. [McBeth](https://www.runthinkshootlive.com/posts/mcbeth/) 124. [Medieval World](http://www.moddb.com/mods/medieval-world) -125. [Mel Soaring 2: Star Rancor](http://www.etherealhell.com/etherealhell/index.php/my-levels/half-life) +125. [Mel Soaring 2: Star Rancor](https://www.moddb.com/addons/mel-soaring-2-star-rancor) 126. [MINIMICUS](http://twhl.info/vault.php?map=163) 127. [Mission Failed](http://www.moddb.com/mods/mission-failed) 128. [Mission MC Poker](http://www.moddb.com/mods/half-life-mission-mc-poker) @@ -176,7 +176,7 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 143. [Operation: Nova](http://www.moddb.com/mods/half-life-operation-nova) (there is an inner bug with an M60 machinegun on a map with Osprey: it can be activated and used if you are staying in front of it, not behind it) 144. [Optimum Fear](https://www.fileplanet.com/7472/0/fileinfo/Optimum-Fear) 145. [Outrun](http://www.moddb.com/mods/outrun) -146. [Outwards (Day One)](http://www.visgi.info/maps/) +146. [Outwards (Day One)](https://drive.google.com/file/d/1mjQ8wUazYbwTq9Ocg0Vs0raq4avbTRne/view?usp=sharing) 147. [Overhaul Pack](http://www.moddb.com/mods/half-life-overhaul-pack) (Just HD pack for Half-Life) 148. [P.I.Z.D.E.C.](https://www.runthinkshootlive.com/posts/pizdec/) 149. [pagoda](http://www.moddb.com/mods/pagoda1) @@ -184,7 +184,7 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 151. [Phobos IV](http://www.moddb.com/mods/phobos-iv) 152. [Pimp My Car](https://www.runthinkshootlive.com/posts/pimp-my-car/) (there is an inner issue with incorrect responses of buttons on combination locks) 153. [Point Blank – Stackdeath Complex](https://www.runthinkshootlive.com/posts/half-life-point-blank-stackdeath-complex/) -154. [Prisoner Escaped](http://four0four.org/downloads/maps/) +154. [Prisoner Escaped](https://www.moddb.com/mods/prisoner-escaped-1-2) 155. [Prisoner of Event](http://wp.vondur.net/?page_id=40) 156. [Prisoner of War](https://www.runthinkshootlive.com/posts/prisoner-of-war/) 157. [Project Quantum Leap](http://www.moddb.com/mods/project-quantum-leap) @@ -282,7 +282,7 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 6. [Catacombs: Part 1](https://www.runthinkshootlive.com/posts/catacombs/) 7. [Cro-man's Office Mappack](http://twhl.info/vault.php?map=5547) 8. [Half-Life Episode Two Demo Alpha v1.0](http://hl.loess.ru/?mod=451) -9. [Hazardous-Course (first version of Hazardous-Course 2)](http://www.richmans-maps.ch.vu/) (link dead!) +9. [Hazardous-Course (first version of Hazardous-Course 2)](https://drive.google.com/file/d/16djJZSpNWKyScSxSyMqX-u_S-2i400rl/view?usp=sharing) 10. [High Tech v0.2](http://hl.loess.ru/?mod=499) 11. [HL Shadows, Part 1](https://www.fileplanet.com/7466/0/fileinfo/'HL-Shadows',-Part-1) 12. [HLNewEnd](http://twhl.info/vault.php?map=2275) @@ -305,11 +305,11 @@ For mappacks - place *.bsp files to **valve/maps** folder, *.wad files to **valv 29. [Shortcut v1.0 Beta](https://www.runthinkshootlive.com/posts/shortcut/) 30. [Stoka](https://www.ceskemody.cz/mapy.php?lng=1&clanek=222&razeni_hra=1&pn=stoka) 31. [Striker's Compo 26](http://twhl.info/vault.php?map=5190) (buggy) -32. [Technology Test 2](http://www.richmans-maps.ch.vu/) (link dead!) +32. [Technology Test 2](https://drive.google.com/file/d/1LfldzsMIN5j07bgtNkfY5v0Xl0S9de_G/view?usp=sharing) 33. [The Gate Playable Demo](https://www.fileplanet.com/116347/110000/fileinfo/The-Gate-Playable-Demo) 34. [They are Back](https://www.runthinkshootlive.com/posts/they-are-back/) 35. [Tiefseelabor( Deep Sea Laboratory )](https://www.runthinkshootlive.com/posts/deep-sea-laboratory/) -36. [Time-Shift](http://www.richmans-maps.ch.vu/) (link dead!) +36. [Time-Shift](https://drive.google.com/file/d/1pK1s-2SIlSMQHVRPMLdC_94wTa__8_DX/view?usp=sharing) 37. [Train Single Beta](https://cs-mapping.com.ua/forum/showthread.php?t=36394) (Remove **gfx.wad** from **TrainSingle** folder) 38. [WAR: The Killer Beta 0.1](http://www.moddb.com/mods/war) 39. [White Force Beta( Residual Point prototype )](http://www.moddb.com/mods/hl-residual-point/downloads/white-force-beta-2002) @@ -379,7 +379,7 @@ Mods: 5. [Half-Life Baby v1.4](http://www.moddb.com/games/half-life/addons/half-life-baby) (it's an unfinished but playable mod; after installing of the mod open **liblist.gam** or **gameinfo.txt** file in the mod's folder and correct the line **gamedll "..\hlbaby\dlls\hl.dll"** for **gamedll "dlls\hl.dll"**, otherwise you'll not be able to start a game) 6. [Half-Secret](http://www.moddb.com/mods/half-secret) (this mod has custom **weapon_snark** code) 7. [Induction](http://www.moddb.com/mods/half-life-induction) (this mod has new item - **item_flashlight**) -8. [Lost in Black Mesa(first version without HLFX)](http://half-life.ru/forum/showthread.php?threadid=13959) +8. [Lost in Black Mesa(first version without HLFX)](https://drive.google.com/file/d/1bEnm_AxJs-ly8hTEZIQBw_v2BsXdSiGa/view?usp=sharing) 9. [Soldier](http://www.moddb.com/mods/half-life-soldier) 10. [Solo Operations](http://www.moddb.com/mods/solo-operations) 11. [The Blood v1.1](http://hl.loess.ru/?mods=&search=The+Blood) (there are some inner bugs in the mod, but they don't interfere with a game progress) @@ -440,7 +440,7 @@ Mods: 51. [Data-base](https://www.runthinkshootlive.com/posts/database/) 52. [de_dust2_azabetfeN](https://cs-mapping.com.ua/forum/showthread.php?t=36394) (remove **cl_dlls** & **dlls** folders from inside of mod's directory before you start the game) 53. [De-railed](http://twhl.info/competitions.php?results=7) -54. [Dead Shift Beta](http://www.moddb.com/mods/dead-shift) - [Demo 1](https://www.gamewatcher.com/mods/half-life-mod/dead-shift-1-0-beta) & [Demo 2](https://www.gamefront.com/files/13532512) (3rd link dead!) +54. [Dead Shift Beta](http://www.moddb.com/mods/dead-shift) - [Demo 1](https://www.gamewatcher.com/mods/half-life-mod/dead-shift-1-0-beta) & [Demo 2](https://drive.google.com/file/d/1uHvr8xONogTTNOy-8X6G4ehFV6Eawvbq/view?usp=sharing) 55. [Deep](http://twhl.info/vault.php?map=1669) 56. [Desert Attack](http://fyzzer.narod.ru/index.html) 57. [Desert Combat Demo](https://www.fileplanet.com/190487/190000/fileinfo/Half-Life---Desert-Combat-Mod) (despite a big file size there's only one small unfinished map) @@ -469,7 +469,7 @@ Mods: 80. [Extinct Lifeform Hunt](https://www.fileplanet.com/13501/10000/fileinfo/Extinct-Lifeform-Hunt) 81. [Facility](http://twhl.info/vault.php?map=5482) 82. [Facility Escape](http://twhl.info/vault.php?map=3673) -83. [Fallout](http://www.thewall.de/forum/thread/hl1-sp-48h-mapping-contest/64975.4.html) (map by *simb*) +83. [Fallout](http://www.thewall.de/forum/thread/hl1-sp-48h-mapping-contest/64975.4.html) (map by *simb*) (link dead!) 84. [Final Assault](http://twhl.info/vault.php?map=4500) 85. [Flat](http://hl.loess.ru/?mods=&search=Flat) 86. [Freeman's Allegiance](https://www.runthinkshootlive.com/posts/freemans-allegiance/) @@ -498,8 +498,8 @@ Mods: 109. [Hospital](https://gamebanana.com/maps/167446) (you need to edit **liblist.gam** file in the mod's folder - delete *gamedll & type* strings from it before you start to play) 110. [Hostage](https://www.runthinkshootlive.com/posts/hostage-2/) 111. [House](http://www.artpeter.net/Data/HalfLife/Hl.php) -112. [Impulse 101 Fun - The Train](http://web.archive.org/web/20070305075816/http://www.twhl.co.za/mapvault/2817.zip) (map from *TWHL* by *Archie* aka *The Hunter*) -113. [In America](http://www.lambda-force.org/load/half_life/karty/half_life_in_america/18-1-0-476) +112. [Impulse 101 Fun - The Train](https://drive.google.com/file/d/1jAuMCCmREH0mfAEAscqaG8FQGa2uNknb/view?usp=sharing) (map from *TWHL* by *Archie* aka *The Hunter*) +113. [In America](https://drive.google.com/file/d/1gOC8zfnUvBxRy8Rr8juNiUNbLYjoZpnn/view?usp=sharing) 114. [In the Kitchen](http://twhl.info/vault.php?map=4314) 115. [Infiltration](https://www.fileplanet.com/7467/0/fileinfo/Infiltration) 116. [Interactivity & Lots of Entities](http://twhl.info/vault.php?map=5744) (link dead!) @@ -668,7 +668,7 @@ Mods: 279. [X-treme Violence](http://www.moddb.com/mods/x-treme-violence) 280. [XargoL's Entry for Vassy's Compo](http://twhl.info/vault.php?map=769) 281. [Xen Again](https://www.fileplanet.com/9132/0/fileinfo/XEN-AGAIN) -282. [Xen World](http://www.lambda-force.org/load/half_life/karty/half_life_xen_world/18-1-0-481) +282. [Xen World](http://www.lambda-force.org/load/half_life/karty/half_life_xen_world/18-1-0-481) (link dead!) 283. [XUnil](https://www.fileplanet.com/7883/0/fileinfo/XUNIL) 284. [Zeeba-G's TWHL Compo 26 Entry](http://twhl.info/competitions.php?results=26) 285. [Zombies!](https://gamebanana.com/maps/160812) @@ -694,7 +694,7 @@ For Linux and OS X you must download crossbuild from [here](http://www.moddb.com 2. [Big Scientists](http://www.moddb.com/mods/big-scientists) 3. [Black Silla Assault DEMO v1.2](http://www.moddb.com/mods/black-silla-assault) 4. [Blbej Den](http://www.moddb.com/mods/blbej-den) -5. [Cold Experiment v1](http://jpmaps.wz.cz/epizody.htm) (you will need some files from SoHL to play it properly; download SoHL 1.2, extract files and copy following folders into **coldexv1** folder: cl_dlls, dlls, models, sprites) +5. [Cold Experiment v1](https://www.moddb.com/games/half-life/addons/cold-experiment) (you will need some files from SoHL to play it properly; download SoHL 1.2, extract files and copy following folders into **coldexv1** folder: cl_dlls, dlls, models, sprites) 6. [Dead Sector v1.0a](http://www.moddb.com/mods/dead-sector) 7. [Death Is Dead](http://www.moddb.com/games/half-life/addons/death-is-dead) (there is a fog-related inner bug at the first map) 8. [Escape from Black Mesa Alpha](http://www.moddb.com/mods/escape-from-black-mesa) @@ -710,9 +710,9 @@ For Linux and OS X you must download crossbuild from [here](http://www.moddb.com 18. [Santa's Revenge](http://twhl.info/vault.php?map=4332) (set **fps_max** to **60** to avoid an inner problem of the last map with final scripted sequence, otherwise the mod can not be finished properly) 19. [Sector 6](https://www.moddb.com/mods/sector-6/) 20. [Space Prisoner v1.1](http://www.moddb.com/games/half-life/addons/space-prisoner-v11) (after installing of the mod open **liblist.gam** or **gameinfo.txt** file in the mod's folder and correct the line **gamedll "..\prison\dlls\spirit.dll"** for **gamedll "dlls\spirit.dll"**, otherwise you'll not be able to start a game; there is also a scripting error on a third map of the mod, so you'll be forced to use **noclip** to pass around bugged place) -21. [Terrorist Attack 2](http://terroristattack.wz.cz/mody.html) (link dead!) +21. [Terrorist Attack 2](https://www.moddb.com/games/half-life/addons/terrorist-attack-2) 22. [Timeline III: The Heart of Darkness](http://www.moddb.com/mods/timeline-series) -23. [Underground 2 Demo](http://www.isolated-design.de/half-life-mods/underground-2/) +23. [Underground 2 Demo](https://www.moddb.com/games/half-life/addons/underground-2-demo) ## Mods which based on modified Spirit of Half-Life 1.2 or older(Partially playable with SoHL 1.2 libraries or not playable at all) 1. [Black Death](http://www.moddb.com/mods/half-life-black-death) @@ -825,10 +825,10 @@ Mods: 3. [Bomb Squad](http://www.snarkpit.net/index.php?s=maps&map=3471) 4. [Corruption](http://www.snarkpit.net/index.php?s=maps&map=3154) 5. [Critical Mass](http://www.moddb.com/mods/half-life-critical-mass) [C3M1 Build 1 Demo](https://www.fileplanet.com/125746/120000/fileinfo/C3M1-Build-1) -6. [EPRST!](http://www.planetphillip.com/posts/ep-rst-opposing-force/) (link dead!) +6. [EPRST!](https://www.runthinkshootlive.com/posts/ep-rst/) 7. [Firing Range](https://www.runthinkshootlive.com/posts/firing-range/) 8. [Friendly Fire](https://www.runthinkshootlive.com/posts/friendly-fire/) -9. [Guitar Star Pre-Alpha](http://hl-lab.ru/eng/mods_goldsrc/Guitar_Star_(Prealpha)) (link dead!) +9. [Guitar Star Pre-Alpha](http://gamer-lab.com/rus/mods_goldsrc/Guitar_Star_(Prealpha)) 10. [J2000](https://www.runthinkshootlive.com/posts/j2000/) 11. [Killing House for OF](https://www.runthinkshootlive.com/posts/scientist-rescue-aka-cqb/) 12. [Klabautermann](https://www.runthinkshootlive.com/posts/klabautermann/) (though the map has some inner issues, it can be properly finished, just don't let the welder soldier die and don't press the fifth button until you mount a special gun in its' place) diff --git a/README.md b/README.md index f5d5c5fe..76f50813 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Regular upstream Xash3D README.md follows. --- # Xash3D FWGS Engine -[![GitHub Actions Status](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml) [![FreeBSD Build Status](https://img.shields.io/cirrus/github/FWGS/xash3d-fwgs?label=freebsd%20build)](https://cirrus-ci.com/github/FWGS/xash3d-fwgs) [![Discord Server](https://img.shields.io/discord/355697768582610945.svg)](http://discord.fwgs.ru/) \ +[![GitHub Actions Status](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml) [![FreeBSD Build Status](https://img.shields.io/cirrus/github/FWGS/xash3d-fwgs?label=freebsd%20build)](https://cirrus-ci.com/github/FWGS/xash3d-fwgs) [![Discord Server](https://img.shields.io/discord/355697768582610945.svg)](http://fwgsdiscord.mentality.rip/) \ [![Download Stable](https://img.shields.io/badge/download-stable-yellow)](https://github.com/FWGS/xash3d-fwgs/releases/latest) [![Download Testing](https://img.shields.io/badge/downloads-testing-orange)](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous) Xash3D FWGS is a fork of Xash3D Engine by Unkle Mike with extended features and crossplatform. @@ -67,16 +67,13 @@ Read more about Xash3D on ModDB: https://www.moddb.com/engines/xash3d-engine * Vulkan renderer ## Installation & Running -0) Get Xash3D binaries: you can use [testing](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous) build or you can compile engine from source code. -Choose proper build package depending on which platform you're using. +0) Get Xash3D FWGS binaries: you can use [testing](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous) build or you can compile engine from source code. 1) Copy engine binaries to some directory. 2) Copy `valve` directory from [Half-Life](https://store.steampowered.com/app/70/HalfLife/) to directory with engine binaries. -Also if you're using Windows: you should copy `vgui.dll` library from Half-Life directory to Xash3D directory. -As alternative, you can compile [hlsdk-xash3d](https://github.com/FWGS/hlsdk-xash3d) instead of using official Valve game binaries, but you still needed to copy `valve` directory as all game resources located in there. -3) Download [extras.pak](https://github.com/FWGS/xash-extras/releases/tag/v0.19.2) and place it to `valve` directory. -4) Run `xash3d.exe`/`xash3d.sh`/`xash3d` depending on which platform you're using. - -Note: on Linux, you may need to create an sh file with the command `LD_LIBRARY_PATH=. ./xash3d`. +If your CPU is NOT x86 compatible or you're running 64-bit version of the engine, you may want to compile [Half-Life SDK](https://github.com/FWGS/hlsdk-xash3d). +This repository contains our fork of HLSDK and restored source code for some of the mods. Not all of them, of course. +You still needed to copy `valve` directory as all game resources located there. +3) Run the main executable (`xash3d.exe` or AppImage). For additional info, run Xash3D with `-help` command line key. @@ -91,6 +88,13 @@ We are using Waf build system. If you have some Waf-related questions, I recomme NOTE: NEVER USE GitHub's ZIP ARCHIVES. GitHub doesn't include external dependencies we're using! ### Prerequisites + +If your CPU is x86 compatible, we are building 32-bit code by default. This was dont for keeping compatibility with Steam releases of Half-Life and based on it's engine games. +Even if Xash3D FWGS does support targetting 64-bit, you can't load games without recompiling them from source code! + +If your CPU is NOT x86 compatible or you decided build 64-bit version of engine, you may want to compile [Half-Life SDK](https://github.com/FWGS/hlsdk-xash3d). +This repository contains our fork of HLSDK and restored source code for some of the mods. Not all of them, of course. + #### Windows (Visual Studio) * Install Visual Studio. * Install latest [Python](https://python.org) **OR** run `cinst python.install` if you have Chocolatey. @@ -100,10 +104,6 @@ NOTE: NEVER USE GitHub's ZIP ARCHIVES. GitHub doesn't include external dependenc * Make sure you have at least 12GB of free space to store all build-time dependencies: ~10GB for Visual Studio, 300 MB for Git, 100 MB for Python and other. #### GNU/Linux -NOTE FOR USERS WITH X86 COMPATIBLE CPUs: -We have forced Waf to throw an error, if you're trying to build 64-bit engine. This was done for keeping compatibility with Steam releases of Half-Life and based on it's engine games. -Even if Xash3D FWGS does support targetting 64-bit, you can't load games without recompiling them from source code! - ##### Debian/Ubuntu * Enable i386 on your system, if you're compiling 32-bit engine on amd64. If not, skip this @@ -126,11 +126,10 @@ Even if Xash3D FWGS does support targetting 64-bit, you can't load games without 5) Install: `waf install --destdir=c:/path/to/any/output/directory` #### Linux +If compiling 32-bit on amd64, you may need to supply `export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig` prior to running configure. + 0) Examine which build options are available: `./waf --help` 1) Configure build: `./waf configure -T release` (You need to pass `-8` to compile 64-bit engine on 64-bit x86 processor) 2) Compile: `./waf build` 3) Install(optional): `./waf install --destdir=/path/to/any/output/directory` - -Note: if compiling 32-bit on amd64, you may need to supply `export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig` prior to running configure. - diff --git a/common/com_image.h b/common/com_image.h index 063309ab..c270cf26 100644 --- a/common/com_image.h +++ b/common/com_image.h @@ -9,7 +9,7 @@ NOTE: number at end of pixelformat name it's a total bitscount e.g. PF_RGB_24 == ======================================================================== */ #define ImageRAW( type ) (type == PF_RGBA_32 || type == PF_BGRA_32 || type == PF_RGB_24 || type == PF_BGR_24 || type == PF_LUMINANCE) -#define ImageDXT( type ) (type == PF_DXT1 || type == PF_DXT3 || type == PF_DXT5 || type == PF_ATI2) +#define ImageDXT( type ) (type == PF_DXT1 || type == PF_DXT3 || type == PF_DXT5 || type == PF_ATI2 || type == PF_BC6H_SIGNED || type == PF_BC6H_UNSIGNED || type == PF_BC7) typedef enum { @@ -21,10 +21,13 @@ typedef enum PF_RGB_24, // uncompressed dds or another 24-bit image PF_BGR_24, // big-endian RGB (MacOS) PF_LUMINANCE, - PF_DXT1, // s3tc DXT1 format - PF_DXT3, // s3tc DXT3 format - PF_DXT5, // s3tc DXT5 format - PF_ATI2, // latc ATI2N format + PF_DXT1, // s3tc DXT1/BC1 format + PF_DXT3, // s3tc DXT3/BC2 format + PF_DXT5, // s3tc DXT5/BC3 format + PF_ATI2, // latc ATI2N/BC5 format + PF_BC6H_SIGNED, // bptc BC6H signed FP16 format + PF_BC6H_UNSIGNED, // bptc BC6H unsigned FP16 format + PF_BC7, // bptc BC7 format PF_TOTALCOUNT, // must be last } pixformat_t; diff --git a/common/com_model.h b/common/com_model.h index 9f8ceda8..36f2e936 100644 --- a/common/com_model.h +++ b/common/com_model.h @@ -530,7 +530,7 @@ typedef struct #define MAX_DEMOS 32 #define MAX_MOVIES 8 #define MAX_CDTRACKS 32 -#define MAX_CLIENT_SPRITES 256 // SpriteTextures +#define MAX_CLIENT_SPRITES 512 // SpriteTextures (0-256 hud, 256-512 client) #define MAX_EFRAGS 8192 // Arcane Dimensions required #define MAX_REQUESTS 64 diff --git a/common/const.h b/common/const.h index 2533ec09..a7e3d08b 100644 --- a/common/const.h +++ b/common/const.h @@ -725,7 +725,6 @@ typedef int string_t; typedef unsigned short word; #include "xash3d_types.h" -#define Q_isspace( ch ) (ch < 32 || ch > 255) typedef struct { diff --git a/common/gameinfo.h b/common/gameinfo.h index 597ec1fb..467185cc 100644 --- a/common/gameinfo.h +++ b/common/gameinfo.h @@ -18,6 +18,7 @@ GNU General Public License for more details. #define GFL_NOMODELS (1<<0) #define GFL_NOSKILLS (1<<1) +#define GFL_RENDER_PICBUTTON_TEXT (1<<2) /* ======================================================================== diff --git a/common/port.h b/common/port.h index 183f1fe7..ae60122f 100644 --- a/common/port.h +++ b/common/port.h @@ -20,8 +20,6 @@ GNU General Public License for more details. #include "build.h" #if !XASH_WIN32 - - #if XASH_APPLE #include #define OS_LIB_EXT "dylib" @@ -30,67 +28,32 @@ GNU General Public License for more details. #define OS_LIB_EXT "so" #define OPEN_COMMAND "xdg-open" #endif - #define OS_LIB_PREFIX "lib" - - #if XASH_ANDROID - //#if defined(LOAD_HARDFP) - // #define POSTFIX "_hardfp" - //#else - #define POSTFIX - //#endif - #else - #endif - #define VGUI_SUPPORT_DLL "libvgui_support." OS_LIB_EXT // Windows-specific #define __cdecl #define __stdcall - #define _inline static inline #define FORCEINLINE inline __attribute__((always_inline)) -#if XASH_POSIX - #define PATH_SPLITTER "/" - #include - #include - #define O_BINARY 0 // O_BINARY is Windows extension - #define O_TEXT 0 // O_TEXT is Windows extension - // Windows functions to posix equivalent - #define _mkdir( x ) mkdir( x, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ) - #define LoadLibrary( x ) dlopen( x, RTLD_NOW ) - #define GetProcAddress( x, y ) dlsym( x, y ) - #define FreeLibrary( x ) dlclose( x ) - #define tell( a ) lseek(a, 0, SEEK_CUR) - #define HAVE_DUP -#endif + #if XASH_POSIX + #include + #include -#if XASH_DOS4GW - #define PATH_SPLITTER "\\" - #define LoadLibrary( x ) (0) - #define GetProcAddress( x, y ) (0) - #define FreeLibrary( x ) (0) -#endif - //#define MAKEWORD( a, b ) ((short int)(((unsigned char)(a))|(((short int)((unsigned char)(b)))<<8))) - #define max( a, b ) (((a) > (b)) ? (a) : (b)) - #define min( a, b ) (((a) < (b)) ? (a) : (b)) + #define PATH_SPLITTER "/" + #define HAVE_DUP - /// TODO: check if we may clean this defines, it should not appear in non-platform code! - typedef unsigned char BYTE; - typedef short int WORD; - typedef unsigned int DWORD; - typedef int LONG; - typedef unsigned int ULONG; - typedef int WPARAM; - typedef unsigned int LPARAM; + #define O_BINARY 0 + #define O_TEXT 0 + #define _mkdir( x ) mkdir( x, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ) + #elif XASH_DOS4GW + #define PATH_SPLITTER "\\" + #endif typedef void* HANDLE; - typedef void* HMODULE; typedef void* HINSTANCE; - typedef char* LPSTR; - typedef struct tagPOINT { int x, y; @@ -108,19 +71,6 @@ GNU General Public License for more details. #define read _read #define alloca _alloca - // shut-up compiler warnings - #pragma warning(disable : 4244) // MIPS - #pragma warning(disable : 4018) // signed/unsigned mismatch - #pragma warning(disable : 4305) // truncation from const double to float - #pragma warning(disable : 4115) // named type definition in parentheses - #pragma warning(disable : 4100) // unreferenced formal parameter - #pragma warning(disable : 4127) // conditional expression is constant - #pragma warning(disable : 4057) // differs in indirection to slightly different base types - #pragma warning(disable : 4201) // nonstandard extension used - #pragma warning(disable : 4706) // assignment within conditional expression - #pragma warning(disable : 4054) // type cast' : from function pointer - #pragma warning(disable : 4310) // cast truncates constant value - #define HSPRITE WINAPI_HSPRITE #define WIN32_LEAN_AND_MEAN #include @@ -130,13 +80,9 @@ GNU General Public License for more details. #define OS_LIB_PREFIX "" #define OS_LIB_EXT "dll" #define VGUI_SUPPORT_DLL "../vgui_support." OS_LIB_EXT - #ifdef XASH_64BIT - // windows NameForFunction not implemented yet - #define XASH_ALLOW_SAVERESTORE_OFFSETS - #endif #define HAVE_DUP - #endif //WIN32 + #ifndef XASH_LOW_MEMORY #define XASH_LOW_MEMORY 0 #endif diff --git a/common/xash3d_types.h b/common/xash3d_types.h index e1d1a0ae..33f82bb1 100644 --- a/common/xash3d_types.h +++ b/common/xash3d_types.h @@ -2,7 +2,9 @@ #ifndef XASH_TYPES_H #define XASH_TYPES_H -#ifdef _WIN32 +#include "build.h" + +#if XASH_WIN32 #include // off_t #endif // _WIN32 @@ -64,19 +66,41 @@ typedef uint64_t longtime_t; #define ColorIndex( c ) ((( c ) - '0' ) & 7 ) #if defined(__GNUC__) -#ifdef __i386__ -#define EXPORT __attribute__ ((visibility ("default"),force_align_arg_pointer)) -#define GAME_EXPORT __attribute((force_align_arg_pointer)) -#else -#define EXPORT __attribute__ ((visibility ("default"))) -#define GAME_EXPORT -#endif + #ifdef __i386__ + #define EXPORT __attribute__ ((visibility ("default"),force_align_arg_pointer)) + #define GAME_EXPORT __attribute((force_align_arg_pointer)) + #else + #define EXPORT __attribute__ ((visibility ("default"))) + #define GAME_EXPORT + #endif + #define _format(x) __attribute__((format(printf, x, x+1))) + #define NORETURN __attribute__((noreturn)) #elif defined(_MSC_VER) -#define EXPORT __declspec( dllexport ) -#define GAME_EXPORT + #define EXPORT __declspec( dllexport ) + #define GAME_EXPORT + #define _format(x) + #define NORETURN #else -#define EXPORT -#define GAME_EXPORT + #define EXPORT + #define GAME_EXPORT + #define _format(x) + #define NORETURN +#endif + +#if ( __GNUC__ >= 3 ) + #define unlikely(x) __builtin_expect(x, 0) + #define likely(x) __builtin_expect(x, 1) +#elif defined( __has_builtin ) + #if __has_builtin( __builtin_expect ) + #define unlikely(x) __builtin_expect(x, 0) + #define likely(x) __builtin_expect(x, 1) + #else + #define unlikely(x) (x) + #define likely(x) (x) + #endif +#else + #define unlikely(x) (x) + #define likely(x) (x) #endif @@ -114,9 +138,13 @@ typedef unsigned int dword; typedef unsigned int uint; typedef char string[MAX_STRING]; typedef struct file_s file_t; // normal file -typedef struct wfile_s wfile_t; // wad file typedef struct stream_s stream_t; // sound stream for background music playing typedef off_t fs_offset_t; +#if XASH_WIN32 +typedef int fs_size_t; // return type of _read, _write funcs +#else /* !XASH_WIN32 */ +typedef ssize_t fs_size_t; +#endif /* !XASH_WIN32 */ typedef struct dllfunc_s { @@ -132,7 +160,7 @@ typedef struct dll_info_s void *link; // hinstance of loading library } dll_info_t; -typedef void (*setpair_t)( const char *key, const void *value, void *buffer, void *numpairs ); +typedef void (*setpair_t)( const char *key, const void *value, const void *buffer, void *numpairs ); // config strings are a general means of communication from // the server to all connected clients. diff --git a/engine/client/cl_custom.c b/engine/client/cl_custom.c index e1baee8f..b279b361 100644 --- a/engine/client/cl_custom.c +++ b/engine/client/cl_custom.c @@ -106,7 +106,7 @@ void CL_RemoveFromResourceList( resource_t *pResource ) if( pResource->pPrev == NULL || pResource->pNext == NULL ) Host_Error( "mislinked resource in CL_RemoveFromResourceList\n" ); - if ( pResource->pNext == pResource || pResource->pPrev == pResource ) + if( pResource->pNext == pResource || pResource->pPrev == pResource ) Host_Error( "attempt to free last entry in list.\n" ); pResource->pPrev->pNext = pResource->pNext; diff --git a/engine/client/cl_efrag.c b/engine/client/cl_efrag.c index 5df1b6bd..366134a2 100644 --- a/engine/client/cl_efrag.c +++ b/engine/client/cl_efrag.c @@ -188,8 +188,6 @@ void R_StoreEfrags( efrag_t **ppefrag, int framecount ) case mod_brush: case mod_studio: case mod_sprite: - pent = pefrag->entity; - if( pent->visframe != framecount ) { if( CL_AddVisibleEntity( pent, ET_FRAGMENTED )) diff --git a/engine/client/cl_efx.c b/engine/client/cl_efx.c index 9db01f93..d7ad1196 100644 --- a/engine/client/cl_efx.c +++ b/engine/client/cl_efx.c @@ -46,7 +46,7 @@ short GAME_EXPORT R_LookupColor( byte r, byte g, byte b ) float rf, gf, bf; bestdiff = 999999; - best = 65535; + best = -1; for( i = 0; i < 256; i++ ) { @@ -683,42 +683,46 @@ void CL_ParseViewBeam( sizebuf_t *msg, int beamType ) switch( beamType ) { case TE_BEAMPOINTS: - start[0] = MSG_ReadCoord( msg ); - start[1] = MSG_ReadCoord( msg ); - start[2] = MSG_ReadCoord( msg ); - end[0] = MSG_ReadCoord( msg ); - end[1] = MSG_ReadCoord( msg ); - end[2] = MSG_ReadCoord( msg ); - modelIndex = MSG_ReadShort( msg ); - startFrame = MSG_ReadByte( msg ); - frameRate = (float)MSG_ReadByte( msg ); - life = (float)(MSG_ReadByte( msg ) * 0.1f); - width = (float)(MSG_ReadByte( msg ) * 0.1f); - noise = (float)(MSG_ReadByte( msg ) * 0.01f); - r = (float)MSG_ReadByte( msg ) / 255.0f; - g = (float)MSG_ReadByte( msg ) / 255.0f; - b = (float)MSG_ReadByte( msg ) / 255.0f; - a = (float)MSG_ReadByte( msg ) / 255.0f; - speed = (float)(MSG_ReadByte( msg ) * 0.1f); - R_BeamPoints( start, end, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); - break; case TE_BEAMENTPOINT: - startEnt = MSG_ReadShort( msg ); - end[0] = MSG_ReadCoord( msg ); - end[1] = MSG_ReadCoord( msg ); - end[2] = MSG_ReadCoord( msg ); + case TE_BEAMENTS: + if( beamType == TE_BEAMENTS ) + { + startEnt = MSG_ReadShort( msg ); + endEnt = MSG_ReadShort( msg ); + } + else + { + if( beamType == TE_BEAMENTPOINT ) + { + startEnt = MSG_ReadShort( msg ); + } + else + { + start[0] = MSG_ReadCoord( msg ); + start[1] = MSG_ReadCoord( msg ); + start[2] = MSG_ReadCoord( msg ); + } + end[0] = MSG_ReadCoord( msg ); + end[1] = MSG_ReadCoord( msg ); + end[2] = MSG_ReadCoord( msg ); + } modelIndex = MSG_ReadShort( msg ); startFrame = MSG_ReadByte( msg ); - frameRate = (float)MSG_ReadByte( msg ); - life = (float)(MSG_ReadByte( msg ) * 0.1f); - width = (float)(MSG_ReadByte( msg ) * 0.1f); - noise = (float)(MSG_ReadByte( msg ) * 0.01f); + frameRate = (float)MSG_ReadByte( msg ) * 0.1f; + life = (float)MSG_ReadByte( msg ) * 0.1f; + width = (float)MSG_ReadByte( msg ) * 0.1f; + noise = (float)MSG_ReadByte( msg ) * 0.01f; r = (float)MSG_ReadByte( msg ) / 255.0f; g = (float)MSG_ReadByte( msg ) / 255.0f; b = (float)MSG_ReadByte( msg ) / 255.0f; a = (float)MSG_ReadByte( msg ) / 255.0f; - speed = (float)(MSG_ReadByte( msg ) * 0.1f); - R_BeamEntPoint( startEnt, end, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); + speed = (float)MSG_ReadByte( msg ) * 0.1f; + if( beamType == TE_BEAMENTS ) + R_BeamEnts( startEnt, endEnt, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); + else if( beamType == TE_BEAMENTPOINT ) + R_BeamEntPoint( startEnt, end, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); + else + R_BeamPoints( start, end, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); break; case TE_LIGHTNING: start[0] = MSG_ReadCoord( msg ); @@ -727,27 +731,11 @@ void CL_ParseViewBeam( sizebuf_t *msg, int beamType ) end[0] = MSG_ReadCoord( msg ); end[1] = MSG_ReadCoord( msg ); end[2] = MSG_ReadCoord( msg ); - life = (float)(MSG_ReadByte( msg ) * 0.1f); - width = (float)(MSG_ReadByte( msg ) * 0.1f); - noise = (float)(MSG_ReadByte( msg ) * 0.01f); + life = (float)MSG_ReadByte( msg ) * 0.1f; + width = (float)MSG_ReadByte( msg ) * 0.1f; + noise = (float)MSG_ReadByte( msg ) * 0.01f; modelIndex = MSG_ReadShort( msg ); - R_BeamLightning( start, end, modelIndex, life, width, noise, 0.6F, 3.5f ); - break; - case TE_BEAMENTS: - startEnt = MSG_ReadShort( msg ); - endEnt = MSG_ReadShort( msg ); - modelIndex = MSG_ReadShort( msg ); - startFrame = MSG_ReadByte( msg ); - frameRate = (float)(MSG_ReadByte( msg ) * 0.1f); - life = (float)(MSG_ReadByte( msg ) * 0.1f); - width = (float)(MSG_ReadByte( msg ) * 0.1f); - noise = (float)(MSG_ReadByte( msg ) * 0.01f); - r = (float)MSG_ReadByte( msg ) / 255.0f; - g = (float)MSG_ReadByte( msg ) / 255.0f; - b = (float)MSG_ReadByte( msg ) / 255.0f; - a = (float)MSG_ReadByte( msg ) / 255.0f; - speed = (float)(MSG_ReadByte( msg ) * 0.1f); - R_BeamEnts( startEnt, endEnt, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); + R_BeamLightning( start, end, modelIndex, life, width, noise, 0.6f, 3.5f ); break; case TE_BEAM: break; @@ -773,21 +761,21 @@ void CL_ParseViewBeam( sizebuf_t *msg, int beamType ) end[2] = MSG_ReadCoord( msg ); modelIndex = MSG_ReadShort( msg ); startFrame = MSG_ReadByte( msg ); - frameRate = (float)(MSG_ReadByte( msg )); - life = (float)(MSG_ReadByte( msg ) * 0.1f); - width = (float)(MSG_ReadByte( msg )); - noise = (float)(MSG_ReadByte( msg ) * 0.1f); + frameRate = (float)MSG_ReadByte( msg ) * 0.1f; + life = (float)MSG_ReadByte( msg ) * 0.1f; + width = (float)MSG_ReadByte( msg ); + noise = (float)MSG_ReadByte( msg ) * 0.01f; r = (float)MSG_ReadByte( msg ) / 255.0f; g = (float)MSG_ReadByte( msg ) / 255.0f; b = (float)MSG_ReadByte( msg ) / 255.0f; a = (float)MSG_ReadByte( msg ) / 255.0f; - speed = (float)(MSG_ReadByte( msg ) / 0.1f); + speed = (float)MSG_ReadByte( msg ) * 0.1f; R_BeamCirclePoints( beamType, start, end, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); break; case TE_BEAMFOLLOW: startEnt = MSG_ReadShort( msg ); modelIndex = MSG_ReadShort( msg ); - life = (float)(MSG_ReadByte( msg ) * 0.1f); + life = (float)MSG_ReadByte( msg ) * 0.1f; width = (float)MSG_ReadByte( msg ); r = (float)MSG_ReadByte( msg ) / 255.0f; g = (float)MSG_ReadByte( msg ) / 255.0f; @@ -800,15 +788,15 @@ void CL_ParseViewBeam( sizebuf_t *msg, int beamType ) endEnt = MSG_ReadShort( msg ); modelIndex = MSG_ReadShort( msg ); startFrame = MSG_ReadByte( msg ); - frameRate = (float)MSG_ReadByte( msg ); - life = (float)(MSG_ReadByte( msg ) * 0.1f); - width = (float)(MSG_ReadByte( msg ) * 0.1f); - noise = (float)(MSG_ReadByte( msg ) * 0.01f); + frameRate = (float)MSG_ReadByte( msg ) * 0.1f; + life = (float)MSG_ReadByte( msg ) * 0.1f; + width = (float)MSG_ReadByte( msg ) * 0.1f; + noise = (float)MSG_ReadByte( msg ) * 0.01f; r = (float)MSG_ReadByte( msg ) / 255.0f; g = (float)MSG_ReadByte( msg ) / 255.0f; b = (float)MSG_ReadByte( msg ) / 255.0f; a = (float)MSG_ReadByte( msg ) / 255.0f; - speed = (float)(MSG_ReadByte( msg ) * 0.1f); + speed = (float)MSG_ReadByte( msg ) * 0.1f; R_BeamRing( startEnt, endEnt, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); break; case TE_BEAMHOSE: @@ -1265,7 +1253,7 @@ void GAME_EXPORT R_BloodStream( const vec3_t org, const vec3_t dir, int pcolor, particle_t *p; int i, j; float arc; - float accel = speed; + int accel = speed; // must be integer due to bug in GoldSrc for( arc = 0.05f, i = 0; i < 100; i++ ) { diff --git a/engine/client/cl_events.c b/engine/client/cl_events.c index bcf3f76d..0107a65e 100644 --- a/engine/client/cl_events.c +++ b/engine/client/cl_events.c @@ -158,7 +158,7 @@ CL_EventIndex */ word CL_EventIndex( const char *name ) { - int i; + word i; if( !COM_CheckString( name )) return 0; @@ -484,7 +484,7 @@ void GAME_EXPORT CL_PlaybackEvent( int flags, const edict_t *pInvoker, word even return; // first check event for out of bounds - if( eventindex < 1 || eventindex > MAX_EVENTS ) + if( eventindex < 1 || eventindex >= MAX_EVENTS ) { Con_DPrintf( S_ERROR "CL_PlaybackEvent: invalid eventindex %i\n", eventindex ); return; diff --git a/engine/client/cl_frame.c b/engine/client/cl_frame.c index 7582cfdf..dd1777e6 100644 --- a/engine/client/cl_frame.c +++ b/engine/client/cl_frame.c @@ -58,10 +58,13 @@ void CL_UpdatePositions( cl_entity_t *ent ) ent->current_position = (ent->current_position + 1) & HISTORY_MASK; ph = &ent->ph[ent->current_position]; - VectorCopy( ent->curstate.origin, ph->origin ); VectorCopy( ent->curstate.angles, ph->angles ); - ph->animtime = ent->curstate.animtime; // !!! + + if( ent->model && ent->model->type == mod_brush ) + ph->animtime = ent->curstate.animtime; + else + ph->animtime = cl.time; } /* @@ -219,7 +222,7 @@ void CL_UpdateLatchedVars( cl_entity_t *ent ) if( ent->curstate.sequence != ent->prevstate.sequence ) { - memcpy( ent->prevstate.blending, ent->latched.prevseqblending, sizeof( ent->prevstate.blending )); + memcpy( ent->latched.prevseqblending, ent->prevstate.blending, sizeof( ent->latched.prevseqblending )); ent->latched.prevsequence = ent->prevstate.sequence; ent->latched.sequencetime = ent->curstate.animtime; } @@ -310,7 +313,7 @@ void CL_ProcessEntityUpdate( cl_entity_t *ent ) if( FBitSet( ent->curstate.entityType, ENTITY_NORMAL )) COM_NormalizeAngles( ent->curstate.angles ); - parametric = CL_ParametricMove( ent ); + parametric = ent->curstate.starttime != 0.0f && ent->curstate.impacttime != 0.0f; // allow interpolation on bmodels too if( ent->model && ent->model->type == mod_brush ) @@ -349,7 +352,7 @@ find two timestamps qboolean CL_FindInterpolationUpdates( cl_entity_t *ent, float targettime, position_history_t **ph0, position_history_t **ph1 ) { qboolean extrapolate = true; - int i, i0, i1, imod; + uint i, i0, i1, imod; float at; imod = ent->current_position; @@ -406,7 +409,7 @@ void CL_PureOrigin( cl_entity_t *ent, float t, vec3_t outorigin, vec3_t outangle VectorSubtract( ph0->origin, ph1->origin, delta ); - if( t0 != t1 ) + if( !Q_equal( t0, t1 )) frac = ( t - t1 ) / ( t0 - t1 ); else frac = 1.0f; @@ -486,7 +489,9 @@ int CL_InterpolateModel( cl_entity_t *e ) return 0; } - if( t2 == t1 ) + // HACKHACK: workaround buggy position history animtime + // going backward sometimes + if( Q_equal( t2, t1 ) || t2 < t1 ) { VectorCopy( ph0->origin, e->origin ); VectorCopy( ph0->angles, e->angles ); @@ -978,8 +983,13 @@ qboolean CL_AddVisibleEntity( cl_entity_t *ent, int entityType ) } // don't add the player in firstperson mode - if( RP_LOCALCLIENT( ent ) && !CL_IsThirdPerson( ) && ( ent->index == cl.viewentity )) - return false; + if( RP_LOCALCLIENT( ent )) + { + cl.local.apply_effects = true; + + if( !CL_IsThirdPerson( ) && ( ent->index == cl.viewentity )) + return false; + } if( entityType == ET_BEAM ) { @@ -993,12 +1003,8 @@ qboolean CL_AddVisibleEntity( cl_entity_t *ent, int entityType ) // because pTemp->entity.curstate.effects // is already occupied by FTENT_FLICKER - if( entityType != ET_TEMPENTITY ) + if( entityType != ET_TEMPENTITY && !RP_LOCALCLIENT( ent ) ) { - // no reason to do it twice - if( RP_LOCALCLIENT( ent )) - cl.local.apply_effects = false; - // apply client-side effects CL_AddEntityEffects( ent ); @@ -1050,7 +1056,6 @@ void CL_LinkPlayers( frame_t *frame ) // apply muzzleflash to weaponmodel if( ent && FBitSet( ent->curstate.effects, EF_MUZZLEFLASH )) SetBits( clgame.viewent.curstate.effects, EF_MUZZLEFLASH ); - cl.local.apply_effects = true; // check all the clients but add only visible for( i = 0, state = frame->playerstate; i < MAX_CLIENTS; i++, state++ ) diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index c5e260bf..f7e6d1dc 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -1235,6 +1235,10 @@ static model_t *CL_LoadSpriteModel( const char *filename, uint type, uint texFla model_t *mod; int i; + // use high indices for client sprites + // for GoldSrc bug-compatibility + const int start = type != SPR_HUDSPRITE ? MAX_CLIENT_SPRITES / 2 : 0; + if( !COM_CheckString( filename )) { Con_Reportf( S_ERROR "CL_LoadSpriteModel: bad name!\n" ); @@ -1244,8 +1248,7 @@ static model_t *CL_LoadSpriteModel( const char *filename, uint type, uint texFla Q_strncpy( name, filename, sizeof( name )); COM_FixSlashes( name ); - // slot 0 isn't used - for( i = 1, mod = clgame.sprites; i < MAX_CLIENT_SPRITES; i++, mod++ ) + for( i = 0, mod = clgame.sprites + start; i < MAX_CLIENT_SPRITES / 2; i++, mod++ ) { if( !Q_stricmp( mod->name, name )) { @@ -1262,12 +1265,12 @@ static model_t *CL_LoadSpriteModel( const char *filename, uint type, uint texFla } // find a free model slot spot - for( i = 1, mod = clgame.sprites; i < MAX_CLIENT_SPRITES; i++, mod++ ) + for( i = 0, mod = clgame.sprites + start; i < MAX_CLIENT_SPRITES / 2; i++, mod++ ) if( !mod->name[0] ) break; // this is a valid spot - if( i == MAX_CLIENT_SPRITES ) + if( i == MAX_CLIENT_SPRITES / 2 ) { - Con_Printf( S_ERROR "MAX_CLIENT_SPRITES limit exceeded (%d)\n", MAX_CLIENT_SPRITES ); + Con_Printf( S_ERROR "MAX_CLIENT_SPRITES limit exceeded (%d)\n", MAX_CLIENT_SPRITES / 2 ); return NULL; } @@ -1308,7 +1311,7 @@ HSPRITE pfnSPR_LoadExt( const char *szPicName, uint texFlags ) if(( spr = CL_LoadSpriteModel( szPicName, SPR_CLIENT, texFlags )) == NULL ) return 0; - return (spr - clgame.sprites); // return index + return (spr - clgame.sprites) + 1; // return index } /* @@ -1324,7 +1327,7 @@ HSPRITE EXPORT pfnSPR_Load( const char *szPicName ) if(( spr = CL_LoadSpriteModel( szPicName, SPR_HUDSPRITE, 0 )) == NULL ) return 0; - return (spr - clgame.sprites); // return index + return (spr - clgame.sprites) + 1; // return index } /* @@ -1336,10 +1339,11 @@ CL_GetSpritePointer const model_t *CL_GetSpritePointer( HSPRITE hSprite ) { model_t *mod; + int index = hSprite - 1; - if( hSprite <= 0 || hSprite >= MAX_CLIENT_SPRITES ) + if( index < 0 || index >= MAX_CLIENT_SPRITES ) return NULL; // bad image - mod = &clgame.sprites[hSprite]; + mod = &clgame.sprites[index]; if( mod->needload == NL_NEEDS_LOADED ) { @@ -2299,9 +2303,9 @@ static void GAME_EXPORT pfnKillEvents( int entnum, const char *eventname ) int i; event_state_t *es; event_info_t *ei; - int eventIndex = CL_EventIndex( eventname ); + word eventIndex = CL_EventIndex( eventname ); - if( eventIndex < 0 || eventIndex >= MAX_EVENTS ) + if( eventIndex >= MAX_EVENTS ) return; if( entnum < 0 || entnum > clgame.maxEntities ) @@ -2666,7 +2670,14 @@ pfnLoadMapSprite */ model_t *pfnLoadMapSprite( const char *filename ) { - return CL_LoadSpriteModel( filename, SPR_MAPSPRITE, 0 ); + model_t *mod; + + mod = Mod_FindName( filename, false ); + + if( CL_LoadHudSprite( filename, mod, SPR_MAPSPRITE, 0 )) + return mod; + + return NULL; } /* @@ -2929,15 +2940,16 @@ pfnDrawString */ static int GAME_EXPORT pfnDrawString( int x, int y, const char *str, int r, int g, int b ) { + int iWidth = 0; Con_UtfProcessChar(0); // draw the string until we hit the null character or a newline character for ( ; *str != 0 && *str != '\n'; str++ ) { - x += pfnVGUI2DrawCharacterAdditive( x, y, (unsigned char)*str, r, g, b, 0 ); + iWidth += pfnVGUI2DrawCharacterAdditive( x + iWidth, y, (unsigned char)*str, r, g, b, 0 ); } - return x; + return iWidth; } /* @@ -2952,8 +2964,7 @@ static int GAME_EXPORT pfnDrawStringReverse( int x, int y, const char *str, int char *szIt; for( szIt = (char*)str; *szIt != 0; szIt++ ) x -= clgame.scrInfo.charWidths[ (unsigned char) *szIt ]; - pfnDrawString( x, y, str, r, g, b ); - return x; + return pfnDrawString( x, y, str, r, g, b ); } /* @@ -3087,11 +3098,7 @@ handle colon separately */ char *pfnParseFile( char *data, char *token ) { - char *out; - - out = _COM_ParseFileSafe( data, token, INT_MAX, PFILE_HANDLECOLON, NULL ); - - return out; + return COM_ParseFileSafe( data, token, PFILE_TOKEN_MAX_LENGTH, PFILE_HANDLECOLON, NULL, NULL ); } /* @@ -3178,16 +3185,7 @@ convert world coordinates (x,y,z) into screen (x, y) */ int TriWorldToScreen( const float *world, float *screen ) { - int retval; - - retval = ref.dllFuncs.WorldToScreen( world, screen ); - - screen[0] = 0.5f * screen[0] * (float)clgame.viewport[2]; - screen[1] = -0.5f * screen[1] * (float)clgame.viewport[3]; - screen[0] += 0.5f * (float)clgame.viewport[2]; - screen[1] += 0.5f * (float)clgame.viewport[3]; - - return retval; + return ref.dllFuncs.WorldToScreen( world, screen ); } /* @@ -3321,7 +3319,7 @@ NetAPI_InitNetworking */ void GAME_EXPORT NetAPI_InitNetworking( void ) { - NET_Config( true ); // allow remote + NET_Config( true, false ); // allow remote } /* @@ -3874,7 +3872,7 @@ static cl_enginefunc_t gEngfuncs = (void*)Cmd_GetName, pfnGetClientOldTime, pfnGetGravity, - Mod_Handle, + CL_ModelHandle, pfnEnableTexSort, pfnSetLightmapColor, pfnSetLightmapScale, @@ -3923,7 +3921,6 @@ void CL_UnloadProgs( void ) Cvar_FullSet( "host_clientloaded", "0", FCVAR_READ_ONLY ); COM_FreeLibrary( clgame.hInstance ); - VGui_Shutdown(); Mem_FreePool( &cls.mempool ); Mem_FreePool( &clgame.mempool ); memset( &clgame, 0, sizeof( clgame )); @@ -3967,7 +3964,7 @@ qboolean CL_LoadProgs( const char *name ) #else // this doesn't mean other platforms uses SDL2 in any case // it just helps input code to stay platform-independent - clgame.client_dll_uses_sdl = false; + clgame.client_dll_uses_sdl = true; #endif clgame.hInstance = COM_LoadLibrary( name, false, false ); @@ -3984,7 +3981,17 @@ qboolean CL_LoadProgs( const char *name ) // trying to fill interface now GetClientAPI( &clgame.dllFuncs ); + } + else if(( GetClientAPI = (void *)COM_GetProcAddress( clgame.hInstance, "F" )) != NULL ) + { + Con_Reportf( "CL_LoadProgs: found single callback export (secured client dlls)\n" ); + // trying to fill interface now + CL_GetSecuredClientAPI( GetClientAPI ); + } + + if ( GetClientAPI != NULL ) + { // check critical functions again for( func = cdll_exports; func && func->name; func++ ) { diff --git a/engine/client/cl_gameui.c b/engine/client/cl_gameui.c index 7b977661..b5c56ac6 100644 --- a/engine/client/cl_gameui.c +++ b/engine/client/cl_gameui.c @@ -418,6 +418,8 @@ static void UI_ConvertGameInfo( GAMEINFO *out, gameinfo_t *in ) out->flags |= GFL_NOMODELS; if( in->noskills ) out->flags |= GFL_NOSKILLS; + if( in->render_picbutton_text ) + out->flags |= GFL_RENDER_PICBUTTON_TEXT; } static qboolean PIC_Scissor( float *x, float *y, float *width, float *height, float *u0, float *v0, float *u1, float *v1 ) @@ -979,7 +981,7 @@ pfnGetGamesList */ static GAMEINFO ** GAME_EXPORT pfnGetGamesList( int *numGames ) { - if( numGames ) *numGames = SI.numgames; + if( numGames ) *numGames = FI->numgames; return gameui.modsInfo; } @@ -1059,10 +1061,7 @@ pfnChangeInstance */ static void GAME_EXPORT pfnChangeInstance( const char *newInstance, const char *szFinalMessage ) { - if( !szFinalMessage ) szFinalMessage = ""; - if( !newInstance || !*newInstance ) return; - - Host_NewInstance( newInstance, szFinalMessage ); + Con_Reportf( S_ERROR "ChangeInstance menu call is deprecated!\n" ); } /* @@ -1119,6 +1118,29 @@ static char *pfnParseFile( char *buf, char *token ) return COM_ParseFile( buf, token, INT_MAX ); } +/* +============= +pfnFileExists + +legacy wrapper +============= +*/ +static int pfnFileExists( const char *path, int gamedironly ) +{ + return FS_FileExists( path, gamedironly ); +} + +/* +============= +pfnDelete + +legacy wrapper +============= +*/ +static int pfnDelete( const char *path ) +{ + return FS_Delete( path ); +} // engine callbacks static ui_enginefuncs_t gEngfuncs = @@ -1166,7 +1188,7 @@ static ui_enginefuncs_t gEngfuncs = pfnRenderScene, pfnAddEntity, Host_Error, - FS_FileExists, + pfnFileExists, pfnGetGameDir, Cmd_CheckMapsList, CL_Active, @@ -1205,7 +1227,7 @@ static ui_enginefuncs_t gEngfuncs = COM_CompareFileTime, VID_GetModeString, (void*)COM_SaveFile, - (void*)FS_Delete + pfnDelete }; static void pfnEnableTextInput( int enable ) @@ -1227,6 +1249,11 @@ static int pfnGetRenderers( unsigned int num, char *shortName, size_t size1, cha return 1; } +static char *pfnParseFileSafe( char *data, char *buf, const int size, unsigned int flags, int *len ) +{ + return COM_ParseFileSafe( data, buf, size, flags, len, NULL ); +} + static ui_extendedfuncs_t gExtendedfuncs = { pfnEnableTextInput, @@ -1235,7 +1262,8 @@ static ui_extendedfuncs_t gExtendedfuncs = Con_UtfMoveRight, pfnGetRenderers, Sys_DoubleTime, - _COM_ParseFileSafe, + pfnParseFileSafe, + NET_AdrToString R_GetRenderDevice }; @@ -1356,13 +1384,13 @@ qboolean UI_LoadProgs( void ) Cvar_FullSet( "host_gameuiloaded", "1", FCVAR_READ_ONLY ); // setup gameinfo - for( i = 0; i < SI.numgames; i++ ) + for( i = 0; i < FI->numgames; i++ ) { gameui.modsInfo[i] = Mem_Calloc( gameui.mempool, sizeof( GAMEINFO )); - UI_ConvertGameInfo( gameui.modsInfo[i], SI.games[i] ); + UI_ConvertGameInfo( gameui.modsInfo[i], FI->games[i] ); } - UI_ConvertGameInfo( &gameui.gameInfo, SI.GameInfo ); // current gameinfo + UI_ConvertGameInfo( &gameui.gameInfo, FI->GameInfo ); // current gameinfo // setup globals gameui.globals->developer = host.allow_console; diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index b3f8f14d..e8e9d5f5 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -70,6 +70,7 @@ convar_t *cl_upmax; convar_t *cl_lw; convar_t *cl_charset; convar_t *cl_trace_messages; +convar_t *cl_nat; convar_t *hud_utf8; convar_t *ui_renderworld; @@ -195,8 +196,6 @@ void CL_CheckClientState( void ) Netchan_ReportFlow( &cls.netchan ); Con_DPrintf( "client connected at %.2f sec\n", Sys_DoubleTime() - cls.timestart ); - if(( cls.demoplayback || cls.disable_servercount != cl.servercount ) && cl.video_prepped ) - SCR_EndLoadingPlaque(); // get rid of loading plaque } } @@ -234,6 +233,7 @@ void CL_SignonReply( void ) Mem_PrintStats(); break; case 2: + SCR_EndLoadingPlaque(); if( cl.proxy_redirect && !cls.spectator ) CL_Disconnect(); cl.proxy_redirect = false; @@ -243,7 +243,7 @@ void CL_SignonReply( void ) float CL_LerpInterval( void ) { - return max( cl_interp->value, 1.f / cl_updaterate->value ); + return Q_max( cl_interp->value, 1.f / cl_updaterate->value ); } /* @@ -296,7 +296,7 @@ static float CL_LerpPoint( void ) if( cl_interp->value > 0.001f ) { // manual lerp value (goldsrc mode) - float td = max( 0.f, cl.time - cl.mtime[0] ); + float td = Q_max( 0.f, cl.time - cl.mtime[0] ); frac = td / CL_LerpInterval(); } else if( server_frametime > 0.001f ) @@ -528,7 +528,7 @@ qboolean CL_ProcessShowTexturesCmds( usercmd_t *cmd ) if( released & ( IN_RIGHT|IN_MOVERIGHT )) Cvar_SetValue( "r_showtextures", gl_showtextures->value + 1 ); if( released & ( IN_LEFT|IN_MOVELEFT )) - Cvar_SetValue( "r_showtextures", max( 1, gl_showtextures->value - 1 )); + Cvar_SetValue( "r_showtextures", Q_max( 1, gl_showtextures->value - 1 )); oldbuttons = cmd->buttons; return true; @@ -1253,7 +1253,7 @@ void CL_Connect_f( void ) // if running a local server, kill it and reissue if( SV_Active( )) Host_ShutdownServer(); - NET_Config( true ); // allow remote + NET_Config( true, !CVAR_TO_BOOL( cl_nat )); // allow remote Con_Printf( "server %s\n", server ); CL_Disconnect(); @@ -1299,7 +1299,7 @@ void CL_Rcon_f( void ) message[3] = (char)255; message[4] = 0; - NET_Config( true ); // allow remote + NET_Config( true, false ); // allow remote Q_strcat( message, "rcon " ); Q_strcat( message, rcon_client_password->string ); @@ -1562,8 +1562,7 @@ void CL_LocalServers_f( void ) netadr_t adr; Con_Printf( "Scanning for servers on the local network area...\n" ); - NET_Config( true ); // allow remote - cls.legacyservercount = 0; + NET_Config( true, true ); // allow remote // send a broadcast packet adr.type = NA_BROADCAST; @@ -1585,13 +1584,12 @@ void CL_InternetServers_f( void ) char *info = fullquery + sizeof( MS_SCAN_REQUEST ) - 1; const size_t remaining = sizeof( fullquery ) - sizeof( MS_SCAN_REQUEST ); - NET_Config( true ); // allow remote + NET_Config( true, true ); // allow remote Con_Printf( "Scanning for servers on the internet area...\n" ); Info_SetValueForKey( info, "gamedir", GI->gamefolder, remaining ); Info_SetValueForKey( info, "clver", XASH_VERSION, remaining ); // let master know about client version - // Info_SetValueForKey( info, "nat", cl_nat->string, remaining ); - cls.legacyservercount = 0; + Info_SetValueForKey( info, "nat", cl_nat->string, remaining ); cls.internetservers_wait = NET_SendToMasters( NS_CLIENT, sizeof( MS_SCAN_REQUEST ) + Q_strlen( info ), fullquery ); cls.internetservers_pending = true; @@ -1722,8 +1720,6 @@ void CL_ParseStatusMessage( netadr_t from, sizebuf_t *msg ) { Netchan_OutOfBandPrint( NS_CLIENT, from, "info %i", PROTOCOL_LEGACY_VERSION ); Con_Printf( "^1Server^7: %s, Info: %s\n", NET_AdrToString( from ), infostring ); - if( cls.legacyservercount < MAX_LEGACY_SERVERS ) - cls.legacyservers[cls.legacyservercount++] = from; return; } @@ -1733,14 +1729,10 @@ void CL_ParseStatusMessage( netadr_t from, sizebuf_t *msg ) return; // unsupported proto } - for( i = 0; i < cls.legacyservercount; i++ ) + if( !COM_CheckString( Info_ValueForKey( infostring, "p" ))) { - if( NET_CompareAdr( cls.legacyservers[i], from ) ) - { - Info_SetValueForKey( infostring, "legacy", "1", sizeof( infostring ) ); - Con_Print("Legacy: "); - break; - } + Info_SetValueForKey( infostring, "legacy", "1", sizeof( infostring ) ); + Con_Print("Legacy: "); } // more info about servers @@ -2161,7 +2153,7 @@ void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) } else if( clgame.request_type == NET_REQUEST_GAMEUI ) { - NET_Config( true ); // allow remote + NET_Config( true, false ); // allow remote Netchan_OutOfBandPrint( NS_CLIENT, servadr, "info %i", PROTOCOL_VERSION ); } } @@ -2353,11 +2345,10 @@ Replace the displayed name for some resources */ const char *CL_CleanFileName( const char *filename ) { - const char *pfilename = filename; - if( COM_CheckString( filename ) && filename[0] == '!' ) - pfilename = "customization"; - return pfilename; + return "customization"; + + return filename; } @@ -2839,6 +2830,7 @@ void CL_InitLocal( void ) cl_updaterate = Cvar_Get( "cl_updaterate", "20", FCVAR_USERINFO|FCVAR_ARCHIVE, "refresh rate of server messages" ); cl_dlmax = Cvar_Get( "cl_dlmax", "0", FCVAR_USERINFO|FCVAR_ARCHIVE, "max allowed outcoming fragment size" ); cl_upmax = Cvar_Get( "cl_upmax", "1200", FCVAR_ARCHIVE, "max allowed incoming fragment size" ); + cl_nat = Cvar_Get( "cl_nat", "0", 0, "show servers running under NAT" ); rate = Cvar_Get( "rate", "3500", FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_FILTERABLE, "player network rate" ); topcolor = Cvar_Get( "topcolor", "0", FCVAR_USERINFO|FCVAR_ARCHIVE, "player top color" ); bottomcolor = Cvar_Get( "bottomcolor", "0", FCVAR_USERINFO|FCVAR_ARCHIVE, "player bottom color" ); @@ -2881,7 +2873,6 @@ void CL_InitLocal( void ) Cmd_AddRestrictedCommand ("kill", NULL, "die instantly" ); Cmd_AddCommand ("god", NULL, "enable godmode" ); Cmd_AddCommand ("fov", NULL, "set client field of view" ); - Cmd_AddCommand ("log", NULL, "logging server events" ); // register our commands Cmd_AddCommand ("pause", NULL, "pause the game (if the server allows pausing)" ); @@ -2921,7 +2912,7 @@ void CL_InitLocal( void ) Cmd_AddCommand ("levelshot", CL_LevelShot_f, "same as \"screenshot\", used for create plaque images" ); Cmd_AddCommand ("saveshot", CL_SaveShot_f, "used for create save previews with LoadGame menu" ); - Cmd_AddRestrictedCommand ("connect", CL_Connect_f, "connect to a server by hostname" ); + Cmd_AddCommand ("connect", CL_Connect_f, "connect to a server by hostname" ); Cmd_AddCommand ("reconnect", CL_Reconnect_f, "reconnect to current level" ); Cmd_AddCommand ("rcon", CL_Rcon_f, "sends a command to the server console (rcon_password and rcon_address required)" ); @@ -3078,7 +3069,6 @@ void CL_Init( void ) MSG_Init( &cls.datagram, "cls.datagram", cls.datagram_buf, sizeof( cls.datagram_buf )); // IN_TouchInit(); - Con_LoadHistory(); COM_GetCommonLibraryPath( LIBRARY_CLIENT, libpath, sizeof( libpath )); @@ -3099,13 +3089,9 @@ CL_Shutdown */ void CL_Shutdown( void ) { - // already freed - if( !cls.initialized ) return; - cls.initialized = false; - Con_Printf( "CL_Shutdown()\n" ); - if( !host.crashed ) + if( !host.crashed && cls.initialized ) { Host_WriteOpenGLConfig (); Host_WriteVideoConfig (); @@ -3119,6 +3105,9 @@ void CL_Shutdown( void ) Mobile_Shutdown (); SCR_Shutdown (); CL_UnloadProgs (); + cls.initialized = false; + + VGui_Shutdown(); FS_Delete( "demoheader.tmp" ); // remove tmp file SCR_FreeCinematic (); // release AVI's *after* client.dll because custom renderer may use them @@ -3126,4 +3115,5 @@ void CL_Shutdown( void ) R_Shutdown (); Con_Shutdown (); + } diff --git a/engine/client/cl_mobile.c b/engine/client/cl_mobile.c index 2dc84d34..3e59b85a 100644 --- a/engine/client/cl_mobile.c +++ b/engine/client/cl_mobile.c @@ -103,6 +103,11 @@ static void pfnTouch_RemoveButton( const char *name ) Touch_RemoveButton( name, true ); } +static char *pfnParseFileSafe( char *data, char *buf, const int size, unsigned int flags, int *len ) +{ + return COM_ParseFileSafe( data, buf, size, flags, len, NULL ); +} + static mobile_engfuncs_t gpMobileEngfuncs = { MOBILITY_API_VERSION, @@ -118,7 +123,7 @@ static mobile_engfuncs_t gpMobileEngfuncs = Sys_Warn, pfnGetNativeObject, ID_SetCustomClientID, - _COM_ParseFileSafe + pfnParseFileSafe }; qboolean Mobile_Init( void ) diff --git a/engine/client/cl_netgraph.c b/engine/client/cl_netgraph.c index a361c9bd..7c9ee99a 100644 --- a/engine/client/cl_netgraph.c +++ b/engine/client/cl_netgraph.c @@ -158,6 +158,7 @@ static void NetGraph_InitColors( void ) f = (float)(i - hfrac) / (float)(NETGRAPH_LERP_HEIGHT - hfrac ); VectorMA( mincolor[1], f, dc[1], netcolors[NETGRAPH_NET_COLORS + i] ); } + netcolors[NETGRAPH_NET_COLORS + i][3] = 255; } } @@ -259,7 +260,7 @@ static void NetGraph_DrawTimes( wrect_t rect, int x, int w ) for( a = 0; a < w; a++ ) { i = ( cls.netchan.outgoing_sequence - a ) & NET_TIMINGS_MASK; - h = ( netstat_cmdinfo[i].cmd_lerp / 3.0f ) * NETGRAPH_LERP_HEIGHT; + h = Q_min(( netstat_cmdinfo[i].cmd_lerp / 3.0f ) * NETGRAPH_LERP_HEIGHT, net_graphheight->value * 0.7f); fill.left = x + w - a - 1; fill.right = fill.bottom = 1; @@ -395,10 +396,10 @@ static void NetGraph_DrawTextFields( int x, int y, int w, wrect_t rect, int coun if( !out ) out = lastout; else lastout = out; - Con_DrawString( x, y, va( "in : %i %.2f k/s", netstat_graph[j].msgbytes, cls.netchan.flow[FLOW_INCOMING].avgkbytespersec ), colors ); + Con_DrawString( x, y, va( "in : %i %.2f kb/s", netstat_graph[j].msgbytes, cls.netchan.flow[FLOW_INCOMING].avgkbytespersec ), colors ); y += 15; - Con_DrawString( x, y, va( "out: %i %.2f k/s", out, cls.netchan.flow[FLOW_OUTGOING].avgkbytespersec ), colors ); + Con_DrawString( x, y, va( "out: %i %.2f kb/s", out, cls.netchan.flow[FLOW_OUTGOING].avgkbytespersec ), colors ); y += 15; if( graphtype > 2 ) @@ -615,7 +616,7 @@ static void NetGraph_GetScreenPos( wrect_t *rect, int *w, int *x, int *y ) *x = rect->left + rect->right - 5 - *w; break; case 2: // center - *x = rect->left + ( rect->right - 10 - *w ) / 2; + *x = ( rect->left + ( rect->right - 10 - *w )) / 2; break; default: // left sided *x = rect->left + 5; @@ -672,7 +673,7 @@ void SCR_DrawNetGraph( void ) if( graphtype < 3 ) { - ref.dllFuncs.GL_SetRenderMode( kRenderTransAdd ); + ref.dllFuncs.GL_SetRenderMode( kRenderTransColor ); ref.dllFuncs.GL_Bind( XASH_TEXTURE0, R_GetBuiltinTexture( REF_WHITE_TEXTURE ) ); ref.dllFuncs.Begin( TRI_QUADS ); // draw all the fills as a long solid sequence of quads for speedup reasons diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index 997edebc..27c1a3d4 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -484,6 +484,8 @@ void CL_BatchResourceRequest( qboolean initialize ) switch( p->type ) { case t_sound: + case t_model: + case t_eventscript: if( !CL_CheckFile( &msg, p )) break; CL_MoveToOnHandList( p ); @@ -491,11 +493,6 @@ void CL_BatchResourceRequest( qboolean initialize ) case t_skin: CL_MoveToOnHandList( p ); break; - case t_model: - if( !CL_CheckFile( &msg, p )) - break; - CL_MoveToOnHandList( p ); - break; case t_decal: if( !HPAK_GetDataPointer( CUSTOM_RES_PATH, p, NULL, NULL )) { @@ -520,11 +517,6 @@ void CL_BatchResourceRequest( qboolean initialize ) break; CL_MoveToOnHandList( p ); break; - case t_eventscript: - if( !CL_CheckFile( &msg, p )) - break; - CL_MoveToOnHandList( p ); - break; case t_world: ASSERT( 0 ); break; @@ -1322,30 +1314,6 @@ void CL_RegisterUserMessage( sizebuf_t *msg ) CL_LinkUserMessage( pszName, svc_num, size ); } -/* -================ -CL_RegisterUserMessage - -register new user message or update existing -================ -*/ -/* -void CL_LegacyRegisterUserMessage( sizebuf_t *msg ) -{ - char *pszName; - int svc_num, size; - - svc_num = MSG_ReadByte( msg ); - size = MSG_ReadByte( msg ); - pszName = MSG_ReadString( msg ); - - // important stuff - if( size == 0xFF ) size = -1; - svc_num = bound( 0, svc_num, 255 ); - - CL_LinkUserMessage( pszName, svc_num, size ); -} -*/ /* ================ CL_UpdateUserinfo @@ -1819,7 +1787,7 @@ void CL_ParseScreenShake( sizebuf_t *msg ) clgame.shake.amplitude = (float)(word)MSG_ReadShort( msg ) * (1.0f / (float)(1<<12)); clgame.shake.duration = (float)(word)MSG_ReadShort( msg ) * (1.0f / (float)(1<<12)); clgame.shake.frequency = (float)(word)MSG_ReadShort( msg ) * (1.0f / (float)(1<<8)); - clgame.shake.time = cl.time + max( clgame.shake.duration, 0.01f ); + clgame.shake.time = cl.time + Q_max( clgame.shake.duration, 0.01f ); clgame.shake.next_shake = 0.0f; // apply immediately } @@ -2413,14 +2381,11 @@ CL_ParseBaseline void CL_LegacyParseBaseline( sizebuf_t *msg ) { int i, newnum; - entity_state_t nullstate; qboolean player; cl_entity_t *ent; Delta_InitClient (); // finalize client delta's - memset( &nullstate, 0, sizeof( nullstate )); - newnum = MSG_ReadWord( msg ); player = CL_IsPlayerIndex( newnum ); diff --git a/engine/client/cl_pmove.c b/engine/client/cl_pmove.c index 897a717b..04dedf1f 100644 --- a/engine/client/cl_pmove.c +++ b/engine/client/cl_pmove.c @@ -794,6 +794,8 @@ static float GAME_EXPORT pfnTraceModel( physent_t *pe, float *start, float *end, matrix4x4 matrix; hull_t *hull; + PM_InitTrace( trace, end ); + old_usehull = clgame.pmove->usehull; clgame.pmove->usehull = 2; @@ -992,7 +994,6 @@ void CL_SetupPMove( playermove_t *pmove, local_state_t *from, usercmd_t *ucmd, q pmove->flFallVelocity = ps->flFallVelocity; pmove->flSwimTime = cd->flSwimTime; VectorCopy( cd->punchangle, pmove->punchangle ); - pmove->flSwimTime = cd->flSwimTime; pmove->flNextPrimaryAttack = 0.0f; // not used by PM_ code pmove->effects = ps->effects; pmove->flags = cd->flags; diff --git a/engine/client/cl_qparse.c b/engine/client/cl_qparse.c index bfdf753e..76865362 100644 --- a/engine/client/cl_qparse.c +++ b/engine/client/cl_qparse.c @@ -864,7 +864,7 @@ void CL_QuakeExecStuff( void ) if( !*text ) break; - text = _COM_ParseFileSafe( text, token, sizeof( token ), PFILE_IGNOREBRACKET, NULL ); + text = COM_ParseFileSafe( text, token, sizeof( token ), PFILE_IGNOREBRACKET, NULL, NULL ); if( !text ) break; diff --git a/engine/client/cl_render.c b/engine/client/cl_render.c index 88a25f69..9af5aec7 100644 --- a/engine/client/cl_render.c +++ b/engine/client/cl_render.c @@ -117,7 +117,7 @@ static void R_EnvShot( const float *vieworg, const char *name, qboolean skyshot, else cls.scrshot_action = scrshot_envshot; // catch negative values - cls.envshot_viewsize = max( 0, shotsize ); + cls.envshot_viewsize = Q_max( 0, shotsize ); } /* diff --git a/engine/client/cl_scrn.c b/engine/client/cl_scrn.c index afc3a959..ee212d46 100644 --- a/engine/client/cl_scrn.c +++ b/engine/client/cl_scrn.c @@ -418,7 +418,6 @@ void SCR_BeginLoadingPlaque( qboolean is_background ) cls.draw_changelevel = !is_background; SCR_UpdateScreen(); cls.disable_screen = host.realtime; - cls.disable_servercount = cl.servercount; cl.background = is_background; // set right state before svc_serverdata is came if( !Host_IsDedicated() ) @@ -781,6 +780,7 @@ SCR_VidInit */ void SCR_VidInit( void ) { + string libpath; if( !ref.initialized ) // don't call VidInit too soon return; @@ -795,7 +795,8 @@ void SCR_VidInit( void ) gameui.globals->scrHeight = refState.height; } - VGui_Startup( NULL, refState.width, refState.height ); // initialized already, so pass NULL + COM_GetCommonLibraryPath( LIBRARY_CLIENT, libpath, sizeof( libpath )); + VGui_Startup( libpath, refState.width, refState.height ); CL_ClearSpriteTextures(); // now all hud sprites are invalid diff --git a/engine/client/cl_securedstub.c b/engine/client/cl_securedstub.c new file mode 100644 index 00000000..fd7746b1 --- /dev/null +++ b/engine/client/cl_securedstub.c @@ -0,0 +1,460 @@ +/* +cl_securedstub.c - secured client dll stub +Copyright (C) 2022 FWGS + +This program 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, either version 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "common.h" +#include "client.h" + +typedef struct cldll_func_src_s +{ + int (*pfnInitialize)( cl_enginefunc_t *pEnginefuncs, int iVersion ); + void (*pfnInit)( void ); + int (*pfnVidInit)( void ); + int (*pfnRedraw)( float flTime, int intermission ); + int (*pfnUpdateClientData)( client_data_t *cdata, float flTime ); + void (*pfnReset)( void ); + void (*pfnPlayerMove)( struct playermove_s *ppmove, int server ); + void (*pfnPlayerMoveInit)( struct playermove_s *ppmove ); + char (*pfnPlayerMoveTexture)( char *name ); + void (*IN_ActivateMouse)( void ); + void (*IN_DeactivateMouse)( void ); + void (*IN_MouseEvent)( int mstate ); + void (*IN_ClearStates)( void ); + void (*IN_Accumulate)( void ); + void (*CL_CreateMove)( float frametime, struct usercmd_s *cmd, int active ); + int (*CL_IsThirdPerson)( void ); + void (*CL_CameraOffset)( float *ofs ); // unused + void *(*KB_Find)( const char *name ); + void (*CAM_Think)( void ); // camera stuff + void (*pfnCalcRefdef)( ref_params_t *pparams ); + int (*pfnAddEntity)( int type, cl_entity_t *ent, const char *modelname ); + void (*pfnCreateEntities)( void ); + void (*pfnDrawNormalTriangles)( void ); + void (*pfnDrawTransparentTriangles)( void ); + void (*pfnStudioEvent)( const struct mstudioevent_s *event, const cl_entity_t *entity ); + void (*pfnPostRunCmd)( struct local_state_s *from, struct local_state_s *to, usercmd_t *cmd, int runfuncs, double time, unsigned int random_seed ); + void (*pfnShutdown)( void ); + void (*pfnTxferLocalOverrides)( entity_state_t *state, const clientdata_t *client ); + void (*pfnProcessPlayerState)( entity_state_t *dst, const entity_state_t *src ); + void (*pfnTxferPredictionData)( entity_state_t *ps, const entity_state_t *pps, clientdata_t *pcd, const clientdata_t *ppcd, weapon_data_t *wd, const weapon_data_t *pwd ); + void (*pfnDemo_ReadBuffer)( int size, byte *buffer ); + int (*pfnConnectionlessPacket)( const struct netadr_s *net_from, const char *args, char *buffer, int *size ); + int (*pfnGetHullBounds)( int hullnumber, float *mins, float *maxs ); + void (*pfnFrame)( double time ); + int (*pfnKey_Event)( int eventcode, int keynum, const char *pszCurrentBinding ); + void (*pfnTempEntUpdate)( double frametime, double client_time, double cl_gravity, struct tempent_s **ppTempEntFree, struct tempent_s **ppTempEntActive, int ( *Callback_AddVisibleEntity )( cl_entity_t *pEntity ), void ( *Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp )); + cl_entity_t *(*pfnGetUserEntity)( int index ); + void (*pfnVoiceStatus)( int entindex, qboolean bTalking ); + void (*pfnDirectorMessage)( int iSize, void *pbuf ); + int (*pfnGetStudioModelInterface)( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio ); + void (*pfnChatInputPosition)( int *x, int *y ); + int (*pfnGetPlayerTeam)( int iPlayer ); + void *(*pfnClientFactory)( void ); +} cldll_func_src_t; + +typedef struct cldll_func_dst_s +{ + void (*pfnInitialize)( cl_enginefunc_t **pEnginefuncs, int *iVersion ); + void (*pfnInit)( void ); + void (*pfnVidInit)( void ); + void (*pfnRedraw)( float *flTime, int *intermission ); + void (*pfnUpdateClientData)( client_data_t **cdata, float *flTime ); + void (*pfnReset)( void ); + void (*pfnPlayerMove)( struct playermove_s **ppmove, int *server ); + void (*pfnPlayerMoveInit)( struct playermove_s **ppmove ); + void (*pfnPlayerMoveTexture)( char **name ); + void (*IN_ActivateMouse)( void ); + void (*IN_DeactivateMouse)( void ); + void (*IN_MouseEvent)( int *mstate ); + void (*IN_ClearStates)( void ); + void (*IN_Accumulate)( void ); + void (*CL_CreateMove)( float *frametime, struct usercmd_s **cmd, int *active ); + void (*CL_IsThirdPerson)( void ); + void (*CL_CameraOffset)( float **ofs ); + void (*KB_Find)( const char **name ); + void (*CAM_Think)( void ); + void (*pfnCalcRefdef)( ref_params_t **pparams ); + void (*pfnAddEntity)( int *type, cl_entity_t **ent, const char **modelname ); + void (*pfnCreateEntities)( void ); + void (*pfnDrawNormalTriangles)( void ); + void (*pfnDrawTransparentTriangles)( void ); + void (*pfnStudioEvent)( const struct mstudioevent_s **event, const cl_entity_t **entity ); + void (*pfnPostRunCmd)( struct local_state_s **from, struct local_state_s **to, usercmd_t **cmd, int *runfuncs, double *time, unsigned int *random_seed ); + void (*pfnShutdown)( void ); + void (*pfnTxferLocalOverrides)( entity_state_t **state, const clientdata_t **client ); + void (*pfnProcessPlayerState)( entity_state_t **dst, const entity_state_t **src ); + void (*pfnTxferPredictionData)( entity_state_t **ps, const entity_state_t **pps, clientdata_t **pcd, const clientdata_t **ppcd, weapon_data_t **wd, const weapon_data_t **pwd ); + void (*pfnDemo_ReadBuffer)( int *size, byte **buffer ); + void (*pfnConnectionlessPacket)( const struct netadr_s **net_from, const char **args, char **buffer, int **size ); + void (*pfnGetHullBounds)( int *hullnumber, float **mins, float **maxs ); + void (*pfnFrame)( double *time ); + void (*pfnKey_Event)( int *eventcode, int *keynum, const char **pszCurrentBinding ); + void (*pfnTempEntUpdate)( double *frametime, double *client_time, double *cl_gravity, struct tempent_s ***ppTempEntFree, struct tempent_s ***ppTempEntActive, int ( **Callback_AddVisibleEntity )( cl_entity_t *pEntity ), void ( **Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp )); + void (*pfnGetUserEntity)( int *index ); + void (*pfnVoiceStatus)( int *entindex, qboolean *bTalking ); + void (*pfnDirectorMessage)( int *iSize, void **pbuf ); + void (*pfnGetStudioModelInterface)( int *version, struct r_studio_interface_s ***ppinterface, struct engine_studio_api_s **pstudio ); + void (*pfnChatInputPosition)( int **x, int **y ); + void (*pfnGetPlayerTeam)( int *iPlayer ); +} cldll_func_dst_t; + +struct cl_enginefunc_dst_s; +struct modshelpers_s; +struct modchelpers_s; +struct engdata_s; + +typedef struct modfuncs_s +{ + void (*m_pfnLoadMod)( char *pchModule ); + void (*m_pfnCloseMod)( void ); + int (*m_pfnNCall)( int ijump, int cnArg, ... ); + void (*m_pfnGetClDstAddrs)( cldll_func_dst_t *pcldstAddrs ); + void (*m_pfnGetEngDstAddrs)( struct cl_enginefunc_dst_s *pengdstAddrs ); + void (*m_pfnModuleLoaded)( void ); + void (*m_pfnProcessOutgoingNet)( struct netchan_s *pchan, struct sizebuf_s *psizebuf ); + qboolean (*m_pfnProcessIncomingNet)( struct netchan_s *pchan, struct sizebuf_s *psizebuf ); + void (*m_pfnTextureLoad)( char *pszName, int dxWidth, int dyHeight, char *pbData ); + void (*m_pfnModelLoad)( struct model_s *pmodel, void *pvBuf ); + void (*m_pfnFrameBegin)( void ); + void (*m_pfnFrameRender1)( void ); + void (*m_pfnFrameRender2)( void ); + void (*m_pfnSetModSHelpers)( struct modshelpers_s *pmodshelpers ); + void (*m_pfnSetModCHelpers)( struct modchelpers_s *pmodchelpers ); + void (*m_pfnSetEngData)( struct engdata_s *pengdata ); + int m_nVersion; + void (*m_pfnConnectClient)( int iPlayer ); + void (*m_pfnRecordIP)( unsigned int pnIP ); + void (*m_pfnPlayerStatus)( unsigned char *pbData, int cbData ); + void (*m_pfnSetEngineVersion)( int nVersion ); + int m_nVoid2; + int m_nVoid3; + int m_nVoid4; + int m_nVoid5; + int m_nVoid6; + int m_nVoid7; + int m_nVoid8; + int m_nVoid9; +} modfuncs_t; + +static void DstInitialize( cl_enginefunc_t **pEnginefuncs, int *iVersion ) +{ + // stub +} + +static void DstInit( void ) +{ + // stub +} + +static void DstVidInit( void ) +{ + // stub +} + +static void DstRedraw( float *flTime, int *intermission ) +{ + // stub +} + +static void DstUpdateClientData( client_data_t **cdata, float *flTime ) +{ + // stub +} + +static void DstReset( void ) +{ + // stub +} + +static void DstPlayerMove( struct playermove_s **ppmove, int *server ) +{ + // stub +} + +static void DstPlayerMoveInit( struct playermove_s **ppmove ) +{ + // stub +} + +static void DstPlayerMoveTexture( char **name ) +{ + // stub +} + +static void DstIN_ActivateMouse( void ) +{ + // stub +} + +static void DstIN_DeactivateMouse( void ) +{ + // stub +} + +static void DstIN_MouseEvent( int *mstate ) +{ + // stub +} + +static void DstIN_ClearStates( void ) +{ + // stub +} + +static void DstIN_Accumulate( void ) +{ + // stub +} + +static void DstCL_CreateMove( float *frametime, struct usercmd_s **cmd, int *active ) +{ + // stub +} + +static void DstCL_IsThirdPerson( void ) +{ + // stub +} + +static void DstCL_CameraOffset( float **ofs ) +{ + // stub +} + +static void DstKB_Find( const char **name ) +{ + // stub +} + +static void DstCAM_Think( void ) +{ + // stub +} + +static void DstCalcRefdef( ref_params_t **pparams ) +{ + // stub +} + +static void DstAddEntity( int *type, cl_entity_t **ent, const char **modelname ) +{ + // stub +} + +static void DstCreateEntities( void ) +{ + // stub +} + +static void DstDrawNormalTriangles( void ) +{ + // stub +} + +static void DstDrawTransparentTriangles( void ) +{ + // stub +} + +static void DstStudioEvent( const struct mstudioevent_s **event, const cl_entity_t **entity ) +{ + // stub +} + +static void DstPostRunCmd( struct local_state_s **from, struct local_state_s **to, usercmd_t **cmd, int *runfuncs, double *time, unsigned int *random_seed ) +{ + // stub +} + +static void DstShutdown( void ) +{ + // stub +} + +static void DstTxferLocalOverrides( entity_state_t **state, const clientdata_t **client ) +{ + // stub +} + +static void DstProcessPlayerState( entity_state_t **dst, const entity_state_t **src ) +{ + // stub +} + +static void DstTxferPredictionData( entity_state_t **ps, const entity_state_t **pps, clientdata_t **pcd, const clientdata_t **ppcd, weapon_data_t **wd, const weapon_data_t **pwd ) +{ + // stub +} + +static void DstDemo_ReadBuffer( int *size, byte **buffer ) +{ + // stub +} + +static void DstConnectionlessPacket( const struct netadr_s **net_from, const char **args, char **buffer, int **size ) +{ + // stub +} + +static void DstGetHullBounds( int *hullnumber, float **mins, float **maxs ) +{ + // stub +} + +static void DstFrame( double *time ) +{ + // stub +} + +static void DstKey_Event( int *eventcode, int *keynum, const char **pszCurrentBinding ) +{ + // stub +} + +static void DstTempEntUpdate( double *frametime, double *client_time, double *cl_gravity, struct tempent_s ***ppTempEntFree, struct tempent_s ***ppTempEntActive, int ( **Callback_AddVisibleEntity )( cl_entity_t *pEntity ), void ( **Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp ) ) +{ + // stub +} + +static void DstGetUserEntity( int *index ) +{ + // stub +} + +static void DstVoiceStatus( int *entindex, qboolean *bTalking ) +{ + // stub +} + +static void DstDirectorMessage( int *iSize, void **pbuf ) +{ + // stub +} + +static void DstGetStudioModelInterface( int *version, struct r_studio_interface_s ***ppinterface, struct engine_studio_api_s **pstudio ) +{ + // stub +} + +static void DstChatInputPosition( int **x, int **y ) +{ + // stub +} + +static void DstGetPlayerTeam( int *iPlayer ) +{ + // stub +} + +static cldll_func_dst_t cldllFuncDst = +{ + DstInitialize, + DstInit, + DstVidInit, + DstRedraw, + DstUpdateClientData, + DstReset, + DstPlayerMove, + DstPlayerMoveInit, + DstPlayerMoveTexture, + DstIN_ActivateMouse, + DstIN_DeactivateMouse, + DstIN_MouseEvent, + DstIN_ClearStates, + DstIN_Accumulate, + DstCL_CreateMove, + DstCL_IsThirdPerson, + DstCL_CameraOffset, + DstKB_Find, + DstCAM_Think, + DstCalcRefdef, + DstAddEntity, + DstCreateEntities, + DstDrawNormalTriangles, + DstDrawTransparentTriangles, + DstStudioEvent, + DstPostRunCmd, + DstShutdown, + DstTxferLocalOverrides, + DstProcessPlayerState, + DstTxferPredictionData, + DstDemo_ReadBuffer, + DstConnectionlessPacket, + DstGetHullBounds, + DstFrame, + DstKey_Event, + DstTempEntUpdate, + DstGetUserEntity, + DstVoiceStatus, + DstDirectorMessage, + DstGetStudioModelInterface, + DstChatInputPosition, + DstGetPlayerTeam, +}; + +void CL_GetSecuredClientAPI( CL_EXPORT_FUNCS F ) +{ + cldll_func_src_t cldllFuncSrc = { 0 }; + modfuncs_t modFuncs = { 0 }; + + // secured client dlls need these + *(cldll_func_dst_t **)&cldllFuncSrc.pfnVidInit = &cldllFuncDst; + *(modfuncs_t **)&cldllFuncSrc.pfnInitialize = &modFuncs; + + // trying to fill interface now + F( &cldllFuncSrc ); + + // map exports to xash's cldll_func_t + clgame.dllFuncs.pfnInitialize = cldllFuncSrc.pfnInitialize; + clgame.dllFuncs.pfnInit = cldllFuncSrc.pfnInit; + clgame.dllFuncs.pfnVidInit = cldllFuncSrc.pfnVidInit; + clgame.dllFuncs.pfnRedraw = cldllFuncSrc.pfnRedraw; + clgame.dllFuncs.pfnUpdateClientData = cldllFuncSrc.pfnUpdateClientData; + clgame.dllFuncs.pfnReset = cldllFuncSrc.pfnReset; + clgame.dllFuncs.pfnPlayerMove = cldllFuncSrc.pfnPlayerMove; + clgame.dllFuncs.pfnPlayerMoveInit = cldllFuncSrc.pfnPlayerMoveInit; + clgame.dllFuncs.pfnPlayerMoveTexture = cldllFuncSrc.pfnPlayerMoveTexture; + clgame.dllFuncs.IN_ActivateMouse = cldllFuncSrc.IN_ActivateMouse; + clgame.dllFuncs.IN_DeactivateMouse = cldllFuncSrc.IN_DeactivateMouse; + clgame.dllFuncs.IN_MouseEvent = cldllFuncSrc.IN_MouseEvent; + clgame.dllFuncs.IN_ClearStates = cldllFuncSrc.IN_ClearStates; + clgame.dllFuncs.IN_Accumulate = cldllFuncSrc.IN_Accumulate; + clgame.dllFuncs.CL_CreateMove = cldllFuncSrc.CL_CreateMove; + clgame.dllFuncs.CL_IsThirdPerson = cldllFuncSrc.CL_IsThirdPerson; + clgame.dllFuncs.CL_CameraOffset = cldllFuncSrc.CL_CameraOffset; + clgame.dllFuncs.KB_Find = cldllFuncSrc.KB_Find; + clgame.dllFuncs.CAM_Think = cldllFuncSrc.CAM_Think; + clgame.dllFuncs.pfnCalcRefdef = cldllFuncSrc.pfnCalcRefdef; + clgame.dllFuncs.pfnAddEntity = cldllFuncSrc.pfnAddEntity; + clgame.dllFuncs.pfnCreateEntities = cldllFuncSrc.pfnCreateEntities; + clgame.dllFuncs.pfnDrawNormalTriangles = cldllFuncSrc.pfnDrawNormalTriangles; + clgame.dllFuncs.pfnDrawTransparentTriangles = cldllFuncSrc.pfnDrawTransparentTriangles; + clgame.dllFuncs.pfnStudioEvent = cldllFuncSrc.pfnStudioEvent; + clgame.dllFuncs.pfnPostRunCmd = cldllFuncSrc.pfnPostRunCmd; + clgame.dllFuncs.pfnShutdown = cldllFuncSrc.pfnShutdown; + clgame.dllFuncs.pfnTxferLocalOverrides = cldllFuncSrc.pfnTxferLocalOverrides; + clgame.dllFuncs.pfnProcessPlayerState = cldllFuncSrc.pfnProcessPlayerState; + clgame.dllFuncs.pfnTxferPredictionData = cldllFuncSrc.pfnTxferPredictionData; + clgame.dllFuncs.pfnDemo_ReadBuffer = cldllFuncSrc.pfnDemo_ReadBuffer; + clgame.dllFuncs.pfnConnectionlessPacket = cldllFuncSrc.pfnConnectionlessPacket; + clgame.dllFuncs.pfnGetHullBounds = cldllFuncSrc.pfnGetHullBounds; + clgame.dllFuncs.pfnFrame = cldllFuncSrc.pfnFrame; + clgame.dllFuncs.pfnKey_Event = cldllFuncSrc.pfnKey_Event; + clgame.dllFuncs.pfnTempEntUpdate = cldllFuncSrc.pfnTempEntUpdate; + clgame.dllFuncs.pfnGetUserEntity = cldllFuncSrc.pfnGetUserEntity; + clgame.dllFuncs.pfnVoiceStatus = cldllFuncSrc.pfnVoiceStatus; + clgame.dllFuncs.pfnDirectorMessage = cldllFuncSrc.pfnDirectorMessage; + clgame.dllFuncs.pfnGetStudioModelInterface = cldllFuncSrc.pfnGetStudioModelInterface; + clgame.dllFuncs.pfnChatInputPosition = cldllFuncSrc.pfnChatInputPosition; +} diff --git a/engine/client/cl_tent.c b/engine/client/cl_tent.c index 30fa652e..06a588fc 100644 --- a/engine/client/cl_tent.c +++ b/engine/client/cl_tent.c @@ -361,8 +361,8 @@ void CL_TempEntPlaySound( TEMPENTITY *pTemp, float damp ) sound_t handle; if( isshellcasing ) - fvol *= min ( 1.0f, ((float)zvel) / 350.0f ); - else fvol *= min ( 1.0f, ((float)zvel) / 450.0f ); + fvol *= Q_min( 1.0f, ((float)zvel) / 350.0f ); + else fvol *= Q_min( 1.0f, ((float)zvel) / 450.0f ); if( !COM_RandomLong( 0, 3 ) && !isshellcasing ) pitch = COM_RandomLong( 95, 105 ); @@ -1482,7 +1482,6 @@ void GAME_EXPORT R_FunnelSprite( const vec3_t org, int modelIndex, int reverse ) pTemp->entity.baseline.angles[2] = COM_RandomFloat( -100.0f, 100.0f ); pTemp->entity.curstate.framerate = COM_RandomFloat( 0.1f, 0.4f ); pTemp->flags = FTENT_ROTATE|FTENT_FADEOUT; - pTemp->entity.curstate.framerate = 10; vel = dest[2] / 8.0f; if( vel < 64.0f ) vel = 64.0f; @@ -1515,17 +1514,18 @@ R_RicochetSound Make a random ricochet sound ============== */ -void GAME_EXPORT R_RicochetSound( const vec3_t pos ) +static void R_RicochetSound_( const vec3_t pos, int sound ) { - int iPitch = COM_RandomLong( 90, 105 ); - float fvol = COM_RandomFloat( 0.7f, 0.9f ); - char soundpath[32]; sound_t handle; - Q_strncpy( soundpath, cl_ricochet_sounds[COM_RandomLong( 0, 4 )], sizeof( soundpath ) ); - handle = S_RegisterSound( soundpath ); + handle = S_RegisterSound( cl_ricochet_sounds[sound] ); - S_StartSound( pos, 0, CHAN_AUTO, handle, fvol, ATTN_NORM, iPitch, 0 ); + S_StartSound( pos, 0, CHAN_AUTO, handle, VOL_NORM, 1.0, 100, 0 ); +} + +void GAME_EXPORT R_RicochetSound( const vec3_t pos ) +{ + R_RicochetSound_( pos, COM_RandomLong( 0, 4 )); } /* @@ -1656,7 +1656,7 @@ void GAME_EXPORT R_Explosion( vec3_t pos, int model, float scale, float framerat dl->color.g = 250; dl->color.b = 150; dl->die = cl.time + 0.01f; - dl->decay = 80; + dl->decay = 800; // red glow dl = CL_AllocDlight( 0 ); @@ -1904,7 +1904,7 @@ handle temp-entity messages void CL_ParseTempEntity( sizebuf_t *msg ) { sizebuf_t buf; - byte pbuf[256]; + byte pbuf[2048]; int iSize; int type, color, count, flags; int decalIndex, modelIndex, entityIndex; @@ -1915,6 +1915,7 @@ void CL_ParseTempEntity( sizebuf_t *msg ) TEMPENTITY *pTemp; cl_entity_t *pEnt; dlight_t *dl; + sound_t hSound; if( cls.legacymode ) iSize = MSG_ReadByte( msg ); @@ -1922,6 +1923,10 @@ void CL_ParseTempEntity( sizebuf_t *msg ) decalIndex = modelIndex = entityIndex = 0; + // this will probably be fatal anyway + if( iSize > sizeof( pbuf )) + Con_Printf( S_ERROR "%s: Temp buffer overflow!\n", __FUNCTION__ ); + // parse user message into buffer MSG_ReadBytes( msg, pbuf, iSize ); @@ -2014,15 +2019,46 @@ void CL_ParseTempEntity( sizebuf_t *msg ) color = MSG_ReadByte( &buf ); count = MSG_ReadByte( &buf ); R_ParticleExplosion2( pos, color, count ); + + dl = CL_AllocDlight( 0 ); + VectorCopy( pos, dl->origin ); + dl->radius = 350; + dl->die = cl.time + 0.5; + dl->decay = 300; + + hSound = S_RegisterSound( cl_explode_sounds[0] ); + S_StartSound( pos, 0, CHAN_STATIC, hSound, VOL_NORM, 0.6f, PITCH_NORM, 0 ); break; case TE_BSPDECAL: + case TE_DECAL: + case TE_WORLDDECAL: + case TE_WORLDDECALHIGH: + case TE_DECALHIGH: pos[0] = MSG_ReadCoord( &buf ); pos[1] = MSG_ReadCoord( &buf ); pos[2] = MSG_ReadCoord( &buf ); - decalIndex = MSG_ReadShort( &buf ); - entityIndex = MSG_ReadShort( &buf ); - if( entityIndex ) modelIndex = MSG_ReadShort( &buf ); - CL_DecalShoot( CL_DecalIndex( decalIndex ), entityIndex, modelIndex, pos, FDECAL_PERMANENT ); + if( type == TE_BSPDECAL ) + { + decalIndex = MSG_ReadShort( &buf ); + entityIndex = MSG_ReadShort( &buf ); + if( entityIndex ) + modelIndex = MSG_ReadShort( &buf ); + else modelIndex = 0; + } + else + { + decalIndex = MSG_ReadByte( &buf ); + if( type == TE_DECALHIGH || type == TE_WORLDDECALHIGH ) + decalIndex += 256; + + if( type == TE_DECALHIGH || type == TE_DECAL ) + entityIndex = MSG_ReadShort( &buf ); + else entityIndex = 0; + + pEnt = CL_GetEntityByIndex( entityIndex ); + modelIndex = pEnt->curstate.modelindex; + } + CL_DecalShoot( CL_DecalIndex( decalIndex ), entityIndex, modelIndex, pos, type == TE_BSPDECAL ? FDECAL_PERMANENT : 0 ); break; case TE_IMPLOSION: pos[0] = MSG_ReadCoord( &buf ); @@ -2042,10 +2078,12 @@ void CL_ParseTempEntity( sizebuf_t *msg ) pos2[2] = MSG_ReadCoord( &buf ); modelIndex = MSG_ReadShort( &buf ); count = MSG_ReadByte( &buf ); - life = (float)(MSG_ReadByte( &buf ) * 0.1f); - scale = (float)(MSG_ReadByte( &buf ) * 0.1f); - vel = (float)MSG_ReadByte( &buf ); - random = (float)MSG_ReadByte( &buf ); + life = (float)MSG_ReadByte( &buf ) * 0.1f; + scale = (float)MSG_ReadByte( &buf ); + if( !scale ) scale = 1.0f; + else scale *= 0.1f; + vel = (float)MSG_ReadByte( &buf ) * 10; + random = (float)MSG_ReadByte( &buf ) * 10; R_Sprite_Trail( type, pos, pos2, modelIndex, count, life, scale, random, 255, vel ); break; case TE_SPRITE: @@ -2053,36 +2091,23 @@ void CL_ParseTempEntity( sizebuf_t *msg ) pos[1] = MSG_ReadCoord( &buf ); pos[2] = MSG_ReadCoord( &buf ); modelIndex = MSG_ReadShort( &buf ); - scale = (float)(MSG_ReadByte( &buf ) * 0.1f); - brightness = (float)MSG_ReadByte( &buf ); + scale = (float)MSG_ReadByte( &buf ) * 0.1f; + brightness = (float)MSG_ReadByte( &buf ) / 255.0f; - if(( pTemp = R_DefaultSprite( pos, modelIndex, 0 )) != NULL ) - { - pTemp->entity.curstate.scale = scale; - pTemp->entity.baseline.renderamt = brightness; - pTemp->entity.curstate.renderamt = brightness; - pTemp->entity.curstate.rendermode = kRenderTransAdd; - } + R_TempSprite( pos, vec3_origin, scale, modelIndex, + kRenderTransAdd, kRenderFxNone, brightness, 0.0, FTENT_SPRANIMATE ); break; case TE_GLOWSPRITE: pos[0] = MSG_ReadCoord( &buf ); pos[1] = MSG_ReadCoord( &buf ); pos[2] = MSG_ReadCoord( &buf ); modelIndex = MSG_ReadShort( &buf ); - life = (float)(MSG_ReadByte( &buf ) * 0.1f); - scale = (float)(MSG_ReadByte( &buf ) * 0.1f); - brightness = (float)MSG_ReadByte( &buf ); + life = (float)MSG_ReadByte( &buf ) * 0.1f; + scale = (float)MSG_ReadByte( &buf ) * 0.1f; + brightness = (float)MSG_ReadByte( &buf ) / 255.0f; - if(( pTemp = R_DefaultSprite( pos, modelIndex, 0 )) != NULL ) - { - pTemp->entity.curstate.scale = scale; - pTemp->entity.curstate.rendermode = kRenderGlow; - pTemp->entity.curstate.renderfx = kRenderFxNoDissipation; - pTemp->entity.baseline.renderamt = brightness; - pTemp->entity.curstate.renderamt = brightness; - pTemp->flags = FTENT_FADEOUT; - pTemp->die = cl.time + life; - } + R_TempSprite( pos, vec3_origin, scale, modelIndex, + kRenderGlow, kRenderFxNoDissipation, brightness, life, FTENT_FADEOUT ); break; case TE_STREAK_SPLASH: pos[0] = MSG_ReadCoord( &buf ); @@ -2172,23 +2197,6 @@ void CL_ParseTempEntity( sizebuf_t *msg ) pos2[2] = MSG_ReadCoord( &buf ); R_ShowLine( pos, pos2 ); break; - case TE_DECAL: - case TE_DECALHIGH: - case TE_WORLDDECAL: - case TE_WORLDDECALHIGH: - pos[0] = MSG_ReadCoord( &buf ); - pos[1] = MSG_ReadCoord( &buf ); - pos[2] = MSG_ReadCoord( &buf ); - decalIndex = MSG_ReadByte( &buf ); - if( type == TE_DECAL || type == TE_DECALHIGH ) - entityIndex = MSG_ReadShort( &buf ); - else entityIndex = 0; - if( type == TE_DECALHIGH || type == TE_WORLDDECALHIGH ) - decalIndex += 256; - pEnt = CL_GetEntityByIndex( entityIndex ); - if( pEnt ) modelIndex = pEnt->curstate.modelindex; - CL_DecalShoot( CL_DecalIndex( decalIndex ), entityIndex, modelIndex, pos, 0 ); - break; case TE_FIZZ: entityIndex = MSG_ReadShort( &buf ); modelIndex = MSG_ReadShort( &buf ); @@ -2244,10 +2252,11 @@ void CL_ParseTempEntity( sizebuf_t *msg ) pos[2] = MSG_ReadCoord( &buf ); entityIndex = MSG_ReadShort( &buf ); decalIndex = MSG_ReadByte( &buf ); - pEnt = CL_GetEntityByIndex( entityIndex ); CL_DecalShoot( CL_DecalIndex( decalIndex ), entityIndex, 0, pos, 0 ); R_BulletImpactParticles( pos ); - R_RicochetSound( pos ); + flags = COM_RandomLong( 0, 0x7fff ); + if( flags < 0x3fff ) + R_RicochetSound_( pos, flags % 5 ); break; case TE_SPRAY: case TE_SPRITE_SPRAY: @@ -2393,7 +2402,7 @@ void CL_ParseTempEntity( sizebuf_t *msg ) // throw warning if( MSG_CheckOverflow( &buf )) - Con_DPrintf( S_WARN "ParseTempEntity: overflow TE message\n" ); + Con_DPrintf( S_WARN "ParseTempEntity: overflow TE message %i\n", type ); } @@ -2693,7 +2702,7 @@ apply various effects to entity origin or attachment void CL_AddEntityEffects( cl_entity_t *ent ) { // yellow flies effect 'monster stuck in the wall' - if( FBitSet( ent->curstate.effects, EF_BRIGHTFIELD )) + if( FBitSet( ent->curstate.effects, EF_BRIGHTFIELD ) && !RP_LOCALCLIENT( ent )) R_EntityParticles( ent ); if ( FBitSet( ent->curstate.effects, EF_DIMLIGHT )) @@ -2917,7 +2926,7 @@ void CL_PlayerDecal( int playernum, int customIndex, int entityIndex, float *pos { if( FBitSet( pCust->resource.ucFlags, RES_CUSTOM ) && pCust->resource.type == t_decal && pCust->bTranslated ) { - if( !pCust->nUserData1 && pCust->pInfo != NULL ) + if( !pCust->nUserData1 ) { const char *decalname = va( "player%dlogo%d", playernum, customIndex ); pCust->nUserData1 = GL_LoadTextureInternal( decalname, pCust->pInfo, TF_DECAL ); diff --git a/engine/client/cl_view.c b/engine/client/cl_view.c index 26930bd1..310d3aac 100644 --- a/engine/client/cl_view.c +++ b/engine/client/cl_view.c @@ -35,30 +35,39 @@ void V_CalcViewRect( void ) int sb_lines; float size; - // intermission is always full screen - if( cl.intermission ) size = 120.0f; - else size = scr_viewsize->value; + if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + { + // intermission is always full screen + if( cl.intermission ) size = 120.0f; + else size = scr_viewsize->value; - if( size >= 120.0f ) - sb_lines = 0; // no status bar at all - else if( size >= 110.0f ) - sb_lines = 24; // no inventory - else sb_lines = 48; + if( size >= 120.0f ) + sb_lines = 0; // no status bar at all + else if( size >= 110.0f ) + sb_lines = 24; // no inventory + else sb_lines = 48; - if( scr_viewsize->value >= 100.0f ) + if( scr_viewsize->value >= 100.0f ) + { + full = true; + size = 100.0f; + } + else size = scr_viewsize->value; + + if( cl.intermission ) + { + size = 100.0f; + sb_lines = 0; + full = true; + } + size /= 100.0f; + } + else { full = true; - size = 100.0f; - } - else size = scr_viewsize->value; - - if( cl.intermission ) - { - size = 100.0f; sb_lines = 0; - full = true; + size = 1.0f; } - size /= 100.0f; clgame.viewport[2] = refState.width * size; clgame.viewport[3] = refState.height * size; diff --git a/engine/client/client.h b/engine/client/client.h index dc50afcc..fc4f9a9c 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -465,7 +465,7 @@ typedef struct string cdtracks[MAX_CDTRACKS]; // 32 cd-tracks read from cdaudio.txt - model_t sprites[MAX_CLIENT_SPRITES]; // client spritetextures + model_t sprites[MAX_CLIENT_SPRITES]; // hud&client spritetexturesz int viewport[4]; // viewport sizes client_draw_t ds; // draw2d stuff (hud, weaponmenu etc) @@ -527,9 +527,6 @@ typedef struct float disable_screen; // showing loading plaque between levels // or changing rendering dlls // if time gets > 30 seconds ahead, break it - int disable_servercount; // when we receive a frame and cl.servercount - // > cls.disable_servercount, clear disable_screen - qboolean draw_changelevel; // draw changelevel image 'Loading...' keydest_t key_dest; @@ -625,8 +622,6 @@ typedef struct // legacy mode support qboolean legacymode; // one-way 48 protocol compatibility netadr_t legacyserver; - netadr_t legacyservers[MAX_LEGACY_SERVERS]; - int legacyservercount; int extensions; netadr_t serveradr; @@ -707,7 +702,7 @@ dlight_t *CL_GetEntityLight( int number ); // // cl_cmds.c // -void CL_Quit_f( void ) NORETURN; +void CL_Quit_f( void ); void CL_ScreenShot_f( void ); void CL_SnapShot_f( void ); void CL_PlayCDTrack_f( void ); @@ -1048,7 +1043,6 @@ void Con_Bottom( void ); void Con_Top( void ); void Con_PageDown( int lines ); void Con_PageUp( int lines ); -void Con_LoadHistory( void ); // // s_main.c @@ -1112,6 +1106,11 @@ void pfnPIC_DrawAdditive( int x, int y, int width, int height, const wrect_t *pr qboolean Mobile_Init( void ); void Mobile_Shutdown( void ); +// +// cl_securedstub.c +// +void CL_GetSecuredClientAPI( CL_EXPORT_FUNCS F ); + // // cl_video.c // diff --git a/engine/client/console.c b/engine/client/console.c index c908972e..bae1a35d 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -81,6 +81,14 @@ typedef struct con_lineinfo_s double addtime; // notify stuff } con_lineinfo_t; +typedef struct con_history_s +{ + field_t lines[CON_HISTORY]; + field_t backup; + int line; // the line being displayed from history buffer will be <= nextHistoryLine + int next; // the last line in the history buffer, not masked +} con_history_t; + typedef struct { qboolean initialized; @@ -118,10 +126,8 @@ typedef struct string chat_cmd; // can be overrieded by user // console history - field_t historyLines[CON_HISTORY]; - int historyLine; // the line being displayed from history buffer will be <= nextHistoryLine - int nextHistoryLine; // the last line in the history buffer, not masked - field_t backup; + con_history_t history; + qboolean historyLoaded; notify_t notify[MAX_DBG_NOTIFY]; // for Con_NXPrintf qboolean draw_notify; // true if we have NXPrint message @@ -135,6 +141,9 @@ static console_t con; void Con_ClearField( field_t *edit ); void Field_CharEvent( field_t *edit, int ch ); +static void Con_LoadHistory( con_history_t *self ); +static void Con_SaveHistory( con_history_t *self ); + /* ================ Con_Clear_f @@ -198,8 +207,6 @@ Con_ClearTyping */ void Con_ClearTyping( void ) { - int i; - Con_ClearField( &con.input ); con.input.widthInChars = con.linewidth; @@ -276,6 +283,8 @@ void Con_ToggleConsole_f( void ) if( !host.allow_console || UI_CreditsActive( )) return; // disabled + SCR_EndLoadingPlaque(); + // show console only in game or by special call from menu if( cls.state != ca_active || cls.key_dest == key_menu ) return; @@ -486,7 +495,7 @@ void Con_CheckResize( void ) con.input.widthInChars = con.linewidth; for( i = 0; i < CON_HISTORY; i++ ) - con.historyLines[i].widthInChars = con.linewidth; + con.history.lines[i].widthInChars = con.linewidth; } /* @@ -1062,7 +1071,7 @@ int Con_DrawGenericString( int x, int y, const char *string, rgba_t setColor, qb Con_UtfProcessChar( 0 ); // draw the colored text - *(uint *)color = *(uint *)setColor; + memcpy( color, setColor, sizeof( color )); s = string; while( *s ) @@ -1113,61 +1122,6 @@ int Con_DrawString( int x, int y, const char *string, rgba_t setColor ) return Con_DrawGenericString( x, y, string, setColor, false, -1 ); } -void Con_LoadHistory( void ) -{ - const byte *aFile = FS_LoadFile( "console_history.txt", NULL, true ); - const char *pLine = (char *)aFile, *pFile = (char *)aFile; - int i; - - if( !aFile ) - return; - - while( true ) - { - if( !*pFile ) - break; - if( *pFile == '\n') - { - int len = pFile - pLine + 1; - field_t *f; - if( len > 255 ) len = 255; - Con_ClearField( &con.historyLines[con.nextHistoryLine] ); - f = &con.historyLines[con.nextHistoryLine % CON_HISTORY]; - f->widthInChars = con.linewidth; - f->cursor = len - 1; - Q_strncpy( f->buffer, pLine, len); - con.nextHistoryLine++; - pLine = pFile + 1; - } - pFile++; - } - - for( i = con.nextHistoryLine; i < CON_HISTORY; i++ ) - { - Con_ClearField( &con.historyLines[i] ); - con.historyLines[i].widthInChars = con.linewidth; - } - - con.historyLine = con.nextHistoryLine; - -} - -void Con_SaveHistory( void ) -{ - int historyStart = con.nextHistoryLine - CON_HISTORY; - int i; - file_t *f; - - if( historyStart < 0 ) - historyStart = 0; - - f = FS_Open("console_history.txt", "w", true ); - - for( i = historyStart; i < con.nextHistoryLine; i++ ) - FS_Printf( f, "%s\n", con.historyLines[i % CON_HISTORY].buffer ); - - FS_Close(f); -} /* ================ @@ -1207,7 +1161,7 @@ void Con_Init( void ) con.chat.widthInChars = con.linewidth; Cmd_AddCommand( "toggleconsole", Con_ToggleConsole_f, "opens or closes the console" ); - Cmd_AddCommand( "clear", Con_Clear_f, "clear console history" ); + Cmd_AddRestrictedCommand( "clear", Con_Clear_f, "clear console history" ); Cmd_AddCommand( "messagemode", Con_MessageMode_f, "enable message mode \"say\"" ); Cmd_AddCommand( "messagemode2", Con_MessageMode2_f, "enable message mode \"say_team\"" ); Cmd_AddCommand( "contimes", Con_SetTimes_f, "change number of console overlay lines (4-64)" ); @@ -1233,7 +1187,7 @@ void Con_Shutdown( void ) con.buffer = NULL; con.lines = NULL; - Con_SaveHistory(); + Con_SaveHistory( &con.history ); } /* @@ -1468,11 +1422,22 @@ Con_ClearField */ void Con_ClearField( field_t *edit ) { - memset(edit->buffer, 0, MAX_STRING); + memset( edit->buffer, 0, MAX_STRING ); edit->cursor = 0; edit->scroll = 0; } +/* +================ +Field_Set +================ +*/ +static void Field_Set( field_t *f, const char *string ) +{ + f->scroll = 0; + f->cursor = Q_strncpy( f->buffer, string, MAX_STRING ); +} + /* ================ Field_Paste @@ -1492,6 +1457,18 @@ void Field_Paste( field_t *edit ) Field_CharEvent( edit, cbd[i] ); } +/* +================= +Field_GoTo + +================= +*/ +static void Field_GoTo( field_t *edit, int pos ) +{ + edit->cursor = pos; + edit->scroll = Q_max( 0, edit->cursor - edit->widthInChars ); +} + /* ================= Field_KeyDownEvent @@ -1544,20 +1521,20 @@ void Field_KeyDownEvent( field_t *edit, int key ) if( key == K_LEFTARROW ) { - if( edit->cursor > 0 ) edit->cursor= Con_UtfMoveLeft( edit->buffer, edit->cursor ); + if( edit->cursor > 0 ) edit->cursor = Con_UtfMoveLeft( edit->buffer, edit->cursor ); if( edit->cursor < edit->scroll ) edit->scroll--; return; } if( key == K_HOME || ( Q_tolower(key) == 'a' && Key_IsDown( K_CTRL ))) { - edit->cursor = 0; + Field_GoTo( edit, 0 ); return; } if( key == K_END || ( Q_tolower(key) == 'e' && Key_IsDown( K_CTRL ))) { - edit->cursor = len; + Field_GoTo( edit, len ); return; } @@ -1596,16 +1573,14 @@ void Field_CharEvent( field_t *edit, int ch ) if( ch == 'a' - 'a' + 1 ) { // ctrl-a is home - edit->cursor = 0; - edit->scroll = 0; + Field_GoTo( edit, 0 ); return; } if( ch == 'e' - 'a' + 1 ) { // ctrl-e is end - edit->cursor = len; - edit->scroll = edit->cursor - edit->widthInChars; + Field_GoTo( edit, len ); return; } @@ -1708,12 +1683,134 @@ void Field_DrawInputLine( int x, int y, field_t *edit ) } } +/* +============================================================================= + +CONSOLE HISTORY HANDLING + +============================================================================= +*/ +/* +=================== +Con_HistoryUp + +=================== +*/ +static void Con_HistoryUp( con_history_t *self, field_t *in ) +{ + if( self->line == self->next ) + self->backup = *in; + + if(( self->next - self->line ) < CON_HISTORY ) + self->line = Q_max( 0, self->line - 1 ); + + *in = self->lines[self->line % CON_HISTORY]; +} + +/* +=================== +Con_HistoryDown + +=================== +*/ +static void Con_HistoryDown( con_history_t *self, field_t *in ) +{ + self->line = Q_min( self->next, self->line + 1 ); + if( self->line == self->next ) + *in = self->backup; + else *in = self->lines[self->line % CON_HISTORY]; +} + +/* +=================== +Con_HistoryAppend +=================== +*/ +static void Con_HistoryAppend( con_history_t *self, field_t *from ) +{ + int prevLine = Q_max( 0, self->line - 1 ); + const char *buf = from->buffer; + + // skip backslashes + if( from->buffer[0] == '\\' || from->buffer[1] == '/' ) + buf++; + + // only if non-empty + if( !from->buffer[0] ) + return; + + // skip empty commands + if( Q_isspace( buf )) + return; + + // if not copy (don't ignore backslashes) + if( !Q_strcmp( from->buffer, self->lines[prevLine % CON_HISTORY].buffer )) + return; + + self->lines[self->next % CON_HISTORY] = *from; + self->line = ++self->next; +} + +static void Con_LoadHistory( con_history_t *self ) +{ + const byte *aFile = FS_LoadFile( "console_history.txt", NULL, true ); + const char *pLine, *pFile; + int i, len; + field_t *f; + + if( !aFile ) + return; + + for( pFile = pLine = (char *)aFile; *pFile; pFile++ ) + { + if( *pFile != '\n' ) + continue; + + Con_ClearField( &self->lines[self->next] ); + + len = Q_min( pFile - pLine + 1, sizeof( f->buffer )); + f = &self->lines[self->next % CON_HISTORY]; + f->widthInChars = con.linewidth; + f->cursor = len - 1; + Q_strncpy( f->buffer, pLine, len); + + self->next++; + + pLine = pFile + 1; + } + + for( i = self->next; i < CON_HISTORY; i++ ) + { + Con_ClearField( &self->lines[i] ); + self->lines[i].widthInChars = con.linewidth; + } + + self->line = self->next; +} + +static void Con_SaveHistory( con_history_t *self ) +{ + int historyStart = self->next - CON_HISTORY, i; + file_t *f; + + if( historyStart < 0 ) + historyStart = 0; + + f = FS_Open( "console_history.txt", "w", true ); + + for( i = historyStart; i < self->next; i++ ) + FS_Printf( f, "%s\n", self->lines[i % CON_HISTORY].buffer ); + + FS_Close( f ); +} + + /* ============================================================================= CONSOLE LINE EDITING -============================================================================== +============================================================================= */ /* ==================== @@ -1734,16 +1831,6 @@ void Key_Console( int key ) // enter finishes the line if( key == K_ENTER || key == K_KP_ENTER ) { - // if not in the game explicitly prepent a slash if needed - if( cls.state != ca_active && con.input.buffer[0] != '\\' && con.input.buffer[0] != '/' ) - { - char temp[MAX_SYSPATH]; - - Q_strncpy( temp, con.input.buffer, sizeof( temp )); - Q_sprintf( con.input.buffer, "\\%s", temp ); - con.input.cursor++; - } - // backslash text are commands, else chat if( con.input.buffer[0] == '\\' || con.input.buffer[0] == '/' ) Cbuf_AddText( con.input.buffer + 1 ); // skip backslash @@ -1754,9 +1841,7 @@ void Key_Console( int key ) Con_Printf( ">%s\n", con.input.buffer ); // copy line to history buffer - con.historyLines[con.nextHistoryLine % CON_HISTORY] = con.input; - con.nextHistoryLine++; - con.historyLine = con.nextHistoryLine; + Con_HistoryAppend( &con.history, &con.input ); Con_ClearField( &con.input ); con.input.widthInChars = con.linewidth; @@ -1781,23 +1866,13 @@ void Key_Console( int key ) // command history (ctrl-p ctrl-n for unix style) if(( key == K_MWHEELUP && Key_IsDown( K_SHIFT )) || ( key == K_UPARROW ) || (( Q_tolower(key) == 'p' ) && Key_IsDown( K_CTRL ))) { - if( con.historyLine == con.nextHistoryLine ) - con.backup = con.input; - if( con.nextHistoryLine - con.historyLine < CON_HISTORY && con.historyLine > 0 ) - con.historyLine--; - con.input = con.historyLines[con.historyLine % CON_HISTORY]; + Con_HistoryUp( &con.history, &con.input ); return; } if(( key == K_MWHEELDOWN && Key_IsDown( K_SHIFT )) || ( key == K_DOWNARROW ) || (( Q_tolower(key) == 'n' ) && Key_IsDown( K_CTRL ))) { - if( con.historyLine >= con.nextHistoryLine - 1 ) - con.input = con.backup; - else - { - con.historyLine++; - con.input = con.historyLines[con.historyLine % CON_HISTORY]; - } + Con_HistoryDown( &con.history, &con.input ); return; } @@ -2101,6 +2176,9 @@ void Con_DrawSolidConsole( int lines ) int i, x, y; float fraction; int start; + int stringLen, width = 0, charH; + string curbuild; + byte color[4]; if( lines <= 0 ) return; @@ -2114,28 +2192,20 @@ void Con_DrawSolidConsole( int lines ) if( !con.curFont || !host.allow_console ) return; // nothing to draw - if( host.allow_console ) - { - // draw current version - int stringLen, width = 0, charH; - string curbuild; - byte color[4]; + // draw current version + memcpy( color, g_color_table[7], sizeof( color )); - memcpy( color, g_color_table[7], sizeof( color )); + Q_snprintf( curbuild, MAX_STRING, "%s %i/%s (%s-%s build %i)", XASH_ENGINE_NAME, PROTOCOL_VERSION, XASH_VERSION, Q_buildos(), Q_buildarch(), Q_buildnum( )); + Con_DrawStringLen( curbuild, &stringLen, &charH ); + start = refState.width - stringLen; + stringLen = Con_StringLength( curbuild ); - Q_snprintf( curbuild, MAX_STRING, "%s %i/%s (%s-%s build %i)", XASH_ENGINE_NAME, PROTOCOL_VERSION, XASH_VERSION, Q_buildos(), Q_buildarch(), Q_buildnum( )); + fraction = lines / (float)refState.height; + color[3] = Q_min( fraction * 2.0f, 1.0f ) * 255; // fadeout version number - Con_DrawStringLen( curbuild, &stringLen, &charH ); - start = refState.width - stringLen; - stringLen = Con_StringLength( curbuild ); - - fraction = lines / (float)refState.height; - color[3] = Q_min( fraction * 2.0f, 1.0f ) * 255; // fadeout version number - - for( i = 0; i < stringLen; i++ ) - width += Con_DrawCharacter( start + width, 0, curbuild[i], color ); - } + for( i = 0; i < stringLen; i++ ) + width += Con_DrawCharacter( start + width, 0, curbuild[i], color ); // draw the text if( CON_LINES_COUNT > 0 ) @@ -2172,7 +2242,7 @@ void Con_DrawSolidConsole( int lines ) Con_DrawInput( lines ); y = lines - ( con.curFont->charHeight * 1.2f ); - SCR_DrawFPS( max( y, 4 )); // to avoid to hide fps counter + SCR_DrawFPS( Q_max( y, 4 )); // to avoid to hide fps counter ref.dllFuncs.Color4ub( 255, 255, 255, 255 ); } @@ -2388,6 +2458,12 @@ INTERNAL RESOURCE */ void Con_VidInit( void ) { + if( !con.historyLoaded ) + { + Con_LoadHistory( &con.history ); + con.historyLoaded = true; + } + Con_LoadConchars(); Con_CheckResize(); #if XASH_LOW_MEMORY @@ -2423,7 +2499,6 @@ void Con_VidInit( void ) } } - if( !con.background ) // last chance - quake conback image { qboolean draw_to_console = false; @@ -2511,3 +2586,48 @@ void GAME_EXPORT Con_DefaultColor( int r, int g, int b ) b = bound( 0, b, 255 ); MakeRGBA( g_color_table[7], r, g, b, 255 ); } + +#if XASH_ENGINE_TESTS +#include "tests.h" + +static void Test_RunConHistory( void ) +{ + con_history_t hist = { 0 }; + field_t input = { 0 }; + const char *strs1[] = { "map t0a0", "quit", "wtf", "wtf", "", "nyan" }; + const char *strs2[] = { "nyan", "wtf", "quit", "map t0a0" }; + const char *testbackup = "unfinished_edit"; + int i; + + for( i = 0; i < ARRAYSIZE( strs1 ); i++ ) + { + Field_Set( &input, strs1[i] ); + Con_HistoryAppend( &hist, &input ); + } + + Field_Set( &input, testbackup ); + + for( i = 0; i < ARRAYSIZE( strs2 ); i++ ) + { + Con_HistoryUp( &hist, &input ); + TASSERT_STR( input.buffer, strs2[i] ); + } + + // check for overrun + Con_HistoryUp( &hist, &input ); + + for( i = ARRAYSIZE( strs2 ) - 1; i >= 0; i-- ) + { + TASSERT_STR( input.buffer, strs2[i] ); + Con_HistoryDown( &hist, &input ); + } + + TASSERT_STR( input.buffer, testbackup ); +} + +void Test_RunCon( void ) +{ + TRUN( Test_RunConHistory() ); +} + +#endif /* XASH_ENGINE_TESTS */ diff --git a/engine/client/in_touch.c b/engine/client/in_touch.c index 8a72bc04..5bd454d0 100644 --- a/engine/client/in_touch.c +++ b/engine/client/in_touch.c @@ -503,20 +503,14 @@ void Touch_SetClientOnly( byte state ) touch.move_finger = touch.look_finger = -1; touch.forward = touch.side = 0; - /// TODO: touch sdl platform -#if 0 if( state ) { - SDL_SetRelativeMouseMode( SDL_FALSE ); - SDL_ShowCursor( true ); IN_DeactivateMouse(); } else { - SDL_ShowCursor( false ); - SDL_GetRelativeMouseState( 0, 0 ); + IN_ActivateMouse(); } -#endif } static void Touch_SetClientOnly_f( void ) @@ -536,7 +530,7 @@ static void Touch_RemoveButtonFromList( touchbuttonlist_t *list, const char *nam IN_TouchEditClear(); - while(( button = Touch_FindFirst( &touch.list_user, name, !privileged ))) + while(( button = Touch_FindFirst( &touch.list_user, name, privileged ))) { if( button->prev ) button->prev->next = button->next; @@ -792,7 +786,6 @@ static touch_button_t *Touch_AddButton( touchbuttonlist_t *list, button->flags = privileged ? 0 : TOUCH_FL_UNPRIVILEGED | TOUCH_FL_CLIENT; MakeRGBA( button->color, color[0], color[1], color[2], color[3] ); button->command[0] = 0; - button->flags = 0; button->fade = 1; Touch_SetCommand( button, command ); diff --git a/engine/client/input.c b/engine/client/input.c index 69143274..69c41ca8 100644 --- a/engine/client/input.c +++ b/engine/client/input.c @@ -18,7 +18,7 @@ GNU General Public License for more details. #include "client.h" #include "vgui_draw.h" -#ifdef XASH_SDL +#if XASH_SDL #include #endif @@ -30,17 +30,16 @@ qboolean in_mouseinitialized; qboolean in_mouse_suspended; POINT in_lastvalidpos; qboolean in_mouse_savedpos; +static int in_mstate = 0; static struct inputstate_s { float lastpitch, lastyaw; } inputstate; extern convar_t *vid_fullscreen; -convar_t *m_enginemouse; convar_t *m_pitch; convar_t *m_yaw; -convar_t *m_enginesens; convar_t *m_ignore; convar_t *cl_forwardspeed; convar_t *cl_sidespeed; @@ -48,6 +47,8 @@ convar_t *cl_backspeed; convar_t *look_filter; convar_t *m_rawinput; +static qboolean s_bRawInput, s_bMouseGrab; + /* ================ IN_CollectInputDevices @@ -113,8 +114,6 @@ void IN_StartupMouse( void ) { m_ignore = Cvar_Get( "m_ignore", DEFAULT_M_IGNORE, FCVAR_ARCHIVE | FCVAR_FILTERABLE, "ignore mouse events" ); - m_enginemouse = Cvar_Get( "m_enginemouse", "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "read mouse events in engine instead of client" ); - m_enginesens = Cvar_Get( "m_enginesens", "0.3", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "mouse sensitivity, when m_enginemouse enabled" ); m_pitch = Cvar_Get( "m_pitch", "0.022", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "mouse pitch value" ); m_yaw = Cvar_Get( "m_yaw", "0.022", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "mouse yaw value" ); look_filter = Cvar_Get( "look_filter", "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "filter look events making it smoother" ); @@ -127,21 +126,9 @@ void IN_StartupMouse( void ) in_mouseinitialized = true; } -static void IN_ActivateCursor( void ) -{ - if( cls.key_dest == key_menu ) - { -#ifdef XASH_SDL - SDL_SetCursor( in_mousecursor ); -#endif - } -} - void GAME_EXPORT IN_SetCursor( void *hCursor ) { - in_mousecursor = hCursor; - - IN_ActivateCursor(); + // stub } /* @@ -190,47 +177,15 @@ void IN_ToggleClientMouse( int newstate, int oldstate ) if( oldstate == key_game ) { - if( cls.initialized ) - clgame.dllFuncs.IN_DeactivateMouse(); + IN_DeactivateMouse(); } else if( newstate == key_game ) { - // reset mouse pos, so cancel effect in game -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - if( CVAR_TO_BOOL( touch_enable ) ) - { - SDL_SetRelativeMouseMode( SDL_FALSE ); - SDL_SetWindowGrab( host.hWnd, SDL_FALSE ); - } - else -#endif - { - Platform_SetMousePos( host.window_center_x, host.window_center_y ); -#if XASH_SDL - SDL_SetWindowGrab( host.hWnd, SDL_TRUE ); -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - if ( clgame.dllFuncs.pfnLookEvent || ( clgame.client_dll_uses_sdl && CVAR_TO_BOOL( m_rawinput ) ) ) - { - SDL_SetRelativeMouseMode( SDL_TRUE ); - } -#endif -#endif // XASH_SDL - } - if( cls.initialized ) - clgame.dllFuncs.IN_ActivateMouse(); + IN_ActivateMouse(); } if( ( newstate == key_menu || newstate == key_console || newstate == key_message ) && ( !CL_IsBackgroundMap() || CL_IsBackgroundDemo( ))) { -#ifdef XASH_SDL - SDL_SetWindowGrab(host.hWnd, SDL_FALSE); -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - if ( clgame.dllFuncs.pfnLookEvent || ( clgame.client_dll_uses_sdl && CVAR_TO_BOOL( m_rawinput ) ) ) - { - SDL_SetRelativeMouseMode( SDL_FALSE ); - } -#endif -#endif // XASH_SDL #if XASH_ANDROID Android_ShowMouse( true ); #endif @@ -249,6 +204,65 @@ void IN_ToggleClientMouse( int newstate, int oldstate ) } } +void IN_CheckMouseState( qboolean active ) +{ +#if XASH_WIN32 + qboolean useRawInput = CVAR_TO_BOOL( m_rawinput ) && clgame.client_dll_uses_sdl || clgame.dllFuncs.pfnLookEvent; +#else + qboolean useRawInput = true; // always use SDL code +#endif + + if( active && useRawInput && !host.mouse_visible && cls.state == ca_active ) + { + if( !s_bRawInput ) + { +#if XASH_SDL == 2 + SDL_GetRelativeMouseState( NULL, NULL ); + SDL_SetRelativeMouseMode( SDL_TRUE ); +#endif + + // Con_Printf( "Enable relative mode\n" ); + s_bRawInput = true; + } + } + else + { + if( s_bRawInput ) + { +#if XASH_SDL == 2 + SDL_GetRelativeMouseState( NULL, NULL ); + SDL_SetRelativeMouseMode( SDL_FALSE ); +#endif + // Con_Printf( "Disable relative mode\n" ); + s_bRawInput = false; + } + } + + if( active && !host.mouse_visible && cls.state == ca_active ) + { + if( !s_bMouseGrab ) + { +#if XASH_SDL + SDL_SetWindowGrab( host.hWnd, SDL_TRUE ); +#endif + // Con_Printf( "Enable grab\n" ); + s_bMouseGrab = true; + } + } + else + { + if( s_bMouseGrab ) + { +#if XASH_SDL + SDL_SetWindowGrab( host.hWnd, SDL_FALSE ); +#endif + + // Con_Printf( "Disable grab\n" ); + s_bMouseGrab = false; + } + } +} + /* =========== IN_ActivateMouse @@ -256,58 +270,15 @@ IN_ActivateMouse Called when the window gains focus or changes in some way =========== */ -void IN_ActivateMouse( qboolean force ) +void IN_ActivateMouse( void ) { - int width, height; - static int oldstate; - if( !in_mouseinitialized ) return; - if( CL_Active() && host.mouse_visible && !force ) - return; // VGUI controls - - if( cls.key_dest == key_menu && !Cvar_VariableInteger( "fullscreen" )) - { - // check for mouse leave-entering - if( !in_mouse_suspended && !UI_MouseInRect( )) - in_mouse_suspended = true; - - if( oldstate != in_mouse_suspended ) - { - if( in_mouse_suspended ) - { -#ifdef XASH_SDL - /// TODO: Platform_ShowCursor - if( !touch_emulate ) - SDL_ShowCursor( SDL_FALSE ); -#endif - UI_ShowCursor( false ); - } - } - - oldstate = in_mouse_suspended; - - if( in_mouse_suspended ) - { - in_mouse_suspended = false; - in_mouseactive = false; // re-initialize mouse - UI_ShowCursor( true ); - } - } - - if( in_mouseactive ) return; - in_mouseactive = true; - - if( UI_IsVisible( )) return; - - if( cls.key_dest == key_game ) - { + IN_CheckMouseState( true ); + if( clgame.dllFuncs.IN_ActivateMouse ) clgame.dllFuncs.IN_ActivateMouse(); -#ifdef XASH_SDL - SDL_GetRelativeMouseState( 0, 0 ); // Reset mouse position -#endif - } + in_mouseactive = true; } /* @@ -319,20 +290,17 @@ Called when the window loses focus */ void IN_DeactivateMouse( void ) { - if( !in_mouseinitialized || !in_mouseactive ) + if( !in_mouseinitialized ) return; - if( cls.key_dest == key_game ) - { + IN_CheckMouseState( false ); + if( clgame.dllFuncs.IN_DeactivateMouse ) clgame.dllFuncs.IN_DeactivateMouse(); - } - in_mouseactive = false; -#ifdef XASH_SDL - SDL_SetWindowGrab( host.hWnd, SDL_FALSE ); -#endif // XASH_SDL } + + /* ================ IN_MouseMove @@ -342,7 +310,7 @@ void IN_MouseMove( void ) { POINT current_pos; - if( !in_mouseinitialized || !in_mouseactive || !UI_IsVisible( )) + if( !in_mouseinitialized ) return; // find mouse movement @@ -352,15 +320,13 @@ void IN_MouseMove( void ) // HACKHACK: show cursor in UI, as mainui doesn't call // platform-dependent SetCursor anymore -#ifdef XASH_SDL +#if XASH_SDL if( UI_IsVisible() ) SDL_ShowCursor( SDL_TRUE ); #endif // if the menu is visible, move the menu cursor UI_MouseMove( current_pos.x, current_pos.y ); - - IN_ActivateCursor(); } /* @@ -368,73 +334,32 @@ void IN_MouseMove( void ) IN_MouseEvent =========== */ -void IN_MouseEvent( void ) +void IN_MouseEvent( int key, int down ) { int i; - // touch emu: handle motion - if( CVAR_TO_BOOL( touch_emulate )) - { - if( Key_IsDown( K_SHIFT ) ) - Touch_KeyEvent( K_MOUSE2, 2 ); - else - Touch_KeyEvent( K_MOUSE1, 2 ); - } - - if( !in_mouseinitialized || !in_mouseactive ) + if( !in_mouseinitialized ) return; - - if( m_ignore->value ) - return; + if( down ) + SetBits( in_mstate, BIT( key )); + else ClearBits( in_mstate, BIT( key )); if( cls.key_dest == key_game ) { -#if defined( XASH_SDL ) - static qboolean ignore; // igonre mouse warp event - int x, y; - Platform_GetMousePos(&x, &y); - if( host.mouse_visible ) - SDL_ShowCursor( SDL_TRUE ); - else if( !CVAR_TO_BOOL( touch_emulate ) ) - SDL_ShowCursor( SDL_FALSE ); + // perform button actions + VGui_KeyEvent( K_MOUSE1 + key, down ); - if( x < host.window_center_x / 2 || - y < host.window_center_y / 2 || - x > host.window_center_x + host.window_center_x / 2 || - y > host.window_center_y + host.window_center_y / 2 ) - { - Platform_SetMousePos( host.window_center_x, host.window_center_y ); - ignore = 1; // next mouse event will be mouse warp - return; - } - - if ( !ignore ) - { - if( !m_enginemouse->value ) - { - // a1ba: mouse keys are now separated - // so pass 0 here - clgame.dllFuncs.IN_MouseEvent( 0 ); - } - } - else - { - SDL_GetRelativeMouseState( 0, 0 ); // reset relative state - ignore = 0; - } -#endif - return; + // don't do Key_Event here + // client may override IN_MouseEvent + // but by default it calls back to Key_Event anyway + if( in_mouseactive ) + clgame.dllFuncs.IN_MouseEvent( in_mstate ); } else { -#if XASH_SDL && !XASH_WIN32 -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - SDL_SetRelativeMouseMode( SDL_FALSE ); -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) - SDL_ShowCursor( SDL_TRUE ); -#endif // XASH_SDL && !XASH_WIN32 - IN_MouseMove(); + // perform button actions + Key_Event( K_MOUSE1 + key, down ); } } @@ -571,13 +496,9 @@ static void IN_JoyAppendMove( usercmd_t *cmd, float forwardmove, float sidemove } } -void IN_CollectInput( float *forward, float *side, float *pitch, float *yaw, qboolean includeMouse, qboolean includeSdlMouse ) +static void IN_CollectInput( float *forward, float *side, float *pitch, float *yaw, qboolean includeMouse ) { - if( includeMouse -#if XASH_SDL - && includeSdlMouse -#endif - ) + if( includeMouse ) { float x, y; Platform_MouseMove( &x, &y ); @@ -626,7 +547,7 @@ void IN_EngineAppendMove( float frametime, void *cmd1, qboolean active ) { float sensitivity = 1;//( (float)cl.local.scr_fov / (float)90.0f ); - IN_CollectInput( &forward, &side, &pitch, &yaw, in_mouseinitialized && !CVAR_TO_BOOL( m_ignore ), m_enginemouse->value ); + IN_CollectInput( &forward, &side, &pitch, &yaw, false ); IN_JoyAppendMove( cmd, forward, side ); @@ -635,32 +556,22 @@ void IN_EngineAppendMove( float frametime, void *cmd1, qboolean active ) cmd->viewangles[YAW] += yaw * sensitivity; cmd->viewangles[PITCH] += pitch * sensitivity; cmd->viewangles[PITCH] = bound( -90, cmd->viewangles[PITCH], 90 ); - VectorCopy(cmd->viewangles, cl.viewangles); + VectorCopy( cmd->viewangles, cl.viewangles ); } } } -/* -================== -Host_InputFrame - -Called every frame, even if not generating commands -================== -*/ -void Host_InputFrame( void ) +void IN_Commands( void ) { - qboolean shutdownMouse = false; - float forward = 0, side = 0, pitch = 0, yaw = 0; - - Sys_SendKeyEvents (); - #ifdef XASH_USE_EVDEV IN_EvdevFrame(); #endif if( clgame.dllFuncs.pfnLookEvent ) { - IN_CollectInput( &forward, &side, &pitch, &yaw, in_mouseinitialized && !CVAR_TO_BOOL( m_ignore ), true ); + float forward = 0, side = 0, pitch = 0, yaw = 0; + + IN_CollectInput( &forward, &side, &pitch, &yaw, in_mouseinitialized && !CVAR_TO_BOOL( m_ignore ) ); if( cls.key_dest == key_game ) { @@ -672,22 +583,21 @@ void Host_InputFrame( void ) if( !in_mouseinitialized ) return; - if( host.status != HOST_FRAME ) - { - IN_DeactivateMouse(); - return; - } + IN_CheckMouseState( in_mouseactive ); +} - // release mouse during pause or console typeing - if( cl.paused && cls.key_dest == key_game ) - shutdownMouse = true; +/* +================== +Host_InputFrame - if( shutdownMouse && !Cvar_VariableInteger( "fullscreen" )) - { - IN_DeactivateMouse(); - return; - } +Called every frame, even if not generating commands +================== +*/ +void Host_InputFrame( void ) +{ + Sys_SendKeyEvents (); + + IN_Commands(); - IN_ActivateMouse( false ); IN_MouseMove(); } diff --git a/engine/client/input.h b/engine/client/input.h index 184220df..32ad2b6b 100644 --- a/engine/client/input.h +++ b/engine/client/input.h @@ -33,8 +33,8 @@ extern qboolean in_mouseinitialized; void IN_Init( void ); void Host_InputFrame( void ); void IN_Shutdown( void ); -void IN_MouseEvent( void ); -void IN_ActivateMouse( qboolean force ); +void IN_MouseEvent( int key, int down ); +void IN_ActivateMouse( void ); void IN_DeactivateMouse( void ); void IN_MouseSavePos( void ); void IN_MouseRestorePos( void ); diff --git a/engine/client/keys.c b/engine/client/keys.c index fce03218..682f44ad 100644 --- a/engine/client/keys.c +++ b/engine/client/keys.c @@ -814,9 +814,9 @@ void Key_EnableTextInput( qboolean enable, qboolean force ) OSK_EnableTextInput( enable, force ); return; } - if( enable && ( !host.textmode || force ) ) + if( enable && ( !host.textmode || force )) Platform_EnableTextInput( true ); - else if( !enable ) + else if( !enable && ( host.textmode || force )) Platform_EnableTextInput( false ); host.textmode = enable; @@ -1044,7 +1044,7 @@ static qboolean OSK_KeyEvent( int key, int down ) else osk.curlayout++; - osk.shift = osk.curbutton.val == OSK_SHIFT; + osk.shift = true; osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x]; break; case OSK_BACKSPACE: diff --git a/engine/client/ref_common.c b/engine/client/ref_common.c index 639bdbb3..22a3e364 100644 --- a/engine/client/ref_common.c +++ b/engine/client/ref_common.c @@ -335,10 +335,6 @@ static ref_api_t gEngfuncs = COM_FreeLibrary, COM_GetProcAddress, - FS_LoadFile, - FS_FileExists, - FS_AllowDirectPaths, - R_Init_Video_, R_Free_Video, @@ -386,6 +382,8 @@ static ref_api_t gEngfuncs = pfnDrawTransparentTriangles, &clgame.drawFuncs, + &g_fsapi, + XVK_GetInstanceExtensions, XVK_GetVkGetInstanceProcAddr, XVK_CreateSurface, diff --git a/engine/client/s_dsp.c b/engine/client/s_dsp.c index 39a54d4d..d0bbca0b 100644 --- a/engine/client/s_dsp.c +++ b/engine/client/s_dsp.c @@ -33,7 +33,6 @@ GNU General Public License for more details. #define MAXDLY (STEREODLY + 1) #define MAXLP 10 -#define MAXPRESETS 29 typedef struct sx_preset_s { @@ -79,7 +78,7 @@ typedef struct dly_s int *lpdelayline; } dly_t; -const sx_preset_t rgsxpre[MAXPRESETS] = +const sx_preset_t rgsxpre[] = { // -------reverb-------- -------delay-------- // lp mod size refl rvblp delay feedback dlylp left @@ -116,7 +115,7 @@ const sx_preset_t rgsxpre[MAXPRESETS] = // 0x0045dca8 enginegl.exe // SHA256: 42383d32cd712e59ee2c1bd78b7ba48814e680e7026c4223e730111f34a60d66 -const sx_preset_t rgsxpre_hlalpha052[MAXPRESETS] = +const sx_preset_t rgsxpre_hlalpha052[] = { // -------reverb-------- -------delay-------- // lp mod size refl rvblp delay feedback dlylp left @@ -482,7 +481,7 @@ void DLY_CheckNewDelayVal( void ) } else { - delay = min( delay, MAX_MONO_DELAY ); + delay = Q_min( delay, MAX_MONO_DELAY ); dly->delaysamples = (int)(delay * idsp_dma_speed) << sxhires; // init dly @@ -882,7 +881,7 @@ void CheckNewDspPresets( void ) else idsp_room = room_type->value; // don't pass invalid presets - idsp_room = bound( 0, idsp_room, MAXPRESETS - 1 ); + idsp_room = bound( 0, idsp_room, MAX_ROOM_TYPES ); if( FBitSet( hisound->flags, FCVAR_CHANGED )) { @@ -893,9 +892,6 @@ void CheckNewDspPresets( void ) if( idsp_room == room_typeprev && idsp_room == 0 ) return; - if( idsp_room > MAX_ROOM_TYPES ) - return; - if( idsp_room != room_typeprev ) { const sx_preset_t *cur; diff --git a/engine/client/s_main.c b/engine/client/s_main.c index ba7ed298..6c926552 100644 --- a/engine/client/s_main.c +++ b/engine/client/s_main.c @@ -533,6 +533,8 @@ void S_StartSound( const vec3_t pos, int ent, int chan, sound_t handle, float fv // spatialize memset( target_chan, 0, sizeof( *target_chan )); + pitch *= (sys_timescale.value + 1) / 2; + VectorCopy( pos, target_chan->origin ); target_chan->staticsound = ( ent == 0 ) ? true : false; target_chan->use_loop = (flags & SND_STOP_LOOPING) ? false : true; @@ -788,6 +790,8 @@ void S_AmbientSound( const vec3_t pos, int ent, sound_t handle, float fvol, floa return; } + pitch *= (sys_timescale.value + 1) / 2; + // never update positions if source entity is 0 ch->staticsound = ( ent == 0 ) ? true : false; ch->use_loop = (flags & SND_STOP_LOOPING) ? false : true; @@ -1151,7 +1155,6 @@ void S_RawSamples( uint samples, uint rate, word width, word channels, const byt int snd_vol = 128; if( entnum < 0 ) snd_vol = 256; // bg track or movie track - if( snd_vol < 0 ) snd_vol = 0; // fixup negative values S_RawEntSamples( entnum, samples, rate, width, channels, data, snd_vol ); } diff --git a/engine/client/s_mix.c b/engine/client/s_mix.c index f79f7aee..8cceac98 100644 --- a/engine/client/s_mix.c +++ b/engine/client/s_mix.c @@ -809,7 +809,7 @@ void S_MixBufferUpsample2x( int count, portable_samplepair_t *pbuffer, portable_ // zero out all paintbuffers void MIX_ClearAllPaintBuffers( int SampleCount, qboolean clearFilters ) { - int count = min( SampleCount, PAINTBUFFER_SIZE ); + int count = Q_min( SampleCount, PAINTBUFFER_SIZE ); int i; // zero out all paintbuffer data (ignore sampleCount) diff --git a/engine/client/s_vox.c b/engine/client/s_vox.c index b421fe35..73818943 100644 --- a/engine/client/s_vox.c +++ b/engine/client/s_vox.c @@ -16,407 +16,12 @@ GNU General Public License for more details. #include "common.h" #include "sound.h" #include "const.h" +#include "sequence.h" #include -sentence_t g_Sentences[MAX_SENTENCES]; -static uint g_numSentences; -static char *rgpparseword[CVOXWORDMAX]; // array of pointers to parsed words -static char voxperiod[] = "_period"; // vocal pause -static char voxcomma[] = "_comma"; // vocal pause - -static int IsNextWord( const char c ) -{ - if( c == '.' || c == ',' || c == ' ' || c == '(' ) - return 1; - return 0; -} - -static int IsSkipSpace( const char c ) -{ - if( c == ',' || c == '.' || c == ' ' ) - return 1; - return 0; -} - -static int IsWhiteSpace( const char space ) -{ - if( space == ' ' || space == '\t' || space == '\r' || space == '\n' ) - return 1; - return 0; -} - -static int IsCommandChar( const char c ) -{ - if( c == 'v' || c == 'p' || c == 's' || c == 'e' || c == 't' ) - return 1; - return 0; -} - -static int IsDelimitChar( const char c ) -{ - if( c == '(' || c == ')' ) - return 1; - return 0; -} - -static char *ScanForwardUntil( char *string, const char scan ) -{ - while( string[0] ) - { - if( string[0] == scan ) - return string; - string++; - } - return string; -} - -// backwards scan psz for last '/' -// return substring in szpath null terminated -// if '/' not found, return 'vox/' -static char *VOX_GetDirectory( char *szpath, char *psz ) -{ - char c; - int cb = 0, len; - char *p; - - len = Q_strlen( psz ); - p = psz + len - 1; - - // scan backwards until first '/' or start of string - c = *p; - while( p > psz && c != '/' ) - { - c = *( --p ); - cb++; - } - - if( c != '/' ) - { - // didn't find '/', return default directory - Q_strcpy( szpath, "vox/" ); - return psz; - } - - cb = len - cb; - memcpy( szpath, psz, cb ); - szpath[cb] = 0; - - return p + 1; -} - -// scan g_Sentences, looking for pszin sentence name -// return pointer to sentence data if found, null if not -// CONSIDER: if we have a large number of sentences, should -// CONSIDER: sort strings in g_Sentences and do binary search. -char *VOX_LookupString( const char *pSentenceName, int *psentencenum ) -{ - int i; - - if( Q_isdigit( pSentenceName ) && (i = Q_atoi( pSentenceName )) < g_numSentences ) - { - if( psentencenum ) *psentencenum = i; - return (g_Sentences[i].pName + Q_strlen( g_Sentences[i].pName ) + 1 ); - } - - for( i = 0; i < g_numSentences; i++ ) - { - if( !Q_stricmp( pSentenceName, g_Sentences[i].pName )) - { - if( psentencenum ) *psentencenum = i; - return (g_Sentences[i].pName + Q_strlen( g_Sentences[i].pName ) + 1 ); - } - } - - return NULL; -} - -// parse a null terminated string of text into component words, with -// pointers to each word stored in rgpparseword -// note: this code actually alters the passed in string! -char **VOX_ParseString( char *psz ) -{ - int i, fdone = 0; - char c, *p = psz; - - memset( rgpparseword, 0, sizeof( char* ) * CVOXWORDMAX ); - - if( !psz ) return NULL; - - i = 0; - rgpparseword[i++] = psz; - - while( !fdone && i < CVOXWORDMAX ) - { - // scan up to next word - c = *p; - while( c && !IsNextWord( c )) - c = *(++p); - - // if '(' then scan for matching ')' - if( c == '(' ) - { - p = ScanForwardUntil( p, ')' ); - c = *(++p); - if( !c ) fdone = 1; - } - - if( fdone || !c ) - { - fdone = 1; - } - else - { - // if . or , insert pause into rgpparseword, - // unless this is the last character - if(( c == '.' || c == ',' ) && *(p+1) != '\n' && *(p+1) != '\r' && *(p+1) != 0 ) - { - if( c == '.' ) rgpparseword[i++] = voxperiod; - else rgpparseword[i++] = voxcomma; - - if( i >= CVOXWORDMAX ) - break; - } - - // null terminate substring - *p++ = 0; - - // skip whitespace - c = *p; - while( c && IsSkipSpace( c )) - c = *(++p); - - if( !c ) fdone = 1; - else rgpparseword[i++] = p; - } - } - - return rgpparseword; -} - -float VOX_GetVolumeScale( channel_t *pchan ) -{ - if( pchan->currentWord ) - { - if ( pchan->words[pchan->wordIndex].volume ) - { - float volume = pchan->words[pchan->wordIndex].volume * 0.01f; - if( volume < 1.0f ) return volume; - } - } - - return 1.0f; -} - -void VOX_SetChanVol( channel_t *ch ) -{ - float scale; - - if( !ch->currentWord ) - return; - - scale = VOX_GetVolumeScale( ch ); - if( scale == 1.0f ) return; - - ch->rightvol = (int)(ch->rightvol * scale); - ch->leftvol = (int)(ch->leftvol * scale); -} - -float VOX_ModifyPitch( channel_t *ch, float pitch ) -{ - if( ch->currentWord ) - { - if( ch->words[ch->wordIndex].pitch > 0 ) - { - pitch += ( ch->words[ch->wordIndex].pitch - PITCH_NORM ) * 0.01f; - } - } - - return pitch; -} - -//=============================================================================== -// Get any pitch, volume, start, end params into voxword -// and null out trailing format characters -// Format: -// someword(v100 p110 s10 e20) -// -// v is volume, 0% to n% -// p is pitch shift up 0% to n% -// s is start wave offset % -// e is end wave offset % -// t is timecompression % -// -// pass fFirst == 1 if this is the first string in sentence -// returns 1 if valid string, 0 if parameter block only. -// -// If a ( xxx ) parameter block does not directly follow a word, -// then that 'default' parameter block will be used as the default value -// for all following words. Default parameter values are reset -// by another 'default' parameter block. Default parameter values -// for a single word are overridden for that word if it has a parameter block. -// -//=============================================================================== -int VOX_ParseWordParams( char *psz, voxword_t *pvoxword, int fFirst ) -{ - char *pszsave = psz; - char c, ct, sznum[8]; - static voxword_t voxwordDefault; - int i; - - // init to defaults if this is the first word in string. - if( fFirst ) - { - voxwordDefault.pitch = -1; - voxwordDefault.volume = 100; - voxwordDefault.start = 0; - voxwordDefault.end = 100; - voxwordDefault.fKeepCached = 0; - voxwordDefault.timecompress = 0; - } - - *pvoxword = voxwordDefault; - - // look at next to last char to see if we have a - // valid format: - c = *( psz + Q_strlen( psz ) - 1 ); - - // no formatting, return - if( c != ')' ) return 1; - - // scan forward to first '(' - c = *psz; - while( !IsDelimitChar( c )) - c = *(++psz); - - // bogus formatting - if( c == ')' ) return 0; - - // null terminate - *psz = 0; - ct = *(++psz); - - while( 1 ) - { - // scan until we hit a character in the commandSet - while( ct && !IsCommandChar( ct )) - ct = *(++psz); - - if( ct == ')' ) - break; - - memset( sznum, 0, sizeof( sznum )); - i = 0; - - c = *(++psz); - - if( !isdigit( c )) - break; - - // read number - while( isdigit( c ) && i < sizeof( sznum ) - 1 ) - { - sznum[i++] = c; - c = *(++psz); - } - - // get value of number - i = Q_atoi( sznum ); - - switch( ct ) - { - case 'v': pvoxword->volume = i; break; - case 'p': pvoxword->pitch = i; break; - case 's': pvoxword->start = i; break; - case 'e': pvoxword->end = i; break; - case 't': pvoxword->timecompress = i; break; - } - - ct = c; - } - - // if the string has zero length, this was an isolated - // parameter block. Set default voxword to these - // values - if( Q_strlen( pszsave ) == 0 ) - { - voxwordDefault = *pvoxword; - return 0; - } - - return 1; -} - -void VOX_LoadWord( channel_t *pchan ) -{ - if( pchan->words[pchan->wordIndex].sfx ) - { - wavdata_t *pSource = S_LoadSound( pchan->words[pchan->wordIndex].sfx ); - - if( pSource ) - { - int start = pchan->words[pchan->wordIndex].start; - int end = pchan->words[pchan->wordIndex].end; - - // apply mixer - pchan->currentWord = &pchan->pMixer; - pchan->currentWord->pData = pSource; - - // don't allow overlapped ranges - if( end <= start ) end = 0; - - if( start || end ) - { - int sampleCount = pSource->samples; - - if( start ) - { - S_SetSampleStart( pchan, pSource, (int)(sampleCount * 0.01f * start)); - } - - if( end ) - { - S_SetSampleEnd( pchan, pSource, (int)(sampleCount * 0.01f * end)); - } - } - } - } -} - -void VOX_FreeWord( channel_t *pchan ) -{ - pchan->currentWord = NULL; // sentence is finished - memset( &pchan->pMixer, 0, sizeof( pchan->pMixer )); - - // release unused sounds - if( pchan->words[pchan->wordIndex].sfx ) - { - // If this wave wasn't precached by the game code - if( !pchan->words[pchan->wordIndex].fKeepCached ) - { - FS_FreeSound( pchan->words[pchan->wordIndex].sfx->cache ); - pchan->words[pchan->wordIndex].sfx->cache = NULL; - pchan->words[pchan->wordIndex].sfx = NULL; - } - } -} - -void VOX_LoadFirstWord( channel_t *pchan, voxword_t *pwords ) -{ - int i = 0; - - // copy each pointer in the sfx temp array into the - // sentence array, and set the channel to point to the - // sentence array - while( pwords[i].sfx != NULL ) - { - pchan->words[i] = pwords[i]; - i++; - } - pchan->words[i].sfx = NULL; - - pchan->wordIndex = 0; - VOX_LoadWord( pchan ); -} +static int cszrawsentences = 0; +static char *rgpszrawsentence[CVOXFILESENTENCEMAX]; +static const char *voxperiod = "_period", *voxcomma = "_comma"; // return number of samples mixed int VOX_MixDataToDevice( channel_t *pchan, int sampleCount, int outputRate, int outputOffset ) @@ -451,244 +56,561 @@ int VOX_MixDataToDevice( channel_t *pchan, int sampleCount, int outputRate, int return outputOffset - startingOffset; } -// link all sounds in sentence, start playing first word. -void VOX_LoadSound( channel_t *pchan, const char *pszin ) +void VOX_LoadWord( channel_t *ch ) { - char buffer[512]; - int i, cword; - char pathbuffer[64]; - char szpath[32]; - voxword_t rgvoxword[CVOXWORDMAX]; - char *psz; + const voxword_t *word = &ch->words[ch->wordIndex]; + wavdata_t *data; + int start, end, samples; - if( !pszin || !*pszin ) + if( !word->sfx ) return; - memset( rgvoxword, 0, sizeof( voxword_t ) * CVOXWORDMAX ); - memset( buffer, 0, sizeof( buffer )); + data = S_LoadSound( word->sfx ); - // lookup actual string in g_Sentences, - // set pointer to string data - if( pszin[0] == '#' ) - psz = (char *)pszin + 1; - else - psz = VOX_LookupString( pszin, NULL ); + if( !data ) + return; + + ch->currentWord = &ch->pMixer; + ch->currentWord->pData = data; + + samples = data->samples; + start = word->start; + end = word->end; + + if( end <= start ) end = 0; + + if( start ) + S_SetSampleStart( ch, data, start * 0.01f * samples ); + + if( end ) + S_SetSampleEnd( ch, data, end * 0.01f * samples ); +} + +void VOX_FreeWord( channel_t *ch ) +{ + voxword_t *word = &ch->words[ch->wordIndex]; + + ch->currentWord = NULL; + memset( &ch->pMixer, 0, sizeof( ch->pMixer )); + + if( !word->sfx && !word->fKeepCached ) + return; + + FS_FreeSound( word->sfx->cache ); + word->sfx->cache = NULL; + word->sfx = NULL; +} + +void VOX_SetChanVol( channel_t *ch ) +{ + voxword_t *word; + if( !ch->currentWord ) + return; + + word = &ch->words[ch->wordIndex]; + + if( word->volume == 100 ) + return; + + ch->leftvol = ch->leftvol * word->volume * 0.01f; + ch->rightvol = ch->rightvol * word->volume * 0.01f; +} + +float VOX_ModifyPitch( channel_t *ch, float pitch ) +{ + voxword_t *word; + if( !ch->currentWord ) + return pitch; + + word = &ch->words[ch->wordIndex]; + + if( word->pitch < 0 ) + return pitch; + + pitch += ( word->pitch - PITCH_NORM ) * 0.01f; + + return pitch; +} + +static const char *VOX_GetDirectory( char *szpath, const char *psz, int nsize ) +{ + const char *p; + int len; + + // search / backwards + p = Q_strrchr( psz, '/' ); + + if( !p ) + { + Q_strcpy( szpath, "vox/" ); + return psz; + } + + len = p - psz + 1; + + if( len > nsize ) + { + Con_Printf( "VOX_GetDirectory: invalid directory in: %s\n", psz ); + return NULL; + } + + memcpy( szpath, psz, len ); + szpath[len] = 0; + + return p + 1; +} + +static const char *VOX_LookupString( const char *pszin ) +{ + int i = -1, len; + const char *c; + + // check if we are a CSCZ or immediate sentence + if( *pszin == '#' ) + { + // Q_atoi is too smart and allows negative values + // so check with Q_isdigit beforehand + if( Q_isdigit( pszin + 1 )) + { + sentenceEntry_s *sentenceEntry; + i = Q_atoi( pszin + 1 ); + if(( sentenceEntry = Sequence_GetSentenceByIndex( i ))) + return sentenceEntry->data; + } + else + { + // immediate sentence, probably coming from "speak" command + return pszin + 1; + } + } + + // check if we received an index + if( Q_isdigit( pszin )) + { + i = Q_atoi( pszin ); + + if( i >= cszrawsentences ) + i = -1; + } + + // last hope: find it in sentences array + if( i == -1 ) + { + for( i = 0; i < cszrawsentences; i++ ) + { + if( !Q_stricmp( pszin, rgpszrawsentence[i] )) + break; + } + } + + // not found, exit + if( i == cszrawsentences ) + return NULL; + + len = Q_strlen( rgpszrawsentence[i] ); + + c = &rgpszrawsentence[i][len + 1]; + for( ; *c == ' ' || *c == '\t'; c++ ); + + return c; +} + +static int VOX_ParseString( char *psz, char *rgpparseword[CVOXWORDMAX] ) +{ + int i = 0; + + if( !psz ) + return i; + + rgpparseword[i++] = psz; + + while( i < CVOXWORDMAX ) + { + // skip to next word + for( ; *psz && + *psz != ' ' && + *psz != '.' && + *psz != ',' && + *psz != '('; psz++ ); + + // skip anything in between ( and ) + if( *psz == '(' ) + { + for( ; *psz && *psz != ')'; psz++ ); + psz++; + } + + if( !*psz ) + return i; + + // . and , are special but if not end of string + if(( *psz == '.' || *psz == ',' ) && + psz[1] != '\n' && psz[1] != '\r' && psz[1] != '\0' ) + { + if( *psz == '.' ) + rgpparseword[i++] = (char *)voxperiod; + else rgpparseword[i++] = (char *)voxcomma; + + if( i >= CVOXWORDMAX ) + return i; + } + + *psz++ = 0; + + for( ; *psz && ( *psz == '.' || *psz == ' ' || *psz == ',' ); + psz++ ); + + if( !*psz ) + return i; + + rgpparseword[i++] = psz; + } + + return i; +} + +static qboolean VOX_ParseWordParams( char *psz, voxword_t *pvoxword, qboolean fFirst ) +{ + int len, i; + char sznum[8], *pszsave = psz; + static voxword_t voxwordDefault; + + if( fFirst ) + { + voxwordDefault.fKeepCached = 0; + voxwordDefault.pitch = -1; + voxwordDefault.volume = 100; + voxwordDefault.start = 0; + voxwordDefault.end = 100; + voxwordDefault.timecompress = 0; + } + + *pvoxword = voxwordDefault; + + len = Q_strlen( psz ); + + if( len == 0 ) + return false; + + // no special params + if( psz[len-1] != ')' ) + return true; + + for( ; *psz != '(' && *psz != ')'; psz++ ); + + // invalid syntax + if( *psz == ')' ) + return false; + + // split filename and params + *psz++ = '\0'; + + for( ;; ) + { + char command; + + // find command + for( ; *psz && + *psz != 'v' && + *psz != 'p' && + *psz != 's' && + *psz != 'e' && + *psz != 't'; psz++ ) + { + if( *psz == ')' ) + break; + } + + command = *psz++; + + if( !isdigit( *psz )) + break; + + memset( sznum, 0, sizeof( sznum )); + for( i = 0; i < sizeof( sznum ) - 1 && isdigit( *psz ); i++, psz++ ) + sznum[i] = *psz; + + i = Q_atoi( sznum ); + switch( command ) + { + case 'e': pvoxword->end = i; break; + case 'p': pvoxword->pitch = i; break; + case 's': pvoxword->start = i; break; + case 't': pvoxword->timecompress = i; break; + case 'v': pvoxword->volume = i; break; + } + } + + // no actual word but new defaults + if( Q_strlen( pszsave ) == 0 ) + { + voxwordDefault = *pvoxword; + return false; + } + + return true; +} + +void VOX_LoadSound( channel_t *ch, const char *pszin ) +{ + char buffer[512], szpath[32], pathbuffer[64]; + char *rgpparseword[CVOXWORDMAX]; + const char *psz; + int i, j; + + if( !pszin ) + return; + + memset( buffer, 0, sizeof( buffer )); + memset( rgpparseword, 0, sizeof( rgpparseword )); + + psz = VOX_LookupString( pszin ); if( !psz ) { - Con_DPrintf( S_ERROR "VOX_LoadSound: no such sentence %s\n", pszin ); + Con_Printf( "VOX_LoadSound: no sentence named %s\n", pszin ); return; } - // get directory from string, advance psz - psz = VOX_GetDirectory( szpath, psz ); + psz = VOX_GetDirectory( szpath, psz, sizeof( szpath )); - if( Q_strlen( psz ) > sizeof( buffer ) - 1 ) + if( !psz ) { - Con_Printf( S_ERROR "VOX_LoadSound: sentence is too long %s\n", psz ); + Con_Printf( "VOX_LoadSound: failed getting directory for %s\n", pszin ); return; } - // copy into buffer - Q_strcpy( buffer, psz ); - psz = buffer; - - // parse sentence (also inserts null terminators between words) - VOX_ParseString( psz ); - - // for each word in the sentence, construct the filename, - // lookup the sfx and save each pointer in a temp array - - i = 0; - cword = 0; - while( rgpparseword[i] ) + if( Q_strlen( psz ) >= sizeof( buffer ) ) { - // Get any pitch, volume, start, end params into voxword - if( VOX_ParseWordParams( rgpparseword[i], &rgvoxword[cword], i == 0 )) - { - // this is a valid word (as opposed to a parameter block) - Q_strcpy( pathbuffer, szpath ); - Q_strncat( pathbuffer, rgpparseword[i], sizeof( pathbuffer )); - Q_strncat( pathbuffer, ".wav", sizeof( pathbuffer )); - - // find name, if already in cache, mark voxword - // so we don't discard when word is done playing - rgvoxword[cword].sfx = S_FindName( pathbuffer, &( rgvoxword[cword].fKeepCached )); - cword++; - } - i++; + Con_Printf( "VOX_LoadSound: sentence is too long %s", psz ); + return; } - VOX_LoadFirstWord( pchan, rgvoxword ); + Q_strncpy( buffer, psz, sizeof( buffer )); + VOX_ParseString( buffer, rgpparseword ); - pchan->isSentence = true; - pchan->sfx = rgvoxword[0].sfx; + j = 0; + for( i = 0; rgpparseword[i]; i++ ) + { + if( !VOX_ParseWordParams( rgpparseword[i], &ch->words[j], i == 0 )) + continue; + + Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s%s.wav", szpath, rgpparseword[i] ); + + ch->words[j].sfx = S_FindName( pathbuffer, &ch->words[j].fKeepCached ); + + j++; + } + + ch->words[j].sfx = NULL; + ch->sfx = ch->words[0].sfx; + ch->wordIndex = 0; + ch->isSentence = true; + + VOX_LoadWord( ch ); } -//----------------------------------------------------------------------------- -// Purpose: Take a NULL terminated sentence, and parse any commands contained in -// {}. The string is rewritten in place with those commands removed. -// -// Input : *pSentenceData - sentence data to be modified in place -// sentenceIndex - global sentence table index for any data that is -// parsed out -//----------------------------------------------------------------------------- -void VOX_ParseLineCommands( char *pSentenceData, int sentenceIndex ) +static void VOX_ReadSentenceFile_( byte *buf, fs_offset_t size ) { - char tempBuffer[512]; - char *pNext, *pStart; - int length, tempBufferPos = 0; + char *p, *last; - if( !pSentenceData ) - return; + p = (char *)buf; + last = p + size; - pStart = pSentenceData; - - while( *pSentenceData ) + while( p < last ) { - pNext = ScanForwardUntil( pSentenceData, '{' ); + char *name = NULL, *value = NULL; - // find length of "good" portion of the string (not a {} command) - length = pNext - pSentenceData; - if( tempBufferPos + length > sizeof( tempBuffer )) + if( cszrawsentences >= CVOXFILESENTENCEMAX ) + break; + + for( ; p < last && ( *p == '\n' || *p == '\r' || *p == '\t' || *p == ' ' ); + p++ ); + + if( *p != '/' ) { - Con_Printf( S_ERROR "Sentence too long (max length %lu characters)\n", sizeof(tempBuffer) - 1 ); - return; + name = p; + + for( ; p < last && *p != ' ' && *p != '\t' ; p++ ); + + if( p < last ) + *p++ = 0; + + value = p; } - // Copy good string to temp buffer - memcpy( tempBuffer + tempBufferPos, pSentenceData, length ); + for( ; p < last && *p != '\n' && *p != '\r'; p++ ); - // move the copy position - tempBufferPos += length; + if( p < last ) + *p++ = 0; - pSentenceData = pNext; - - // skip ahead of the opening brace - if( *pSentenceData ) pSentenceData++; - - // skip whitespace - while( *pSentenceData && *pSentenceData <= 32 ) - pSentenceData++; - - // simple comparison of string commands: - switch( Q_tolower( *pSentenceData )) + if( name ) { - case 'l': - // all commands starting with the letter 'l' here - if( !Q_strnicmp( pSentenceData, "len", 3 )) - { - g_Sentences[sentenceIndex].length = Q_atof( pSentenceData + 3 ); - } - break; - case 0: - default: - break; + int index = cszrawsentences; + int size = strlen( name ) + strlen( value ) + 2; + + rgpszrawsentence[index] = Mem_Malloc( host.mempool, size ); + memcpy( rgpszrawsentence[index], name, size ); + rgpszrawsentence[index][size - 1] = 0; + cszrawsentences++; } - - pSentenceData = ScanForwardUntil( pSentenceData, '}' ); - - // skip the closing brace - if( *pSentenceData ) pSentenceData++; - - // skip trailing whitespace - while( *pSentenceData && *pSentenceData <= 32 ) - pSentenceData++; - } - - if( tempBufferPos < sizeof( tempBuffer )) - { - // terminate cleaned up copy - tempBuffer[tempBufferPos] = 0; - - // copy it over the original data - Q_strcpy( pStart, tempBuffer ); } } -// Load sentence file into memory, insert null terminators to -// delimit sentence name/sentence pairs. Keep pointer to each -// sentence name so we can search later. -void VOX_ReadSentenceFile( const char *psentenceFileName ) +static void VOX_ReadSentenceFile( const char *path ) { - char c, *pch, *pFileData; - char *pchlast, *pSentenceData; - fs_offset_t fileSize; + byte *buf; + fs_offset_t size; - // load file - pFileData = (char *)FS_LoadFile( psentenceFileName, &fileSize, false ); - if( !pFileData ) return; // this game just doesn't used vox sound system + VOX_Shutdown(); - pch = pFileData; - pchlast = pch + fileSize; + buf = FS_LoadFile( path, &size, false ); + if( !buf ) return; - while( pch < pchlast ) - { - if( g_numSentences >= MAX_SENTENCES ) - { - Con_Printf( S_ERROR "VOX_Init: too many sentences specified, max is %d\n", MAX_SENTENCES ); - break; - } + VOX_ReadSentenceFile_( buf, size ); - // only process this pass on sentences - pSentenceData = NULL; - - // skip newline, cr, tab, space - - c = *pch; - while( pch < pchlast && IsWhiteSpace( c )) - c = *(++pch); - - // skip entire line if first char is / - if( *pch != '/' ) - { - sentence_t *pSentence = &g_Sentences[g_numSentences++]; - - pSentence->pName = pch; - pSentence->length = 0; - - // scan forward to first space, insert null terminator - // after sentence name - - c = *pch; - while( pch < pchlast && c != ' ' ) - c = *(++pch); - - if( pch < pchlast ) - *pch++ = 0; - - // a sentence may have some line commands, make an extra pass - pSentenceData = pch; - } - - // scan forward to end of sentence or eof - while( pch < pchlast && pch[0] != '\n' && pch[0] != '\r' ) - pch++; - - // insert null terminator - if( pch < pchlast ) *pch++ = 0; - - // If we have some sentence data, parse out any line commands - if( pSentenceData && pSentenceData < pchlast ) - { - int index = g_numSentences - 1; - - // the current sentence has an index of count-1 - VOX_ParseLineCommands( pSentenceData, index ); - } - } + Mem_Free( buf ); } void VOX_Init( void ) { - memset( g_Sentences, 0, sizeof( g_Sentences )); - g_numSentences = 0; - VOX_ReadSentenceFile( DEFAULT_SOUNDPATH "sentences.txt" ); } - void VOX_Shutdown( void ) { - g_numSentences = 0; + int i; + + for( i = 0; i < cszrawsentences; i++ ) + Mem_Free( rgpszrawsentence[i] ); + + cszrawsentences = 0; } + +#if XASH_ENGINE_TESTS +#include "tests.h" + +static void Test_VOX_GetDirectory( void ) +{ + const char *data[] = + { + "", "", "vox/", + "bark bark", "bark bark", "vox/", + "barney/meow", "meow", "barney/" + + }; + int i; + + for( i = 0; i < sizeof( data ) / sizeof( data[0] ); i += 3 ) + { + string szpath; + const char *p = VOX_GetDirectory( szpath, data[i+0], sizeof( szpath )); + + TASSERT_STR( p, data[i+1] ); + TASSERT_STR( szpath, data[i+2] ); + } +} + +static void Test_VOX_LookupString( void ) +{ + int i; + const char *p, *data[] = + { + "0", "123", + "3", "SPAAACE", + "-2", NULL, + "404", NULL, + "not found", NULL, + "exactmatch", "123", + "caseinsensitive", "456", + "SentenceWithTabs", "789", + "SentenceWithSpaces", "SPAAACE", + }; + + VOX_Shutdown(); + + rgpszrawsentence[cszrawsentences++] = (char*)"exactmatch\000123"; + rgpszrawsentence[cszrawsentences++] = (char*)"CaseInsensitive\000456"; + rgpszrawsentence[cszrawsentences++] = (char*)"SentenceWithTabs\0\t\t\t789"; + rgpszrawsentence[cszrawsentences++] = (char*)"SentenceWithSpaces\0 SPAAACE"; + rgpszrawsentence[cszrawsentences++] = (char*)"SentenceWithTabsAndSpaces\0\t \t\t MEOW"; + + for( i = 0; i < sizeof( data ) / sizeof( data[0] ); i += 2 ) + { + p = VOX_LookupString( data[i] ); + + TASSERT_STR( p, data[i+1] ); + } + + cszrawsentences = 0; +} + +static void Test_VOX_ParseString( void ) +{ + char *rgpparseword[CVOXWORDMAX]; + const char *data[] = + { + "(p100) my ass is, heavy!(p80 t20) clik.", + "(p100)", "my", "ass", "is", "_comma", "heavy!(p80 t20)", "clik", NULL, + "freeman...", + "freeman", "_period", NULL, + }; + int i = 0; + + while( i < sizeof( data ) / sizeof( data[0] )) + { + char buffer[4096]; + int wordcount, j = 0; + Q_strncpy( buffer, data[i], sizeof( buffer )); + wordcount = VOX_ParseString( buffer, rgpparseword ); + + i++; + + while( data[i] ) + { + TASSERT_STR( data[i], rgpparseword[j] ); + i++; + j++; + } + + TASSERT( j == wordcount ); + + i++; + } +} + +static void Test_VOX_ParseWordParams( void ) +{ + string buffer; + qboolean ret; + voxword_t word; + + Q_strncpy( buffer, "heavy!(p80)", sizeof( buffer )); + ret = VOX_ParseWordParams( buffer, &word, true ); + TASSERT_STR( buffer, "heavy!" ); + TASSERT( word.pitch == 80 ); + TASSERT( ret ); + + Q_strncpy( buffer, "(p105)", sizeof( buffer )); + ret = VOX_ParseWordParams( buffer, &word, false ); + TASSERT_STR( buffer, "" ); + TASSERT( word.pitch == 105 ); + TASSERT( !ret ); + + Q_strncpy( buffer, "quiet(v50)", sizeof( buffer )); + ret = VOX_ParseWordParams( buffer, &word, false ); + TASSERT_STR( buffer, "quiet" ); + TASSERT( word.pitch == 105 ); // defaulted + TASSERT( word.volume == 50 ); + TASSERT( ret ); +} + +void Test_RunVOX( void ) +{ + TRUN( Test_VOX_GetDirectory() ); + TRUN( Test_VOX_LookupString() ); + TRUN( Test_VOX_ParseString() ); + TRUN( Test_VOX_ParseWordParams() ); +} + +#endif /* XASH_ENGINE_TESTS */ diff --git a/engine/client/vgui/vgui_draw.c b/engine/client/vgui/vgui_draw.c index b61c1d85..56f456f1 100644 --- a/engine/client/vgui/vgui_draw.c +++ b/engine/client/vgui/vgui_draw.c @@ -21,31 +21,13 @@ GNU General Public License for more details. #include "keydefs.h" #include "ref_common.h" #include "input.h" -#ifdef XASH_SDL -#include -static SDL_Cursor* s_pDefaultCursor[20]; -#endif #include "platform/platform.h" static enum VGUI_KeyCode s_pVirtualKeyTrans[256]; -static enum VGUI_DefaultCursor s_currentCursor; +static VGUI_DefaultCursor s_currentCursor; static HINSTANCE s_pVGuiSupport; // vgui_support library static convar_t *vgui_utf8 = NULL; -// Helper functions for vgui backend - -/*void VGUI_HideCursor( void ) -{ - host.mouse_visible = false; - SDL_HideCursor(); -} - -void VGUI_ShowCursor( void ) -{ - host.mouse_visible = true; - SDL_ShowCursor(); -}*/ - void GAME_EXPORT *VGUI_EngineMalloc(size_t size) { return Z_Malloc( size ); @@ -66,70 +48,10 @@ void GAME_EXPORT VGUI_GetMousePos( int *_x, int *_y ) *_x = x / xscale, *_y = y / yscale; } -void VGUI_InitCursors( void ) +void GAME_EXPORT VGUI_CursorSelect( VGUI_DefaultCursor cursor ) { - // load up all default cursors -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - s_pDefaultCursor[dc_none] = NULL; - s_pDefaultCursor[dc_arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); - s_pDefaultCursor[dc_ibeam] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); - s_pDefaultCursor[dc_hourglass]= SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT); - s_pDefaultCursor[dc_crosshair]= SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR); - s_pDefaultCursor[dc_up] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); - s_pDefaultCursor[dc_sizenwse] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE); - s_pDefaultCursor[dc_sizenesw] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW); - s_pDefaultCursor[dc_sizewe] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); - s_pDefaultCursor[dc_sizens] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS); - s_pDefaultCursor[dc_sizeall] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL); - s_pDefaultCursor[dc_no] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO); - s_pDefaultCursor[dc_hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); - //host.mouse_visible = true; - SDL_SetCursor( s_pDefaultCursor[dc_arrow] ); -#endif -} - -void GAME_EXPORT VGUI_CursorSelect(enum VGUI_DefaultCursor cursor ) -{ - qboolean visible; - if( cls.key_dest != key_game || cl.paused ) - return; - - switch( cursor ) - { - case dc_user: - case dc_none: - visible = false; - break; - default: - visible = true; - break; - } - -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - /// TODO: platform cursors - - if( CVAR_TO_BOOL( touch_emulate ) ) - return; - if( host.mouse_visible ) - { - SDL_SetRelativeMouseMode( SDL_FALSE ); - SDL_SetCursor( s_pDefaultCursor[cursor] ); - SDL_ShowCursor( true ); - } - else - { - SDL_ShowCursor( false ); - if( host.mouse_visible ) - SDL_GetRelativeMouseState( NULL, NULL ); - Key_EnableTextInput( false, true ); - } - //SDL_SetRelativeMouseMode(false); -#endif - if( s_currentCursor == cursor ) - return; - + Platform_SetCursorType( cursor ); s_currentCursor = cursor; - host.mouse_visible = visible; } byte GAME_EXPORT VGUI_GetColor( int i, int j) @@ -138,21 +60,9 @@ byte GAME_EXPORT VGUI_GetColor( int i, int j) } // Define and initialize vgui API - -void GAME_EXPORT VGUI_SetVisible( qboolean state ) -{ - host.mouse_visible=state; -#ifdef XASH_SDL - SDL_ShowCursor( state ); - if( !state ) - SDL_GetRelativeMouseState( NULL, NULL ); -#endif - Key_EnableTextInput( false, true ); -} - int GAME_EXPORT VGUI_UtfProcessChar( int in ) { - if( CVAR_TO_BOOL( vgui_utf8 ) ) + if( CVAR_TO_BOOL( vgui_utf8 )) return Con_UtfProcessCharForce( in ); else return in; @@ -175,14 +85,15 @@ vguiapi_t vgui = NULL, // VGUI_GetTextureSizes, NULL, // VGUI_GenerateTexture, VGUI_EngineMalloc, -/* VGUI_ShowCursor, - VGUI_HideCursor,*/ VGUI_CursorSelect, VGUI_GetColor, VGUI_IsInGame, - VGUI_SetVisible, + NULL, VGUI_GetMousePos, VGUI_UtfProcessChar, + Platform_GetClipboardText, + Platform_SetClipboardText, + Platform_GetKeyModifiers, NULL, NULL, NULL, @@ -190,6 +101,7 @@ vguiapi_t vgui = NULL, NULL, NULL, + NULL }; qboolean VGui_IsActive( void ) @@ -240,7 +152,6 @@ void VGui_Startup( const char *clientlib, int width, int height ) VGui_FillAPIFromRef( &vgui, &ref.dllFuncs ); -#ifdef XASH_INTERNAL_GAMELIBS s_pVGuiSupport = COM_LoadLibrary( clientlib, false, false ); if( s_pVGuiSupport ) @@ -250,58 +161,70 @@ void VGui_Startup( const char *clientlib, int width, int height ) { F( &vgui ); vgui.initialized = true; - VGUI_InitCursors(); - Con_Reportf( "vgui_support: found interal client support\n" ); + Con_Reportf( "vgui_support: found internal client support\n" ); + } + else + { + COM_FreeLibrary( s_pVGuiSupport ); } } -#endif // XASH_INTERNAL_GAMELIBS - // HACKHACK: load vgui with correct path first if specified. - // it will be reused while resolving vgui support and client deps - if( Sys_GetParmFromCmdLine( "-vguilib", vguilib ) ) + if( !vgui.initialized ) { - if( Q_strstr( vguilib, ".dll") ) + // HACKHACK: load vgui with correct path first if specified. + // it will be reused while resolving vgui support and client deps + if( Sys_GetParmFromCmdLine( "-vguilib", vguilib )) + { + if( Q_strstr( vguilib, ".dll" )) + Q_strncpy( vguiloader, "vgui_support.dll", 256 ); + else + Q_strncpy( vguiloader, VGUI_SUPPORT_DLL, 256 ); + + if( !COM_LoadLibrary( vguilib, false, false )) + Con_Reportf( S_WARN "VGUI preloading failed. Default library will be used! Reason: %s\n", COM_GetLibraryError() ); + } + + if( Q_strstr( clientlib, ".dll" )) Q_strncpy( vguiloader, "vgui_support.dll", 256 ); - else + + if( !vguiloader[0] && !Sys_GetParmFromCmdLine( "-vguiloader", vguiloader )) Q_strncpy( vguiloader, VGUI_SUPPORT_DLL, 256 ); - if( !COM_LoadLibrary( vguilib, false, false ) ) - Con_Reportf( S_WARN "VGUI preloading failed. Default library will be used! Reason: %s\n", COM_GetLibraryError()); - } + s_pVGuiSupport = COM_LoadLibrary( vguiloader, false, false ); - if( Q_strstr( clientlib, ".dll" ) ) - Q_strncpy( vguiloader, "vgui_support.dll", 256 ); - - if( !vguiloader[0] && !Sys_GetParmFromCmdLine( "-vguiloader", vguiloader ) ) - Q_strncpy( vguiloader, VGUI_SUPPORT_DLL, 256 ); - - s_pVGuiSupport = COM_LoadLibrary( vguiloader, false, false ); - - if( !s_pVGuiSupport ) - { - s_pVGuiSupport = COM_LoadLibrary( va( "../%s", vguiloader ), false, false ); - } - - if( !s_pVGuiSupport ) - { - if( FS_FileExists( vguiloader, false ) ) - Con_Reportf( S_ERROR "Failed to load vgui_support library: %s\n", COM_GetLibraryError() ); - else - Con_Reportf( "vgui_support: not found\n" ); - } - else - { - F = COM_GetProcAddress( s_pVGuiSupport, "InitAPI" ); - if( F ) + if( !s_pVGuiSupport ) { - F( &vgui ); - vgui.initialized = true; - VGUI_InitCursors(); + s_pVGuiSupport = COM_LoadLibrary( va( "../%s", vguiloader ), false, false ); + } + + if( !s_pVGuiSupport ) + { + if( FS_FileExists( vguiloader, false )) + { + Con_Reportf( S_ERROR "Failed to load vgui_support library: %s\n", COM_GetLibraryError() ); + } + else + { + Con_Reportf( "vgui_support: not found\n" ); + } + failed = true; } else - Con_Reportf( S_ERROR "Failed to find vgui_support library entry point!\n" ); + { + F = COM_GetProcAddress( s_pVGuiSupport, "InitAPI" ); + if( F ) + { + F( &vgui ); + vgui.initialized = true; + } + else + { + Con_Reportf( S_ERROR "Failed to find vgui_support library entry point!\n" ); + failed = true; + COM_FreeLibrary( s_pVGuiSupport ); + } + } } - } if( height < 480 ) @@ -498,8 +421,9 @@ void VGui_KeyEvent( int key, int down ) switch( key ) { case K_MOUSE1: - if( down && host.mouse_visible ) - Key_EnableTextInput( true, false ); + if( down && host.mouse_visible ) { + Key_EnableTextInput(true, false); + } vgui.Mouse( down ? MA_PRESSED : MA_RELEASED, MOUSE_LEFT ); return; case K_MOUSE2: @@ -535,7 +459,7 @@ void VGui_MouseMove( int x, int y ) void VGui_Paint( void ) { - if(vgui.initialized) + if( vgui.initialized ) vgui.Paint(); } @@ -551,3 +475,9 @@ void *GAME_EXPORT VGui_GetPanel( void ) return vgui.GetPanel(); return NULL; } + +void VGui_ReportTextInput( const char *text ) +{ + if ( vgui.initialized ) + vgui.TextInput( text ); +} diff --git a/engine/client/vgui/vgui_draw.h b/engine/client/vgui/vgui_draw.h index bf9c4396..dd5a4ad2 100644 --- a/engine/client/vgui/vgui_draw.h +++ b/engine/client/vgui/vgui_draw.h @@ -33,6 +33,7 @@ void VGui_KeyEvent( int key, int down ); void VGui_MouseMove( int x, int y ); qboolean VGui_IsActive( void ); void *VGui_GetPanel( void ); +void VGui_ReportTextInput( const char *text ); #ifdef __cplusplus } #endif diff --git a/engine/client/vox.h b/engine/client/vox.h index 8e6e557b..56967959 100644 --- a/engine/client/vox.h +++ b/engine/client/vox.h @@ -16,9 +16,9 @@ GNU General Public License for more details. #ifndef VOX_H #define VOX_H -#define CVOXWORDMAX 64 -#define CVOXZEROSCANMAX 255 // scan up to this many samples for next zero crossing -#define MAX_SENTENCES 4096 +#define CVOXWORDMAX 64 +#define CVOXFILESENTENCEMAX 4096 + #define SENTENCE_INDEX -99999 // unique sentence index typedef struct voxword_s @@ -34,13 +34,6 @@ typedef struct voxword_s sfx_t *sfx; // name and cache pointer } voxword_t; - -typedef struct -{ - char *pName; - float length; -} sentence_t; - struct channel_s; void VOX_LoadWord( struct channel_s *pchan ); void VOX_FreeWord( struct channel_s *pchan ); diff --git a/engine/common/base_cmd.c b/engine/common/base_cmd.c index b1685626..efdf718e 100644 --- a/engine/common/base_cmd.c +++ b/engine/common/base_cmd.c @@ -229,7 +229,7 @@ void BaseCmd_Stats_f( void ) Con_Printf( "Empty buckets: %d\n", empty ); } -static void BaseCmd_CheckCvars( const char *key, const char *value, void *buffer, void *ptr ) +static void BaseCmd_CheckCvars( const char *key, const char *value, const void *unused, void *ptr ) { base_command_t *v = BaseCmd_Find( HM_CVAR, key ); qboolean *invalid = ptr; diff --git a/engine/common/cmd.c b/engine/common/cmd.c index 335596dd..1df7287b 100644 --- a/engine/common/cmd.c +++ b/engine/common/cmd.c @@ -510,8 +510,8 @@ typedef struct cmd_s struct cmd_s *next; char *name; xcommand_t function; - char *desc; int flags; + char *desc; } cmd_t; static int cmd_argc; @@ -647,7 +647,7 @@ void Cmd_TokenizeString( const char *text ) if( cmd_argc == 1 ) cmd_args = text; - text = _COM_ParseFileSafe( (char*)text, cmd_token, sizeof( cmd_token ), PFILE_IGNOREBRACKET, NULL ); + text = COM_ParseFileSafe( (char*)text, cmd_token, sizeof( cmd_token ), PFILE_IGNOREBRACKET, NULL, NULL ); if( !text ) return; @@ -671,21 +671,21 @@ static int Cmd_AddCommandEx( const char *funcname, const char *cmd_name, xcomman if( !COM_CheckString( cmd_name )) { - Con_Reportf( S_ERROR "Cmd_AddCommand: NULL name\n" ); + Con_Reportf( S_ERROR "%s: NULL name\n", funcname ); return 0; } // fail if the command is a variable name if( Cvar_FindVar( cmd_name )) { - Con_DPrintf( S_ERROR "Cmd_AddServerCommand: %s already defined as a var\n", cmd_name ); + Con_DPrintf( S_ERROR "%s: %s already defined as a var\n", funcname, cmd_name ); return 0; } // fail if the command already exists if( Cmd_Exists( cmd_name )) { - Con_DPrintf( S_ERROR "Cmd_AddServerCommand: %s already defined\n", cmd_name ); + Con_DPrintf( S_ERROR "%s: %s already defined\n", funcname, cmd_name ); return 0; } @@ -1093,7 +1093,7 @@ static void Cmd_ExecuteStringWithPrivilegeCheck( const char *text, qboolean isPr if( Cvar_CommandWithPrivilegeCheck( cvar, isPrivileged )) return; if( host.apply_game_config ) - return; // don't send nothing to server: we is a server! + return; // don't send nothing to server: we are a server! // forward the command line to the server, so the entity DLL can parse it if( host.type == HOST_NORMAL ) @@ -1107,7 +1107,7 @@ static void Cmd_ExecuteStringWithPrivilegeCheck( const char *text, qboolean isPr #endif // XASH_DEDICATED if( Cvar_VariableInteger( "host_gameloaded" )) { - Con_Printf( S_WARN "Unknown command \"%s\"\n", text ); + Con_Printf( S_WARN "Unknown command \"%s\"\n", Cmd_Argv( 0 ) ); } } } @@ -1384,13 +1384,13 @@ void Cmd_Init( void ) Cmd_AddCommand( "echo", Cmd_Echo_f, "print a message to the console (useful in scripts)" ); Cmd_AddCommand( "wait", Cmd_Wait_f, "make script execution wait for some rendered frames" ); Cmd_AddCommand( "cmdlist", Cmd_List_f, "display all console commands beginning with the specified prefix" ); - Cmd_AddCommand( "stuffcmds", Cmd_StuffCmds_f, "execute commandline parameters (must be present in .rc script)" ); + Cmd_AddRestrictedCommand( "stuffcmds", Cmd_StuffCmds_f, "execute commandline parameters (must be present in .rc script)" ); Cmd_AddCommand( "apropos", Cmd_Apropos_f, "lists all console variables/commands/aliases containing the specified string in the name or description" ); #if !XASH_DEDICATED Cmd_AddCommand( "cmd", Cmd_ForwardToServer, "send a console commandline to the server" ); #endif // XASH_DEDICATED - Cmd_AddCommand( "alias", Cmd_Alias_f, "create a script function. Without arguments show the list of all alias" ); - Cmd_AddCommand( "unalias", Cmd_UnAlias_f, "remove a script function" ); + Cmd_AddRestrictedCommand( "alias", Cmd_Alias_f, "create a script function. Without arguments show the list of all alias" ); + Cmd_AddRestrictedCommand( "unalias", Cmd_UnAlias_f, "remove a script function" ); Cmd_AddCommand( "if", Cmd_If_f, "compare and set condition bits" ); Cmd_AddCommand( "else", Cmd_Else_f, "invert condition bit" ); diff --git a/engine/common/com_strings.h b/engine/common/com_strings.h index 53c3318a..c8685cb9 100644 --- a/engine/common/com_strings.h +++ b/engine/common/com_strings.h @@ -64,6 +64,8 @@ GNU General Public License for more details. #define DEFAULT_UPDATE_PAGE "https://github.com/FWGS/xash3d-fwgs/releases/latest" #define XASH_ENGINE_NAME "Xash3D FWGS" +#define XASH_VERSION "0.20" // engine current version +#define XASH_COMPAT_VERSION "0.99" // version we are based on // renderers order is important, software is always a last chance fallback #define DEFAULT_RENDERERS { "vk", "gl", "gles1", "gles2", "gl4es", "soft" } diff --git a/engine/common/common.c b/engine/common/common.c index c8baf28b..684718dd 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -585,23 +585,6 @@ void COM_TrimSpace( const char *source, char *dest ) dest[length] = 0; } -/* -============ -COM_FixSlashes - -Changes all '/' characters into '\' characters, in place. -============ -*/ -void COM_FixSlashes( char *pname ) -{ - while( *pname ) - { - if( *pname == '\\' ) - *pname = '/'; - pname++; - } -} - /* ================== COM_Nibble @@ -1182,22 +1165,22 @@ void Test_RunCommon( void ) Msg( "Checking COM_ParseFile...\n" ); - file = _COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len ); + file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); TASSERT( !Q_strcmp( buf, "q" ) && len == 1); - file = _COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len ); + file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); TASSERT( !Q_strcmp( buf, "asdf" ) && len == 4); - file = _COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len ); + file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); TASSERT( !Q_strcmp( buf, "qwer" ) && len == -1); - file = _COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len ); + file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); TASSERT( !Q_strcmp( buf, "f \"f" ) && len == 4); - file = _COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len ); + file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); TASSERT( !Q_strcmp( buf, "meow" ) && len == -1); - file = _COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len ); + file = COM_ParseFileSafe( file, buf, sizeof( buf ), 0, &len, NULL ); TASSERT( !Q_strcmp( buf, "bark" ) && len == 4); } #endif diff --git a/engine/common/common.h b/engine/common/common.h index ad4ccc59..f70337e2 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -76,13 +76,6 @@ XASH SPECIFIC - sort of hack that works only in Xash3D not in GoldSrc #define HACKS_RELATED_HLMODS // some HL-mods works differently under Xash and can't be fixed without some hacks at least at current time -typedef struct -{ - int numfilenames; - char **filenames; - char *filenamesbuffer; -} search_t; - enum { DEV_NONE = 0, @@ -118,9 +111,8 @@ typedef enum #include "cvar.h" #include "con_nprint.h" #include "crclib.h" - -#define XASH_VERSION "0.20" // engine current version -#define XASH_COMPAT_VERSION "0.99" // version we are based on +#include "ref_api.h" +#include "fscallback.h" // PERFORMANCE INFO #define MIN_FPS 20.0f // host minimum fps value for maxfps. @@ -150,18 +142,6 @@ typedef enum #define MAX_STATIC_ENTITIES 32 // static entities that moved on the client when level is spawn #endif -// filesystem flags -#define FS_STATIC_PATH ( 1U << 0 ) // FS_ClearSearchPath will be ignore this path -#define FS_NOWRITE_PATH ( 1U << 1 ) // default behavior - last added gamedir set as writedir. This flag disables it -#define FS_GAMEDIR_PATH ( 1U << 2 ) // just a marker for gamedir path -#define FS_CUSTOM_PATH ( 1U << 3 ) // custom directory -#define FS_GAMERODIR_PATH ( 1U << 4 ) // caseinsensitive - -#define FS_GAMEDIRONLY_SEARCH_FLAGS ( FS_GAMEDIR_PATH | FS_CUSTOM_PATH | FS_GAMERODIR_PATH ) - -#define GI SI.GameInfo -#define FS_Gamedir() SI.GameInfo->gamefolder -#define FS_Title() SI.GameInfo->title #define GameState (&host.game) #define FORCE_DRAW_VERSION_TIME 5.0f // draw version for 5 seconds @@ -184,7 +164,8 @@ extern convar_t host_developer; extern convar_t *host_limitlocal; extern convar_t *host_framerate; extern convar_t *host_maxfps; -extern convar_t cl_filterstuffcmd; +extern convar_t sys_timescale; +extern convar_t cl_filterstuffcmd; /* ============================================================== @@ -201,60 +182,6 @@ GAMEINFO stuff internal shared gameinfo structure (readonly for engine parts) ======================================================================== */ -typedef struct gameinfo_s -{ - // filesystem info - char gamefolder[MAX_QPATH]; // used for change game '-game x' - char basedir[MAX_QPATH]; // base game directory (like 'id1' for Quake or 'valve' for Half-Life) - char falldir[MAX_QPATH]; // used as second basedir - char startmap[MAX_QPATH];// map to start singleplayer game - char trainmap[MAX_QPATH];// map to start hazard course (if specified) - char title[64]; // Game Main Title - float version; // game version (optional) - - // .dll pathes - char dll_path[MAX_QPATH]; // e.g. "bin" or "cl_dlls" - char game_dll[MAX_QPATH]; // custom path for game.dll - - // .ico path - char iconpath[MAX_QPATH]; // "game.ico" by default - - // about mod info - string game_url; // link to a developer's site - string update_url; // link to updates page - char type[MAX_QPATH]; // single, toolkit, multiplayer etc - char date[MAX_QPATH]; - size_t size; - - int gamemode; - qboolean secure; // prevent to console acess - qboolean nomodels; // don't let player to choose model (use player.mdl always) - qboolean noskills; // disable skill menu selection - - char sp_entity[32]; // e.g. info_player_start - char mp_entity[32]; // e.g. info_player_deathmatch - char mp_filter[32]; // filtering multiplayer-maps - - char ambientsound[NUM_AMBIENTS][MAX_QPATH]; // quake ambient sounds - - int max_edicts; // min edicts is 600, max edicts is 8196 - int max_tents; // min temp ents is 300, max is 2048 - int max_beams; // min beams is 64, max beams is 512 - int max_particles; // min particles is 4096, max particles is 32768 - - char game_dll_linux[64]; // custom path for game.dll - char game_dll_osx[64]; // custom path for game.dll - - qboolean added; -} gameinfo_t; - -typedef enum -{ - GAME_NORMAL, - GAME_SINGLEPLAYER_ONLY, - GAME_MULTIPLAYER_ONLY -} gametype_t; - typedef struct sysinfo_s { string exeName; // exe.filename @@ -262,9 +189,6 @@ typedef struct sysinfo_s string basedirName; // name of base directory string gamedll; string clientlib; - gameinfo_t *GameInfo; // current GameInfo - gameinfo_t *games[MAX_MODS]; // environment games (founded at each engine start) - int numgames; } sysinfo_t; typedef enum @@ -471,6 +395,13 @@ extern sysinfo_t SI; typedef void (*xcommand_t)( void ); +// +// filesystem_engine.c +// +qboolean FS_LoadProgs( void ); +void FS_Init( void ); +void FS_Shutdown( void ); + // // cmd.c // @@ -530,56 +461,6 @@ void Mem_PrintStats( void ); #define Mem_IsAllocated( mem ) Mem_IsAllocatedExt( NULL, mem ) #define Mem_Check() _Mem_Check( __FILE__, __LINE__ ) -// -// filesystem.c -// -void FS_Init( void ); -void FS_Path( void ); -void FS_Rescan( void ); -void FS_Shutdown( void ); -void FS_ClearSearchPath( void ); -void FS_AllowDirectPaths( qboolean enable ); -void FS_AddGameDirectory( const char *dir, uint flags ); -void FS_AddGameHierarchy( const char *dir, uint flags ); -void FS_LoadGameInfo( const char *rootfolder ); -const char *FS_GetDiskPath( const char *name, qboolean gamedironly ); -byte *W_LoadLump( wfile_t *wad, const char *lumpname, size_t *lumpsizeptr, const char type ); -void W_Close( wfile_t *wad ); -byte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ); -qboolean CRC32_File( dword *crcvalue, const char *filename ); -qboolean MD5_HashFile( byte digest[16], const char *pszFileName, uint seed[4] ); -byte *FS_LoadDirectFile( const char *path, fs_offset_t *filesizeptr ); -qboolean FS_WriteFile( const char *filename, const void *data, fs_offset_t len ); -qboolean COM_ParseVector( char **pfile, float *v, size_t size ); -void COM_NormalizeAngles( vec3_t angles ); -int COM_FileSize( const char *filename ); -void COM_FixSlashes( char *pname ); -void COM_FreeFile( void *buffer ); -int COM_CompareFileTime( const char *filename1, const char *filename2, int *iCompare ); -search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ); -file_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly ); -fs_offset_t FS_Write( file_t *file, const void *data, size_t datasize ); -fs_offset_t FS_Read( file_t *file, void *buffer, size_t buffersize ); -int FS_VPrintf( file_t *file, const char *format, va_list ap ); -int FS_Seek( file_t *file, fs_offset_t offset, int whence ); -int FS_Gets( file_t *file, byte *string, size_t bufsize ); -int FS_Printf( file_t *file, const char *format, ... ) _format( 2 ); -fs_offset_t FS_FileSize( const char *filename, qboolean gamedironly ); -int FS_FileTime( const char *filename, qboolean gamedironly ); -int FS_Print( file_t *file, const char *msg ); -qboolean FS_Rename( const char *oldname, const char *newname ); -int FS_FileExists( const char *filename, int gamedironly ); -int FS_SetCurrentDirectory( const char *path ); -qboolean FS_SysFileExists( const char *path, qboolean casesensitive ); -qboolean FS_FileCopy( file_t *pOutput, file_t *pInput, int fileSize ); -qboolean FS_Delete( const char *path ); -int FS_UnGetc( file_t *file, byte c ); -fs_offset_t FS_Tell( file_t *file ); -qboolean FS_Eof( file_t *file ); -int FS_Close( file_t *file ); -int FS_Getc( file_t *file ); -fs_offset_t FS_FileLength( file_t *f ); - // // imagelib // @@ -668,16 +549,6 @@ void FS_FreeStream( stream_t *stream ); qboolean Sound_Process( wavdata_t **wav, int rate, int width, uint flags ); uint Sound_GetApproxWavePlayLen( const char *filepath ); -// -// build.c -// -int Q_buildnum( void ); -int Q_buildnum_compat( void ); -const char *Q_buildos( void ); -const char *Q_buildarch( void ); -const char *Q_buildcommit( void ); - - // // host.c // @@ -689,7 +560,7 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa int Host_CompareFileTime( int ft1, int ft2 ); void Host_NewInstance( const char *name, const char *finalmsg ); void Host_EndGame( qboolean abort, const char *message, ... ) _format( 2 ); -void Host_AbortCurrentFrame( void ); +void Host_AbortCurrentFrame( void ) NORETURN; void Host_WriteServerConfig( const char *name ); void Host_WriteOpenGLConfig( void ); void Host_WriteVideoConfig( void ); @@ -754,7 +625,6 @@ void pfnDrawSetTextColor( float r, float g, float b ); void pfnDrawConsoleStringLen( const char *pText, int *length, int *height ); void *Cache_Check( poolhandle_t mempool, struct cache_user_s *c ); void COM_TrimSpace( const char *source, char *dest ); -edict_t* pfnPEntityOfEntIndex( int iEntIndex ); void pfnGetModelBounds( model_t *mod, float *mins, float *maxs ); void pfnCVarDirectSet( cvar_t *var, const char *szValue ); int COM_CheckParm( char *parm, char **ppnext ); @@ -954,6 +824,11 @@ void UI_SetActiveMenu( qboolean fActive ); void UI_ShowConnectionWarning( void ); void Cmd_Null_f( void ); void Rcon_Print( const char *pMsg ); +qboolean COM_ParseVector( char **pfile, float *v, size_t size ); +void COM_NormalizeAngles( vec3_t angles ); +int COM_FileSize( const char *filename ); +void COM_FreeFile( void *buffer ); +int COM_CompareFileTime( const char *filename1, const char *filename2, int *iCompare ); // soundlib shared exports qboolean S_Init( void ); diff --git a/engine/common/con_utils.c b/engine/common/con_utils.c index c6b2cd3c..8bafcb31 100644 --- a/engine/common/con_utils.c +++ b/engine/common/con_utils.c @@ -761,10 +761,10 @@ qboolean Cmd_GetGamesList( const char *s, char *completedname, int length ) // compare gamelist with current keyword len = Q_strlen( s ); - for( i = 0, numgamedirs = 0; i < SI.numgames; i++ ) + for( i = 0, numgamedirs = 0; i < FI->numgames; i++ ) { - if(( *s == '*' ) || !Q_strnicmp( SI.games[i]->gamefolder, s, len)) - Q_strcpy( gamedirs[numgamedirs++], SI.games[i]->gamefolder ); + if(( *s == '*' ) || !Q_strnicmp( FI->games[i]->gamefolder, s, len)) + Q_strcpy( gamedirs[numgamedirs++], FI->games[i]->gamefolder ); } if( !numgamedirs ) return false; @@ -1216,7 +1216,7 @@ void Con_CompleteCommand( field_t *field ) if( con.matchCount == 1 ) { - Q_sprintf( con.completionField->buffer, "\\%s", con.cmds[0] ); + Q_strncpy( con.completionField->buffer, con.cmds[0], sizeof( con.completionField->buffer )); if( Cmd_Argc() == 1 ) Q_strncat( con.completionField->buffer, " ", sizeof( con.completionField->buffer ) ); else Con_ConcatRemaining( temp.buffer, con.completionString ); con.completionField->cursor = Q_strlen( con.completionField->buffer ); @@ -1244,7 +1244,7 @@ void Con_CompleteCommand( field_t *field ) con.shortestMatch[len] = 0; // multiple matches, complete to shortest - Q_sprintf( con.completionField->buffer, "\\%s", con.shortestMatch ); + Q_strncpy( con.completionField->buffer, con.shortestMatch, sizeof( con.completionField->buffer )); con.completionField->cursor = Q_strlen( con.completionField->buffer ); Con_ConcatRemaining( temp.buffer, con.completionString ); diff --git a/engine/common/crashhandler.c b/engine/common/crashhandler.c index 525237c3..fac7474a 100644 --- a/engine/common/crashhandler.c +++ b/engine/common/crashhandler.c @@ -24,9 +24,11 @@ Crash handler, called from system ================ */ #if XASH_CRASHHANDLER == CRASHHANDLER_DBGHELP || XASH_CRASHHANDLER == CRASHHANDLER_WIN32 + #if XASH_CRASHHANDLER == CRASHHANDLER_DBGHELP + #pragma comment( lib, "dbghelp" ) -#pragma comment( lib, "psapi" ) + #include #include #include @@ -35,7 +37,7 @@ Crash handler, called from system typedef ULONG_PTR DWORD_PTR, *PDWORD_PTR; #endif -int ModuleName( HANDLE process, char *name, void *address, int len ) +static int Sys_ModuleName( HANDLE process, char *name, void *address, int len ) { DWORD_PTR baseAddress = 0; static HMODULE *moduleArray; @@ -44,27 +46,24 @@ int ModuleName( HANDLE process, char *name, void *address, int len ) DWORD bytesRequired; int i; - if(len < 3) + if( len < 3 ) return 0; - if ( !moduleArray && EnumProcessModules( process, NULL, 0, &bytesRequired ) ) + if( !moduleArray && EnumProcessModules( process, NULL, 0, &bytesRequired ) ) { if ( bytesRequired ) { moduleArrayBytes = (LPBYTE)LocalAlloc( LPTR, bytesRequired ); - if ( moduleArrayBytes ) + if( moduleArrayBytes && EnumProcessModules( process, (HMODULE *)moduleArrayBytes, bytesRequired, &bytesRequired ) ) { - if( EnumProcessModules( process, (HMODULE *)moduleArrayBytes, bytesRequired, &bytesRequired ) ) - { - moduleCount = bytesRequired / sizeof( HMODULE ); - moduleArray = (HMODULE *)moduleArrayBytes; - } + moduleCount = bytesRequired / sizeof( HMODULE ); + moduleArray = (HMODULE *)moduleArrayBytes; } } } - for( i = 0; iContextRecord, sizeof(CONTEXT) ); + memcpy( &context, pInfo->ContextRecord, sizeof( CONTEXT )); + options = SymGetOptions(); options |= SYMOPT_DEBUG; options |= SYMOPT_LOAD_LINES; @@ -97,9 +98,7 @@ static void stack_trace( PEXCEPTION_POINTERS pInfo ) SymInitialize( process, NULL, TRUE ); - - - ZeroMemory( &stackframe, sizeof(STACKFRAME64) ); + ZeroMemory( &stackframe, sizeof( STACKFRAME64 )); #ifdef _M_IX86 image = IMAGE_FILE_MACHINE_I386; @@ -127,19 +126,25 @@ static void stack_trace( PEXCEPTION_POINTERS pInfo ) stackframe.AddrBStore.Mode = AddrModeFlat; stackframe.AddrStack.Offset = context.IntSp; stackframe.AddrStack.Mode = AddrModeFlat; +#elif +#error #endif - len += Q_snprintf( message + len, 1024 - len, "Sys_Crash: address %p, code %p\n", pInfo->ExceptionRecord->ExceptionAddress, (void*)pInfo->ExceptionRecord->ExceptionCode ); + len += Q_snprintf( message + len, 1024 - len, "Sys_Crash: address %p, code %p\n", + pInfo->ExceptionRecord->ExceptionAddress, (void*)pInfo->ExceptionRecord->ExceptionCode ); if( SymGetLineFromAddr64( process, (DWORD64)pInfo->ExceptionRecord->ExceptionAddress, &dline, &line ) ) { - len += Q_snprintf(message + len, 1024 - len,"Exception: %s:%d:%d\n", (char*)line.FileName, (int)line.LineNumber, (int)dline); + len += Q_snprintf(message + len, 1024 - len, "Exception: %s:%d:%d\n", + (char*)line.FileName, (int)line.LineNumber, (int)dline); } if( SymGetLineFromAddr64( process, stackframe.AddrPC.Offset, &dline, &line ) ) { - len += Q_snprintf(message + len, 1024 - len,"PC: %s:%d:%d\n", (char*)line.FileName, (int)line.LineNumber, (int)dline); + len += Q_snprintf(message + len, 1024 - len,"PC: %s:%d:%d\n", + (char*)line.FileName, (int)line.LineNumber, (int)dline); } if( SymGetLineFromAddr64( process, stackframe.AddrFrame.Offset, &dline, &line ) ) { - len += Q_snprintf(message + len, 1024 - len,"Frame: %s:%d:%d\n", (char*)line.FileName, (int)line.LineNumber, (int)dline); + len += Q_snprintf(message + len, 1024 - len,"Frame: %s:%d:%d\n", + (char*)line.FileName, (int)line.LineNumber, (int)dline); } for( i = 0; i < 25; i++ ) { @@ -149,39 +154,43 @@ static void stack_trace( PEXCEPTION_POINTERS pInfo ) image, process, thread, &stackframe, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL); - DWORD64 displacement = 0; + if( !result ) break; - symbol->SizeOfStruct = sizeof(SYMBOL_INFO); symbol->MaxNameLen = MAX_SYM_NAME; - len += Q_snprintf( message + len, 1024 - len, "% 2d %p", i, (void*)stackframe.AddrPC.Offset ); + len += Q_snprintf( message + len, 1024 - len, "% 2d %p", + i, (void*)stackframe.AddrPC.Offset ); if( SymFromAddr( process, stackframe.AddrPC.Offset, &displacement, symbol ) ) { len += Q_snprintf( message + len, 1024 - len, " %s ", symbol->Name ); } if( SymGetLineFromAddr64( process, stackframe.AddrPC.Offset, &dline, &line ) ) { - len += Q_snprintf(message + len, 1024 - len,"(%s:%d:%d) ", (char*)line.FileName, (int)line.LineNumber, (int)dline); + len += Q_snprintf(message + len, 1024 - len,"(%s:%d:%d) ", + (char*)line.FileName, (int)line.LineNumber, (int)dline); } len += Q_snprintf( message + len, 1024 - len, "("); - len += ModuleName( process, message + len, (void*)stackframe.AddrPC.Offset, 1024 - len ); + len += Sys_ModuleName( process, message + len, (void*)stackframe.AddrPC.Offset, 1024 - len ); len += Q_snprintf( message + len, 1024 - len, ")\n"); } -#ifdef XASH_SDL - if( host.type != HOST_DEDICATED ) // let system to restart server automaticly - SDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR,"Sys_Crash", message, host.hWnd ); -#endif - Sys_PrintLog(message); - SymCleanup(process); +#if XASH_SDL == 2 + if( host.type != HOST_DEDICATED ) // let system to restart server automaticly + SDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, "Sys_Crash", message, host.hWnd ); +#endif + + Sys_PrintLog( message ); + + SymCleanup( process ); } -#endif //DBGHELP +#endif /* XASH_CRASHHANDLER == CRASHHANDLER_DBGHELP */ + LPTOP_LEVEL_EXCEPTION_FILTER oldFilter; -long _stdcall Sys_Crash( PEXCEPTION_POINTERS pInfo ) +static long _stdcall Sys_Crash( PEXCEPTION_POINTERS pInfo ) { // save config if( host.status != HOST_CRASHED ) @@ -190,7 +199,7 @@ long _stdcall Sys_Crash( PEXCEPTION_POINTERS pInfo ) host.crashed = true; #if XASH_CRASHHANDLER == CRASHHANDLER_DBGHELP - stack_trace( pInfo ); + Sys_StackTrace( pInfo ); #else Sys_Warn( "Sys_Crash: call %p at address %p", pInfo->ExceptionRecord->ExceptionAddress, pInfo->ExceptionRecord->ExceptionCode ); #endif @@ -233,13 +242,14 @@ void Sys_RestoreCrashHandler( void ) #include "library.h" -#if XASH_FREEBSD || XASH_NETBSD || XASH_ANDROID || XASH_LINUX +#if XASH_FREEBSD || XASH_NETBSD || XASH_OPENBSD || XASH_ANDROID || XASH_LINUX #define HAVE_UCONTEXT_H 1 #endif #ifdef HAVE_UCONTEXT_H #include #endif + #include #include @@ -261,7 +271,7 @@ static int d_dladdr( void *sym, Dl_info *info ) #define dladdr d_dladdr #endif -int printframe( char *buf, int len, int i, void *addr ) +static int Sys_PrintFrame( char *buf, int len, int i, void *addr ) { Dl_info dlinfo; if( len <= 0 ) @@ -271,7 +281,7 @@ int printframe( char *buf, int len, int i, void *addr ) { if( dlinfo.dli_sname ) return Q_snprintf( buf, len, "%2d: %p <%s+%lu> (%s)\n", i, addr, dlinfo.dli_sname, - (unsigned long)addr - (unsigned long)dlinfo.dli_saddr, dlinfo.dli_fname ); // print symbol, module and address + (unsigned long)addr - (unsigned long)dlinfo.dli_saddr, dlinfo.dli_fname ); // print symbol, module and address else return Q_snprintf( buf, len, "%2d: %p (%s)\n", i, addr, dlinfo.dli_fname ); // print module and address } @@ -281,10 +291,11 @@ int printframe( char *buf, int len, int i, void *addr ) struct sigaction oldFilter; -#define STACK_BACKTRACE_STR_LEN 17 -#define STACK_BACKTRACE_STR "Stack backtrace:\n" -#define STACK_DUMP_STR_LEN 12 -#define STACK_DUMP_STR "Stack dump:\n" +#define STACK_BACKTRACE_STR "Stack backtrace:\n" +#define STACK_DUMP_STR "Stack dump:\n" + +#define STACK_BACKTRACE_STR_LEN (sizeof( STACK_BACKTRACE_STR ) - 1) +#define STACK_DUMP_STR_LEN (sizeof( STACK_DUMP_STR ) - 1) #define ALIGN( x, y ) (((uintptr_t) (x) + ((y)-1)) & ~((y)-1)) static void Sys_Crash( int signal, siginfo_t *si, void *context) @@ -356,7 +367,7 @@ static void Sys_Crash( int signal, siginfo_t *si, void *context) len += Q_snprintf( message + len, sizeof( message ) - len, "Crash: signal %d errno %d with code %d at %p\n", signal, si->si_errno, si->si_code, si->si_addr ); #endif - write( 2, message, len ); + write( STDERR_FILENO, message, len ); // flush buffers before writing directly to descriptors fflush( stdout ); @@ -370,9 +381,9 @@ static void Sys_Crash( int signal, siginfo_t *si, void *context) { size_t pagesize = sysconf( _SC_PAGESIZE ); // try to print backtrace - write( 2, STACK_BACKTRACE_STR, STACK_BACKTRACE_STR_LEN ); + write( STDERR_FILENO, STACK_BACKTRACE_STR, STACK_BACKTRACE_STR_LEN ); write( logfd, STACK_BACKTRACE_STR, STACK_BACKTRACE_STR_LEN ); - strncpy( message + len, STACK_BACKTRACE_STR, sizeof( message ) - len ); + Q_strncpy( message + len, STACK_BACKTRACE_STR, sizeof( message ) - len ); len += STACK_BACKTRACE_STR_LEN; // false on success, true on failure @@ -384,8 +395,8 @@ static void Sys_Crash( int signal, siginfo_t *si, void *context) do { - int line = printframe( message + len, sizeof( message ) - len, ++i, pc); - write( 2, message + len, line ); + int line = Sys_PrintFrame( message + len, sizeof( message ) - len, ++i, pc); + write( STDERR_FILENO, message + len, line ); write( logfd, message + len, line ); len += line; //if( !dladdr(bp,0) ) break; // only when bp is in module @@ -399,17 +410,17 @@ static void Sys_Crash( int signal, siginfo_t *si, void *context) while( bp && i < 128 ); // try to print stack - write( 2, STACK_DUMP_STR, STACK_DUMP_STR_LEN ); + write( STDERR_FILENO, STACK_DUMP_STR, STACK_DUMP_STR_LEN ); write( logfd, STACK_DUMP_STR, STACK_DUMP_STR_LEN ); - strncpy( message + len, STACK_DUMP_STR, sizeof( message ) - len ); + Q_strncpy( message + len, STACK_DUMP_STR, sizeof( message ) - len ); len += STACK_DUMP_STR_LEN; if( !try_allow_read( sp, pagesize ) ) { for( i = 0; i < 32; i++ ) { - int line = printframe( message + len, sizeof( message ) - len, i, sp[i] ); - write( 2, message + len, line ); + int line = Sys_PrintFrame( message + len, sizeof( message ) - len, i, sp[i] ); + write( STDERR_FILENO, message + len, line ); write( logfd, message + len, line ); len += line; } diff --git a/engine/common/cvar.c b/engine/common/cvar.c index 108b3956..0e33ef31 100644 --- a/engine/common/cvar.c +++ b/engine/common/cvar.c @@ -21,7 +21,7 @@ GNU General Public License for more details. convar_t *cvar_vars = NULL; // head of list convar_t *cmd_scripting; -CVAR_DEFINE_AUTO( cl_filterstuffcmd, "1", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "filter commands coming from server" ); +CVAR_DEFINE_AUTO( cl_filterstuffcmd, "0", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "filter commands coming from server" ); /* ============ diff --git a/engine/common/dedicated.c b/engine/common/dedicated.c index 965c75c7..07b77a50 100644 --- a/engine/common/dedicated.c +++ b/engine/common/dedicated.c @@ -306,11 +306,6 @@ void SCR_CheckStartupVids( void ) } -void Sys_SetClipboardData( const char *text, size_t size ) -{ - -} - void CL_StopPlayback( void ) { diff --git a/engine/common/filesystem.h b/engine/common/filesystem.h deleted file mode 100644 index 3abba403..00000000 --- a/engine/common/filesystem.h +++ /dev/null @@ -1,200 +0,0 @@ -/* -filesystem.h - engine FS -Copyright (C) 2007 Uncle Mike - -This program 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, either version 3 of the License, or -(at your option) any later version. - -This program 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. -*/ - -#ifndef FILESYSTEM_H -#define FILESYSTEM_H - -/* -======================================================================== -PAK FILES - -The .pak files are just a linear collapse of a directory tree -======================================================================== -*/ -// header -#define IDPACKV1HEADER (('K'<<24)+('C'<<16)+('A'<<8)+'P') // little-endian "PACK" - -#define MAX_FILES_IN_PACK 65536 // pak - -typedef struct -{ - int ident; - int dirofs; - int dirlen; -} dpackheader_t; - -typedef struct -{ - char name[56]; // total 64 bytes - int filepos; - int filelen; -} dpackfile_t; - -/* -======================================================================== -.WAD archive format (WhereAllData - WAD) - -List of compressed files, that can be identify only by TYPE_* - - -header: dwadinfo_t[dwadinfo_t] -file_1: byte[dwadinfo_t[num]->disksize] -file_2: byte[dwadinfo_t[num]->disksize] -file_3: byte[dwadinfo_t[num]->disksize] -... -file_n: byte[dwadinfo_t[num]->disksize] -infotable dlumpinfo_t[dwadinfo_t->numlumps] -======================================================================== -*/ -#define WAD3_NAMELEN 16 -#define HINT_NAMELEN 5 // e.g. _mask, _norm -#define MAX_FILES_IN_WAD 65535 // real limit as above <2Gb size not a lumpcount - -#include "const.h" - -typedef struct -{ - int ident; // should be WAD3 - int numlumps; // num files - int infotableofs; // LUT offset -} dwadinfo_t; - -typedef struct -{ - int filepos; // file offset in WAD - int disksize; // compressed or uncompressed - int size; // uncompressed - signed char type; // TYP_* - signed char attribs; // file attribs - signed char pad0; - signed char pad1; - char name[WAD3_NAMELEN]; // must be null terminated -} dlumpinfo_t; - -#include "custom.h" - -/* -======================================================================== -.HPK archive format (Hash PAK - HPK) - -List of compressed files, that can be identify only by TYPE_* - - -header: dwadinfo_t[dwadinfo_t] -file_1: byte[dwadinfo_t[num]->disksize] -file_2: byte[dwadinfo_t[num]->disksize] -file_3: byte[dwadinfo_t[num]->disksize] -... -file_n: byte[dwadinfo_t[num]->disksize] -infotable dlumpinfo_t[dwadinfo_t->numlumps] -======================================================================== -*/ - -#define IDHPAKHEADER (('K'<<24)+('A'<<16)+('P'<<8)+'H') // little-endian "HPAK" -#define IDHPAK_VERSION 1 - -typedef struct -{ - int ident; // should be equal HPAK - int version; - int infotableofs; -} hpak_header_t; - -typedef struct -{ - resource_t resource; - int filepos; - int disksize; -} hpak_lump_t; - -typedef struct -{ - int count; - hpak_lump_t *entries; // variable sized. -} hpak_info_t; - -#define ZIP_HEADER_LF (('K'<<8)+('P')+(0x03<<16)+(0x04<<24)) -#define ZIP_HEADER_SPANNED ((0x08<<24)+(0x07<<16)+('K'<<8)+'P') - -#define ZIP_HEADER_CDF ((0x02<<24)+(0x01<<16)+('K'<<8)+'P') -#define ZIP_HEADER_EOCD ((0x06<<24)+(0x05<<16)+('K'<<8)+'P') - -#define ZIP_COMPRESSION_NO_COMPRESSION 0 -#define ZIP_COMPRESSION_DEFLATED 8 - -#define ZIP_ZIP64 0xffffffff - -#pragma pack( push, 1 ) -typedef struct zip_header_s -{ - unsigned int signature; // little endian ZIP_HEADER - unsigned short version; // version of pkzip need to unpack - unsigned short flags; // flags (16 bits == 16 flags) - unsigned short compression_flags; // compression flags (bits) - unsigned int dos_date; // file modification time and file modification date - unsigned int crc32; //crc32 - unsigned int compressed_size; - unsigned int uncompressed_size; - unsigned short filename_len; - unsigned short extrafield_len; -} zip_header_t; - -/* - in zip64 comp and uncompr size == 0xffffffff remeber this - compressed and uncompress filesize stored in extra field -*/ - -typedef struct zip_header_extra_s -{ - unsigned int signature; // ZIP_HEADER_SPANNED - unsigned int crc32; - unsigned int compressed_size; - unsigned int uncompressed_size; -} zip_header_extra_t; - -typedef struct zip_cdf_header_s -{ - unsigned int signature; - unsigned short version; - unsigned short version_need; - unsigned short generalPurposeBitFlag; - unsigned short flags; - unsigned short modification_time; - unsigned short modification_date; - unsigned int crc32; - unsigned int compressed_size; - unsigned int uncompressed_size; - unsigned short filename_len; - unsigned short extrafield_len; - unsigned short file_commentary_len; - unsigned short disk_start; - unsigned short internal_attr; - unsigned int external_attr; - unsigned int local_header_offset; -} zip_cdf_header_t; - -typedef struct zip_header_eocd_s -{ - unsigned short disk_number; - unsigned short start_disk_number; - unsigned short number_central_directory_record; - unsigned short total_central_directory_record; - unsigned int size_of_central_directory; - unsigned int central_directory_offset; - unsigned short commentary_len; -} zip_header_eocd_t; -#pragma pack( pop ) - -#endif//FILESYSTEM_H diff --git a/engine/common/filesystem_engine.c b/engine/common/filesystem_engine.c new file mode 100644 index 00000000..6d3ff03e --- /dev/null +++ b/engine/common/filesystem_engine.c @@ -0,0 +1,153 @@ + /* +filesystem.c - game filesystem based on DP fs +Copyright (C) 2007 Uncle Mike + +This program 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, either version 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "common.h" +#include "library.h" + +fs_api_t g_fsapi; +fs_globals_t *FI; + +static HINSTANCE fs_hInstance; + +static void FS_Rescan_f( void ) +{ + FS_Rescan(); +} + +static void FS_ClearPaths_f( void ) +{ + FS_ClearSearchPath(); +} + +static void FS_Path_f_( void ) +{ + FS_Path_f(); +} + +static fs_interface_t fs_memfuncs = +{ + Con_Printf, + Con_DPrintf, + Con_Reportf, + Sys_Error, + + _Mem_AllocPool, + _Mem_FreePool, + _Mem_Alloc, + _Mem_Realloc, + _Mem_Free, +}; + +static void FS_UnloadProgs( void ) +{ + COM_FreeLibrary( fs_hInstance ); + fs_hInstance = 0; +} + +#ifdef XASH_INTERNAL_GAMELIBS +#define FILESYSTEM_STDIO_DLL "filesystem_stdio" +#else +#define FILESYSTEM_STDIO_DLL "filesystem_stdio." OS_LIB_EXT +#endif + +qboolean FS_LoadProgs( void ) +{ + const char *name = FILESYSTEM_STDIO_DLL; + FSAPI GetFSAPI; + + fs_hInstance = COM_LoadLibrary( name, false, true ); + + if( !fs_hInstance ) + { + Host_Error( "FS_LoadProgs: can't load filesystem library %s: %s\n", name, COM_GetLibraryError() ); + return false; + } + + if( !( GetFSAPI = (FSAPI)COM_GetProcAddress( fs_hInstance, GET_FS_API ))) + { + FS_UnloadProgs(); + Host_Error( "FS_LoadProgs: can't find GetFSAPI entry point in %s\n", name ); + return false; + } + + if( !GetFSAPI( FS_API_VERSION, &g_fsapi, &FI, &fs_memfuncs )) + { + FS_UnloadProgs(); + Host_Error( "FS_LoadProgs: can't initialize filesystem API: wrong version\n" ); + return false; + } + + Con_DPrintf( "FS_LoadProgs: filesystem_stdio successfully loaded\n" ); + + return true; +} + +/* +================ +FS_Init +================ +*/ +void FS_Init( void ) +{ + qboolean hasBaseDir = false; + qboolean hasGameDir = false; + qboolean caseinsensitive = true; + int i; + string gamedir; + + Cmd_AddRestrictedCommand( "fs_rescan", FS_Rescan_f, "rescan filesystem search pathes" ); + Cmd_AddRestrictedCommand( "fs_path", FS_Path_f_, "show filesystem search pathes" ); + Cmd_AddRestrictedCommand( "fs_clearpaths", FS_ClearPaths_f, "clear filesystem search pathes" ); + +#if !XASH_WIN32 + if( Sys_CheckParm( "-casesensitive" ) ) + caseinsensitive = false; +#endif + + if( !Sys_GetParmFromCmdLine( "-game", gamedir )) + Q_strncpy( gamedir, SI.basedirName, sizeof( gamedir )); // gamedir == basedir + + if( !FS_InitStdio( caseinsensitive, host.rootdir, SI.basedirName, gamedir, host.rodir )) + { + Host_Error( "Can't init filesystem_stdio!\n" ); + return; + } + + if( !Sys_GetParmFromCmdLine( "-dll", SI.gamedll )) + SI.gamedll[0] = 0; + + if( !Sys_GetParmFromCmdLine( "-clientlib", SI.clientlib )) + SI.clientlib[0] = 0; +} + +/* +================ +FS_Shutdown +================ +*/ +void FS_Shutdown( void ) +{ + int i; + + FS_ShutdownStdio(); + + memset( &SI, 0, sizeof( sysinfo_t )); + + FS_UnloadProgs(); +} + + + + diff --git a/engine/common/host.c b/engine/common/host.c index 47d5a565..f259cdca 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -51,6 +51,7 @@ struct tests_stats_s tests_stats; #endif CVAR_DEFINE( host_developer, "developer", "0", FCVAR_FILTERABLE, "engine is in development-mode" ); +CVAR_DEFINE_AUTO( sys_timescale, "1.0", FCVAR_CHEAT|FCVAR_FILTERABLE, "scale frame time" ); CVAR_DEFINE_AUTO( sys_ticrate, "100", 0, "framerate in dedicated mode" ); convar_t *host_serverstate; @@ -85,9 +86,11 @@ void Sys_PrintUsage( void ) O("-dev [level] ","set log verbosity 0-2") O("-log ","write log to \"engine.log\"") O("-nowriteconfig ","disable config save") + #if !XASH_WIN32 O("-casesensitive ","disable case-insensitive FS emulation") #endif // !XASH_WIN32 + #if !XASH_MOBILE_PLATFORM O("-daemonize ","run engine in background, dedicated only") #endif // !XASH_MOBILE_PLATFORM @@ -98,53 +101,53 @@ void Sys_PrintUsage( void ) O("-height ","set window height") O("-oldfont ","enable unused Quake font in Half-Life") - #if !XASH_MOBILE_PLATFORM +#if !XASH_MOBILE_PLATFORM O("-fullscreen ","run engine in fullscreen mode") O("-windowed ","run engine in windowed mode") O("-dedicated ","run engine in dedicated server mode") - #endif // XASH_MOBILE_PLATFORM +#endif // XASH_MOBILE_PLATFORM - #if XASH_ANDROID - O("-nativeegl ","use native egl implementation. Use if screen does not update or black") - #endif // XASH_ANDROID +#if XASH_ANDROID + O("-nativeegl ","use native egl implementation. Use if screen does not update or black") +#endif // XASH_ANDROID - #if XASH_WIN32 - O("-noavi ","disable AVI support") - O("-nointro ","disable intro video") - #endif // XASH_WIN32 +#if XASH_WIN32 + O("-noavi ","disable AVI support") + O("-nointro ","disable intro video") +#endif // XASH_WIN32 - #if XASH_DOS +#if XASH_DOS O("-novesa ","disable vesa") - #endif // XASH_DOS +#endif // XASH_DOS - #if XASH_VIDEO == VIDEO_FBDEV +#if XASH_VIDEO == VIDEO_FBDEV O("-fbdev ","open selected framebuffer") O("-ttygfx ","set graphics mode in tty") O("-doublebuffer ","enable doublebuffering") - #endif // XASH_VIDEO == VIDEO_FBDEV +#endif // XASH_VIDEO == VIDEO_FBDEV - #if XASH_SOUND == SOUND_ALSA +#if XASH_SOUND == SOUND_ALSA O("-alsadev ","open selected ALSA device") - #endif // XASH_SOUND == SOUND_ALSA +#endif // XASH_SOUND == SOUND_ALSA O("-nojoy ","disable joystick support") - #ifdef XASH_SDL + +#ifdef XASH_SDL O("-sdl_joy_old_api ","use SDL legacy joystick API") O("-sdl_renderer ","use alternative SDL_Renderer for software") - #endif // XASH_SDL +#endif // XASH_SDL O("-nosound ","disable sound") O("-noenginemouse ","disable mouse completely") O("-ref ","use selected renderer dll") - O("-gldebug ","enable OpenGL debug log") - + O("-gldebug ","enable OpenGL debug log") #endif // XASH_DEDICATED O("-noip ","disable TCP/IP") O("-noch ","disable crashhandler") O("-disablehelp ","disable this message") O("-dll ","override server DLL path") -#ifndef XASH_DEDICATED +#if !XASH_DEDICATED O("-clientlib ","override client DLL path") #endif O("-rodir ","set read-only base directory, experimental") @@ -302,7 +305,9 @@ void Host_NewInstance( const char *name, const char *finalmsg ) host.change_game = true; Q_strncpy( host.finalmsg, finalmsg, sizeof( host.finalmsg )); - pChangeGame( name ); // call from hl.exe + + if( !Sys_NewInstance( name )) + pChangeGame( name ); // call from hl.exe } /* @@ -323,13 +328,13 @@ void Host_ChangeGame_f( void ) } // validate gamedir - for( i = 0; i < SI.numgames; i++ ) + for( i = 0; i < FI->numgames; i++ ) { - if( !Q_stricmp( SI.games[i]->gamefolder, Cmd_Argv( 1 ))) + if( !Q_stricmp( FI->games[i]->gamefolder, Cmd_Argv( 1 ))) break; } - if( i == SI.numgames ) + if( i == FI->numgames ) { Con_Printf( "%s not exist\n", Cmd_Argv( 1 )); } @@ -340,7 +345,7 @@ void Host_ChangeGame_f( void ) else { const char *arg1 = va( "%s%s", (host.type == HOST_NORMAL) ? "" : "#", Cmd_Argv( 1 )); - const char *arg2 = va( "change game to '%s'", SI.games[i]->title ); + const char *arg2 = va( "change game to '%s'", FI->games[i]->title ); Host_NewInstance( arg1, arg2 ); } @@ -626,8 +631,9 @@ qboolean Host_FilterTime( float time ) { static double oldtime; double fps; + double scale = sys_timescale.value; - host.realtime += time; + host.realtime += time * scale; fps = Host_CalcFPS( ); // clamp the fps in multiplayer games @@ -638,12 +644,12 @@ qboolean Host_FilterTime( float time ) if( Host_IsDedicated() ) { - if(( host.realtime - oldtime ) < ( 1.0 / ( fps + 1.0 ))) + if(( host.realtime - oldtime ) < ( 1.0 / ( fps + 1.0 )) * scale) return false; } else { - if(( host.realtime - oldtime ) < ( 1.0 / fps )) + if(( host.realtime - oldtime ) < ( 1.0 / fps ) * scale ) return false; } } @@ -654,7 +660,7 @@ qboolean Host_FilterTime( float time ) // NOTE: allow only in singleplayer while demos are not active if( host_framerate->value > 0.0f && Host_IsLocalGame() && !CL_IsPlaybackDemo() && !CL_IsRecordDemo( )) - host.frametime = bound( MIN_FRAMETIME, host_framerate->value, MAX_FRAMETIME ); + host.frametime = bound( MIN_FRAMETIME, host_framerate->value * scale, MAX_FRAMETIME ); else host.frametime = bound( MIN_FRAMETIME, host.frametime, MAX_FRAMETIME ); return true; @@ -717,7 +723,6 @@ void GAME_EXPORT Host_Error( const char *error, ... ) else if( host.framecount == host.errorframe ) { Sys_Error( "Host_MultiError: %s", hosterror2 ); - return; } else { @@ -737,7 +742,6 @@ void GAME_EXPORT Host_Error( const char *error, ... ) { Con_Printf( "Host_RecursiveError: %s", hosterror2 ); Sys_Error( "%s", hosterror1 ); - return; // don't multiple executes } recursive = true; @@ -820,9 +824,15 @@ static void Host_RunTests( int stage ) Test_RunCommon(); Test_RunCmd(); Test_RunCvar(); +#if !XASH_DEDICATED + Test_RunCon(); +#endif /* XASH_DEDICATED */ break; case 1: // after FS load Test_RunImagelib(); +#if !XASH_DEDICATED + Test_RunVOX(); +#endif Msg( "Done! %d passed, %d failed\n", tests_stats.passed, tests_stats.failed ); Sys_Quit(); } @@ -860,7 +870,7 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha host.enabledll = !Sys_CheckParm( "-nodll" ); - host.change_game = bChangeGame; + host.change_game = bChangeGame || Sys_CheckParm( "-changegame" ); host.config_executed = false; host.status = HOST_INIT; // initialzation started @@ -998,7 +1008,7 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha len = Q_strlen( host.rootdir ); - if( host.rootdir[len - 1] == '/' ) + if( len && host.rootdir[len - 1] == '/' ) host.rootdir[len - 1] = 0; // get readonly root. The order is: check for arg, then env. @@ -1017,18 +1027,27 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha if( len && host.rodir[len - 1] == '/' ) host.rodir[len - 1] = 0; - if( !COM_CheckStringEmpty( host.rootdir ) || FS_SetCurrentDirectory( host.rootdir ) != 0 ) + if( !COM_CheckStringEmpty( host.rootdir )) + { + Sys_Error( "Changing working directory failed (empty working directory)\n" ); + return; + } + + FS_LoadProgs(); + + if( FS_SetCurrentDirectory( host.rootdir ) != 0 ) Con_Reportf( "%s is working directory now\n", host.rootdir ); else Sys_Error( "Changing working directory to %s failed.\n", host.rootdir ); + FS_Init(); + Sys_InitLog(); Cmd_AddCommand( "exec", Host_Exec_f, "execute a script file" ); Cmd_AddCommand( "memlist", Host_MemStats_f, "prints memory pool information" ); Cmd_AddRestrictedCommand( "userconfigd", Host_Userconfigd_f, "execute all scripts from userconfig.d" ); - FS_Init(); Image_Init(); Sound_Init(); @@ -1038,8 +1057,16 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha #endif FS_LoadGameInfo( NULL ); + + if( FS_FileExists( va( "%s.rc", SI.basedirName ), false )) + Q_strncpy( SI.rcName, SI.basedirName, sizeof( SI.rcName )); // e.g. valve.rc + else Q_strncpy( SI.rcName, SI.exeName, sizeof( SI.rcName )); // e.g. quake.rc + Q_strncpy( host.gamefolder, GI->gamefolder, sizeof( host.gamefolder )); + Image_CheckPaletteQ1 (); + Host_InitDecals (); // reload decals + // DEPRECATED: by FWGS fork #if 0 if( GI->secure ) @@ -1095,10 +1122,12 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa host_clientloaded = Cvar_Get( "host_clientloaded", "0", FCVAR_READ_ONLY, "inidcates a loaded client.dll" ); host_limitlocal = Cvar_Get( "host_limitlocal", "0", 0, "apply cl_cmdrate and rate to loopback connection" ); con_gamemaps = Cvar_Get( "con_mapfilter", "1", FCVAR_ARCHIVE, "when true show only maps in game folder" ); + Cvar_RegisterVariable( &sys_timescale ); build = Cvar_Get( "buildnum", va( "%i", Q_buildnum_compat()), FCVAR_READ_ONLY, "returns a current build number" ); ver = Cvar_Get( "ver", va( "%i/%s (hw build %i)", PROTOCOL_VERSION, XASH_COMPAT_VERSION, Q_buildnum_compat()), FCVAR_READ_ONLY, "shows an engine version" ); Cvar_Get( "host_ver", va( "%i %s %s %s %s", Q_buildnum(), XASH_VERSION, Q_buildos(), Q_buildarch(), Q_buildcommit() ), FCVAR_READ_ONLY, "detailed info about this build" ); + Cvar_Get( "host_lowmemorymode", va( "%i", XASH_LOW_MEMORY ), FCVAR_READ_ONLY, "indicates if engine compiled for low RAM consumption (0 - normal, 1 - low engine limits, 2 - low protocol limits)" ); Mod_Init(); NET_Init(); diff --git a/engine/common/hpak.c b/engine/common/hpak.c index 0da8e8a7..076c3428 100644 --- a/engine/common/hpak.c +++ b/engine/common/hpak.c @@ -14,7 +14,7 @@ GNU General Public License for more details. */ #include "common.h" -#include "filesystem.h" +#include "hpak.h" #define HPAK_MAX_ENTRIES 0x8000 #define HPAK_MIN_SIZE (1 * 1024) @@ -402,7 +402,7 @@ static qboolean HPAK_Validate( const char *filename, qboolean quiet ) FS_Seek( f, hdr.infotableofs, SEEK_SET ); FS_Read( f, &num_lumps, sizeof( num_lumps )); - if( num_lumps < 1 || num_lumps > MAX_FILES_IN_WAD ) + if( num_lumps < 1 || num_lumps > HPAK_MAX_ENTRIES ) { Con_DPrintf( S_ERROR "HPAK_ValidatePak: %s has too many lumps %u.\n", pakname, num_lumps ); FS_Close( f ); diff --git a/engine/common/hpak.h b/engine/common/hpak.h new file mode 100644 index 00000000..4d5c8fde --- /dev/null +++ b/engine/common/hpak.h @@ -0,0 +1,60 @@ +/* +hpak.c - custom user package to send other clients +Copyright (C) 2010 Uncle Mike + +This program 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, either version 3 of the License, or +(at your option) any later version. + +This program 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. +*/ +#ifndef HPAK_H +#define HPAK_H + +#include "custom.h" + +/* +======================================================================== +.HPK archive format (Hash PAK - HPK) + +List of compressed files, that can be identify only by TYPE_* + + +header: dwadinfo_t[dwadinfo_t] +file_1: byte[dwadinfo_t[num]->disksize] +file_2: byte[dwadinfo_t[num]->disksize] +file_3: byte[dwadinfo_t[num]->disksize] +... +file_n: byte[dwadinfo_t[num]->disksize] +infotable dlumpinfo_t[dwadinfo_t->numlumps] +======================================================================== +*/ + +#define IDHPAKHEADER (('K'<<24)+('A'<<16)+('P'<<8)+'H') // little-endian "HPAK" +#define IDHPAK_VERSION 1 + +typedef struct +{ + int ident; // should be equal HPAK + int version; + int infotableofs; +} hpak_header_t; + +typedef struct +{ + resource_t resource; + int filepos; + int disksize; +} hpak_lump_t; + +typedef struct +{ + int count; + hpak_lump_t *entries; // variable sized. +} hpak_info_t; + +#endif // HPAK_H diff --git a/engine/common/imagelib/img_bmp.c b/engine/common/imagelib/img_bmp.c index 74583716..c81c3701 100644 --- a/engine/common/imagelib/img_bmp.c +++ b/engine/common/imagelib/img_bmp.c @@ -41,7 +41,7 @@ qboolean Image_LoadBMP( const char *name, const byte *buffer, fs_offset_t filesi buf_p = (byte *)buffer; memcpy( &bhdr, buf_p, sizeof( bmp_t )); - buf_p += sizeof( bmp_t ); + buf_p += BI_FILE_HEADER_SIZE + bhdr.bitmapHeaderSize; // bogus file header check if( bhdr.reserved0 != 0 ) return false; @@ -53,9 +53,9 @@ qboolean Image_LoadBMP( const char *name, const byte *buffer, fs_offset_t filesi return false; } - if( bhdr.bitmapHeaderSize != 0x28 ) + if(!( bhdr.bitmapHeaderSize == 40 || bhdr.bitmapHeaderSize == 108 || bhdr.bitmapHeaderSize == 124 )) { - Con_DPrintf( S_ERROR "Image_LoadBMP: invalid header size %i\n", bhdr.bitmapHeaderSize ); + Con_DPrintf( S_ERROR "Image_LoadBMP: %s have non-standard header size %i\n", name, bhdr.bitmapHeaderSize ); return false; } @@ -187,6 +187,7 @@ qboolean Image_LoadBMP( const char *name, const byte *buffer, fs_offset_t filesi return false; } + image.depth = 1; image.size = image.width * image.height * bpp; image.rgba = Mem_Malloc( host.imagepool, image.size ); @@ -313,8 +314,8 @@ qboolean Image_LoadBMP( const char *name, const byte *buffer, fs_offset_t filesi } VectorDivide( reflectivity, ( image.width * image.height ), image.fogParams ); - if( image.palette ) Image_GetPaletteBMP( image.palette ); - image.depth = 1; + if( image.palette ) + Image_GetPaletteBMP( image.palette ); return true; } diff --git a/engine/common/imagelib/img_bmp.h b/engine/common/imagelib/img_bmp.h index 72b4d8a2..a5728eeb 100644 --- a/engine/common/imagelib/img_bmp.h +++ b/engine/common/imagelib/img_bmp.h @@ -22,6 +22,7 @@ GNU General Public License for more details. ======================================================================== */ +#define BI_FILE_HEADER_SIZE 14 #define BI_SIZE 40 // size of bitmap info header. #if !defined(BI_RGB) #define BI_RGB 0 // uncompressed RGB bitmap(defined in wingdi.h) diff --git a/engine/common/imagelib/img_dds.c b/engine/common/imagelib/img_dds.c index 845813c6..361ddae2 100644 --- a/engine/common/imagelib/img_dds.c +++ b/engine/common/imagelib/img_dds.c @@ -91,7 +91,7 @@ qboolean Image_CheckDXT5Alpha( dds_t *hdr, byte *fin ) return false; } -void Image_DXTGetPixelFormat( dds_t *hdr ) +void Image_DXTGetPixelFormat( dds_t *hdr, dds_header_dxt10_t *headerExt ) { uint bits = hdr->dsPixelFormat.dwRGBBitCount; @@ -100,29 +100,53 @@ void Image_DXTGetPixelFormat( dds_t *hdr ) if( FBitSet( hdr->dsPixelFormat.dwFlags, DDS_FOURCC )) { - switch( hdr->dsPixelFormat.dwFourCC ) + if( hdr->dsPixelFormat.dwFourCC == TYPE_DX10 ) { - case TYPE_DXT1: - image.type = PF_DXT1; - break; - case TYPE_DXT2: - image.flags &= ~IMAGE_HAS_ALPHA; // alpha is already premultiplied by color - // intentionally fallthrough - case TYPE_DXT3: - image.type = PF_DXT3; - break; - case TYPE_DXT4: - image.flags &= ~IMAGE_HAS_ALPHA; // alpha is already premultiplied by color - // intentionally fallthrough - case TYPE_DXT5: - image.type = PF_DXT5; - break; - case TYPE_ATI2: - image.type = PF_ATI2; - break; - default: - image.type = PF_UNKNOWN; // assume error - break; + switch( headerExt->dxgiFormat ) + { + case DXGI_FORMAT_BC6H_SF16: + image.type = PF_BC6H_SIGNED; + break; + case DXGI_FORMAT_BC6H_UF16: + case DXGI_FORMAT_BC6H_TYPELESS: + image.type = PF_BC6H_UNSIGNED; + break; + case DXGI_FORMAT_BC7_UNORM: + case DXGI_FORMAT_BC7_UNORM_SRGB: + case DXGI_FORMAT_BC7_TYPELESS: + image.type = PF_BC7; + break; + default: + image.type = PF_UNKNOWN; + break; + } + } + else + { + switch( hdr->dsPixelFormat.dwFourCC ) + { + case TYPE_DXT1: + image.type = PF_DXT1; + break; + case TYPE_DXT2: + image.flags &= ~IMAGE_HAS_ALPHA; // alpha is already premultiplied by color + // intentionally fallthrough + case TYPE_DXT3: + image.type = PF_DXT3; + break; + case TYPE_DXT4: + image.flags &= ~IMAGE_HAS_ALPHA; // alpha is already premultiplied by color + // intentionally fallthrough + case TYPE_DXT5: + image.type = PF_DXT5; + break; + case TYPE_ATI2: + image.type = PF_ATI2; + break; + default: + image.type = PF_UNKNOWN; // assume error + break; + } } } else @@ -171,6 +195,9 @@ size_t Image_DXTGetLinearSize( int type, int width, int height, int depth ) case PF_DXT1: return ((( width + 3 ) / 4 ) * (( height + 3 ) / 4 ) * depth * 8 ); case PF_DXT3: case PF_DXT5: + case PF_BC6H_SIGNED: + case PF_BC6H_UNSIGNED: + case PF_BC7: case PF_ATI2: return ((( width + 3 ) / 4 ) * (( height + 3 ) / 4 ) * depth * 16 ); case PF_LUMINANCE: return (width * height * depth); case PF_BGR_24: @@ -254,16 +281,18 @@ qboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesi { dds_t header; byte *fin; + int headersOffset; + dds_header_dxt10_t header2; - if( filesize < sizeof( dds_t )) + if( filesize < sizeof( header )) return false; - memcpy( &header, buffer, sizeof( dds_t )); + memcpy( &header, buffer, sizeof( header )); if( header.dwIdent != DDSHEADER ) return false; // it's not a dds file, just skip it - if( header.dwSize != sizeof( dds_t ) - sizeof( uint )) // size of the structure (minus MagicNum) + if( header.dwSize != sizeof( header ) - sizeof( uint )) // size of the structure (minus MagicNum) { Con_DPrintf( S_ERROR "Image_LoadDDS: (%s) have corrupted header\n", name ); return false; @@ -275,6 +304,13 @@ qboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesi return false; } + headersOffset = sizeof( header ); + if( header.dsPixelFormat.dwFourCC == TYPE_DX10 ) + { + memcpy( &header2, buffer + sizeof( header ), sizeof( header2 )); + headersOffset += sizeof( header2 ); + } + image.width = header.dwWidth; image.height = header.dwHeight; @@ -284,7 +320,7 @@ qboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesi if( !Image_ValidSize( name )) return false; - Image_DXTGetPixelFormat( &header ); // and image type too :) + Image_DXTGetPixelFormat( &header, &header2 ); // and image type too :) Image_DXTAdjustVolume( &header ); if( !Image_CheckFlag( IL_DDS_HARDWARE ) && ImageDXT( image.type )) @@ -296,9 +332,9 @@ qboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesi return false; } - image.size = Image_DXTCalcSize( name, &header, filesize - 128 ); + image.size = Image_DXTCalcSize( name, &header, filesize - headersOffset ); if( image.size == 0 ) return false; // just in case - fin = (byte *)(buffer + sizeof( dds_t )); + fin = (byte *)( buffer + headersOffset ); // copy an encode method image.encode = (word)header.dwReserved1[0]; @@ -320,6 +356,8 @@ qboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesi SetBits( image.flags, IMAGE_HAS_ALPHA ); else if( image.type == PF_DXT5 && Image_CheckDXT5Alpha( &header, fin )) SetBits( image.flags, IMAGE_HAS_ALPHA ); + else if ( image.type == PF_BC7 ) + SetBits(image.flags, IMAGE_HAS_ALPHA); if( !FBitSet( header.dsPixelFormat.dwFlags, DDS_LUMINANCE )) SetBits( image.flags, IMAGE_HAS_COLOR ); break; diff --git a/engine/common/imagelib/img_dds.h b/engine/common/imagelib/img_dds.h index 43329233..b13c342c 100644 --- a/engine/common/imagelib/img_dds.h +++ b/engine/common/imagelib/img_dds.h @@ -29,6 +29,7 @@ GNU General Public License for more details. #define TYPE_DXT3 (('3'<<24)+('T'<<16)+('X'<<8)+'D') // little-endian "DXT3" #define TYPE_DXT4 (('4'<<24)+('T'<<16)+('X'<<8)+'D') // little-endian "DXT4" #define TYPE_DXT5 (('5'<<24)+('T'<<16)+('X'<<8)+'D') // little-endian "DXT5" +#define TYPE_DX10 (('0'<<24)+('1'<<16)+('X'<<8)+'D') // little-endian "DX10" #define TYPE_ATI1 (('1'<<24)+('I'<<16)+('T'<<8)+'A') // little-endian "ATI1" #define TYPE_ATI2 (('2'<<24)+('I'<<16)+('T'<<8)+'A') // little-endian "ATI2" #define TYPE_RXGB (('B'<<24)+('G'<<16)+('X'<<8)+'R') // little-endian "RXGB" doom3 normalmaps @@ -75,6 +76,141 @@ GNU General Public License for more details. #define DDS_CUBEMAP_ALL_SIDES 0x0000FC00L #define DDS_VOLUME 0x00200000L +typedef enum +{ + DXGI_FORMAT_UNKNOWN = 0, + DXGI_FORMAT_R32G32B32A32_TYPELESS = 1, + DXGI_FORMAT_R32G32B32A32_FLOAT = 2, + DXGI_FORMAT_R32G32B32A32_UINT = 3, + DXGI_FORMAT_R32G32B32A32_SINT = 4, + DXGI_FORMAT_R32G32B32_TYPELESS = 5, + DXGI_FORMAT_R32G32B32_FLOAT = 6, + DXGI_FORMAT_R32G32B32_UINT = 7, + DXGI_FORMAT_R32G32B32_SINT = 8, + DXGI_FORMAT_R16G16B16A16_TYPELESS = 9, + DXGI_FORMAT_R16G16B16A16_FLOAT = 10, + DXGI_FORMAT_R16G16B16A16_UNORM = 11, + DXGI_FORMAT_R16G16B16A16_UINT = 12, + DXGI_FORMAT_R16G16B16A16_SNORM = 13, + DXGI_FORMAT_R16G16B16A16_SINT = 14, + DXGI_FORMAT_R32G32_TYPELESS = 15, + DXGI_FORMAT_R32G32_FLOAT = 16, + DXGI_FORMAT_R32G32_UINT = 17, + DXGI_FORMAT_R32G32_SINT = 18, + DXGI_FORMAT_R32G8X24_TYPELESS = 19, + DXGI_FORMAT_D32_FLOAT_S8X24_UINT = 20, + DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS = 21, + DXGI_FORMAT_X32_TYPELESS_G8X24_UINT = 22, + DXGI_FORMAT_R10G10B10A2_TYPELESS = 23, + DXGI_FORMAT_R10G10B10A2_UNORM = 24, + DXGI_FORMAT_R10G10B10A2_UINT = 25, + DXGI_FORMAT_R11G11B10_FLOAT = 26, + DXGI_FORMAT_R8G8B8A8_TYPELESS = 27, + DXGI_FORMAT_R8G8B8A8_UNORM = 28, + DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29, + DXGI_FORMAT_R8G8B8A8_UINT = 30, + DXGI_FORMAT_R8G8B8A8_SNORM = 31, + DXGI_FORMAT_R8G8B8A8_SINT = 32, + DXGI_FORMAT_R16G16_TYPELESS = 33, + DXGI_FORMAT_R16G16_FLOAT = 34, + DXGI_FORMAT_R16G16_UNORM = 35, + DXGI_FORMAT_R16G16_UINT = 36, + DXGI_FORMAT_R16G16_SNORM = 37, + DXGI_FORMAT_R16G16_SINT = 38, + DXGI_FORMAT_R32_TYPELESS = 39, + DXGI_FORMAT_D32_FLOAT = 40, + DXGI_FORMAT_R32_FLOAT = 41, + DXGI_FORMAT_R32_UINT = 42, + DXGI_FORMAT_R32_SINT = 43, + DXGI_FORMAT_R24G8_TYPELESS = 44, + DXGI_FORMAT_D24_UNORM_S8_UINT = 45, + DXGI_FORMAT_R24_UNORM_X8_TYPELESS = 46, + DXGI_FORMAT_X24_TYPELESS_G8_UINT = 47, + DXGI_FORMAT_R8G8_TYPELESS = 48, + DXGI_FORMAT_R8G8_UNORM = 49, + DXGI_FORMAT_R8G8_UINT = 50, + DXGI_FORMAT_R8G8_SNORM = 51, + DXGI_FORMAT_R8G8_SINT = 52, + DXGI_FORMAT_R16_TYPELESS = 53, + DXGI_FORMAT_R16_FLOAT = 54, + DXGI_FORMAT_D16_UNORM = 55, + DXGI_FORMAT_R16_UNORM = 56, + DXGI_FORMAT_R16_UINT = 57, + DXGI_FORMAT_R16_SNORM = 58, + DXGI_FORMAT_R16_SINT = 59, + DXGI_FORMAT_R8_TYPELESS = 60, + DXGI_FORMAT_R8_UNORM = 61, + DXGI_FORMAT_R8_UINT = 62, + DXGI_FORMAT_R8_SNORM = 63, + DXGI_FORMAT_R8_SINT = 64, + DXGI_FORMAT_A8_UNORM = 65, + DXGI_FORMAT_R1_UNORM = 66, + DXGI_FORMAT_R9G9B9E5_SHAREDEXP = 67, + DXGI_FORMAT_R8G8_B8G8_UNORM = 68, + DXGI_FORMAT_G8R8_G8B8_UNORM = 69, + DXGI_FORMAT_BC1_TYPELESS = 70, + DXGI_FORMAT_BC1_UNORM = 71, + DXGI_FORMAT_BC1_UNORM_SRGB = 72, + DXGI_FORMAT_BC2_TYPELESS = 73, + DXGI_FORMAT_BC2_UNORM = 74, + DXGI_FORMAT_BC2_UNORM_SRGB = 75, + DXGI_FORMAT_BC3_TYPELESS = 76, + DXGI_FORMAT_BC3_UNORM = 77, + DXGI_FORMAT_BC3_UNORM_SRGB = 78, + DXGI_FORMAT_BC4_TYPELESS = 79, + DXGI_FORMAT_BC4_UNORM = 80, + DXGI_FORMAT_BC4_SNORM = 81, + DXGI_FORMAT_BC5_TYPELESS = 82, + DXGI_FORMAT_BC5_UNORM = 83, + DXGI_FORMAT_BC5_SNORM = 84, + DXGI_FORMAT_B5G6R5_UNORM = 85, + DXGI_FORMAT_B5G5R5A1_UNORM = 86, + DXGI_FORMAT_B8G8R8A8_UNORM = 87, + DXGI_FORMAT_B8G8R8X8_UNORM = 88, + DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM = 89, + DXGI_FORMAT_B8G8R8A8_TYPELESS = 90, + DXGI_FORMAT_B8G8R8A8_UNORM_SRGB = 91, + DXGI_FORMAT_B8G8R8X8_TYPELESS = 92, + DXGI_FORMAT_B8G8R8X8_UNORM_SRGB = 93, + DXGI_FORMAT_BC6H_TYPELESS = 94, + DXGI_FORMAT_BC6H_UF16 = 95, + DXGI_FORMAT_BC6H_SF16 = 96, + DXGI_FORMAT_BC7_TYPELESS = 97, + DXGI_FORMAT_BC7_UNORM = 98, + DXGI_FORMAT_BC7_UNORM_SRGB = 99, + DXGI_FORMAT_AYUV = 100, + DXGI_FORMAT_Y410 = 101, + DXGI_FORMAT_Y416 = 102, + DXGI_FORMAT_NV12 = 103, + DXGI_FORMAT_P010 = 104, + DXGI_FORMAT_P016 = 105, + DXGI_FORMAT_420_OPAQUE = 106, + DXGI_FORMAT_YUY2 = 107, + DXGI_FORMAT_Y210 = 108, + DXGI_FORMAT_Y216 = 109, + DXGI_FORMAT_NV11 = 110, + DXGI_FORMAT_AI44 = 111, + DXGI_FORMAT_IA44 = 112, + DXGI_FORMAT_P8 = 113, + DXGI_FORMAT_A8P8 = 114, + DXGI_FORMAT_B4G4R4A4_UNORM = 115, + DXGI_FORMAT_P208 = 130, + DXGI_FORMAT_V208 = 131, + DXGI_FORMAT_V408 = 132, + DXGI_FORMAT_SAMPLER_FEEDBACK_MIN_MIP_OPAQUE, + DXGI_FORMAT_SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE, + DXGI_FORMAT_FORCE_UINT = 0xffffffff +} dxgi_format_t; + +typedef enum +{ + D3D10_RESOURCE_DIMENSION_UNKNOWN = 0, + D3D10_RESOURCE_DIMENSION_BUFFER = 1, + D3D10_RESOURCE_DIMENSION_TEXTURE1D = 2, + D3D10_RESOURCE_DIMENSION_TEXTURE2D = 3, + D3D10_RESOURCE_DIMENSION_TEXTURE3D = 4 +} dds_resource_dimension_t; + typedef struct dds_pf_s { uint32_t dwSize; @@ -96,6 +232,15 @@ typedef struct dds_caps_s uint32_t dwCaps4; // currently unused } dds_caps_t; +typedef struct dds_header_dxt10_s +{ + dxgi_format_t dxgiFormat; + dds_resource_dimension_t resourceDimension; + uint32_t miscFlag; + uint32_t arraySize; + uint32_t miscFlags2; +} dds_header_dxt10_t; + typedef struct dds_s { uint32_t dwIdent; // must matched with DDSHEADER diff --git a/engine/common/imagelib/img_main.c b/engine/common/imagelib/img_main.c index 70e67ce1..1b99e0c0 100644 --- a/engine/common/imagelib/img_main.c +++ b/engine/common/imagelib/img_main.c @@ -566,4 +566,25 @@ void Test_RunImagelib( void ) Z_Free( rgb.buffer ); } +#define IMPLEMENT_IMAGELIB_FUZZ_TARGET( export, target ) \ +int EXPORT export( const uint8_t *Data, size_t Size ) \ +{ \ + rgbdata_t *rgb; \ + host.type = HOST_NORMAL; \ + Memory_Init(); \ + Image_Init(); \ + if( target( "#internal", Data, Size )) \ + { \ + rgb = ImagePack(); \ + FS_FreeImage( rgb ); \ + } \ + Image_Shutdown(); \ + return 0; \ +} \ + +IMPLEMENT_IMAGELIB_FUZZ_TARGET( Fuzz_Image_LoadBMP, Image_LoadBMP ) +IMPLEMENT_IMAGELIB_FUZZ_TARGET( Fuzz_Image_LoadPNG, Image_LoadPNG ) +IMPLEMENT_IMAGELIB_FUZZ_TARGET( Fuzz_Image_LoadDDS, Image_LoadDDS ) +IMPLEMENT_IMAGELIB_FUZZ_TARGET( Fuzz_Image_LoadTGA, Image_LoadTGA ) + #endif /* XASH_ENGINE_TESTS */ diff --git a/engine/common/imagelib/img_png.c b/engine/common/imagelib/img_png.c index 9569a241..2c129ba1 100644 --- a/engine/common/imagelib/img_png.c +++ b/engine/common/imagelib/img_png.c @@ -13,7 +13,6 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ -#define MINIZ_HEADER_FILE_ONLY #include "miniz.h" #include "imagelib.h" #include "xash3d_mathlib.h" diff --git a/engine/common/imagelib/img_utils.c b/engine/common/imagelib/img_utils.c index 49976277..ce2e57bd 100644 --- a/engine/common/imagelib/img_utils.c +++ b/engine/common/imagelib/img_utils.c @@ -96,8 +96,8 @@ static const loadpixformat_t load_null[] = static const loadpixformat_t load_game[] = { { "%s%s.%s", "dds", Image_LoadDDS, IL_HINT_NO }, // dds for world and studio models -{ "%s%s.%s", "tga", Image_LoadTGA, IL_HINT_NO }, // hl vgui menus { "%s%s.%s", "bmp", Image_LoadBMP, IL_HINT_NO }, // WON menu images +{ "%s%s.%s", "tga", Image_LoadTGA, IL_HINT_NO }, // hl vgui menus { "%s%s.%s", "png", Image_LoadPNG, IL_HINT_NO }, // NightFire 007 menus { "%s%s.%s", "mip", Image_LoadMIP, IL_HINT_NO }, // hl textures from wad or buffer { "%s%s.%s", "mdl", Image_LoadMDL, IL_HINT_HL }, // hl studio model skins @@ -456,8 +456,8 @@ void Image_PaletteHueReplace( byte *palSrc, int newHue, int start, int end, int g = palSrc[i*pal_size+1]; b = palSrc[i*pal_size+2]; - maxcol = max( max( r, g ), b ) / 255.0f; - mincol = min( min( r, g ), b ) / 255.0f; + maxcol = Q_max( Q_max( r, g ), b ) / 255.0f; + mincol = Q_min( Q_min( r, g ), b ) / 255.0f; if( maxcol == 0 ) continue; diff --git a/engine/common/launcher.c b/engine/common/launcher.c index 7f85ff06..9410024c 100644 --- a/engine/common/launcher.c +++ b/engine/common/launcher.c @@ -23,66 +23,42 @@ GNU General Public License for more details. #if XASH_EMSCRIPTEN #include -#endif - -#if XASH_WIN32 -#define XASH_NOCONHOST 1 -#endif - -static char szGameDir[128]; // safe place to keep gamedir -static int g_iArgc; -static char **g_pszArgv; - -void Launcher_ChangeGame( const char *progname ) +#elif XASH_WIN32 +extern "C" { +// Enable NVIDIA High Performance Graphics while using Integrated Graphics. +__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; + +// Enable AMD High Performance Graphics while using Integrated Graphics. +__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; +} +#endif + +#define E_GAME "XASH3D_GAME" // default env dir to start from +#define GAME_PATH "valve" // default dir to start from + +static char szGameDir[128]; // safe place to keep gamedir +static int szArgc; +static char **szArgv; + +static void Sys_ChangeGame( const char *progname ) +{ + // a1ba: may never be called within engine + // if platform supports execv() function Q_strncpy( szGameDir, progname, sizeof( szGameDir ) - 1 ); Host_Shutdown( ); - exit( Host_Main( g_iArgc, g_pszArgv, szGameDir, 1, &Launcher_ChangeGame ) ); + exit( Host_Main( szArgc, szArgv, szGameDir, 1, &Sys_ChangeGame ) ); } -#if XASH_NOCONHOST -#include -#include // CommandLineToArgvW -int __stdcall WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdLine, int nShow) +_inline int Sys_Start( void ) { - int szArgc; - char **szArgv; - LPWSTR* lpArgv = CommandLineToArgvW(GetCommandLineW(), &szArgc); - int i = 0; + int ret; + const char *game = getenv( E_GAME ); - szArgv = (char**)malloc(szArgc*sizeof(char*)); - for (; i < szArgc; ++i) - { - size_t size = wcslen(lpArgv[i]) + 1; - szArgv[i] = (char*)malloc(size); - wcstombs(szArgv[i], lpArgv[i], size); - } - szArgv[i] = NULL; - - LocalFree(lpArgv); - - main( szArgc, szArgv ); - - for( i = 0; i < szArgc; ++i ) - free( szArgv[i] ); - free( szArgv ); -} -#endif -int main( int argc, char** argv ) -{ - char gamedir_buf[32] = ""; - const char *gamedir = getenv( "XASH3D_GAMEDIR" ); - - if( !COM_CheckString( gamedir ) ) - { - gamedir = "valve"; - } - else - { - Q_strncpy( gamedir_buf, gamedir, 32 ); - gamedir = gamedir_buf; - } + if( !game ) + game = GAME_PATH; + Q_strncpy( szGameDir, game, sizeof( szGameDir )); #if XASH_EMSCRIPTEN #ifdef EMSCRIPTEN_LIB_FS // For some unknown reason emscripten refusing to load libraries later @@ -98,17 +74,56 @@ int main( int argc, char** argv ) FS.chdir('/xash'); }catch(e){};); #endif -#endif - - g_iArgc = argc; - g_pszArgv = argv; -#if XASH_IOS +#elif XASH_IOS { void IOS_LaunchDialog( void ); IOS_LaunchDialog(); } #endif - return Host_Main( g_iArgc, g_pszArgv, gamedir, 0, &Launcher_ChangeGame ); + + ret = Host_Main( szArgc, szArgv, game, 0, Sys_ChangeGame ); + + return ret; } +#if !XASH_WIN32 +int main( int argc, char **argv ) +{ + szArgc = argc; + szArgv = argv; + + return Sys_Start(); +} +#else +int __stdcall WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdLine, int nShow) +{ + LPWSTR* lpArgv; + int ret, i; + + lpArgv = CommandLineToArgvW( GetCommandLineW(), &szArgc ); + szArgv = ( char** )malloc( (szArgc + 1) * sizeof( char* )); + + for( i = 0; i < szArgc; ++i ) + { + size_t size = wcslen(lpArgv[i]) + 1; + + // just in case, allocate some more memory + szArgv[i] = ( char * )malloc( size * sizeof( wchar_t )); + wcstombs( szArgv[i], lpArgv[i], size ); + } + szArgv[szArgc] = 0; + + LocalFree( lpArgv ); + + ret = Sys_Start(); + + for( ; i < szArgc; ++i ) + free( szArgv[i] ); + free( szArgv ); + + return ret; +} +#endif // XASH_WIN32 + + #endif diff --git a/engine/common/lib_common.c b/engine/common/lib_common.c index 9fd9e31e..cdd3f8b6 100644 --- a/engine/common/lib_common.c +++ b/engine/common/lib_common.c @@ -86,6 +86,38 @@ const char *COM_OffsetNameForFunction( void *function ) return sname; } +dll_user_t *FS_FindLibrary( const char *dllname, qboolean directpath ) +{ + dll_user_t *p; + fs_dllinfo_t dllInfo; + + // no fs loaded yet, but let engine find fs + if( !g_fsapi.FindLibrary ) + { + p = Mem_Calloc( host.mempool, sizeof( dll_user_t )); + Q_strncpy( p->shortPath, dllname, sizeof( p->shortPath )); + Q_strncpy( p->fullPath, dllname, sizeof( p->fullPath )); + Q_strncpy( p->dllName, dllname, sizeof( p->dllName )); + + return p; + } + + // fs can't find library + if( !g_fsapi.FindLibrary( dllname, directpath, &dllInfo )) + return NULL; + + // NOTE: for libraries we not fail even if search is NULL + // let the OS find library himself + p = Mem_Calloc( host.mempool, sizeof( dll_user_t )); + Q_strncpy( p->shortPath, dllInfo.shortPath, sizeof( p->shortPath )); + Q_strncpy( p->fullPath, dllInfo.fullPath, sizeof( p->fullPath )); + Q_strncpy( p->dllName, dllname, sizeof( p->dllName )); + p->custom_loader = dllInfo.custom_loader; + p->encrypted = dllInfo.encrypted; + + return p; +} + /* ============================================================================= @@ -291,12 +323,12 @@ static char *COM_GetItaniumName( const char * const in_name ) len = len * 10 + ( *f - '0' ); // sane value - len = min( remaining, len ); + len = Q_min( remaining, len ); if( len == 0 ) goto invalid_format; - Q_strncpy( symbols[i], f, min( len + 1, sizeof( out_name ))); + Q_strncpy( symbols[i], f, Q_min( len + 1, sizeof( out_name ))); f += len; remaining -= len; @@ -361,7 +393,7 @@ char **COM_ConvertToLocalPlatform( EFunctionMangleType to, const char *from, siz if( at ) len = (uint)( at - prev ); else len = (uint)Q_strlen( prev ); - Q_strncpy( symbols[i], prev, min( len + 1, sizeof( symbols[i] ))); + Q_strncpy( symbols[i], prev, Q_min( len + 1, sizeof( symbols[i] ))); prev = at + 1; if( !at ) diff --git a/engine/common/masterlist.c b/engine/common/masterlist.c index 4dbd325b..192372e7 100644 --- a/engine/common/masterlist.c +++ b/engine/common/masterlist.c @@ -254,6 +254,5 @@ void NET_InitMasters( void ) // keep main master always there NET_AddMaster( MASTERSERVER_ADR, false ); - NET_AddMaster( MASTERSERVER_ADR2, false ); NET_LoadMasters( ); } diff --git a/engine/common/mod_bmodel.c b/engine/common/mod_bmodel.c index 8d8dcae8..294d0dd8 100644 --- a/engine/common/mod_bmodel.c +++ b/engine/common/mod_bmodel.c @@ -1020,6 +1020,8 @@ Fills in surf->texturemins[] and surf->extents[] */ static void Mod_CalcSurfaceExtents( msurface_t *surf ) { + // this place is VERY critical to precision + // keep it as float, don't use double, because it causes issues with lightmap float mins[2], maxs[2], val; float lmmins[2], lmmaxs[2]; int bmins[2], bmaxs[2]; @@ -1049,14 +1051,14 @@ static void Mod_CalcSurfaceExtents( msurface_t *surf ) for( j = 0; j < 2; j++ ) { - val = DotProduct( v->position, surf->texinfo->vecs[j] ) + surf->texinfo->vecs[j][3]; + val = DotProductPrecise( v->position, surf->texinfo->vecs[j] ) + surf->texinfo->vecs[j][3]; mins[j] = Q_min( val, mins[j] ); maxs[j] = Q_max( val, maxs[j] ); } for( j = 0; j < 2; j++ ) { - val = DotProduct( v->position, info->lmvecs[j] ) + info->lmvecs[j][3]; + val = DotProductPrecise( v->position, info->lmvecs[j] ) + info->lmvecs[j][3]; lmmins[j] = Q_min( val, lmmins[j] ); lmmaxs[j] = Q_max( val, lmmaxs[j] ); } @@ -2734,6 +2736,37 @@ static void Mod_LoadLighting( dbspmodel_t *bmod ) } } +/* +================= +Mod_LumpLooksLikePlanes + +================= +*/ +static qboolean Mod_LumpLooksLikePlanes( const byte *in, dlump_t *lump, qboolean fast ) +{ + int numplanes, i; + const dplane_t *planes; + + if( lump->filelen < sizeof( dplane_t ) && + lump->filelen % sizeof( dplane_t ) != 0 ) + return false; + + if( fast ) + return true; + + numplanes = lump->filelen / sizeof( dplane_t ); + planes = (const dplane_t*)(in + lump->fileofs); + + for( i = 0; i < numplanes; i++ ) + { + // planes can only be from 0 to 5: PLANE_X, Y, Z and PLANE_ANYX, Y and Z + if( planes[i].type < 0 || planes[i].type > 5 ) + return false; + } + + return true; +} + /* ================= Mod_LoadBmodelLumps @@ -2783,8 +2816,8 @@ qboolean Mod_LoadBmodelLumps( const byte *mod_base, qboolean isworld ) if( header->version == HLBSP_VERSION ) { // only relevant for half-life maps - if( header->lumps[LUMP_ENTITIES].fileofs <= 1024 && - (header->lumps[LUMP_ENTITIES].filelen % sizeof( dplane_t )) == 0 ) + if( !Mod_LumpLooksLikePlanes( mod_base, &header->lumps[LUMP_PLANES], false ) && + Mod_LumpLooksLikePlanes( mod_base, &header->lumps[LUMP_ENTITIES], false )) { // blue-shift swapped lumps srclumps[0].lumpnumber = LUMP_PLANES; @@ -2908,13 +2941,28 @@ qboolean Mod_TestBmodelLumps( const char *name, const byte *mod_base, qboolean s break; } - if( header->version == HLBSP_VERSION && - header->lumps[LUMP_ENTITIES].fileofs <= 1024 && - (header->lumps[LUMP_ENTITIES].filelen % sizeof( dplane_t )) == 0 ) + if( header->version == HLBSP_VERSION ) { - // blue-shift swapped lumps - srclumps[0].lumpnumber = LUMP_PLANES; - srclumps[1].lumpnumber = LUMP_ENTITIES; + // only relevant for half-life maps + if( Mod_LumpLooksLikePlanes( mod_base, &header->lumps[LUMP_ENTITIES], true ) && + !Mod_LumpLooksLikePlanes( mod_base, &header->lumps[LUMP_PLANES], true )) + { + // blue-shift swapped lumps + srclumps[0].lumpnumber = LUMP_PLANES; + srclumps[1].lumpnumber = LUMP_ENTITIES; + } + else + { + // everything else + srclumps[0].lumpnumber = LUMP_ENTITIES; + srclumps[1].lumpnumber = LUMP_PLANES; + } + } + else + { + // everything else + srclumps[0].lumpnumber = LUMP_ENTITIES; + srclumps[1].lumpnumber = LUMP_PLANES; } // loading base lumps diff --git a/engine/common/mod_local.h b/engine/common/mod_local.h index 0a794e5f..5c7e579d 100644 --- a/engine/common/mod_local.h +++ b/engine/common/mod_local.h @@ -143,7 +143,6 @@ model_t *Mod_ForName( const char *name, qboolean crash, qboolean trackCRC ); qboolean Mod_ValidateCRC( const char *name, CRC32_t crc ); void Mod_NeedCRC( const char *name, qboolean needCRC ); void Mod_FreeUnused( void ); -model_t *Mod_Handle( int handle ); // // mod_bmodel.c diff --git a/engine/common/model.c b/engine/common/model.c index 1b75a2df..ad8e80ca 100644 --- a/engine/common/model.c +++ b/engine/common/model.c @@ -600,20 +600,3 @@ void Mod_NeedCRC( const char *name, qboolean needCRC ) if( needCRC ) SetBits( p->flags, FCRC_SHOULD_CHECKSUM ); else ClearBits( p->flags, FCRC_SHOULD_CHECKSUM ); } - - -/* -================== -Mod_Handle - -================== -*/ -model_t *GAME_EXPORT Mod_Handle( int handle ) -{ - if( handle < 0 || handle >= MAX_MODELS ) - { - Con_Reportf( "Mod_Handle: bad handle #%i\n", handle ); - return NULL; - } - return &mod_known[handle]; -} diff --git a/engine/common/net_encode.c b/engine/common/net_encode.c index 3c4c8c37..dc1c0f6c 100644 --- a/engine/common/net_encode.c +++ b/engine/common/net_encode.c @@ -980,10 +980,6 @@ qboolean Delta_CompareField( delta_t *pField, void *from, void *to, double timeb } else if( pField->flags & DT_INTEGER ) { -#if defined __GNUC__ && __GNUC_MAJOR < 9 && !defined __clang__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wduplicated-branches" -#endif if( pField->flags & DT_SIGNED ) { fromF = *(int *)((byte *)from + pField->offset ); @@ -994,9 +990,6 @@ qboolean Delta_CompareField( delta_t *pField, void *from, void *to, double timeb fromF = *(uint *)((byte *)from + pField->offset ); toF = *(uint *)((byte *)to + pField->offset ); } -#if defined __GNUC__ && __GNUC_MAJOR < 9 && !defined __clang__ -#pragma GCC diagnostic pop -#endif fromF = Delta_ClampIntegerField( pField, fromF, bSigned, pField->bits ); toF = Delta_ClampIntegerField( pField, toF, bSigned, pField->bits ); if( !Q_equal( pField->multiplier, 1.0 ) ) @@ -1121,7 +1114,7 @@ assume from and to is valid qboolean Delta_WriteField( sizebuf_t *msg, delta_t *pField, void *from, void *to, double timebase ) { qboolean bSigned = ( pField->flags & DT_SIGNED ) ? true : false; - float flValue, flAngle, flTime; + float flValue, flAngle; uint iValue; const char *pStr; @@ -1161,17 +1154,10 @@ qboolean Delta_WriteField( sizebuf_t *msg, delta_t *pField, void *from, void *to } else if( pField->flags & DT_INTEGER ) { -#if defined __GNUC__ && __GNUC_MAJOR < 9 && !defined __clang__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wduplicated-branches" -#endif if( bSigned ) iValue = *(int32_t *)((int8_t *)to + pField->offset ); else iValue = *(uint32_t *)((int8_t *)to + pField->offset ); -#if defined __GNUC__ && __GNUC_MAJOR < 9 && !defined __clang__ -#pragma GCC diagnostic pop -#endif iValue = Delta_ClampIntegerField( pField, iValue, bSigned, pField->bits ); if( !Q_equal( pField->multiplier, 1.0 ) ) @@ -1218,6 +1204,53 @@ qboolean Delta_WriteField( sizebuf_t *msg, delta_t *pField, void *from, void *to return true; } +/* +==================== +Delta_CopyField + +==================== +*/ +static void Delta_CopyField( delta_t *pField, void *from, void *to, double timebase ) +{ + qboolean bSigned = FBitSet( pField->flags, DT_SIGNED ); + uint8_t *to_field = (uint8_t *)to + pField->offset; + uint8_t *from_field = (uint8_t *)from + pField->offset; + + if( FBitSet( pField->flags, DT_BYTE )) + { + if( bSigned ) + *(int8_t *)( to_field ) = *(int8_t *)( from_field ); + else + *(uint8_t *)( to_field ) = *(uint8_t *)( from_field ); + } + else if( FBitSet( pField->flags, DT_SHORT )) + { + if( bSigned ) + *(int16_t *)( to_field ) = *(int16_t *)( from_field ); + else + *(uint16_t *)( to_field ) = *(uint16_t *)( from_field ); + } + else if( FBitSet( pField->flags, DT_INTEGER )) + { + if( bSigned ) + *(int32_t *)( to_field ) = *(int32_t *)( from_field ); + else + *(uint32_t *)( to_field ) = *(uint32_t *)( from_field ); + } + else if( FBitSet( pField->flags, DT_FLOAT|DT_ANGLE|DT_TIMEWINDOW_8|DT_TIMEWINDOW_BIG )) + { + *(float *)( to_field ) = *(float *)( from_field ); + } + else if( FBitSet( pField->flags, DT_STRING )) + { + Q_strncpy( to_field, from_field, pField->size ); + } + else + { + Assert( 0 ); + } +} + /* ===================== Delta_ReadField @@ -1230,30 +1263,24 @@ qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, void *to, { qboolean bSigned = ( pField->flags & DT_SIGNED ) ? true : false; float flValue, flAngle, flTime; - qboolean bChanged; uint iValue; const char *pStr; char *pOut; - bChanged = MSG_ReadOneBit( msg ); + if( !MSG_ReadOneBit( msg ) ) + { + Delta_CopyField( pField, from, to, timebase ); + return false; + } Assert( pField->multiplier != 0.0f ); if( pField->flags & DT_BYTE ) { - if( bChanged ) - { - iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); - if( !Q_equal( pField->multiplier, 1.0 ) ) - iValue /= pField->multiplier; - } - else - { - if( bSigned ) - iValue = *(int8_t *)((uint8_t *)from + pField->offset ); - else - iValue = *(uint8_t *)((uint8_t *)from + pField->offset ); - } + iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); + if( !Q_equal( pField->multiplier, 1.0 ) ) + iValue /= pField->multiplier; + if( bSigned ) *(int8_t *)((uint8_t *)to + pField->offset ) = iValue; else @@ -1261,19 +1288,10 @@ qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, void *to, } else if( pField->flags & DT_SHORT ) { - if( bChanged ) - { - iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); - if( !Q_equal( pField->multiplier, 1.0 ) ) - iValue /= pField->multiplier; - } - else - { - if( bSigned ) - iValue = *(int16_t *)((uint8_t *)from + pField->offset ); - else - iValue = *(uint16_t *)((uint8_t *)from + pField->offset ); - } + iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); + if( !Q_equal( pField->multiplier, 1.0 ) ) + iValue /= pField->multiplier; + if( bSigned ) *(int16_t *)((uint8_t *)to + pField->offset ) = iValue; else @@ -1281,112 +1299,63 @@ qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, void *to, } else if( pField->flags & DT_INTEGER ) { -#if defined __GNUC__ && __GNUC_MAJOR < 9 && !defined __clang__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wduplicated-branches" -#endif - if( bChanged ) - { - iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); - if( !Q_equal( pField->multiplier, 1.0 ) ) - iValue /= pField->multiplier; - } - else - { - if( bSigned ) - iValue = *(int32_t *)((uint8_t *)from + pField->offset ); - else - iValue = *(uint32_t *)((uint8_t *)from + pField->offset ); - } + iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); + if( !Q_equal( pField->multiplier, 1.0 ) ) + iValue /= pField->multiplier; + if( bSigned ) *(int32_t *)((uint8_t *)to + pField->offset ) = iValue; else *(uint32_t *)((uint8_t *)to + pField->offset ) = iValue; -#if defined __GNUC__ && __GNUC_MAJOR < 9 && !defined __clang__ -#pragma GCC diagnostic pop -#endif } else if( pField->flags & DT_FLOAT ) { - if( bChanged ) - { - iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); - if( bSigned ) - flValue = (int)iValue; - else - flValue = iValue; - - if( !Q_equal( pField->multiplier, 1.0 ) ) - flValue = flValue / pField->multiplier; - - if( !Q_equal( pField->post_multiplier, 1.0 ) ) - flValue = flValue * pField->post_multiplier; - } + iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); + if( bSigned ) + flValue = (int)iValue; else - { - flValue = *(float *)((byte *)from + pField->offset ); - } + flValue = iValue; + + if( !Q_equal( pField->multiplier, 1.0 ) ) + flValue = flValue / pField->multiplier; + + if( !Q_equal( pField->post_multiplier, 1.0 ) ) + flValue = flValue * pField->post_multiplier; + *(float *)((byte *)to + pField->offset ) = flValue; } else if( pField->flags & DT_ANGLE ) { - if( bChanged ) - { - flAngle = MSG_ReadBitAngle( msg, pField->bits ); - } - else - { - flAngle = *(float *)((byte *)from + pField->offset ); - } + flAngle = MSG_ReadBitAngle( msg, pField->bits ); *(float *)((byte *)to + pField->offset ) = flAngle; } else if( pField->flags & DT_TIMEWINDOW_8 ) { - if( bChanged ) - { - bSigned = true; // timewindow is always signed - iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); - flTime = (timebase * 100.0 - iValue) / 100.0; - } - else - { - flTime = *(float *)((byte *)from + pField->offset ); - } + bSigned = true; // timewindow is always signed + iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); + flTime = (timebase * 100.0 - (int)iValue) / 100.0; + *(float *)((byte *)to + pField->offset ) = flTime; } else if( pField->flags & DT_TIMEWINDOW_BIG ) { - if( bChanged ) - { - bSigned = true; // timewindow is always signed - iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); + bSigned = true; // timewindow is always signed + iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); - if( !Q_equal( pField->multiplier, 1.0 ) ) - flTime = ( timebase * pField->multiplier - iValue ) / pField->multiplier; - else - flTime = timebase - iValue; - } + if( !Q_equal( pField->multiplier, 1.0 ) ) + flTime = ( timebase * pField->multiplier - (int)iValue ) / pField->multiplier; else - { - flTime = *(float *)((byte *)from + pField->offset ); - } + flTime = timebase - (int)iValue; + *(float *)((byte *)to + pField->offset ) = flTime; } else if( pField->flags & DT_STRING ) { - if( bChanged ) - { - pStr = MSG_ReadString( msg ); - } - else - { - pStr = (char *)((byte *)from + pField->offset ); - } - + pStr = MSG_ReadString( msg ); pOut = (char *)((byte *)to + pField->offset ); Q_strncpy( pOut, pStr, pField->size ); } - return bChanged; + return true; } /* @@ -1636,6 +1605,7 @@ void MSG_ReadClientData( sizebuf_t *msg, clientdata_t *from, clientdata_t *to, d delta_t *pField; delta_info_t *dt; int i; + qboolean noChanges; dt = Delta_FindStruct( "clientdata_t" ); Assert( dt && dt->bInitialized ); @@ -1643,15 +1613,14 @@ void MSG_ReadClientData( sizebuf_t *msg, clientdata_t *from, clientdata_t *to, d pField = dt->pFields; Assert( pField != NULL ); - *to = *from; - - if( !cls.legacymode && !MSG_ReadOneBit( msg )) - return; // we have no changes + noChanges = !cls.legacymode && !MSG_ReadOneBit( msg ); // process fields for( i = 0; i < dt->numFields; i++, pField++ ) { - Delta_ReadField( msg, pField, from, to, timebase ); + if( noChanges ) + Delta_CopyField( pField, from, to, timebase ); + else Delta_ReadField( msg, pField, from, to, timebase ); } #endif } @@ -1722,8 +1691,6 @@ void MSG_ReadWeaponData( sizebuf_t *msg, weapon_data_t *from, weapon_data_t *to, pField = dt->pFields; Assert( pField != NULL ); - *to = *from; - // process fields for( i = 0; i < dt->numFields; i++, pField++ ) { @@ -1909,7 +1876,7 @@ qboolean MSG_ReadDeltaEntity( sizebuf_t *msg, entity_state_t *from, entity_state from = &cl.instanced_baseline[baseline_offset]; } } - } + } // g-cont. probably is redundant *to = *from; diff --git a/engine/common/net_ws.c b/engine/common/net_ws.c index d8206fef..999fb705 100644 --- a/engine/common/net_ws.c +++ b/engine/common/net_ws.c @@ -1382,10 +1382,10 @@ qboolean NET_BufferToBufferDecompress( byte *dest, uint *destLen, byte *source, /* ==================== -NET_Isocket +NET_IPSocket ==================== */ -static int NET_Isocket( const char *net_interface, int port, qboolean multicast ) +static int NET_IPSocket( const char *net_interface, int port, qboolean multicast ) { struct sockaddr_in addr; int err, net_socket; @@ -1396,7 +1396,7 @@ static int NET_Isocket( const char *net_interface, int port, qboolean multicast { err = WSAGetLastError(); if( err != WSAEAFNOSUPPORT ) - Con_DPrintf( S_WARN "NET_UDsocket: port: %d socket: %s\n", port, NET_ErrorString( )); + Con_DPrintf( S_WARN "NET_UDPSocket: port: %d socket: %s\n", port, NET_ErrorString( )); return INVALID_SOCKET; } @@ -1404,7 +1404,7 @@ static int NET_Isocket( const char *net_interface, int port, qboolean multicast { struct timeval timeout; - Con_DPrintf( S_WARN "NET_UDsocket: port: %d ioctl FIONBIO: %s\n", port, NET_ErrorString( )); + Con_DPrintf( S_WARN "NET_UDPSocket: port: %d ioctl FIONBIO: %s\n", port, NET_ErrorString( )); // try timeout instead of NBIO timeout.tv_sec = timeout.tv_usec = 0; setsockopt( net_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)); @@ -1413,14 +1413,14 @@ static int NET_Isocket( const char *net_interface, int port, qboolean multicast // make it broadcast capable if( NET_IsSocketError( setsockopt( net_socket, SOL_SOCKET, SO_BROADCAST, (char *)&_true, sizeof( _true ) ) ) ) { - Con_DPrintf( S_WARN "NET_UDsocket: port: %d setsockopt SO_BROADCAST: %s\n", port, NET_ErrorString( )); + Con_DPrintf( S_WARN "NET_UDPSocket: port: %d setsockopt SO_BROADCAST: %s\n", port, NET_ErrorString( )); } if( Sys_CheckParm( "-reuse" ) || multicast ) { if( NET_IsSocketError( setsockopt( net_socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&optval, sizeof( optval )) ) ) { - Con_DPrintf( S_WARN "NET_UDsocket: port: %d setsockopt SO_REUSEADDR: %s\n", port, NET_ErrorString( )); + Con_DPrintf( S_WARN "NET_UDPSocket: port: %d setsockopt SO_REUSEADDR: %s\n", port, NET_ErrorString( )); closesocket( net_socket ); return INVALID_SOCKET; } @@ -1435,7 +1435,7 @@ static int NET_Isocket( const char *net_interface, int port, qboolean multicast { err = WSAGetLastError(); if( err != WSAENOPROTOOPT ) - Con_Printf( S_WARN "NET_UDsocket: port: %d setsockopt IP_TOS: %s\n", port, NET_ErrorString( )); + Con_Printf( S_WARN "NET_UDPSocket: port: %d setsockopt IP_TOS: %s\n", port, NET_ErrorString( )); closesocket( net_socket ); return INVALID_SOCKET; } @@ -1452,7 +1452,7 @@ static int NET_Isocket( const char *net_interface, int port, qboolean multicast if( NET_IsSocketError( bind( net_socket, (void *)&addr, sizeof( addr )) ) ) { - Con_DPrintf( S_WARN "NET_UDsocket: port: %d bind: %s\n", port, NET_ErrorString( )); + Con_DPrintf( S_WARN "NET_UDPSocket: port: %d bind: %s\n", port, NET_ErrorString( )); closesocket( net_socket ); return INVALID_SOCKET; } @@ -1461,7 +1461,7 @@ static int NET_Isocket( const char *net_interface, int port, qboolean multicast { optval = 1; if( NET_IsSocketError( setsockopt( net_socket, IPPROTO_IP, IP_MULTICAST_LOOP, (const char *)&optval, sizeof( optval )) ) ) - Con_DPrintf( S_WARN "NET_UDsocket: port %d setsockopt IP_MULTICAST_LOOP: %s\n", port, NET_ErrorString( )); + Con_DPrintf( S_WARN "NET_UDPSocket: port %d setsockopt IP_MULTICAST_LOOP: %s\n", port, NET_ErrorString( )); } return net_socket; @@ -1472,35 +1472,71 @@ static int NET_Isocket( const char *net_interface, int port, qboolean multicast NET_OpenIP ==================== */ -static void NET_OpenIP( void ) +static void NET_OpenIP( qboolean change_port ) { - int port, sv_port = 0, cl_port = 0; + int port; + qboolean sv_nat = Cvar_VariableInteger("sv_nat"); + qboolean cl_nat = Cvar_VariableInteger("cl_nat"); + + if( change_port && ( FBitSet( net_hostport->flags, FCVAR_CHANGED ) || sv_nat )) + { + // reopen socket to set random port + if( NET_IsSocketValid( net.ip_sockets[NS_SERVER] )) + closesocket( net.ip_sockets[NS_SERVER] ); + + net.ip_sockets[NS_SERVER] = INVALID_SOCKET; + ClearBits( net_hostport->flags, FCVAR_CHANGED ); + } if( !NET_IsSocketValid( net.ip_sockets[NS_SERVER] ) ) { port = net_iphostport->value; - if( !port ) port = net_hostport->value; - if( !port ) port = PORT_SERVER; // forcing to default - net.ip_sockets[NS_SERVER] = NET_Isocket( net_ipname->string, port, false ); + if( !port ) + { + if( sv_nat ) + port = PORT_ANY; + else + port = net_hostport->value; + + if( !port ) + port = PORT_SERVER; // forcing to default + } + net.ip_sockets[NS_SERVER] = NET_IPSocket( net_ipname->string, port, false ); if( !NET_IsSocketValid( net.ip_sockets[NS_SERVER] ) && Host_IsDedicated() ) Host_Error( "Couldn't allocate dedicated server IP port %d.\n", port ); - sv_port = port; } // dedicated servers don't need client ports if( Host_IsDedicated() ) return; + if (change_port && ( FBitSet( net_clientport->flags, FCVAR_CHANGED ) || cl_nat )) + { + // reopen socket to set random port + if( NET_IsSocketValid(net.ip_sockets[NS_CLIENT] )) + closesocket( net.ip_sockets[NS_CLIENT] ); + + net.ip_sockets[NS_CLIENT] = INVALID_SOCKET; + ClearBits( net_clientport->flags, FCVAR_CHANGED ); + } + if( !NET_IsSocketValid( net.ip_sockets[NS_CLIENT] ) ) { port = net_ipclientport->value; - if( !port ) port = net_clientport->value; - if( !port ) port = PORT_ANY; // forcing to default - net.ip_sockets[NS_CLIENT] = NET_Isocket( net_ipname->string, port, false ); + if( !port ) + { + if( cl_nat ) + port = PORT_ANY; + else + port = net_clientport->value; + + if( !port ) + port = PORT_ANY; // forcing to default + } + net.ip_sockets[NS_CLIENT] = NET_IPSocket( net_ipname->string, port, false ); if( !NET_IsSocketValid( net.ip_sockets[NS_CLIENT] ) ) - net.ip_sockets[NS_CLIENT] = NET_Isocket( net_ipname->string, PORT_ANY, false ); - cl_port = port; + net.ip_sockets[NS_CLIENT] = NET_IPSocket( net_ipname->string, PORT_ANY, false ); } } @@ -1570,7 +1606,7 @@ NET_Config A single player game will only use the loopback code ==================== */ -void NET_Config( qboolean multiplayer ) +void NET_Config( qboolean multiplayer, qboolean changeport ) { static qboolean bFirst = true; static qboolean old_config; @@ -1586,7 +1622,7 @@ void NET_Config( qboolean multiplayer ) if( multiplayer ) { // open sockets - if( net.allow_ip ) NET_OpenIP(); + if( net.allow_ip ) NET_OpenIP( changeport ); // get our local address, if possible if( bFirst ) @@ -1695,7 +1731,7 @@ void NET_Init( void ) net_clockwindow = Cvar_Get( "clockwindow", "0.5", FCVAR_PRIVILEGED, "timewindow to execute client moves" ); net_address = Cvar_Get( "net_address", "0", FCVAR_READ_ONLY, "contain local address of current client" ); - net_ipname = Cvar_Get( "ip", "localhost", FCVAR_READ_ONLY, "network ip address" ); + net_ipname = Cvar_Get( "ip", "localhost", 0, "network ip address" ); net_iphostport = Cvar_Get( "ip_hostport", "0", FCVAR_READ_ONLY, "network ip host port" ); net_hostport = Cvar_Get( "hostport", va( "%i", PORT_SERVER ), FCVAR_READ_ONLY, "network default host port" ); net_ipclientport = Cvar_Get( "ip_clientport", "0", FCVAR_READ_ONLY, "network ip client port" ); @@ -1756,7 +1792,7 @@ void NET_Shutdown( void ) NET_ClearLagData( true, true ); - NET_Config( false ); + NET_Config( false, false ); #if XASH_WIN32 WSACleanup(); #endif @@ -2280,7 +2316,7 @@ void HTTP_Run( void ) } // update progress - if( !Host_IsDedicated() ) + if( !Host_IsDedicated() && iProgressCount != 0 ) Cvar_SetValue( "scr_download", flProgress/iProgressCount * 100 ); HTTP_AutoClean(); diff --git a/engine/common/net_ws.h b/engine/common/net_ws.h index ede8f0b1..75329383 100644 --- a/engine/common/net_ws.h +++ b/engine/common/net_ws.h @@ -51,7 +51,7 @@ void NET_Shutdown( void ); void NET_Sleep( int msec ); qboolean NET_IsActive( void ); qboolean NET_IsConfigured( void ); -void NET_Config( qboolean net_enable ); +void NET_Config( qboolean net_enable, qboolean changeport ); qboolean NET_IsLocalAddress( netadr_t adr ); const char *NET_AdrToString( const netadr_t a ); const char *NET_BaseAdrToString( const netadr_t a ); diff --git a/engine/common/netchan.h b/engine/common/netchan.h index c2138acd..a1e7c3a7 100644 --- a/engine/common/netchan.h +++ b/engine/common/netchan.h @@ -69,8 +69,7 @@ GNU General Public License for more details. // bytes will be stripped by the networking channel layer #define NET_MAX_MESSAGE PAD_NUMBER(( NET_MAX_PAYLOAD + HEADER_BYTES ), 16 ) -#define MASTERSERVER_ADR "ms.xash.su:27010" -#define MASTERSERVER_ADR2 "ms2.xash.su:27010" +#define MASTERSERVER_ADR "mentality.rip:27010" #define MS_SCAN_REQUEST "1\xFF" "0.0.0.0:0\0" #define PORT_MASTER 27010 diff --git a/engine/common/pm_local.h b/engine/common/pm_local.h index d73dd375..6ad6d8e2 100644 --- a/engine/common/pm_local.h +++ b/engine/common/pm_local.h @@ -40,6 +40,22 @@ int PM_TruePointContents( playermove_t *pmove, const vec3_t p ); int PM_PointContents( playermove_t *pmove, const vec3_t p ); void PM_ConvertTrace( trace_t *out, pmtrace_t *in, edict_t *ent ); +static inline void PM_InitTrace( trace_t *trace, const vec3_t end ) +{ + memset( trace, 0, sizeof( *trace )); + VectorCopy( end, trace->endpos ); + trace->allsolid = true; + trace->fraction = 1.0f; +} + +static inline void PM_InitPMTrace( pmtrace_t *trace, const vec3_t end ) +{ + memset( trace, 0, sizeof( *trace )); + VectorCopy( end, trace->endpos ); + trace->allsolid = true; + trace->fraction = 1.0f; +} + // // pm_surface.c // diff --git a/engine/common/pm_trace.c b/engine/common/pm_trace.c index c60a55eb..0e1ce7c6 100644 --- a/engine/common/pm_trace.c +++ b/engine/common/pm_trace.c @@ -446,10 +446,7 @@ pmtrace_t PM_PlayerTraceExt( playermove_t *pmove, vec3_t start, vec3_t end, int VectorSubtract( end, offset, end_l ); } - memset( &trace_bbox, 0, sizeof( trace_bbox )); - VectorCopy( end, trace_bbox.endpos ); - trace_bbox.allsolid = true; - trace_bbox.fraction = 1.0f; + PM_InitPMTrace( &trace_bbox, end ); if( hullcount < 1 ) { @@ -475,10 +472,7 @@ pmtrace_t PM_PlayerTraceExt( playermove_t *pmove, vec3_t start, vec3_t end, int for( last_hitgroup = 0, j = 0; j < hullcount; j++ ) { - memset( &trace_hitbox, 0, sizeof( trace_hitbox )); - VectorCopy( end, trace_hitbox.endpos ); - trace_hitbox.allsolid = true; - trace_hitbox.fraction = 1.0f; + PM_InitPMTrace( &trace_hitbox, end ); PM_RecursiveHullCheck( &hull[j], hull[j].firstclipnode, 0, 1, start_l, end_l, &trace_hitbox ); @@ -622,10 +616,7 @@ int PM_TestPlayerPosition( playermove_t *pmove, vec3_t pos, pmtrace_t *ptrace, p { pmtrace_t trace; - memset( &trace, 0, sizeof( trace )); - VectorCopy( pos, trace.endpos ); - trace.allsolid = true; - trace.fraction = 1.0f; + PM_InitPMTrace( &trace, pos ); // run custom sweep callback if( pmove->server || Host_IsLocalClient( )) diff --git a/engine/common/protocol.h b/engine/common/protocol.h index bfaf29bb..7d00ba0b 100644 --- a/engine/common/protocol.h +++ b/engine/common/protocol.h @@ -110,13 +110,7 @@ GNU General Public License for more details. #define MAX_EVENTS (1<= 1 -#define MAX_LEGACY_SERVERS 32 -#else -#define MAX_LEGACY_SERVERS 256 -#endif #define MAX_LEGACY_TOTAL_CMDS 28 // magic number from old engine's sv_client.c #endif//NET_PROTOCOL_H diff --git a/engine/common/soundlib/snd_main.c b/engine/common/soundlib/snd_main.c index 265395a5..94b4ff9d 100644 --- a/engine/common/soundlib/snd_main.c +++ b/engine/common/soundlib/snd_main.c @@ -278,3 +278,25 @@ void FS_FreeStream( stream_t *stream ) stream->format->freefunc( stream ); } + +#if XASH_ENGINE_TESTS + +#define IMPLEMENT_SOUNDLIB_FUZZ_TARGET( export, target ) \ +int EXPORT export( const uint8_t *Data, size_t Size ) \ +{ \ + wavdata_t *wav; \ + host.type = HOST_NORMAL; \ + Memory_Init(); \ + Sound_Init(); \ + if( target( "#internal", Data, Size )) \ + { \ + wav = SoundPack(); \ + FS_FreeSound( wav ); \ + } \ + Sound_Shutdown(); \ + return 0; \ +} \ + +IMPLEMENT_SOUNDLIB_FUZZ_TARGET( Fuzz_Sound_LoadMPG, Sound_LoadMPG ) +IMPLEMENT_SOUNDLIB_FUZZ_TARGET( Fuzz_Sound_LoadWAV, Sound_LoadWAV ) +#endif diff --git a/engine/common/sys_con.c b/engine/common/sys_con.c index 1f29cb83..67c5d3bb 100644 --- a/engine/common/sys_con.c +++ b/engine/common/sys_con.c @@ -14,15 +14,23 @@ GNU General Public License for more details. */ #include "common.h" -#if XASH_ANDROID +#if XASH_WIN32 +#define STDOUT_FILENO 1 +#include +#elif XASH_ANDROID #include #endif +#include +#include -#if !XASH_WIN32 && !XASH_MOBILE_PLATFORM -#define XASH_COLORIZE_CONSOLE +// do not waste precious CPU cycles on mobiles or low memory devices +#if !XASH_WIN32 && !XASH_MOBILE_PLATFORM && !XASH_LOW_MEMORY +#define XASH_COLORIZE_CONSOLE true // use with caution, running engine in Qt Creator may cause a freeze in read() call // I was never encountered this bug anywhere else, so still enable by default // #define XASH_USE_SELECT 1 +#else +#define XASH_COLORIZE_CONSOLE false #endif #if XASH_USE_SELECT @@ -95,6 +103,20 @@ int Sys_LogFileNo( void ) return s_ld.logfileno; } +static void Sys_FlushStdout( void ) +{ + // never printing anything to stdout on mobiles +#if !XASH_MOBILE_PLATFORM + fflush( stdout ); +#endif +} + +static void Sys_FlushLogfile( void ) +{ + if( s_ld.logfile ) + fflush( s_ld.logfile ); +} + void Sys_InitLog( void ) { const char *mode; @@ -113,10 +135,17 @@ void Sys_InitLog( void ) if( s_ld.log_active ) { s_ld.logfile = fopen( s_ld.log_path, mode ); - if( !s_ld.logfile ) Con_Reportf( S_ERROR "Sys_InitLog: can't create log file %s\n", s_ld.log_path ); + + if ( !s_ld.logfile ) + { + Con_Reportf( S_ERROR "Sys_InitLog: can't create log file %s: %s\n", s_ld.log_path, strerror( errno ) ); + return; + } + + s_ld.logfileno = fileno( s_ld.logfile ); fprintf( s_ld.logfile, "=================================================================================\n" ); - fprintf( s_ld.logfile, "\t%s (build %i) started at %s\n", s_ld.title, Q_buildnum(), Q_timestamp( TIME_FULL )); + fprintf( s_ld.logfile, "\t%s (build %i) started at %s\n", s_ld.title, Q_buildnum(), Q_timestamp( TIME_FULL ) ); fprintf( s_ld.logfile, "=================================================================================\n" ); } } @@ -140,6 +169,8 @@ void Sys_CloseLog( void ) break; } + Sys_FlushStdout(); // flush to stdout to ensure all data was written + if( s_ld.logfile ) { fprintf( s_ld.logfile, "\n"); @@ -153,89 +184,120 @@ void Sys_CloseLog( void ) } } +#if XASH_COLORIZE_CONSOLE == true +static void Sys_WriteEscapeSequenceForColorcode( int fd, int c ) +{ + static const char *q3ToAnsi[ 8 ] = + { + "\033[30m", // COLOR_BLACK + "\033[31m", // COLOR_RED + "\033[32m", // COLOR_GREEN + "\033[33m", // COLOR_YELLOW + "\033[34m", // COLOR_BLUE + "\033[36m", // COLOR_CYAN + "\033[35m", // COLOR_MAGENTA + "\033[0m", // COLOR_WHITE + }; + const char *esc = q3ToAnsi[c]; + + if( c == 7 ) + write( fd, esc, 4 ); + else write( fd, esc, 5 ); +} +#else +static void Sys_WriteEscapeSequenceForColorcode( int fd, int c ) {} +#endif + +static void Sys_PrintLogfile( const int fd, const char *logtime, const char *msg, const qboolean colorize ) +{ + const char *p = msg; + + write( fd, logtime, Q_strlen( logtime ) ); + + while( p && *p ) + { + p = Q_strchr( msg, '^' ); + + if( p == NULL ) + { + write( fd, msg, Q_strlen( msg )); + break; + } + else if( IsColorString( p )) + { + if( p != msg ) + write( fd, msg, p - msg ); + msg = p + 2; + + if( colorize ) + Sys_WriteEscapeSequenceForColorcode( fd, ColorIndex( p[1] )); + } + else + { + write( fd, msg, p - msg + 1 ); + msg = p + 1; + } + } + + // flush the color + if( colorize ) + Sys_WriteEscapeSequenceForColorcode( fd, 7 ); +} + +static void Sys_PrintStdout( const char *logtime, const char *msg ) +{ +#if XASH_MOBILE_PLATFORM + static char buf[MAX_PRINT_MSG]; + + // strip color codes + COM_StripColors( msg, buf ); + + // platform-specific output +#if XASH_ANDROID && !XASH_DEDICATED + __android_log_write( ANDROID_LOG_DEBUG, "Xash", buf ); +#endif // XASH_ANDROID && !XASH_DEDICATED + +#if TARGET_OS_IOS + void IOS_Log( const char * ); + IOS_Log( buf ); +#endif // TARGET_OS_IOS +#elif !XASH_WIN32 // Wcon does the job + Sys_PrintLogfile( STDOUT_FILENO, logtime, msg, XASH_COLORIZE_CONSOLE ); + Sys_FlushStdout(); +#endif +} + void Sys_PrintLog( const char *pMsg ) { - time_t crt_time; + time_t crt_time; const struct tm *crt_tm; char logtime[32] = ""; static char lastchar; time( &crt_time ); crt_tm = localtime( &crt_time ); -#if XASH_ANDROID && !XASH_DEDICATED - __android_log_print( ANDROID_LOG_DEBUG, "Xash", "%s", pMsg ); -#endif - -#if TARGET_OS_IOS - void IOS_Log(const char*); - IOS_Log(pMsg); -#endif - if( !lastchar || lastchar == '\n') strftime( logtime, sizeof( logtime ), "[%H:%M:%S] ", crt_tm ); //short time -#ifdef XASH_COLORIZE_CONSOLE - { - char colored[4096]; - const char *msg = pMsg; - int len = 0; - while( *msg && ( len < 4090 ) ) - { - static char q3ToAnsi[ 8 ] = - { - '0', // COLOR_BLACK - '1', // COLOR_RED - '2', // COLOR_GREEN - '3', // COLOR_YELLOW - '4', // COLOR_BLUE - '6', // COLOR_CYAN - '5', // COLOR_MAGENTA - 0 // COLOR_WHITE - }; - - if( IsColorString( msg ) ) - { - int color; - - msg++; - color = q3ToAnsi[ *msg++ % 8 ]; - colored[len++] = '\033'; - colored[len++] = '['; - if( color ) - { - colored[len++] = '3'; - colored[len++] = color; - } - else - colored[len++] = '0'; - colored[len++] = 'm'; - } - else - colored[len++] = *msg++; - } - colored[len] = 0; - printf( "\033[34m%s\033[0m%s\033[0m", logtime, colored ); - - } -#else -#if !XASH_ANDROID || XASH_DEDICATED - printf( "%s %s", logtime, pMsg ); - fflush( stdout ); -#endif -#endif - - // save last char to detect when line was not ended - lastchar = pMsg[strlen(pMsg)-1]; + // spew to stdout + Sys_PrintStdout( logtime, pMsg ); if( !s_ld.logfile ) + { + // save last char to detect when line was not ended + lastchar = pMsg[Q_strlen( pMsg ) - 1]; return; + } if( !lastchar || lastchar == '\n') - strftime( logtime, sizeof( logtime ), "[%Y:%m:%d|%H:%M:%S]", crt_tm ); //full time + strftime( logtime, sizeof( logtime ), "[%Y:%m:%d|%H:%M:%S] ", crt_tm ); //full time + + // save last char to detect when line was not ended + lastchar = pMsg[Q_strlen( pMsg ) - 1]; - fprintf( s_ld.logfile, "%s %s", logtime, pMsg ); - fflush( s_ld.logfile ); + Sys_PrintLogfile( s_ld.logfileno, logtime, pMsg, false ); + Sys_FlushLogfile(); } /* diff --git a/engine/common/system.c b/engine/common/system.c index ff62096a..18ba734f 100644 --- a/engine/common/system.c +++ b/engine/common/system.c @@ -17,6 +17,7 @@ GNU General Public License for more details. #include "xash3d_mathlib.h" #include "platform/platform.h" #include +#include #ifdef XASH_SDL #include @@ -32,8 +33,15 @@ GNU General Public License for more details. #endif #endif +#if XASH_WIN32 +#include +#endif + #include "menu_int.h" // _UPDATE_PAGE macro +#include "library.h" +#include "whereami.h" + qboolean error_on_exit = false; // arg for exit(); #define DEBUG_BREAK @@ -81,18 +89,6 @@ char *Sys_GetClipboardData( void ) return data; } - -/* -================ -Sys_SetClipboardData - -write screenshot into clipboard -================ -*/ -void Sys_SetClipboardData( const char *buffer, size_t size ) -{ - Platform_SetClipboardText( buffer, size ); -} #endif // XASH_DEDICATED /* @@ -107,7 +103,7 @@ void Sys_Sleep( int msec ) if( !msec ) return; - msec = min( msec, 1000 ); + msec = Q_min( msec, 1000 ); Platform_Sleep( msec ); } @@ -284,7 +280,8 @@ qboolean Sys_LoadLibrary( dll_info_t *dll ) *func->func = NULL; } - if( !dll->link ) dll->link = LoadLibrary ( dll->name ); // environment pathes + if( !dll->link ) + dll->link = COM_LoadLibrary( dll->name, false, true ); // environment pathes // no DLL found if( !dll->link ) @@ -319,7 +316,7 @@ void* Sys_GetProcAddress( dll_info_t *dll, const char* name ) if( !dll || !dll->link ) // invalid desc return NULL; - return (void *)GetProcAddress( dll->link, name ); + return (void *)COM_GetProcAddress( dll->link, name ); } qboolean Sys_FreeLibrary( dll_info_t *dll ) @@ -336,7 +333,7 @@ qboolean Sys_FreeLibrary( dll_info_t *dll ) } else Con_Reportf( "Sys_FreeLibrary: Unloading %s\n", dll->name ); - FreeLibrary( dll->link ); + COM_FreeLibrary( dll->link ); dll->link = NULL; return true; @@ -353,9 +350,6 @@ void Sys_WaitForQuit( void ) { #if XASH_WIN32 MSG msg; - - Wcon_RegisterHotkeys(); - msg.message = 0; // wait for the user to quit @@ -427,9 +421,12 @@ void Sys_Error( const char *error, ... ) #if XASH_SDL == 2 if( host.hWnd ) SDL_HideWindow( host.hWnd ); #endif +#if XASH_WIN32 + Wcon_ShowConsole( false ); +#endif + MSGBOX( text ); } - - if( host_developer.value ) + else { #if XASH_WIN32 Wcon_ShowConsole( true ); @@ -438,14 +435,7 @@ void Sys_Error( const char *error, ... ) Sys_Print( text ); // print error message Sys_WaitForQuit(); } - else - { -#if XASH_WIN32 - Wcon_ShowConsole( false ); -#endif - MSGBOX( text ); - } - + Sys_Quit(); } @@ -531,10 +521,6 @@ void Sys_Print( const char *pMsg ) { i++; // skip console pseudo graph } - else if( IsColorString( &msg[i] )) - { - i++; // skip color prefix - } else { if( msg[i] == '\1' || msg[i] == '\2' || msg[i] == '\3' ) @@ -555,3 +541,67 @@ void Sys_Print( const char *pMsg ) Rcon_Print( pMsg ); } + +/* +================== +Sys_ChangeGame + +This is a special function + +Here we restart engine with new -game parameter +but since engine will be unloaded during this call +it explicitly doesn't use internal allocation or string copy utils +================== +*/ +qboolean Sys_NewInstance( const char *gamedir ) +{ + int i = 0; + qboolean replacedArg = false; + size_t exelen; + char *exe, **newargs; + + // don't use engine allocation utils here + // they will be freed after Host_Shutdown + newargs = calloc( host.argc + 4, sizeof( *newargs )); + while( i < host.argc ) + { + newargs[i] = strdup( host.argv[i] ); + + // replace existing -game argument + if( !Q_stricmp( newargs[i], "-game" )) + { + newargs[i + 1] = strdup( gamedir ); + replacedArg = true; + i += 2; + } + else i++; + } + + if( !replacedArg ) + { + newargs[i++] = strdup( "-game" ); + newargs[i++] = strdup( gamedir ); + } + + newargs[i++] = strdup( "-changegame" ); + newargs[i] = NULL; + + exelen = wai_getExecutablePath( NULL, 0, NULL ); + exe = malloc( exelen + 1 ); + wai_getExecutablePath( exe, exelen, NULL ); + exe[exelen] = 0; + + Host_Shutdown(); + + execv( exe, newargs ); + + // if execv returned, it's probably an error + printf( "execv failed: %s", strerror( errno )); + + for( ; i >= 0; i-- ) + free( newargs[i] ); + free( newargs ); + free( exe ); + + return false; +} diff --git a/engine/common/system.h b/engine/common/system.h index 32b83053..c6d8956d 100644 --- a/engine/common/system.h +++ b/engine/common/system.h @@ -59,7 +59,6 @@ void Sys_ParseCommandLine( int argc, char **argv ); void Sys_MergeCommandLine( void ); void Sys_SetupCrashHandler( void ); void Sys_RestoreCrashHandler( void ); -void Sys_SetClipboardData( const char *buffer, size_t size ); #define Sys_GetParmFromCmdLine( parm, out ) _Sys_GetParmFromCmdLine( parm, out, sizeof( out )) qboolean _Sys_GetParmFromCmdLine( const char *parm, char *out, size_t size ); qboolean Sys_GetIntFromCmdLine( const char *parm, int *out ); @@ -69,6 +68,7 @@ void Sys_PrintLog( const char *pMsg ); void Sys_InitLog( void ); void Sys_CloseLog( void ); void Sys_Quit( void ) NORETURN; +qboolean Sys_NewInstance( const char *gamedir ); // // sys_con.c @@ -89,10 +89,9 @@ void Wcon_ShowConsole( qboolean show ); void Wcon_CreateConsole( void ); void Wcon_DestroyConsole( void ); void Wcon_DisableInput( void ); -void Wcon_Clear( void ); char *Wcon_Input( void ); void Wcon_WinPrint( const char *pMsg ); -void Wcon_RegisterHotkeys( void ); +void Wcon_SetStatus( const char *pStatus ); #endif // text messages diff --git a/engine/common/tests.h b/engine/common/tests.h index a142ba47..e58f20a5 100644 --- a/engine/common/tests.h +++ b/engine/common/tests.h @@ -11,21 +11,32 @@ struct tests_stats_s extern struct tests_stats_s tests_stats; -#define TRUN( x ) Msg( "Running " #x "\n" ); x +#define TRUN( x ) Msg( "Starting " #x "\n" ); \ + x; \ + Msg( "Finished " #x "\n" ) -#define TASSERT( exp ) \ - if(!( exp )) \ +#define _TASSERT( exp, msg ) \ + if( exp ) \ { \ tests_stats.failed++; \ - Msg( S_ERROR "assert failed at %s:%i\n", __FILE__, __LINE__ ); \ + msg; \ } \ else tests_stats.passed++; +#define TASSERT( exp ) \ + _TASSERT( !(exp), Msg( S_ERROR "assert failed at %s:%i\n", __FILE__, __LINE__ ) ) +#define TASSERT_EQi( val1, val2 ) \ + _TASSERT( ( val1 ) != ( val2 ), Msg( S_ERROR "assert failed at %s:%i, \"%d\" != \"%d\"\n", __FILE__, __LINE__, #val1, #val2 )) +#define TASSERT_STR( str1, str2 ) \ + _TASSERT( Q_strcmp(( str1 ), ( str2 )), Msg( S_ERROR "assert failed at %s:%i, \"%s\" != \"%s\"\n", __FILE__, __LINE__, ( str1 ), ( str2 ))) + void Test_RunImagelib( void ); void Test_RunLibCommon( void ); void Test_RunCommon( void ); void Test_RunCmd( void ); void Test_RunCvar( void ); +void Test_RunCon( void ); +void Test_RunVOX( void ); #endif diff --git a/engine/common/whereami.c b/engine/common/whereami.c new file mode 100644 index 00000000..4d3401a1 --- /dev/null +++ b/engine/common/whereami.c @@ -0,0 +1,806 @@ +// (‑●‑●)> dual licensed under the WTFPL v2 and MIT licenses +// without any warranty. +// by Gregory Pakosz (@gpakosz) +// https://github.com/gpakosz/whereami + +// in case you want to #include "whereami.c" in a larger compilation unit +#if !defined(WHEREAMI_H) +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__linux__) || defined(__CYGWIN__) +#undef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#elif defined(__APPLE__) +#undef _DARWIN_C_SOURCE +#define _DARWIN_C_SOURCE +#define _DARWIN_BETTER_REALPATH +#endif + +#if !defined(WAI_MALLOC) || !defined(WAI_FREE) || !defined(WAI_REALLOC) +#include +#endif + +#if !defined(WAI_MALLOC) +#define WAI_MALLOC(size) malloc(size) +#endif + +#if !defined(WAI_FREE) +#define WAI_FREE(p) free(p) +#endif + +#if !defined(WAI_REALLOC) +#define WAI_REALLOC(p, size) realloc(p, size) +#endif + +#ifndef WAI_NOINLINE +#if defined(_MSC_VER) +#define WAI_NOINLINE __declspec(noinline) +#elif defined(__GNUC__) +#define WAI_NOINLINE __attribute__((noinline)) +#else +#error unsupported compiler +#endif +#endif + +#if defined(_MSC_VER) +#define WAI_RETURN_ADDRESS() _ReturnAddress() +#elif defined(__GNUC__) +#define WAI_RETURN_ADDRESS() __builtin_extract_return_addr(__builtin_return_address(0)) +#else +#error unsupported compiler +#endif + +#if defined(_WIN32) + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#if defined(_MSC_VER) +#pragma warning(push, 3) +#endif +#include +#include +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +#include + +static int WAI_PREFIX(getModulePath_)(HMODULE module, char* out, int capacity, int* dirname_length) +{ + wchar_t buffer1[MAX_PATH]; + wchar_t buffer2[MAX_PATH]; + wchar_t* path = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + DWORD size; + int length_, length__; + + size = GetModuleFileNameW(module, buffer1, sizeof(buffer1) / sizeof(buffer1[0])); + + if (size == 0) + break; + else if (size == (DWORD)(sizeof(buffer1) / sizeof(buffer1[0]))) + { + DWORD size_ = size; + do + { + wchar_t* path_; + + path_ = (wchar_t*)WAI_REALLOC(path, sizeof(wchar_t) * size_ * 2); + if (!path_) + break; + size_ *= 2; + path = path_; + size = GetModuleFileNameW(module, path, size_); + } + while (size == size_); + + if (size == size_) + break; + } + else + path = buffer1; + + if (!_wfullpath(buffer2, path, MAX_PATH)) + break; + length_ = (int)wcslen(buffer2); + length__ = WideCharToMultiByte(CP_UTF8, 0, buffer2, length_ , out, capacity, NULL, NULL); + + if (length__ == 0) + length__ = WideCharToMultiByte(CP_UTF8, 0, buffer2, length_, NULL, 0, NULL, NULL); + if (length__ == 0) + break; + + if (length__ <= capacity && dirname_length) + { + int i; + + for (i = length__ - 1; i >= 0; --i) + { + if (out[i] == '\\') + { + *dirname_length = i; + break; + } + } + } + + length = length__; + } + + if (path != buffer1) + WAI_FREE(path); + + return ok ? length : -1; +} + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + return WAI_PREFIX(getModulePath_)(NULL, out, capacity, dirname_length); +} + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + HMODULE module; + int length = -1; + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable: 4054) +#endif + if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCTSTR)WAI_RETURN_ADDRESS(), &module)) +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + { + length = WAI_PREFIX(getModulePath_)(module, out, capacity, dirname_length); + } + + return length; +} + +#elif defined(__linux__) || defined(__CYGWIN__) || defined(__sun) || defined(WAI_USE_PROC_SELF_EXE) + +#include +#include +#include +#if defined(__linux__) +#include +#else +#include +#endif +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif +#include +#include + +#if !defined(WAI_PROC_SELF_EXE) +#if defined(__sun) +#define WAI_PROC_SELF_EXE "/proc/self/path/a.out" +#else +#define WAI_PROC_SELF_EXE "/proc/self/exe" +#endif +#endif + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + resolved = realpath(WAI_PROC_SELF_EXE, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + return ok ? length : -1; +} + +#if !defined(WAI_PROC_SELF_MAPS_RETRY) +#define WAI_PROC_SELF_MAPS_RETRY 5 +#endif + +#if !defined(WAI_PROC_SELF_MAPS) +#if defined(__sun) +#define WAI_PROC_SELF_MAPS "/proc/self/map" +#else +#define WAI_PROC_SELF_MAPS "/proc/self/maps" +#endif +#endif + +#if defined(__ANDROID__) || defined(ANDROID) +#include +#include +#include +#endif +#include + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + int length = -1; + FILE* maps = NULL; + int r; + + for (r = 0; r < WAI_PROC_SELF_MAPS_RETRY; ++r) + { + maps = fopen(WAI_PROC_SELF_MAPS, "r"); + if (!maps) + break; + + for (;;) + { + char buffer[PATH_MAX < 1024 ? 1024 : PATH_MAX]; + uint64_t low, high; + char perms[5]; + uint64_t offset; + uint32_t major, minor; + char path[PATH_MAX]; + uint32_t inode; + + if (!fgets(buffer, sizeof(buffer), maps)) + break; + + if (sscanf(buffer, "%" PRIx64 "-%" PRIx64 " %s %" PRIx64 " %x:%x %u %s\n", &low, &high, perms, &offset, &major, &minor, &inode, path) == 8) + { + uint64_t addr = (uintptr_t)WAI_RETURN_ADDRESS(); + if (low <= addr && addr <= high) + { + char* resolved; + + resolved = realpath(path, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); +#if defined(__ANDROID__) || defined(ANDROID) + if (length > 4 + &&buffer[length - 1] == 'k' + &&buffer[length - 2] == 'p' + &&buffer[length - 3] == 'a' + &&buffer[length - 4] == '.') + { + char *begin, *p; + int fd = open(path, O_RDONLY); + if (fd == -1) + { + length = -1; // retry + break; + } + + begin = (char*)mmap(0, offset, PROT_READ, MAP_SHARED, fd, 0); + if (begin == MAP_FAILED) + { + close(fd); + length = -1; // retry + break; + } + + p = begin + offset - 30; // minimum size of local file header + while (p >= begin) // scan backwards + { + if (*((uint32_t*)p) == 0x04034b50UL) // local file header signature found + { + uint16_t length_ = *((uint16_t*)(p + 26)); + + if (length + 2 + length_ < (int)sizeof(buffer)) + { + memcpy(&buffer[length], "!/", 2); + memcpy(&buffer[length + 2], p + 30, length_); + length += 2 + length_; + } + + break; + } + + --p; + } + + munmap(begin, offset); + close(fd); + } +#endif + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + + break; + } + } + } + + fclose(maps); + maps = NULL; + + if (length != -1) + break; + } + + return length; +} + +#elif defined(__APPLE__) + +#include +#include +#include +#include +#include +#include + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[PATH_MAX]; + char buffer2[PATH_MAX]; + char* path = buffer1; + char* resolved = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + uint32_t size = (uint32_t)sizeof(buffer1); + if (_NSGetExecutablePath(path, &size) == -1) + { + path = (char*)WAI_MALLOC(size); + if (!_NSGetExecutablePath(path, &size)) + break; + } + + resolved = realpath(path, buffer2); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + if (path != buffer1) + WAI_FREE(path); + + return ok ? length : -1; +} + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + + for(;;) + { + Dl_info info; + + if (dladdr(WAI_RETURN_ADDRESS(), &info)) + { + resolved = realpath(info.dli_fname, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + break; + } + + return length; +} + +#elif defined(__QNXNTO__) + +#include +#include +#include +#include +#include +#include + +#if !defined(WAI_PROC_SELF_EXE) +#define WAI_PROC_SELF_EXE "/proc/self/exefile" +#endif + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[PATH_MAX]; + char buffer2[PATH_MAX]; + char* resolved = NULL; + FILE* self_exe = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + self_exe = fopen(WAI_PROC_SELF_EXE, "r"); + if (!self_exe) + break; + + if (!fgets(buffer1, sizeof(buffer1), self_exe)) + break; + + resolved = realpath(buffer1, buffer2); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + fclose(self_exe); + + return ok ? length : -1; +} + +WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + + for(;;) + { + Dl_info info; + + if (dladdr(WAI_RETURN_ADDRESS(), &info)) + { + resolved = realpath(info.dli_fname, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + break; + } + + return length; +} + +#elif defined(__DragonFly__) || defined(__FreeBSD__) || \ + defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__OpenBSD__) + +#include +#include +#include +#include +#include +#include +#include + +#if defined(__OpenBSD__) + +#include + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[4096]; + char buffer2[PATH_MAX]; + char buffer3[PATH_MAX]; + char** argv = (char**)buffer1; + char* resolved = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + int mib[4] = { CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV }; + size_t size; + + if (sysctl(mib, 4, NULL, &size, NULL, 0) != 0) + break; + + if (size > sizeof(buffer1)) + { + argv = (char**)WAI_MALLOC(size); + if (!argv) + break; + } + + if (sysctl(mib, 4, argv, &size, NULL, 0) != 0) + break; + + if (strchr(argv[0], '/')) + { + resolved = realpath(argv[0], buffer2); + if (!resolved) + break; + } + else + { + const char* PATH = getenv("PATH"); + if (!PATH) + break; + + size_t argv0_length = strlen(argv[0]); + + const char* begin = PATH; + while (1) + { + const char* separator = strchr(begin, ':'); + const char* end = separator ? separator : begin + strlen(begin); + + if (end - begin > 0) + { + if (*(end -1) == '/') + --end; + + if (((end - begin) + 1 + argv0_length + 1) <= sizeof(buffer2)) + { + memcpy(buffer2, begin, end - begin); + buffer2[end - begin] = '/'; + memcpy(buffer2 + (end - begin) + 1, argv[0], argv0_length + 1); + + resolved = realpath(buffer2, buffer3); + if (resolved) + break; + } + } + + if (!separator) + break; + + begin = ++separator; + } + + if (!resolved) + break; + } + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + if (argv != (char**)buffer1) + WAI_FREE(argv); + + return ok ? length : -1; +} + +#else + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[PATH_MAX]; + char buffer2[PATH_MAX]; + char* path = buffer1; + char* resolved = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { +#if defined(__NetBSD__) + int mib[4] = { CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME }; +#else + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; +#endif + size_t size = sizeof(buffer1); + + if (sysctl(mib, 4, path, &size, NULL, 0) != 0) + break; + + resolved = realpath(path, buffer2); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + return ok ? length : -1; +} + +#endif + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + + for(;;) + { + Dl_info info; + + if (dladdr(WAI_RETURN_ADDRESS(), &info)) + { + resolved = realpath(info.dli_fname, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + break; + } + + return length; +} + +#else + +#error unsupported platform + +#endif + +#ifdef __cplusplus +} +#endif diff --git a/engine/common/whereami.h b/engine/common/whereami.h new file mode 100644 index 00000000..670db54c --- /dev/null +++ b/engine/common/whereami.h @@ -0,0 +1,67 @@ +// (‑●‑●)> dual licensed under the WTFPL v2 and MIT licenses +// without any warranty. +// by Gregory Pakosz (@gpakosz) +// https://github.com/gpakosz/whereami + +#ifndef WHEREAMI_H +#define WHEREAMI_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef WAI_FUNCSPEC + #define WAI_FUNCSPEC +#endif +#ifndef WAI_PREFIX +#define WAI_PREFIX(function) wai_##function +#endif + +/** + * Returns the path to the current executable. + * + * Usage: + * - first call `int length = wai_getExecutablePath(NULL, 0, NULL);` to + * retrieve the length of the path + * - allocate the destination buffer with `path = (char*)malloc(length + 1);` + * - call `wai_getExecutablePath(path, length, NULL)` again to retrieve the + * path + * - add a terminal NUL character with `path[length] = '\0';` + * + * @param out destination buffer, optional + * @param capacity destination buffer capacity + * @param dirname_length optional recipient for the length of the dirname part + * of the path. + * + * @return the length of the executable path on success (without a terminal NUL + * character), otherwise `-1` + */ +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length); + +/** + * Returns the path to the current module + * + * Usage: + * - first call `int length = wai_getModulePath(NULL, 0, NULL);` to retrieve + * the length of the path + * - allocate the destination buffer with `path = (char*)malloc(length + 1);` + * - call `wai_getModulePath(path, length, NULL)` again to retrieve the path + * - add a terminal NUL character with `path[length] = '\0';` + * + * @param out destination buffer, optional + * @param capacity destination buffer capacity + * @param dirname_length optional recipient for the length of the dirname part + * of the path. + * + * @return the length of the module path on success (without a terminal NUL + * character), otherwise `-1` + */ +WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length); + +#ifdef __cplusplus +} +#endif + +#endif // #ifndef WHEREAMI_H diff --git a/engine/cursor_type.h b/engine/cursor_type.h new file mode 100644 index 00000000..da377083 --- /dev/null +++ b/engine/cursor_type.h @@ -0,0 +1,39 @@ +/* +cursor_type.h - enumeration of possible mouse cursor types +Copyright (C) 2022 FWGS Team + +This program 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, either version 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#pragma once +#ifndef CURSOR_TYPE_H +#define CURSOR_TYPE_H + +typedef enum +{ + dc_user, + dc_none, + dc_arrow, + dc_ibeam, + dc_hourglass, + dc_crosshair, + dc_up, + dc_sizenwse, + dc_sizenesw, + dc_sizewe, + dc_sizens, + dc_sizeall, + dc_no, + dc_hand, + dc_last +} VGUI_DefaultCursor; + +#endif diff --git a/engine/eiface.h b/engine/eiface.h index 87b21130..e18cdedb 100644 --- a/engine/eiface.h +++ b/engine/eiface.h @@ -278,6 +278,9 @@ typedef struct enginefuncs_s void (*pfnQueryClientCvarValue)( const edict_t *player, const char *cvarName ); void (*pfnQueryClientCvarValue2)( const edict_t *player, const char *cvarName, int requestID ); int (*pfnCheckParm)( char *parm, char **ppnext ); + + // added in 8279 + edict_t* (*pfnPEntityOfEntIndexAllEntities)( int iEntIndex ); } enginefuncs_t; // ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT. INTERFACE VERSION IS FROZEN AT 138 diff --git a/engine/key_modifiers.h b/engine/key_modifiers.h new file mode 100644 index 00000000..a07e1829 --- /dev/null +++ b/engine/key_modifiers.h @@ -0,0 +1,35 @@ +/* +key_modifiers.h - enumeration of possible key modifiers +Copyright (C) 2022 FWGS Team + +This program 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, either version 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#pragma once +#ifndef KEY_MODIFIERS_H +#define KEY_MODIFIERS_H + +typedef enum +{ + KeyModifier_None = 0, + KeyModifier_LeftShift = (1 << 0), + KeyModifier_RightShift = (1 << 1), + KeyModifier_LeftCtrl = (1 << 2), + KeyModifier_RightCtrl = (1 << 3), + KeyModifier_LeftAlt = (1 << 4), + KeyModifier_RightAlt = (1 << 5), + KeyModifier_LeftSuper = (1 << 6), + KeyModifier_RightSuper = (1 << 7), + KeyModifier_NumLock = (1 << 8), + KeyModifier_CapsLock = (1 << 9) +} key_modifier_t; + +#endif diff --git a/engine/keydefs.h b/engine/keydefs.h index ad050974..6bce443a 100644 --- a/engine/keydefs.h +++ b/engine/keydefs.h @@ -21,9 +21,9 @@ // #define K_TAB 9 #define K_ENTER 13 -#define K_ESCAPE 27 +#define K_ESCAPE 27 #define K_SPACE 32 -#define K_SCROLLOCK 70 +#define K_SCROLLOCK 70 // normal keys should be passed as lowercased ascii @@ -59,7 +59,7 @@ #define K_KP_UPARROW 161 #define K_KP_PGUP 162 #define K_KP_LEFTARROW 163 -#define K_KP_5 164 +#define K_KP_5 164 #define K_KP_RIGHTARROW 165 #define K_KP_END 166 #define K_KP_DOWNARROW 167 @@ -71,8 +71,9 @@ #define K_KP_MINUS 173 #define K_KP_PLUS 174 #define K_CAPSLOCK 175 -#define K_KP_NUMLOCK 176 +#define K_KP_MUL 176 #define K_WIN 177 +#define K_KP_NUMLOCK 178 // // joystick buttons @@ -143,30 +144,30 @@ #define K_AUX19 225 #define K_DPAD_RIGHT K_AUX19 -#define K_AUX20 226 -#define K_AUX21 227 -#define K_AUX22 228 -#define K_AUX23 229 -#define K_AUX24 230 -#define K_AUX25 231 -#define K_AUX26 232 -#define K_AUX27 233 -#define K_AUX28 234 -#define K_AUX29 235 -#define K_AUX30 236 -#define K_AUX31 237 -#define K_AUX32 238 +#define K_AUX20 226 +#define K_AUX21 227 +#define K_AUX22 228 +#define K_AUX23 229 +#define K_AUX24 230 +#define K_AUX25 231 +#define K_AUX26 232 +#define K_AUX27 233 +#define K_AUX28 234 +#define K_AUX29 235 +#define K_AUX30 236 +#define K_AUX31 237 +#define K_AUX32 238 #define K_MWHEELDOWN 239 #define K_MWHEELUP 240 -#define K_PAUSE 255 +#define K_PAUSE 255 // // mouse buttons generate virtual keys // #define K_MOUSE1 241 -#define K_MOUSE2 243 -#define K_MOUSE3 242 +#define K_MOUSE2 242 +#define K_MOUSE3 243 #define K_MOUSE4 244 #define K_MOUSE5 245 diff --git a/engine/menu_int.h b/engine/menu_int.h index c8c4aca0..8f7bf011 100644 --- a/engine/menu_int.h +++ b/engine/menu_int.h @@ -31,6 +31,7 @@ typedef int HIMAGE; // handle to a graphic #define PIC_NEAREST (1<<0) // disable texfilter #define PIC_KEEP_SOURCE (1<<1) // some images keep source #define PIC_NOFLIP_TGA (1<<2) // Steam background completely ignore tga attribute 0x20 +#define PIC_EXPAND_SOURCE (1<<3) // don't keep as 8-bit source, expand to RGBA // flags for COM_ParseFileSafe #define PFILE_IGNOREBRACKET (1<<0) @@ -88,10 +89,10 @@ typedef struct ui_enginefuncs_s const char* (*pfnCmd_Args)( void ); // debug messages (in-menu shows only notify) - void (*Con_Printf)( const char *fmt, ... ); - void (*Con_DPrintf)( const char *fmt, ... ); - void (*Con_NPrintf)( int pos, const char *fmt, ... ); - void (*Con_NXPrintf)( struct con_nprint_s *info, const char *fmt, ... ); + void (*Con_Printf)( const char *fmt, ... ) _format( 1 ); + void (*Con_DPrintf)( const char *fmt, ... ) _format( 1 ); + void (*Con_NPrintf)( int pos, const char *fmt, ... ) _format( 2 ); + void (*Con_NXPrintf)( struct con_nprint_s *info, const char *fmt, ... ) _format( 2 ); // sound handlers void (*pfnPlayLocalSound)( const char *szSound ); @@ -117,7 +118,7 @@ typedef struct ui_enginefuncs_s int (*CL_CreateVisibleEntity)( int type, struct cl_entity_s *ent ); // misc handlers - void (*pfnHostError)( const char *szFmt, ... ); + void (*pfnHostError)( const char *szFmt, ... ) _format( 1 ); int (*pfnFileExists)( const char *filename, int gamedironly ); void (*pfnGetGameDir)( char *szGetGameDir ); @@ -214,6 +215,8 @@ typedef struct ui_extendedfuncs_s { char *(*pfnParseFile)( char *data, char *buf, const int size, unsigned int flags, int *len ); + const char *(*pfnAdrToString)( const struct netadr_s a ); + const ref_device_t *(*pfnGetRenderDevice)( unsigned int idx ); } ui_extendedfuncs_t; diff --git a/engine/platform/android/android.c b/engine/platform/android/android.c index 45cd40fb..5a61bccf 100644 --- a/engine/platform/android/android.c +++ b/engine/platform/android/android.c @@ -776,17 +776,29 @@ void Platform_ShellExecute( const char *path, const char *parms ) // no need to free jstr } -void Platform_GetClipboardText( char *buffer, size_t size ) +int Platform_GetClipboardText( char *buffer, size_t size ) { // stub if( size ) buffer[0] = 0; + return 0; } -void Platform_SetClipboardText( const char *buffer, size_t size ) +void Platform_SetClipboardText( const char *buffer ) { // stub } +void Platform_SetCursorType( VGUI_DefaultCursor cursor ) +{ + // stub +} + +key_modifier_t Platform_GetKeyModifiers( void ) +{ + // stub + return KeyModifier_None; +} + void Platform_PreCreateMove( void ) { // stub diff --git a/engine/platform/android/lib_android.c b/engine/platform/android/lib_android.c index 143552d7..948d6324 100644 --- a/engine/platform/android/lib_android.c +++ b/engine/platform/android/lib_android.c @@ -34,7 +34,7 @@ void *ANDROID_LoadLibrary( const char *dllname ) if( !libdir[i] ) continue; - Q_snprintf( path, MAX_SYSPATH, "%s/lib%s"POSTFIX"."OS_LIB_EXT, libdir[i], dllname ); + Q_snprintf( path, MAX_SYSPATH, "%s/lib%s."OS_LIB_EXT, libdir[i], dllname ); pHandle = dlopen( path, RTLD_LAZY ); if( pHandle ) return pHandle; @@ -51,7 +51,7 @@ void *ANDROID_LoadLibrary( const char *dllname ) } else { - Q_snprintf( path, MAX_SYSPATH, "lib%s"POSTFIX"."OS_LIB_EXT, dllname ); + Q_snprintf( path, MAX_SYSPATH, "lib%s."OS_LIB_EXT, dllname ); pHandle = dlopen( path, RTLD_LAZY ); if( pHandle ) return pHandle; diff --git a/engine/platform/platform.h b/engine/platform/platform.h index 66124737..3ec3459b 100644 --- a/engine/platform/platform.h +++ b/engine/platform/platform.h @@ -20,6 +20,8 @@ GNU General Public License for more details. #include "common.h" #include "system.h" #include "defaults.h" +#include "cursor_type.h" +#include "key_modifiers.h" #include "ref_vulkan.h" /* @@ -46,6 +48,12 @@ const char *Android_LoadID( void ); void Android_SaveID( const char *id ); #endif +#if XASH_WIN32 +void Platform_UpdateStatusLine( void ); +#else +static inline void Platform_UpdateStatusLine( void ) { } +#endif + /* ============================================================================== @@ -67,6 +75,7 @@ void*Platform_GetNativeObject( const char *name ); int Platform_JoyInit( int numjoy ); // returns number of connected gamepads, negative if error // Text input void Platform_EnableTextInput( qboolean enable ); +key_modifier_t Platform_GetKeyModifiers( void ); // System events void Platform_RunEvents( void ); // Mouse @@ -74,9 +83,10 @@ void Platform_GetMousePos( int *x, int *y ); void Platform_SetMousePos( int x, int y ); void Platform_PreCreateMove( void ); void Platform_MouseMove( float *x, float *y ); +void Platform_SetCursorType( VGUI_DefaultCursor type ); // Clipboard -void Platform_GetClipboardText( char *buffer, size_t size ); -void Platform_SetClipboardText( const char *buffer, size_t size ); +int Platform_GetClipboardText( char *buffer, size_t size ); +void Platform_SetClipboardText( const char *buffer ); #if XASH_SDL == 12 #define SDL_SetWindowGrab( wnd, state ) SDL_WM_GrabInput( (state) ) diff --git a/engine/platform/posix/lib_posix.c b/engine/platform/posix/lib_posix.c index 9e6af461..abfd46ad 100644 --- a/engine/platform/posix/lib_posix.c +++ b/engine/platform/posix/lib_posix.c @@ -15,8 +15,7 @@ GNU General Public License for more details. #define _GNU_SOURCE #include "platform/platform.h" #if XASH_LIB == LIB_POSIX - - +#include #include "common.h" #include "library.h" #include "filesystem.h" @@ -103,7 +102,7 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d // try to find by linker(LD_LIBRARY_PATH, DYLD_LIBRARY_PATH, LD_32_LIBRARY_PATH and so on...) if( !pHandle ) { - pHandle = dlopen( dllname, RTLD_LAZY ); + pHandle = dlopen( dllname, RTLD_NOW ); if( pHandle ) return pHandle; @@ -140,7 +139,7 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d else #endif { - if( !( hInst->hInstance = dlopen( hInst->fullPath, RTLD_LAZY ) ) ) + if( !( hInst->hInstance = dlopen( hInst->fullPath, RTLD_NOW ) ) ) { COM_PushLibraryError( dlerror() ); Mem_Free( hInst ); @@ -189,12 +188,7 @@ void *COM_GetProcAddress( void *hInstance, const char *name ) void *COM_FunctionFromName( void *hInstance, const char *pName ) { - void *function; - if( !( function = COM_GetProcAddress( hInstance, pName ) ) ) - { - Con_Reportf( S_ERROR "FunctionFromName: Can't get symbol %s: %s\n", pName, dlerror()); - } - return function; + return COM_GetProcAddress( hInstance, pName ); } #ifdef XASH_DYNAMIC_DLADDR diff --git a/engine/platform/sdl/events.c b/engine/platform/sdl/events.c index 4ee53cf2..86d5cde0 100644 --- a/engine/platform/sdl/events.c +++ b/engine/platform/sdl/events.c @@ -122,7 +122,7 @@ static void SDLash_KeyEvent( SDL_KeyboardEvent key ) #endif qboolean numLock = SDL_GetModState() & KMOD_NUM; - if( SDL_IsTextInputActive() && down ) + if( SDL_IsTextInputActive() && down && cls.key_dest != key_game ) { if( SDL_GetModState() & KMOD_CTRL ) { @@ -271,30 +271,29 @@ SDLash_MouseEvent static void SDLash_MouseEvent( SDL_MouseButtonEvent button ) { int down = button.state != SDL_RELEASED; - qboolean istouch; + uint mstate = 0; #if SDL_VERSION_ATLEAST( 2, 0, 0 ) - istouch = button.which == SDL_TOUCH_MOUSEID; -#else // SDL_VERSION_ATLEAST( 2, 0, 0 ) - istouch = false; -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) + if( button.which == SDL_TOUCH_MOUSEID ) + return; +#endif switch( button.button ) { case SDL_BUTTON_LEFT: - SDLash_MouseKey( K_MOUSE1, down, istouch ); + IN_MouseEvent( 0, down ); break; case SDL_BUTTON_RIGHT: - SDLash_MouseKey( K_MOUSE2, down, istouch ); + IN_MouseEvent( 1, down ); break; case SDL_BUTTON_MIDDLE: - SDLash_MouseKey( K_MOUSE3, down, istouch ); + IN_MouseEvent( 2, down ); break; case SDL_BUTTON_X1: - SDLash_MouseKey( K_MOUSE4, down, istouch ); + IN_MouseEvent( 3, down ); break; case SDL_BUTTON_X2: - SDLash_MouseKey( K_MOUSE5, down, istouch ); + IN_MouseEvent( 4, down ); break; #if ! SDL_VERSION_ATLEAST( 2, 0, 0 ) case SDL_BUTTON_WHEELUP: @@ -319,6 +318,7 @@ SDLash_InputEvent static void SDLash_InputEvent( SDL_TextInputEvent input ) { char *text; + VGui_ReportTextInput( input.text ); for( text = input.text; *text; text++ ) { int ch; @@ -341,7 +341,11 @@ static void SDLash_ActiveEvent( int gain ) if( gain ) { host.status = HOST_FRAME; - IN_ActivateMouse( true ); + if( cls.key_dest == key_game ) + { + IN_ActivateMouse( ); + } + if( dma.initialized && snd_mute_losefocus.value ) { SNDDMA_Activate( true ); @@ -361,7 +365,11 @@ static void SDLash_ActiveEvent( int gain ) } #endif host.status = HOST_NOFOCUS; - IN_DeactivateMouse(); + if( cls.key_dest == key_game ) + { + IN_DeactivateMouse(); + } + if( dma.initialized && snd_mute_losefocus.value ) { SNDDMA_Activate( false ); @@ -426,13 +434,10 @@ static void SDLash_EventFilter( SDL_Event *event ) { /* Mouse events */ case SDL_MOUSEMOTION: - if( !host.mouse_visible -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) - && event->motion.which != SDL_TOUCH_MOUSEID ) -#else - ) -#endif - IN_MouseEvent(); + if( host.mouse_visible ) + { + SDL_GetRelativeMouseState( NULL, NULL ); + } break; case SDL_MOUSEBUTTONUP: diff --git a/engine/platform/sdl/events.h b/engine/platform/sdl/events.h index 1d4a69fc..e42df28e 100644 --- a/engine/platform/sdl/events.h +++ b/engine/platform/sdl/events.h @@ -33,5 +33,11 @@ void VID_SaveWindowSize( int width, int height ); // joystick events extern SDL_Joystick *g_joy; +// +// in_sdl.c +// +void SDLash_InitCursors( void ); +void SDLash_FreeCursors( void ); + #endif // XASH_SDL #endif // KEYWRAPPER_H diff --git a/engine/platform/sdl/in_sdl.c b/engine/platform/sdl/in_sdl.c index b2635ea6..7f18c8ab 100644 --- a/engine/platform/sdl/in_sdl.c +++ b/engine/platform/sdl/in_sdl.c @@ -27,6 +27,12 @@ GNU General Public License for more details. SDL_Joystick *g_joy = NULL; #if !SDL_VERSION_ATLEAST( 2, 0, 0 ) #define SDL_WarpMouseInWindow( win, x, y ) SDL_WarpMouse( ( x ), ( y ) ) +#else +static struct +{ + qboolean initialized; + SDL_Cursor *cursors[dc_last]; +} cursors; #endif /* @@ -71,19 +77,28 @@ Platform_GetClipobardText ============= */ -void Platform_GetClipboardText( char *buffer, size_t size ) +int Platform_GetClipboardText( char *buffer, size_t size ) { #if SDL_VERSION_ATLEAST( 2, 0, 0 ) + int textLength; char *sdlbuffer = SDL_GetClipboardText(); if( !sdlbuffer ) - return; + return 0; - Q_strncpy( buffer, sdlbuffer, size ); + if (buffer && size > 0) + { + textLength = Q_strncpy( buffer, sdlbuffer, size ); + } + else { + textLength = Q_strlen( sdlbuffer ); + } SDL_free( sdlbuffer ); + return textLength; #else // SDL_VERSION_ATLEAST( 2, 0, 0 ) buffer[0] = 0; #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) + return 0; } /* @@ -92,7 +107,7 @@ Platform_SetClipobardText ============= */ -void Platform_SetClipboardText( const char *buffer, size_t size ) +void Platform_SetClipboardText( const char *buffer ) { #if SDL_VERSION_ATLEAST( 2, 0, 0 ) SDL_SetClipboardText( buffer ); @@ -237,4 +252,155 @@ int Platform_JoyInit( int numjoy ) return SDLash_JoyInit_Old(numjoy); } +/* +======================== +SDLash_InitCursors + +======================== +*/ +void SDLash_InitCursors( void ) +{ +#if SDL_VERSION_ATLEAST( 2, 0, 0 ) + if( cursors.initialized ) + SDLash_FreeCursors(); + + // load up all default cursors + cursors.cursors[dc_none] = NULL; + cursors.cursors[dc_arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); + cursors.cursors[dc_ibeam] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); + cursors.cursors[dc_hourglass] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT); + cursors.cursors[dc_crosshair] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR); + cursors.cursors[dc_up] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); + cursors.cursors[dc_sizenwse] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE); + cursors.cursors[dc_sizenesw] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW); + cursors.cursors[dc_sizewe] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); + cursors.cursors[dc_sizens] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS); + cursors.cursors[dc_sizeall] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL); + cursors.cursors[dc_no] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO); + cursors.cursors[dc_hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); + cursors.initialized = true; +#endif +} + +/* +======================== +SDLash_FreeCursors + +======================== +*/ +void SDLash_FreeCursors( void ) +{ +#if SDL_VERSION_ATLEAST( 2, 0, 0 ) + int i = 0; + + for( ; i < ARRAYSIZE( cursors.cursors ); i++ ) + { + if( cursors.cursors[i] ) + SDL_FreeCursor( cursors.cursors[i] ); + cursors.cursors[i] = NULL; + } + + cursors.initialized = false; +#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) +} + +/* +======================== +Platform_SetCursorType + +======================== +*/ +void Platform_SetCursorType( VGUI_DefaultCursor type ) +{ + qboolean visible; + +#if SDL_VERSION_ATLEAST( 2, 0, 0 ) + if( !cursors.initialized ) + return; +#endif + + if( cls.key_dest != key_game || cl.paused ) + return; + + switch( type ) + { + case dc_user: + case dc_none: + visible = false; + break; + default: + visible = true; + break; + } + + host.mouse_visible = visible; + + if( CVAR_TO_BOOL( touch_emulate )) + return; + +#if SDL_VERSION_ATLEAST( 2, 0, 0 ) + if( host.mouse_visible ) + { + SDL_SetCursor( cursors.cursors[type] ); + SDL_ShowCursor( true ); + Key_EnableTextInput( true, false ); + } + else + { + SDL_ShowCursor( false ); + Key_EnableTextInput( false, false ); + } +#else + if( host.mouse_visible ) + { + SDL_ShowCursor( true ); + } + else + { + SDL_ShowCursor( false ); + } +#endif +} + +/* +======================== +Platform_GetKeyModifiers + +======================== +*/ +key_modifier_t Platform_GetKeyModifiers( void ) +{ +#if SDL_VERSION_ATLEAST( 2, 0, 0 ) + SDL_Keymod modFlags; + key_modifier_t resultFlags; + + resultFlags = KeyModifier_None; + modFlags = SDL_GetModState(); + if( FBitSet( modFlags, KMOD_LCTRL )) + SetBits( resultFlags, KeyModifier_LeftCtrl ); + if( FBitSet( modFlags, KMOD_RCTRL )) + SetBits( resultFlags, KeyModifier_RightCtrl ); + if( FBitSet( modFlags, KMOD_RSHIFT )) + SetBits( resultFlags, KeyModifier_RightShift ); + if( FBitSet( modFlags, KMOD_LSHIFT )) + SetBits( resultFlags, KeyModifier_LeftShift ); + if( FBitSet( modFlags, KMOD_LALT )) + SetBits( resultFlags, KeyModifier_LeftAlt ); + if( FBitSet( modFlags, KMOD_RALT )) + SetBits( resultFlags, KeyModifier_RightAlt ); + if( FBitSet( modFlags, KMOD_NUM )) + SetBits( resultFlags, KeyModifier_NumLock ); + if( FBitSet( modFlags, KMOD_CAPS )) + SetBits( resultFlags, KeyModifier_CapsLock ); + if( FBitSet( modFlags, KMOD_RGUI )) + SetBits( resultFlags, KeyModifier_RightSuper ); + if( FBitSet( modFlags, KMOD_LGUI )) + SetBits( resultFlags, KeyModifier_LeftSuper ); + + return resultFlags; +#else + return KeyModifier_None; +#endif +} + #endif // XASH_DEDICATED diff --git a/engine/platform/sdl/sys_sdl.c b/engine/platform/sdl/sys_sdl.c index b1ad5838..0194c291 100644 --- a/engine/platform/sdl/sys_sdl.c +++ b/engine/platform/sdl/sys_sdl.c @@ -15,6 +15,7 @@ GNU General Public License for more details. #include #include "platform/platform.h" +#include "events.h" #if XASH_TIMER == TIMER_SDL double Platform_DoubleTime( void ) @@ -67,11 +68,15 @@ void Platform_Init( void ) #ifdef XASH_WIN32 Wcon_CreateConsole(); // system console used by dedicated server or show fatal errors #endif + + SDLash_InitCursors(); } void Platform_Shutdown( void ) { + SDLash_FreeCursors(); + #ifdef XASH_WIN32 Wcon_DestroyConsole(); #endif -} \ No newline at end of file +} diff --git a/engine/platform/sdl/vid_sdl.c b/engine/platform/sdl/vid_sdl.c index 4c2e94e6..cee9089c 100644 --- a/engine/platform/sdl/vid_sdl.c +++ b/engine/platform/sdl/vid_sdl.c @@ -575,7 +575,6 @@ static qboolean VID_SetScreenResolution( int width, int height ) SDL_SetWindowBordered( host.hWnd, SDL_FALSE ); //SDL_SetWindowPosition( host.hWnd, 0, 0 ); - SDL_SetWindowGrab( host.hWnd, SDL_TRUE ); SDL_SetWindowSize( host.hWnd, got.w, got.h ); VID_SaveWindowSize( got.w, got.h ); @@ -591,7 +590,6 @@ void VID_RestoreScreenResolution( void ) if( !Cvar_VariableInteger("fullscreen") ) { SDL_SetWindowBordered( host.hWnd, SDL_TRUE ); - SDL_SetWindowGrab( host.hWnd, SDL_FALSE ); } else { @@ -601,7 +599,7 @@ void VID_RestoreScreenResolution( void ) #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) } -#if XASH_WIN32 && !XASH_64BIT // ICO support only for Win32 +#if XASH_WIN32 // ICO support only for Win32 #include "SDL_syswm.h" static void WIN_SetWindowIcon( HICON ico ) { @@ -612,7 +610,7 @@ static void WIN_SetWindowIcon( HICON ico ) if( SDL_GetWindowWMInfo( host.hWnd, &wminfo ) ) { - SetClassLong( wminfo.info.win.window, GCL_HICON, (LONG)ico ); + SetClassLongPtr( wminfo.info.win.window, GCLP_HICON, (LONG)ico ); } } #endif @@ -631,6 +629,7 @@ qboolean VID_CreateWindow( int width, int height, qboolean fullscreen ) qboolean iconLoaded = false; char iconpath[MAX_STRING]; int xpos, ypos; + const char *localIcoPath; if( vid_highdpi->value ) wndFlags |= SDL_WINDOW_ALLOW_HIGHDPI; Q_strncpy( wndname, GI->title, sizeof( wndname )); @@ -696,14 +695,12 @@ qboolean VID_CreateWindow( int width, int height, qboolean fullscreen ) VID_RestoreScreenResolution(); } -#if XASH_WIN32 && !XASH_64BIT // ICO support only for Win32 - if( FS_FileExists( GI->iconpath, true ) ) +#if XASH_WIN32 // ICO support only for Win32 + if(( localIcoPath = FS_GetDiskPath( GI->iconpath, true ))) { HICON ico; - char localPath[MAX_PATH]; - Q_snprintf( localPath, sizeof( localPath ), "%s/%s", GI->gamefolder, GI->iconpath ); - ico = (HICON)LoadImage( NULL, localPath, IMAGE_ICON, 0, 0, LR_LOADFROMFILE|LR_DEFAULTSIZE ); + ico = (HICON)LoadImage( NULL, localIcoPath, IMAGE_ICON, 0, 0, LR_LOADFROMFILE|LR_DEFAULTSIZE ); if( ico ) { @@ -738,7 +735,7 @@ qboolean VID_CreateWindow( int width, int height, qboolean fullscreen ) } } -#if XASH_WIN32 && !XASH_64BIT // ICO support only for Win32 +#if XASH_WIN32 // ICO support only for Win32 if( !iconLoaded ) { WIN_SetWindowIcon( LoadIcon( host.hInst, MAKEINTRESOURCE( 101 ) ) ); diff --git a/engine/platform/win32/con_win.c b/engine/platform/win32/con_win.c index 1605c9f8..4506063a 100644 --- a/engine/platform/win32/con_win.c +++ b/engine/platform/win32/con_win.c @@ -13,8 +13,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ -#ifdef _WIN32 #include "common.h" +#include "xash3d_mathlib.h" /* =============================================================================== @@ -25,198 +25,429 @@ WIN32 CONSOLE */ // console defines -#define SUBMIT_ID 1 // "submit" button -#define QUIT_ON_ESCAPE_ID 2 // escape event -#define EDIT_ID 110 -#define INPUT_ID 109 -#define IDI_ICON1 101 - -#define SYSCONSOLE "XashConsole" #define COMMAND_HISTORY 64 // system console keep more commands than game console typedef struct { char title[64]; HWND hWnd; - HWND hwndBuffer; - HWND hwndButtonSubmit; - HBRUSH hbrEditBackground; - HFONT hfBufferFont; - HWND hwndInputLine; + HANDLE hInput; + HANDLE hOutput; + int inputLine; + int browseLine; + int cursorPosition; + int totalLines; + int savedConsoleTextLen; + int consoleTextLen; string consoleText; - string returnedText; - string historyLines[COMMAND_HISTORY]; - int nextHistoryLine; - int historyLine; - int status; - int windowWidth, windowHeight; - WNDPROC SysInputLineWndProc; - size_t outLen; + string savedConsoleText; + string statusLine; + string lineBuffer[COMMAND_HISTORY]; + qboolean inputEnabled; + qboolean consoleVisible; // log stuff - qboolean log_active; + qboolean log_active; char log_path[MAX_SYSPATH]; } WinConData; static WinConData s_wcd; +static WORD g_color_table[8] = +{ +FOREGROUND_INTENSITY, // black +FOREGROUND_RED, // red +FOREGROUND_GREEN, // green +FOREGROUND_RED | FOREGROUND_GREEN, // yellow +FOREGROUND_BLUE | FOREGROUND_INTENSITY, // blue +FOREGROUND_GREEN | FOREGROUND_BLUE, // cyan +FOREGROUND_RED | FOREGROUND_BLUE, // magenta +FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // default color (white) +}; + +static BOOL WINAPI Wcon_HandleConsole(DWORD CtrlType) +{ + return TRUE; +} + +static void Wcon_PrintInternal( const char *msg, int length ) +{ + char *pTemp; + DWORD cbWritten; + const char *pMsgString; + static char tmpBuf[2048]; + static char szOutput[2048]; + + Q_strncpy( szOutput, msg, length ? (length + 1) : ( sizeof( szOutput ) - 1 )); + if( length ) + szOutput[length + 1] = '\0'; + else + szOutput[sizeof( szOutput ) - 1] = '\0'; + + pTemp = tmpBuf; + pMsgString = szOutput; + while( pMsgString && *pMsgString ) + { + if( IsColorString( pMsgString )) + { + if (( pTemp - tmpBuf ) > 0 ) + { + // dump accumulated text before change color + *pTemp = 0; // terminate string + WriteFile( s_wcd.hOutput, tmpBuf, Q_strlen(tmpBuf), &cbWritten, 0 ); + pTemp = tmpBuf; + } + + // set new color + SetConsoleTextAttribute( s_wcd.hOutput, g_color_table[ColorIndex(*(pMsgString + 1))] ); + pMsgString += 2; // skip color info + } + else if(( pTemp - tmpBuf ) < sizeof( tmpBuf ) - 1 ) + { + *pTemp++ = *pMsgString++; + } + else + { + // temp buffer is full, dump it now + *pTemp = 0; // terminate string + WriteFile( s_wcd.hOutput, tmpBuf, Q_strlen(tmpBuf), &cbWritten, 0 ); + pTemp = tmpBuf; + } + } + + // check for last portion + if (( pTemp - tmpBuf ) > 0 ) + { + // dump accumulated text + *pTemp = 0; // terminate string + WriteFile( s_wcd.hOutput, tmpBuf, Q_strlen(tmpBuf), &cbWritten, 0 ); + pTemp = tmpBuf; + } + + // restore white color + SetConsoleTextAttribute( s_wcd.hOutput, g_color_table[7] ); +} void Wcon_ShowConsole( qboolean show ) { - if( !s_wcd.hWnd || show == s_wcd.status ) + if( !s_wcd.hWnd || show == s_wcd.consoleVisible ) return; - s_wcd.status = show; + s_wcd.consoleVisible = show; if( show ) - { - ShowWindow( s_wcd.hWnd, SW_SHOWNORMAL ); - SendMessage( s_wcd.hwndBuffer, EM_LINESCROLL, 0, 0xffff ); - } - else ShowWindow( s_wcd.hWnd, SW_HIDE ); + ShowWindow( s_wcd.hWnd, SW_SHOW ); + else + ShowWindow( s_wcd.hWnd, SW_HIDE ); } void Wcon_DisableInput( void ) { - if( host.type != HOST_DEDICATED ) return; - SendMessage( s_wcd.hwndButtonSubmit, WM_ENABLE, 0, 0 ); - SendMessage( s_wcd.hwndInputLine, WM_ENABLE, 0, 0 ); + if( host.type != HOST_DEDICATED ) + return; + + s_wcd.inputEnabled = false; } -void Wcon_SetInputText( const char *inputText ) +static void Wcon_SetInputText( const char *inputText ) { - if( host.type != HOST_DEDICATED ) return; - SetWindowText( s_wcd.hwndInputLine, inputText ); - SendMessage( s_wcd.hwndInputLine, EM_SETSEL, Q_strlen( inputText ), -1 ); + if( host.type != HOST_DEDICATED ) + return; + + while( s_wcd.consoleTextLen-- ) + { + Wcon_PrintInternal( "\b \b", 0 ); + } + Wcon_PrintInternal( inputText, 0 ); + Q_strncpy( s_wcd.consoleText, inputText, sizeof(s_wcd.consoleText) - 1 ); + s_wcd.consoleTextLen = Q_strlen( inputText ); + s_wcd.cursorPosition = s_wcd.consoleTextLen; + s_wcd.browseLine = s_wcd.inputLine; } static void Wcon_Clear_f( void ) { - if( host.type != HOST_DEDICATED ) return; - SendMessage( s_wcd.hwndBuffer, EM_SETSEL, 0, -1 ); - SendMessage( s_wcd.hwndBuffer, EM_REPLACESEL, FALSE, (LPARAM)"" ); - UpdateWindow( s_wcd.hwndBuffer ); + CONSOLE_SCREEN_BUFFER_INFO csbi; + SMALL_RECT scrollRect; + COORD scrollTarget; + CHAR_INFO fill; + + if( host.type != HOST_DEDICATED ) + return; + + if( !GetConsoleScreenBufferInfo( s_wcd.hOutput, &csbi )) + { + return; + } + + scrollRect.Left = 0; + scrollRect.Top = 0; + scrollRect.Right = csbi.dwSize.X; + scrollRect.Bottom = csbi.dwSize.Y; + scrollTarget.X = 0; + scrollTarget.Y = (SHORT)(0 - csbi.dwSize.Y); + fill.Char.UnicodeChar = TEXT(' '); + fill.Attributes = csbi.wAttributes; + ScrollConsoleScreenBuffer( s_wcd.hOutput, &scrollRect, NULL, scrollTarget, &fill ); + + csbi.dwCursorPosition.X = 0; + csbi.dwCursorPosition.Y = 0; + SetConsoleCursorPosition( s_wcd.hOutput, csbi.dwCursorPosition ); + + s_wcd.consoleText[0] = '\0'; + s_wcd.consoleTextLen = 0; + s_wcd.cursorPosition = 0; + s_wcd.inputLine = 0; + s_wcd.browseLine = 0; + s_wcd.totalLines = 0; + Wcon_PrintInternal( "\n", 0 ); } -static int Wcon_KeyEvent( int key, qboolean down ) +static void Wcon_EventUpArrow() { - char inputBuffer[1024]; + int nLastCommandInHistory = s_wcd.inputLine + 1; + if( nLastCommandInHistory > s_wcd.totalLines ) + nLastCommandInHistory = 0; - if( !down ) - return 0; + if( s_wcd.browseLine == nLastCommandInHistory ) + return; + + if( s_wcd.browseLine == s_wcd.inputLine ) + { + if( s_wcd.consoleTextLen > 0 ) + { + Q_strncpy( s_wcd.savedConsoleText, s_wcd.consoleText, s_wcd.consoleTextLen ); + } + s_wcd.savedConsoleTextLen = s_wcd.consoleTextLen; + } + + s_wcd.browseLine--; + if( s_wcd.browseLine < 0 ) + { + s_wcd.browseLine = s_wcd.totalLines - 1; + } + + while( s_wcd.consoleTextLen-- ) + { + Wcon_PrintInternal( "\b \b", 0 ); + } + + Wcon_PrintInternal( s_wcd.lineBuffer[s_wcd.browseLine], 0 ); + Q_strncpy( s_wcd.consoleText, s_wcd.lineBuffer[s_wcd.browseLine], sizeof( s_wcd.consoleText )); + s_wcd.consoleTextLen = Q_strlen( s_wcd.lineBuffer[s_wcd.browseLine] ); + s_wcd.cursorPosition = s_wcd.consoleTextLen; +} + +static void Wcon_EventDownArrow() +{ + if( s_wcd.browseLine == s_wcd.inputLine ) + return; + + if( ++s_wcd.browseLine > s_wcd.totalLines ) + s_wcd.browseLine = 0; + + while( s_wcd.consoleTextLen-- ) + { + Wcon_PrintInternal( "\b \b", 0 ); + } + + if( s_wcd.browseLine == s_wcd.inputLine ) + { + if( s_wcd.savedConsoleTextLen > 0 ) + { + Q_strncpy( s_wcd.consoleText, s_wcd.savedConsoleText, s_wcd.savedConsoleTextLen ); + Wcon_PrintInternal( s_wcd.consoleText, s_wcd.savedConsoleTextLen ); + } + s_wcd.consoleTextLen = s_wcd.savedConsoleTextLen; + } + else + { + Wcon_PrintInternal( s_wcd.lineBuffer[s_wcd.browseLine], 0 ); + Q_strncpy( s_wcd.consoleText, s_wcd.lineBuffer[s_wcd.browseLine], sizeof( s_wcd.consoleText )); + s_wcd.consoleTextLen = Q_strlen( s_wcd.lineBuffer[s_wcd.browseLine] ); + } + s_wcd.cursorPosition = s_wcd.consoleTextLen; +} + +static void Wcon_EventLeftArrow() +{ + if( s_wcd.cursorPosition == 0 ) + return; + + Wcon_PrintInternal( "\b", 0 ); + s_wcd.cursorPosition--; +} + +static void Wcon_EventRightArrow() +{ + if( s_wcd.cursorPosition == s_wcd.consoleTextLen ) + return; + + Wcon_PrintInternal( s_wcd.consoleText + s_wcd.cursorPosition, 1 ); + s_wcd.cursorPosition++; +} + +static int Wcon_EventNewline() +{ + int nLen; + + nLen = 0; + Wcon_PrintInternal( "\n", 0 ); + if( s_wcd.consoleTextLen ) + { + nLen = s_wcd.consoleTextLen; + + s_wcd.consoleText[s_wcd.consoleTextLen] = '\0'; + s_wcd.consoleTextLen = 0; + s_wcd.cursorPosition = 0; + + if (( s_wcd.inputLine == 0 ) || ( Q_strcmp( s_wcd.lineBuffer[s_wcd.inputLine - 1], s_wcd.consoleText ))) + { + Q_strncpy( s_wcd.lineBuffer[s_wcd.inputLine], s_wcd.consoleText, sizeof( s_wcd.consoleText )); + s_wcd.inputLine++; + + if( s_wcd.inputLine > s_wcd.totalLines ) + s_wcd.totalLines = s_wcd.inputLine; + + if( s_wcd.inputLine >= COMMAND_HISTORY ) + s_wcd.inputLine = 0; + } + s_wcd.browseLine = s_wcd.inputLine; + } + return nLen; +} + +static void Wcon_EventBackspace() +{ + int nCount; + + if( s_wcd.cursorPosition < 1 ) + { + return; + } + + s_wcd.consoleTextLen--; + s_wcd.cursorPosition--; + + Wcon_PrintInternal( "\b", 0 ); + + for( nCount = s_wcd.cursorPosition; nCount < s_wcd.consoleTextLen; ++nCount ) + { + s_wcd.consoleText[nCount] = s_wcd.consoleText[nCount + 1]; + Wcon_PrintInternal( s_wcd.consoleText + nCount, 1 ); + } + + Wcon_PrintInternal( " ", 0 ); + + nCount = s_wcd.consoleTextLen; + while( nCount >= s_wcd.cursorPosition ) + { + Wcon_PrintInternal( "\b", 0 ); + nCount--; + } + + s_wcd.browseLine = s_wcd.inputLine; +} + +static void Wcon_EventTab() +{ + s_wcd.consoleText[s_wcd.consoleTextLen] = '\0'; + Cmd_AutoComplete( s_wcd.consoleText ); + Wcon_SetInputText( s_wcd.consoleText ); +} + +static void Wcon_EventCharacter(char c) +{ + int nCount; + + if( s_wcd.consoleTextLen >= ( sizeof( s_wcd.consoleText ) - 2 )) + { + return; + } + + nCount = s_wcd.consoleTextLen; + while( nCount > s_wcd.cursorPosition ) + { + s_wcd.consoleText[nCount] = s_wcd.consoleText[nCount - 1]; + nCount--; + } + + s_wcd.consoleText[s_wcd.cursorPosition] = c; + Wcon_PrintInternal( s_wcd.consoleText + s_wcd.cursorPosition, s_wcd.consoleTextLen - s_wcd.cursorPosition + 1 ); + s_wcd.consoleTextLen++; + s_wcd.cursorPosition++; + + nCount = s_wcd.consoleTextLen; + while( nCount > s_wcd.cursorPosition ) + { + Wcon_PrintInternal( "\b", 0 ); + nCount--; + } + + s_wcd.browseLine = s_wcd.inputLine; +} + +static void Wcon_UpdateStatusLine() +{ + COORD coord; + WORD wAttrib; + DWORD dwWritten; + + coord.X = 0; + coord.Y = 0; + wAttrib = g_color_table[5] | FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; + + FillConsoleOutputCharacter( s_wcd.hOutput, ' ', 80, coord, &dwWritten ); + FillConsoleOutputAttribute( s_wcd.hOutput, wAttrib, 80, coord, &dwWritten ); + WriteConsoleOutputCharacter( s_wcd.hOutput, s_wcd.statusLine, Q_strlen(s_wcd.statusLine), coord, &dwWritten ); +} + +static char *Wcon_KeyEvent( int key, WCHAR character ) +{ + int nLen; + char inputBuffer[1024]; switch( key ) { - case VK_TAB: - GetWindowText( s_wcd.hwndInputLine, inputBuffer, sizeof( inputBuffer )); - Cmd_AutoComplete( inputBuffer ); - Wcon_SetInputText( inputBuffer ); - return 1; - case VK_DOWN: - if( s_wcd.historyLine == s_wcd.nextHistoryLine ) - return 0; - s_wcd.historyLine++; - Wcon_SetInputText( s_wcd.historyLines[s_wcd.historyLine % COMMAND_HISTORY] ); - return 1; case VK_UP: - if( s_wcd.nextHistoryLine - s_wcd.historyLine < COMMAND_HISTORY && s_wcd.historyLine > 0 ) - s_wcd.historyLine--; - Wcon_SetInputText( s_wcd.historyLines[s_wcd.historyLine % COMMAND_HISTORY] ); - return 1; + Wcon_EventUpArrow(); + return NULL; + case VK_DOWN: + Wcon_EventDownArrow(); + return NULL; + case VK_LEFT: + Wcon_EventLeftArrow(); + return NULL; + case VK_RIGHT: + Wcon_EventRightArrow(); + return NULL; } - return 0; -} -static long _stdcall Wcon_WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - switch( uMsg ) + switch( character ) { - case WM_ACTIVATE: - if( LOWORD( wParam ) != WA_INACTIVE ) - SetFocus( s_wcd.hwndInputLine ); - break; - case WM_CLOSE: - if( host.status == HOST_ERR_FATAL ) - { - // send windows message - PostQuitMessage( 0 ); - } - else Sys_Quit(); // otherwise - return 0; - case WM_CTLCOLORSTATIC: - if((HWND)lParam == s_wcd.hwndBuffer ) - { - SetBkColor((HDC)wParam, RGB( 0x90, 0x90, 0x90 )); - SetTextColor((HDC)wParam, RGB( 0xff, 0xff, 0xff )); - return (long)s_wcd.hbrEditBackground; - } - break; - case WM_COMMAND: - if( wParam == SUBMIT_ID ) - { - SendMessage( s_wcd.hwndInputLine, WM_CHAR, 13, 0L ); - SetFocus( s_wcd.hwndInputLine ); - } - break; - case WM_HOTKEY: - switch( LOWORD( wParam )) - { - case QUIT_ON_ESCAPE_ID: - PostQuitMessage( 0 ); + case '\r': // Enter + nLen = Wcon_EventNewline(); + if (nLen) + { + return s_wcd.consoleText; + } + break; + case '\b': // Backspace + Wcon_EventBackspace(); + break; + case '\t': // TAB + Wcon_EventTab(); + break; + default: + // TODO implement converting wide chars to UTF-8 and properly handling it + if (( character >= ' ' ) && ( character <= '~' )) + { + Wcon_EventCharacter(character); + } break; - } - break; - case WM_CREATE: - s_wcd.hbrEditBackground = CreateSolidBrush( RGB( 0x90, 0x90, 0x90 )); - break; } - return DefWindowProc( hWnd, uMsg, wParam, lParam ); + + return NULL; } -long _stdcall Wcon_InputLineProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) -{ - char inputBuffer[1024]; - - switch( uMsg ) - { - case WM_KILLFOCUS: - if(( HWND )wParam == s_wcd.hWnd ) - { - SetFocus( hWnd ); - return 0; - } - break; - case WM_SYSKEYDOWN: - case WM_KEYDOWN: - if( Wcon_KeyEvent( LOWORD( wParam ), true )) - return 0; - break; - case WM_SYSKEYUP: - case WM_KEYUP: - if( Wcon_KeyEvent( LOWORD( wParam ), false )) - return 0; - break; - case WM_CHAR: - if( Wcon_KeyEvent( wParam, true )) - return 0; - if( wParam == 13 && host.status != HOST_ERR_FATAL ) - { - GetWindowText( s_wcd.hwndInputLine, inputBuffer, sizeof( inputBuffer )); - Q_strncat( s_wcd.consoleText, inputBuffer, sizeof( s_wcd.consoleText ) - Q_strlen( s_wcd.consoleText ) - 5 ); - Q_strcat( s_wcd.consoleText, "\n" ); - SetWindowText( s_wcd.hwndInputLine, "" ); - Con_Printf( ">%s\n", inputBuffer ); - - // copy line to history buffer - Q_strncpy( s_wcd.historyLines[s_wcd.nextHistoryLine % COMMAND_HISTORY], inputBuffer, MAX_STRING ); - s_wcd.nextHistoryLine++; - s_wcd.historyLine = s_wcd.nextHistoryLine; - return 0; - } - break; - } - return CallWindowProc( s_wcd.SysInputLineWndProc, hWnd, uMsg, wParam, lParam ); -} - - /* =============================================================================== @@ -224,6 +455,7 @@ WIN32 IO =============================================================================== */ + /* ================ Con_WinPrint @@ -233,24 +465,26 @@ print into window console */ void Wcon_WinPrint( const char *pMsg ) { - size_t len = Q_strlen( pMsg ); - - // replace selection instead of appending if we're overflowing - s_wcd.outLen += len; - if( s_wcd.outLen >= 0x7fff ) + int nLen; + if( s_wcd.consoleTextLen ) { - SendMessage( s_wcd.hwndBuffer, EM_SETSEL, 0, -1 ); - s_wcd.outLen = len; + nLen = s_wcd.consoleTextLen; + while (nLen--) + { + Wcon_PrintInternal( "\b \b", 0 ); + } } - SendMessage( s_wcd.hwndBuffer, EM_REPLACESEL, 0, (LPARAM)pMsg ); + Wcon_PrintInternal( pMsg, 0 ); - // put this text into the windows console - SendMessage( s_wcd.hwndBuffer, EM_LINESCROLL, 0, 0xffff ); - SendMessage( s_wcd.hwndBuffer, EM_SCROLLCARET, 0, 0 ); + if( s_wcd.consoleTextLen ) + { + Wcon_PrintInternal( s_wcd.consoleText, s_wcd.consoleTextLen ); + } + + Wcon_UpdateStatusLine(); } - /* ================ Con_CreateConsole @@ -260,107 +494,38 @@ create win32 console */ void Wcon_CreateConsole( void ) { - HDC hDC; - WNDCLASS wc; - RECT rect; - int nHeight; - int swidth, sheight, fontsize; - int DEDSTYLE = WS_POPUPWINDOW | WS_CAPTION; - int CONSTYLE = WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_BORDER|WS_EX_CLIENTEDGE|ES_LEFT|ES_MULTILINE|ES_AUTOVSCROLL|ES_READONLY; - string FontName; - - wc.style = 0; - wc.lpfnWndProc = (WNDPROC)Wcon_WndProc; - wc.cbClsExtra = 0; - wc.cbWndExtra = 0; - wc.hInstance = host.hInst; - wc.hIcon = LoadIcon( host.hInst, MAKEINTRESOURCE( IDI_ICON1 )); - wc.hCursor = LoadCursor( NULL, IDC_ARROW ); - wc.hbrBackground = (void *)COLOR_3DSHADOW; - wc.lpszClassName = SYSCONSOLE; - wc.lpszMenuName = 0; - if( Sys_CheckParm( "-log" )) s_wcd.log_active = true; if( host.type == HOST_NORMAL ) { - rect.left = 0; - rect.right = 536; - rect.top = 0; - rect.bottom = 364; - Q_strncpy( FontName, "Fixedsys", sizeof( FontName )); Q_strncpy( s_wcd.title, va( "Xash3D %s", XASH_VERSION ), sizeof( s_wcd.title )); Q_strncpy( s_wcd.log_path, "engine.log", sizeof( s_wcd.log_path )); - fontsize = 8; } else // dedicated console { - rect.left = 0; - rect.right = 640; - rect.top = 0; - rect.bottom = 392; - Q_strncpy( FontName, "System", sizeof( FontName )); Q_strncpy( s_wcd.title, va( "XashDS %s", XASH_VERSION ), sizeof( s_wcd.title )); Q_strncpy( s_wcd.log_path, "dedicated.log", sizeof( s_wcd.log_path )); s_wcd.log_active = true; // always make log - fontsize = 14; } - if( !RegisterClass( &wc )) + AllocConsole(); + SetConsoleTitle( s_wcd.title ); + SetConsoleCP( CP_UTF8 ); + SetConsoleOutputCP( CP_UTF8 ); + + s_wcd.hWnd = GetConsoleWindow(); + s_wcd.hInput = GetStdHandle( STD_INPUT_HANDLE ); + s_wcd.hOutput = GetStdHandle( STD_OUTPUT_HANDLE ); + s_wcd.inputEnabled = true; + + if( !SetConsoleCtrlHandler( &Wcon_HandleConsole, TRUE )) { - // print into log - Con_Reportf( S_ERROR "Can't register window class '%s'\n", SYSCONSOLE ); + Con_Reportf( S_ERROR "Couldn't attach console handler function\n" ); return; } - AdjustWindowRect( &rect, DEDSTYLE, FALSE ); - - hDC = GetDC( GetDesktopWindow() ); - swidth = GetDeviceCaps( hDC, HORZRES ); - sheight = GetDeviceCaps( hDC, VERTRES ); - ReleaseDC( GetDesktopWindow(), hDC ); - - s_wcd.windowWidth = rect.right - rect.left; - s_wcd.windowHeight = rect.bottom - rect.top; - - s_wcd.hWnd = CreateWindowEx( WS_EX_DLGMODALFRAME, SYSCONSOLE, s_wcd.title, DEDSTYLE, ( swidth - 600 ) / 2, ( sheight - 450 ) / 2 , rect.right - rect.left + 1, rect.bottom - rect.top + 1, NULL, NULL, host.hInst, NULL ); - if( s_wcd.hWnd == NULL ) - { - Con_Reportf( S_ERROR "Can't create window '%s'\n", s_wcd.title ); - return; - } - - // create fonts - hDC = GetDC( s_wcd.hWnd ); - nHeight = -MulDiv( fontsize, GetDeviceCaps( hDC, LOGPIXELSY ), 72 ); - s_wcd.hfBufferFont = CreateFont( nHeight, 0, 0, 0, FW_LIGHT, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_MODERN|FIXED_PITCH, FontName ); - ReleaseDC( s_wcd.hWnd, hDC ); - - if( host.type == HOST_DEDICATED ) - { - // create the input line - s_wcd.hwndInputLine = CreateWindowEx( WS_EX_CLIENTEDGE, "edit", NULL, WS_CHILD|WS_VISIBLE|WS_BORDER|ES_LEFT|ES_AUTOHSCROLL, 0, 366, 550, 25, s_wcd.hWnd, (HMENU)INPUT_ID, host.hInst, NULL ); - - s_wcd.hwndButtonSubmit = CreateWindow( "button", NULL, BS_PUSHBUTTON|WS_VISIBLE|WS_CHILD|BS_DEFPUSHBUTTON, 552, 367, 87, 25, s_wcd.hWnd, (HMENU)SUBMIT_ID, host.hInst, NULL ); - SendMessage( s_wcd.hwndButtonSubmit, WM_SETTEXT, 0, ( LPARAM ) "submit" ); - } - - // create the scrollbuffer - GetClientRect( s_wcd.hWnd, &rect ); - - s_wcd.hwndBuffer = CreateWindowEx( WS_EX_DLGMODALFRAME|WS_EX_CLIENTEDGE, "edit", NULL, CONSTYLE, 0, 0, rect.right - rect.left, min(365, rect.bottom), s_wcd.hWnd, (HMENU)EDIT_ID, host.hInst, NULL ); - SendMessage( s_wcd.hwndBuffer, WM_SETFONT, (WPARAM)s_wcd.hfBufferFont, 0 ); - - if( host.type == HOST_DEDICATED ) - { -#ifdef XASH_64BIT - s_wcd.SysInputLineWndProc = (WNDPROC)SetWindowLongPtr( s_wcd.hwndInputLine, GWLP_WNDPROC, (LONG_PTR)Wcon_InputLineProc ); -#else - s_wcd.SysInputLineWndProc = (WNDPROC)SetWindowLong( s_wcd.hwndInputLine, GWL_WNDPROC, (long)Wcon_InputLineProc ); -#endif - SendMessage( s_wcd.hwndInputLine, WM_SETFONT, ( WPARAM )s_wcd.hfBufferFont, 0 ); - } + SetWindowPos( s_wcd.hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOREPOSITION | SWP_SHOWWINDOW ); // show console if needed if( host.con_showalways ) @@ -369,13 +534,14 @@ void Wcon_CreateConsole( void ) ShowWindow( s_wcd.hWnd, SW_SHOWDEFAULT ); UpdateWindow( s_wcd.hWnd ); SetForegroundWindow( s_wcd.hWnd ); - - if( host.type != HOST_DEDICATED ) - SetFocus( s_wcd.hWnd ); - else SetFocus( s_wcd.hwndInputLine ); - s_wcd.status = true; + SetFocus( s_wcd.hWnd ); + s_wcd.consoleVisible = true; + } + else + { + s_wcd.consoleVisible = false; + ShowWindow( s_wcd.hWnd, SW_HIDE ); } - else s_wcd.status = false; } /* @@ -387,7 +553,9 @@ register console commands (dedicated only) */ void Wcon_InitConsoleCommands( void ) { - if( host.type != HOST_DEDICATED ) return; + if( host.type != HOST_DEDICATED ) + return; + Cmd_AddCommand( "clear", Wcon_Clear_f, "clear console history" ); } @@ -407,33 +575,15 @@ void Wcon_DestroyConsole( void ) if( s_wcd.hWnd ) { - DeleteObject( s_wcd.hbrEditBackground ); - DeleteObject( s_wcd.hfBufferFont ); - - if( host.type == HOST_DEDICATED ) - { - ShowWindow( s_wcd.hwndButtonSubmit, SW_HIDE ); - DestroyWindow( s_wcd.hwndButtonSubmit ); - s_wcd.hwndButtonSubmit = 0; - - ShowWindow( s_wcd.hwndInputLine, SW_HIDE ); - DestroyWindow( s_wcd.hwndInputLine ); - s_wcd.hwndInputLine = 0; - } - - ShowWindow( s_wcd.hwndBuffer, SW_HIDE ); - DestroyWindow( s_wcd.hwndBuffer ); - s_wcd.hwndBuffer = 0; - ShowWindow( s_wcd.hWnd, SW_HIDE ); - DestroyWindow( s_wcd.hWnd ); s_wcd.hWnd = 0; } - UnregisterClass( SYSCONSOLE, host.hInst ); + FreeConsole(); // place it here in case Sys_Crash working properly - if( host.hMutex ) CloseHandle( host.hMutex ); + if( host.hMutex ) + CloseHandle( host.hMutex ); } /* @@ -445,27 +595,57 @@ returned input text */ char *Wcon_Input( void ) { - if( s_wcd.consoleText[0] == 0 ) + int i; + int eventsCount; + static INPUT_RECORD events[1024]; + + if( !s_wcd.inputEnabled ) return NULL; - Q_strncpy( s_wcd.returnedText, s_wcd.consoleText, sizeof( s_wcd.returnedText )); - s_wcd.consoleText[0] = 0; + while( true ) + { + if( !GetNumberOfConsoleInputEvents( s_wcd.hInput, &eventsCount )) + { + return NULL; + } - return s_wcd.returnedText; + if( eventsCount <= 0 ) + break; + + if( !ReadConsoleInputW( s_wcd.hInput, events, ARRAYSIZE( events ), &eventsCount )) + { + return NULL; + } + + if( eventsCount == 0 ) + return NULL; + + for( i = 0; i < eventsCount; i++ ) + { + INPUT_RECORD *pRec = &events[i]; + if( pRec->EventType != KEY_EVENT ) + continue; + + if( pRec->Event.KeyEvent.bKeyDown ) + return Wcon_KeyEvent( pRec->Event.KeyEvent.wVirtualKeyCode, pRec->Event.KeyEvent.uChar.UnicodeChar ); + } + } + return NULL; } /* ================ -Con_SetFocus +Wcon_SetStatus -change focus to console hwnd +set server status string in console ================ */ -void Wcon_RegisterHotkeys( void ) +void Wcon_SetStatus( const char *pStatus ) { - SetFocus( s_wcd.hWnd ); + if( host.type != HOST_DEDICATED ) + return; - // user can hit escape for quit - RegisterHotKey( s_wcd.hWnd, QUIT_ON_ESCAPE_ID, 0, VK_ESCAPE ); + Q_strncpy( s_wcd.statusLine, pStatus, sizeof( s_wcd.statusLine ) - 1 ); + s_wcd.statusLine[sizeof( s_wcd.statusLine ) - 2] = '\0'; + Wcon_UpdateStatusLine(); } -#endif // _WIN32 diff --git a/engine/platform/win32/lib_custom_win.c b/engine/platform/win32/lib_custom_win.c new file mode 100644 index 00000000..f4dfe4aa --- /dev/null +++ b/engine/platform/win32/lib_custom_win.c @@ -0,0 +1,478 @@ +/* +lib_custom_win.c - win32 custom dlls loader +Copyright (C) 2008 Uncle Mike + +This program 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, either version 3 of the License, or +(at your option) any later version. + +This program 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. +*/ +#include "common.h" + +#if XASH_LIB == LIB_WIN32 && XASH_X86 +#include "lib_win.h" + +#define NUMBER_OF_DIRECTORY_ENTRIES 16 +#ifndef IMAGE_SIZEOF_BASE_RELOCATION +#define IMAGE_SIZEOF_BASE_RELOCATION ( sizeof( IMAGE_BASE_RELOCATION )) +#endif + +typedef struct +{ + PIMAGE_NT_HEADERS headers; + byte *codeBase; + void **modules; + int numModules; + int initialized; +} MEMORYMODULE, *PMEMORYMODULE; + +// Protection flags for memory pages (Executable, Readable, Writeable) +static int ProtectionFlags[2][2][2] = +{ +{ +{ PAGE_NOACCESS, PAGE_WRITECOPY }, // not executable +{ PAGE_READONLY, PAGE_READWRITE }, +}, +{ +{ PAGE_EXECUTE, PAGE_EXECUTE_WRITECOPY }, // executable +{ PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE }, +}, +}; + +typedef BOOL (WINAPI *DllEntryProc)( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved ); + +#define GET_HEADER_DICTIONARY( module, idx ) &(module)->headers->OptionalHeader.DataDirectory[idx] + +static void CopySections( const byte *data, PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module ) +{ + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION( module->headers ); + byte *codeBase = module->codeBase; + int i, size; + byte *dest; + + for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) + { + if( section->SizeOfRawData == 0 ) + { + // section doesn't contain data in the dll itself, but may define + // uninitialized data + size = old_headers->OptionalHeader.SectionAlignment; + + if( size > 0 ) + { + dest = (byte *)VirtualAlloc((byte *)CALCULATE_ADDRESS(codeBase, section->VirtualAddress), size, MEM_COMMIT, PAGE_READWRITE ); + section->Misc.PhysicalAddress = (DWORD)dest; + memset( dest, 0, size ); + } + // section is empty + continue; + } + + // commit memory block and copy data from dll + dest = (byte *)VirtualAlloc((byte *)CALCULATE_ADDRESS(codeBase, section->VirtualAddress), section->SizeOfRawData, MEM_COMMIT, PAGE_READWRITE ); + memcpy( dest, (byte *)CALCULATE_ADDRESS(data, section->PointerToRawData), section->SizeOfRawData ); + section->Misc.PhysicalAddress = (DWORD)dest; + } +} + +static void FreeSections( PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module ) +{ + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers); + byte *codeBase = module->codeBase; + int i, size; + + for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) + { + if( section->SizeOfRawData == 0 ) + { + size = old_headers->OptionalHeader.SectionAlignment; + if( size > 0 ) + { + VirtualFree((byte *)CALCULATE_ADDRESS( codeBase, section->VirtualAddress ), size, MEM_DECOMMIT ); + section->Misc.PhysicalAddress = 0; + } + continue; + } + + VirtualFree((byte *)CALCULATE_ADDRESS( codeBase, section->VirtualAddress ), section->SizeOfRawData, MEM_DECOMMIT ); + section->Misc.PhysicalAddress = 0; + } +} + +static void FinalizeSections( MEMORYMODULE *module ) +{ + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION( module->headers ); + int i; + + // loop through all sections and change access flags + for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) + { + DWORD protect, oldProtect, size; + int executable = (section->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; + int readable = (section->Characteristics & IMAGE_SCN_MEM_READ) != 0; + int writeable = (section->Characteristics & IMAGE_SCN_MEM_WRITE) != 0; + + if( section->Characteristics & IMAGE_SCN_MEM_DISCARDABLE ) + { + // section is not needed any more and can safely be freed + VirtualFree((LPVOID)section->Misc.PhysicalAddress, section->SizeOfRawData, MEM_DECOMMIT); + continue; + } + + // determine protection flags based on characteristics + protect = ProtectionFlags[executable][readable][writeable]; + if( section->Characteristics & IMAGE_SCN_MEM_NOT_CACHED ) + protect |= PAGE_NOCACHE; + + // determine size of region + size = section->SizeOfRawData; + + if( size == 0 ) + { + if( section->Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA ) + size = module->headers->OptionalHeader.SizeOfInitializedData; + else if( section->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA ) + size = module->headers->OptionalHeader.SizeOfUninitializedData; + } + + if( size > 0 ) + { + // change memory access flags + if( !VirtualProtect((LPVOID)section->Misc.PhysicalAddress, size, protect, &oldProtect )) + Sys_Error( "error protecting memory page\n" ); + } + } +} + +static void PerformBaseRelocation( MEMORYMODULE *module, DWORD delta ) +{ + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY( module, IMAGE_DIRECTORY_ENTRY_BASERELOC ); + byte *codeBase = module->codeBase; + DWORD i; + + if( directory->Size > 0 ) + { + PIMAGE_BASE_RELOCATION relocation = (PIMAGE_BASE_RELOCATION)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); + for( ; relocation->VirtualAddress > 0; ) + { + byte *dest = (byte *)CALCULATE_ADDRESS( codeBase, relocation->VirtualAddress ); + word *relInfo = (word *)((byte *)relocation + IMAGE_SIZEOF_BASE_RELOCATION ); + + for( i = 0; i<((relocation->SizeOfBlock-IMAGE_SIZEOF_BASE_RELOCATION) / 2); i++, relInfo++ ) + { + DWORD *patchAddrHL; + int type, offset; + + // the upper 4 bits define the type of relocation + type = *relInfo >> 12; + // the lower 12 bits define the offset + offset = *relInfo & 0xfff; + + switch( type ) + { + case IMAGE_REL_BASED_ABSOLUTE: + // skip relocation + break; + case IMAGE_REL_BASED_HIGHLOW: + // change complete 32 bit address + patchAddrHL = (DWORD *)CALCULATE_ADDRESS( dest, offset ); + *patchAddrHL += delta; + break; + default: + Con_Reportf( S_ERROR "PerformBaseRelocation: unknown relocation: %d\n", type ); + break; + } + } + + // advance to next relocation block + relocation = (PIMAGE_BASE_RELOCATION)CALCULATE_ADDRESS( relocation, relocation->SizeOfBlock ); + } + } +} + +FARPROC MemoryGetProcAddress( void *module, const char *name ) +{ + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY((MEMORYMODULE *)module, IMAGE_DIRECTORY_ENTRY_EXPORT ); + byte *codeBase = ((PMEMORYMODULE)module)->codeBase; + PIMAGE_EXPORT_DIRECTORY exports; + int idx = -1; + DWORD i, *nameRef; + WORD *ordinal; + + if( directory->Size == 0 ) + { + // no export table found + return NULL; + } + + exports = (PIMAGE_EXPORT_DIRECTORY)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); + + if( exports->NumberOfNames == 0 || exports->NumberOfFunctions == 0 ) + { + // DLL doesn't export anything + return NULL; + } + + // search function name in list of exported names + nameRef = (DWORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfNames ); + ordinal = (WORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfNameOrdinals ); + + for( i = 0; i < exports->NumberOfNames; i++, nameRef++, ordinal++ ) + { + // GetProcAddress case insensative ????? + if( !Q_stricmp( name, (const char *)CALCULATE_ADDRESS( codeBase, *nameRef ))) + { + idx = *ordinal; + break; + } + } + + if( idx == -1 ) + { + // exported symbol not found + return NULL; + } + + if((DWORD)idx > exports->NumberOfFunctions ) + { + // name <-> ordinal number don't match + return NULL; + } + + // addressOfFunctions contains the RVAs to the "real" functions + return (FARPROC)CALCULATE_ADDRESS( codeBase, *(DWORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfFunctions + (idx * 4))); +} + +static int BuildImportTable( MEMORYMODULE *module ) +{ + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY( module, IMAGE_DIRECTORY_ENTRY_IMPORT ); + byte *codeBase = module->codeBase; + int result = 1; + + if( directory->Size > 0 ) + { + PIMAGE_IMPORT_DESCRIPTOR importDesc = (PIMAGE_IMPORT_DESCRIPTOR)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); + + for( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR )) && importDesc->Name; importDesc++ ) + { + DWORD *thunkRef, *funcRef; + LPCSTR libname; + void *handle; + + libname = (LPCSTR)CALCULATE_ADDRESS( codeBase, importDesc->Name ); + handle = COM_LoadLibrary( libname, false, true ); + + if( handle == NULL ) + { + Con_Printf( S_ERROR "couldn't load library %s\n", libname ); + result = 0; + break; + } + + module->modules = (void *)Mem_Realloc( host.mempool, module->modules, (module->numModules + 1) * (sizeof( void* ))); + module->modules[module->numModules++] = handle; + + if( importDesc->OriginalFirstThunk ) + { + thunkRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->OriginalFirstThunk ); + funcRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); + } + else + { + // no hint table + thunkRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); + funcRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); + } + + for( ; *thunkRef; thunkRef++, funcRef++ ) + { + LPCSTR funcName; + + if( IMAGE_SNAP_BY_ORDINAL( *thunkRef )) + { + funcName = (LPCSTR)IMAGE_ORDINAL( *thunkRef ); + *funcRef = (DWORD)COM_GetProcAddress( handle, funcName ); + } + else + { + PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME)CALCULATE_ADDRESS( codeBase, *thunkRef ); + funcName = (LPCSTR)&thunkData->Name; + *funcRef = (DWORD)COM_GetProcAddress( handle, funcName ); + } + + if( *funcRef == 0 ) + { + Con_Printf( S_ERROR "%s unable to find address: %s\n", libname, funcName ); + result = 0; + break; + } + } + if( !result ) break; + } + } + return result; +} + +void MemoryFreeLibrary( void *hInstance ) +{ + MEMORYMODULE *module = (MEMORYMODULE *)hInstance; + + if( module != NULL ) + { + int i; + + if( module->initialized != 0 ) + { + // notify library about detaching from process + DllEntryProc DllEntry = (DllEntryProc)CALCULATE_ADDRESS( module->codeBase, module->headers->OptionalHeader.AddressOfEntryPoint ); + (*DllEntry)((HINSTANCE)module->codeBase, DLL_PROCESS_DETACH, 0 ); + module->initialized = 0; + } + + if( module->modules != NULL ) + { + // free previously opened libraries + for( i = 0; i < module->numModules; i++ ) + { + if( module->modules[i] != NULL ) + COM_FreeLibrary( module->modules[i] ); + } + Mem_Free( module->modules ); // Mem_Realloc end + } + + FreeSections( module->headers, module ); + + if( module->codeBase != NULL ) + { + // release memory of library + VirtualFree( module->codeBase, 0, MEM_RELEASE ); + } + + HeapFree( GetProcessHeap(), 0, module ); + } +} + +void *MemoryLoadLibrary( const char *name ) +{ + MEMORYMODULE *result = NULL; + PIMAGE_DOS_HEADER dos_header; + PIMAGE_NT_HEADERS old_header; + byte *code, *headers; + DWORD locationDelta; + DllEntryProc DllEntry; + string errorstring; + qboolean successfull; + void *data = NULL; + + data = FS_LoadFile( name, NULL, false ); + + if( !data ) + { + Q_sprintf( errorstring, "couldn't load %s", name ); + goto library_error; + } + + dos_header = (PIMAGE_DOS_HEADER)data; + if( dos_header->e_magic != IMAGE_DOS_SIGNATURE ) + { + Q_sprintf( errorstring, "%s it's not a valid executable file", name ); + goto library_error; + } + + old_header = (PIMAGE_NT_HEADERS)&((const byte *)(data))[dos_header->e_lfanew]; + if( old_header->Signature != IMAGE_NT_SIGNATURE ) + { + Q_sprintf( errorstring, "%s missing PE header", name ); + goto library_error; + } + + // reserve memory for image of library + code = (byte *)VirtualAlloc((LPVOID)(old_header->OptionalHeader.ImageBase), old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE ); + + if( code == NULL ) + { + // try to allocate memory at arbitrary position + code = (byte *)VirtualAlloc( NULL, old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE ); + } + + if( code == NULL ) + { + Q_sprintf( errorstring, "%s can't reserve memory", name ); + goto library_error; + } + + result = (MEMORYMODULE *)HeapAlloc( GetProcessHeap(), 0, sizeof( MEMORYMODULE )); + result->codeBase = code; + result->numModules = 0; + result->modules = NULL; + result->initialized = 0; + + // XXX: is it correct to commit the complete memory region at once? + // calling DllEntry raises an exception if we don't... + VirtualAlloc( code, old_header->OptionalHeader.SizeOfImage, MEM_COMMIT, PAGE_READWRITE ); + + // commit memory for headers + headers = (byte *)VirtualAlloc( code, old_header->OptionalHeader.SizeOfHeaders, MEM_COMMIT, PAGE_READWRITE ); + + // copy PE header to code + memcpy( headers, dos_header, dos_header->e_lfanew + old_header->OptionalHeader.SizeOfHeaders ); + result->headers = (PIMAGE_NT_HEADERS)&((const byte *)(headers))[dos_header->e_lfanew]; + + // update position + result->headers->OptionalHeader.ImageBase = (DWORD)code; + + // copy sections from DLL file block to new memory location + CopySections( data, old_header, result ); + + // adjust base address of imported data + locationDelta = (DWORD)(code - old_header->OptionalHeader.ImageBase); + if( locationDelta != 0 ) PerformBaseRelocation( result, locationDelta ); + + // load required dlls and adjust function table of imports + if( !BuildImportTable( result )) + { + Q_sprintf( errorstring, "%s failed to build import table", name ); + goto library_error; + } + + // mark memory pages depending on section headers and release + // sections that are marked as "discardable" + FinalizeSections( result ); + + // get entry point of loaded library + if( result->headers->OptionalHeader.AddressOfEntryPoint != 0 ) + { + DllEntry = (DllEntryProc)CALCULATE_ADDRESS( code, result->headers->OptionalHeader.AddressOfEntryPoint ); + if( DllEntry == 0 ) + { + Q_sprintf( errorstring, "%s has no entry point", name ); + goto library_error; + } + + // notify library about attaching to process + successfull = (*DllEntry)((HINSTANCE)code, DLL_PROCESS_ATTACH, 0 ); + if( !successfull ) + { + Q_sprintf( errorstring, "can't attach library %s", name ); + goto library_error; + } + result->initialized = 1; + } + + Mem_Free( data ); // release memory + return (void *)result; +library_error: + // cleanup + if( data ) Mem_Free( data ); + MemoryFreeLibrary( result ); + Con_Printf( S_ERROR "LoadLibrary: %s\n", errorstring ); + + return NULL; +} + +#endif // XASH_LIB == LIB_WIN32 && XASH_X86 \ No newline at end of file diff --git a/engine/platform/win32/lib_win.c b/engine/platform/win32/lib_win.c index b5ee3e76..21ee1fab 100644 --- a/engine/platform/win32/lib_win.c +++ b/engine/platform/win32/lib_win.c @@ -1,5 +1,5 @@ /* -library.c - custom dlls loader +lib_win.c - win32 dynamic library loading Copyright (C) 2008 Uncle Mike This program is free software: you can redistribute it and/or modify @@ -12,481 +12,11 @@ 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. */ -#include "platform/platform.h" -#if XASH_LIB == LIB_WIN32 + #include "common.h" -#include "library.h" -#include -#define CALCULATE_ADDRESS( base, offset ) ( ( DWORD )( base ) + ( DWORD )( offset ) ) - -#if XASH_X86 -/* ---------------------------------------------------------------- - - Custom dlls loader - ---------------------------------------------------------------- -*/ - -#define NUMBER_OF_DIRECTORY_ENTRIES 16 -#ifndef IMAGE_SIZEOF_BASE_RELOCATION -#define IMAGE_SIZEOF_BASE_RELOCATION ( sizeof( IMAGE_BASE_RELOCATION )) -#endif - -typedef struct -{ - PIMAGE_NT_HEADERS headers; - byte *codeBase; - void **modules; - int numModules; - int initialized; -} MEMORYMODULE, *PMEMORYMODULE; - -// Protection flags for memory pages (Executable, Readable, Writeable) -static int ProtectionFlags[2][2][2] = -{ -{ -{ PAGE_NOACCESS, PAGE_WRITECOPY }, // not executable -{ PAGE_READONLY, PAGE_READWRITE }, -}, -{ -{ PAGE_EXECUTE, PAGE_EXECUTE_WRITECOPY }, // executable -{ PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE }, -}, -}; - -typedef BOOL (WINAPI *DllEntryProc)( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved ); - -#define GET_HEADER_DICTIONARY( module, idx ) &(module)->headers->OptionalHeader.DataDirectory[idx] - -static void CopySections( const byte *data, PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module ) -{ - PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION( module->headers ); - byte *codeBase = module->codeBase; - int i, size; - byte *dest; - - for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) - { - if( section->SizeOfRawData == 0 ) - { - // section doesn't contain data in the dll itself, but may define - // uninitialized data - size = old_headers->OptionalHeader.SectionAlignment; - - if( size > 0 ) - { - dest = (byte *)VirtualAlloc((byte *)CALCULATE_ADDRESS(codeBase, section->VirtualAddress), size, MEM_COMMIT, PAGE_READWRITE ); - section->Misc.PhysicalAddress = (DWORD)dest; - memset( dest, 0, size ); - } - // section is empty - continue; - } - - // commit memory block and copy data from dll - dest = (byte *)VirtualAlloc((byte *)CALCULATE_ADDRESS(codeBase, section->VirtualAddress), section->SizeOfRawData, MEM_COMMIT, PAGE_READWRITE ); - memcpy( dest, (byte *)CALCULATE_ADDRESS(data, section->PointerToRawData), section->SizeOfRawData ); - section->Misc.PhysicalAddress = (DWORD)dest; - } -} - -static void FreeSections( PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module ) -{ - PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers); - byte *codeBase = module->codeBase; - int i, size; - - for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) - { - if( section->SizeOfRawData == 0 ) - { - size = old_headers->OptionalHeader.SectionAlignment; - if( size > 0 ) - { - VirtualFree((byte *)CALCULATE_ADDRESS( codeBase, section->VirtualAddress ), size, MEM_DECOMMIT ); - section->Misc.PhysicalAddress = 0; - } - continue; - } - - VirtualFree((byte *)CALCULATE_ADDRESS( codeBase, section->VirtualAddress ), section->SizeOfRawData, MEM_DECOMMIT ); - section->Misc.PhysicalAddress = 0; - } -} - -static void FinalizeSections( MEMORYMODULE *module ) -{ - PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION( module->headers ); - int i; - - // loop through all sections and change access flags - for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) - { - DWORD protect, oldProtect, size; - int executable = (section->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; - int readable = (section->Characteristics & IMAGE_SCN_MEM_READ) != 0; - int writeable = (section->Characteristics & IMAGE_SCN_MEM_WRITE) != 0; - - if( section->Characteristics & IMAGE_SCN_MEM_DISCARDABLE ) - { - // section is not needed any more and can safely be freed - VirtualFree((LPVOID)section->Misc.PhysicalAddress, section->SizeOfRawData, MEM_DECOMMIT); - continue; - } - - // determine protection flags based on characteristics - protect = ProtectionFlags[executable][readable][writeable]; - if( section->Characteristics & IMAGE_SCN_MEM_NOT_CACHED ) - protect |= PAGE_NOCACHE; - - // determine size of region - size = section->SizeOfRawData; - - if( size == 0 ) - { - if( section->Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA ) - size = module->headers->OptionalHeader.SizeOfInitializedData; - else if( section->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA ) - size = module->headers->OptionalHeader.SizeOfUninitializedData; - } - - if( size > 0 ) - { - // change memory access flags - if( !VirtualProtect((LPVOID)section->Misc.PhysicalAddress, size, protect, &oldProtect )) - Sys_Error( "error protecting memory page\n" ); - } - } -} - -static void PerformBaseRelocation( MEMORYMODULE *module, DWORD delta ) -{ - PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY( module, IMAGE_DIRECTORY_ENTRY_BASERELOC ); - byte *codeBase = module->codeBase; - DWORD i; - - if( directory->Size > 0 ) - { - PIMAGE_BASE_RELOCATION relocation = (PIMAGE_BASE_RELOCATION)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); - for( ; relocation->VirtualAddress > 0; ) - { - byte *dest = (byte *)CALCULATE_ADDRESS( codeBase, relocation->VirtualAddress ); - word *relInfo = (word *)((byte *)relocation + IMAGE_SIZEOF_BASE_RELOCATION ); - - for( i = 0; i<((relocation->SizeOfBlock-IMAGE_SIZEOF_BASE_RELOCATION) / 2); i++, relInfo++ ) - { - DWORD *patchAddrHL; - int type, offset; - - // the upper 4 bits define the type of relocation - type = *relInfo >> 12; - // the lower 12 bits define the offset - offset = *relInfo & 0xfff; - - switch( type ) - { - case IMAGE_REL_BASED_ABSOLUTE: - // skip relocation - break; - case IMAGE_REL_BASED_HIGHLOW: - // change complete 32 bit address - patchAddrHL = (DWORD *)CALCULATE_ADDRESS( dest, offset ); - *patchAddrHL += delta; - break; - default: - Con_Reportf( S_ERROR "PerformBaseRelocation: unknown relocation: %d\n", type ); - break; - } - } - - // advance to next relocation block - relocation = (PIMAGE_BASE_RELOCATION)CALCULATE_ADDRESS( relocation, relocation->SizeOfBlock ); - } - } -} - -static FARPROC MemoryGetProcAddress( void *module, const char *name ) -{ - PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY((MEMORYMODULE *)module, IMAGE_DIRECTORY_ENTRY_EXPORT ); - byte *codeBase = ((PMEMORYMODULE)module)->codeBase; - PIMAGE_EXPORT_DIRECTORY exports; - int idx = -1; - DWORD i, *nameRef; - WORD *ordinal; - - if( directory->Size == 0 ) - { - // no export table found - return NULL; - } - - exports = (PIMAGE_EXPORT_DIRECTORY)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); - - if( exports->NumberOfNames == 0 || exports->NumberOfFunctions == 0 ) - { - // DLL doesn't export anything - return NULL; - } - - // search function name in list of exported names - nameRef = (DWORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfNames ); - ordinal = (WORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfNameOrdinals ); - - for( i = 0; i < exports->NumberOfNames; i++, nameRef++, ordinal++ ) - { - // GetProcAddress case insensative ????? - if( !Q_stricmp( name, (const char *)CALCULATE_ADDRESS( codeBase, *nameRef ))) - { - idx = *ordinal; - break; - } - } - - if( idx == -1 ) - { - // exported symbol not found - return NULL; - } - - if((DWORD)idx > exports->NumberOfFunctions ) - { - // name <-> ordinal number don't match - return NULL; - } - - // addressOfFunctions contains the RVAs to the "real" functions - return (FARPROC)CALCULATE_ADDRESS( codeBase, *(DWORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfFunctions + (idx * 4))); -} - -static int BuildImportTable( MEMORYMODULE *module ) -{ - PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY( module, IMAGE_DIRECTORY_ENTRY_IMPORT ); - byte *codeBase = module->codeBase; - int result = 1; - - if( directory->Size > 0 ) - { - PIMAGE_IMPORT_DESCRIPTOR importDesc = (PIMAGE_IMPORT_DESCRIPTOR)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); - - for( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR )) && importDesc->Name; importDesc++ ) - { - DWORD *thunkRef, *funcRef; - LPCSTR libname; - void *handle; - - libname = (LPCSTR)CALCULATE_ADDRESS( codeBase, importDesc->Name ); - handle = COM_LoadLibrary( libname, false, true ); - - if( handle == NULL ) - { - Con_Printf( S_ERROR "couldn't load library %s\n", libname ); - result = 0; - break; - } - - module->modules = (void *)Mem_Realloc( host.mempool, module->modules, (module->numModules + 1) * (sizeof( void* ))); - module->modules[module->numModules++] = handle; - - if( importDesc->OriginalFirstThunk ) - { - thunkRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->OriginalFirstThunk ); - funcRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); - } - else - { - // no hint table - thunkRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); - funcRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); - } - - for( ; *thunkRef; thunkRef++, funcRef++ ) - { - LPCSTR funcName; - - if( IMAGE_SNAP_BY_ORDINAL( *thunkRef )) - { - funcName = (LPCSTR)IMAGE_ORDINAL( *thunkRef ); - *funcRef = (DWORD)COM_GetProcAddress( handle, funcName ); - } - else - { - PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME)CALCULATE_ADDRESS( codeBase, *thunkRef ); - funcName = (LPCSTR)&thunkData->Name; - *funcRef = (DWORD)COM_GetProcAddress( handle, funcName ); - } - - if( *funcRef == 0 ) - { - Con_Printf( S_ERROR "%s unable to find address: %s\n", libname, funcName ); - result = 0; - break; - } - } - if( !result ) break; - } - } - return result; -} - -static void MemoryFreeLibrary( void *hInstance ) -{ - MEMORYMODULE *module = (MEMORYMODULE *)hInstance; - - if( module != NULL ) - { - int i; - - if( module->initialized != 0 ) - { - // notify library about detaching from process - DllEntryProc DllEntry = (DllEntryProc)CALCULATE_ADDRESS( module->codeBase, module->headers->OptionalHeader.AddressOfEntryPoint ); - (*DllEntry)((HINSTANCE)module->codeBase, DLL_PROCESS_DETACH, 0 ); - module->initialized = 0; - } - - if( module->modules != NULL ) - { - // free previously opened libraries - for( i = 0; i < module->numModules; i++ ) - { - if( module->modules[i] != NULL ) - COM_FreeLibrary( module->modules[i] ); - } - Mem_Free( module->modules ); // Mem_Realloc end - } - - FreeSections( module->headers, module ); - - if( module->codeBase != NULL ) - { - // release memory of library - VirtualFree( module->codeBase, 0, MEM_RELEASE ); - } - - HeapFree( GetProcessHeap(), 0, module ); - } -} - -void *MemoryLoadLibrary( const char *name ) -{ - MEMORYMODULE *result = NULL; - PIMAGE_DOS_HEADER dos_header; - PIMAGE_NT_HEADERS old_header; - byte *code, *headers; - DWORD locationDelta; - DllEntryProc DllEntry; - string errorstring; - qboolean successfull; - void *data = NULL; - - data = FS_LoadFile( name, NULL, false ); - - if( !data ) - { - Q_sprintf( errorstring, "couldn't load %s", name ); - goto library_error; - } - - dos_header = (PIMAGE_DOS_HEADER)data; - if( dos_header->e_magic != IMAGE_DOS_SIGNATURE ) - { - Q_sprintf( errorstring, "%s it's not a valid executable file", name ); - goto library_error; - } - - old_header = (PIMAGE_NT_HEADERS)&((const byte *)(data))[dos_header->e_lfanew]; - if( old_header->Signature != IMAGE_NT_SIGNATURE ) - { - Q_sprintf( errorstring, "%s missing PE header", name ); - goto library_error; - } - - // reserve memory for image of library - code = (byte *)VirtualAlloc((LPVOID)(old_header->OptionalHeader.ImageBase), old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE ); - - if( code == NULL ) - { - // try to allocate memory at arbitrary position - code = (byte *)VirtualAlloc( NULL, old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE ); - } - - if( code == NULL ) - { - Q_sprintf( errorstring, "%s can't reserve memory", name ); - goto library_error; - } - - result = (MEMORYMODULE *)HeapAlloc( GetProcessHeap(), 0, sizeof( MEMORYMODULE )); - result->codeBase = code; - result->numModules = 0; - result->modules = NULL; - result->initialized = 0; - - // XXX: is it correct to commit the complete memory region at once? - // calling DllEntry raises an exception if we don't... - VirtualAlloc( code, old_header->OptionalHeader.SizeOfImage, MEM_COMMIT, PAGE_READWRITE ); - - // commit memory for headers - headers = (byte *)VirtualAlloc( code, old_header->OptionalHeader.SizeOfHeaders, MEM_COMMIT, PAGE_READWRITE ); - - // copy PE header to code - memcpy( headers, dos_header, dos_header->e_lfanew + old_header->OptionalHeader.SizeOfHeaders ); - result->headers = (PIMAGE_NT_HEADERS)&((const byte *)(headers))[dos_header->e_lfanew]; - - // update position - result->headers->OptionalHeader.ImageBase = (DWORD)code; - - // copy sections from DLL file block to new memory location - CopySections( data, old_header, result ); - - // adjust base address of imported data - locationDelta = (DWORD)(code - old_header->OptionalHeader.ImageBase); - if( locationDelta != 0 ) PerformBaseRelocation( result, locationDelta ); - - // load required dlls and adjust function table of imports - if( !BuildImportTable( result )) - { - Q_sprintf( errorstring, "%s failed to build import table", name ); - goto library_error; - } - - // mark memory pages depending on section headers and release - // sections that are marked as "discardable" - FinalizeSections( result ); - - // get entry point of loaded library - if( result->headers->OptionalHeader.AddressOfEntryPoint != 0 ) - { - DllEntry = (DllEntryProc)CALCULATE_ADDRESS( code, result->headers->OptionalHeader.AddressOfEntryPoint ); - if( DllEntry == 0 ) - { - Q_sprintf( errorstring, "%s has no entry point", name ); - goto library_error; - } - - // notify library about attaching to process - successfull = (*DllEntry)((HINSTANCE)code, DLL_PROCESS_ATTACH, 0 ); - if( !successfull ) - { - Q_sprintf( errorstring, "can't attach library %s", name ); - goto library_error; - } - result->initialized = 1; - } - - Mem_Free( data ); // release memory - return (void *)result; -library_error: - // cleanup - if( data ) Mem_Free( data ); - MemoryFreeLibrary( result ); - Con_Printf( S_ERROR "LoadLibrary: %s\n", errorstring ); - - return NULL; -} -#endif +#if XASH_LIB == LIB_WIN32 +#include "lib_win.h" static DWORD GetOffsetByRVA( DWORD rva, PIMAGE_NT_HEADERS nt_header ) { @@ -764,70 +294,136 @@ table_error: return false; } -qboolean COM_CheckLibraryDirectDependency( const char *name, const char *depname, qboolean directpath ) +static const char *GetLastErrorAsString( void ) { - PIMAGE_DOS_HEADER dosHeader; - PIMAGE_NT_HEADERS peHeader; - PIMAGE_DATA_DIRECTORY importDir; + DWORD errorcode; + static string errormessage; + + errorcode = GetLastError(); + if ( !errorcode ) return ""; + + FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK, + NULL, errorcode, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), + (LPSTR)&errormessage, sizeof( errormessage ), NULL ); + + return errormessage; +} + +static PIMAGE_IMPORT_DESCRIPTOR GetImportDescriptor( const char *name, byte *data, PIMAGE_NT_HEADERS *peheader ) +{ + PIMAGE_DOS_HEADER dosHeader; + PIMAGE_NT_HEADERS peHeader; + PIMAGE_DATA_DIRECTORY importDir; PIMAGE_IMPORT_DESCRIPTOR importDesc; - string errorstring = ""; - byte *data = NULL; - dll_user_t *hInst; - hInst = FS_FindLibrary( name, directpath ); - if( !hInst ) + if ( !data ) { - return false; // nothing to load + Con_Printf( S_ERROR "%s: couldn't load %s\n", __FUNCTION__, name ); + return NULL; } - data = FS_LoadFile( name, NULL, false ); - if( !data ) + dosHeader = (PIMAGE_DOS_HEADER)data; + if ( dosHeader->e_magic != IMAGE_DOS_SIGNATURE ) { - Q_snprintf( errorstring, sizeof( errorstring ), "couldn't load %s", name ); - goto libraryerror; + Con_Printf( S_ERROR "%s: %s is not a valid executable file\n", __FUNCTION__, name ); + return NULL; } - dosHeader = ( PIMAGE_DOS_HEADER )data; - if( dosHeader->e_magic != IMAGE_DOS_SIGNATURE ) + peHeader = (PIMAGE_NT_HEADERS)( data + dosHeader->e_lfanew ); + if ( peHeader->Signature != IMAGE_NT_SIGNATURE ) { - Q_snprintf( errorstring, sizeof( errorstring ), "%s it's not a valid executable file", name ); - goto libraryerror; - } - - peHeader = ( PIMAGE_NT_HEADERS )(data + dosHeader->e_lfanew); - if( peHeader->Signature != IMAGE_NT_SIGNATURE ) - { - Q_snprintf( errorstring, sizeof( errorstring ), "%s missing PE header", name ); - goto libraryerror; + Con_Printf( S_ERROR "%s: %s is missing a PE header\n", __FUNCTION__, name ); + return NULL; } importDir = &peHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; if( importDir->Size <= 0 ) { - Con_Printf( S_WARN "%s: %s has no dependencies. Is this library valid?\n", __FUNCTION__, name ); - goto libraryerror; + Con_Printf( S_ERROR "%s: %s has no dependencies\n", __FUNCTION__, name ); + return NULL; } - importDesc = (PIMAGE_IMPORT_DESCRIPTOR)CALCULATE_ADDRESS( data, GetOffsetByRVA(importDir->VirtualAddress, peHeader) ); - for( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR)) && importDesc->Name; importDesc++ ) + *peheader = peHeader; + importDesc = (PIMAGE_IMPORT_DESCRIPTOR)CALCULATE_ADDRESS( data, GetOffsetByRVA( importDir->VirtualAddress, peHeader ) ); + + return importDesc; +} + +static void ListMissingModules( dll_user_t *hInst ) +{ + PIMAGE_NT_HEADERS peHeader; + PIMAGE_IMPORT_DESCRIPTOR importDesc; + byte *data; + + if ( !hInst ) return; + + data = FS_LoadFile( hInst->dllName, NULL, false ); + if ( !data ) return; + + importDesc = GetImportDescriptor( hInst->dllName, data, &peHeader ); + if ( !importDesc ) + { + Mem_Free( data ); + return; + } + + for( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR ) ) && importDesc->Name; importDesc++ ) + { + HMODULE hMod; + const char *importName = (const char *)CALCULATE_ADDRESS( data, GetOffsetByRVA( importDesc->Name, peHeader ) ); + + hMod = LoadLibraryEx( importName, NULL, LOAD_LIBRARY_AS_DATAFILE ); + if ( !hMod ) + COM_PushLibraryError( va( "%s not found!", importName ) ); + else + FreeLibrary( hMod ); + } + + Mem_Free( data ); + return; +} + +qboolean COM_CheckLibraryDirectDependency( const char *name, const char *depname, qboolean directpath ) +{ + PIMAGE_NT_HEADERS peHeader; + PIMAGE_IMPORT_DESCRIPTOR importDesc; + byte *data; + dll_user_t *hInst; + qboolean ret = FALSE; + + hInst = FS_FindLibrary( name, directpath ); + if ( !hInst ) return FALSE; + + data = FS_LoadFile( name, NULL, false ); + if ( !data ) + { + COM_FreeLibrary( hInst ); + return FALSE; + } + + importDesc = GetImportDescriptor( name, data, &peHeader ); + if ( !importDesc ) + { + COM_FreeLibrary( hInst ); + Mem_Free( data ); + return FALSE; + } + + for( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR ) ) && importDesc->Name; importDesc++ ) { const char *importName = (const char *)CALCULATE_ADDRESS( data, GetOffsetByRVA( importDesc->Name, peHeader ) ); - Con_Reportf( "library %s has direct dependency %s\n", name, importName ); - if( !Q_stricmp( importName, depname ) ) + if ( !Q_stricmp( importName, depname ) ) { + COM_FreeLibrary( hInst ); Mem_Free( data ); - return true; + return TRUE; } } -libraryerror: - if( errorstring[0] ) - { - Con_Printf( S_ERROR "%s: %s\n", __FUNCTION__, errorstring ); - } - if( data ) Mem_Free( data ); // release memory - return false; + COM_FreeLibrary( hInst ); + Mem_Free( data ); + return FALSE; } /* @@ -841,12 +437,19 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d { dll_user_t *hInst; + COM_ResetLibraryError(); + hInst = FS_FindLibrary( dllname, directpath ); - if( !hInst ) return NULL; // nothing to load + if( !hInst ) + { + COM_PushLibraryError( va( "Failed to find library %s", dllname ) ); + return NULL; + } if( hInst->encrypted ) { - Con_Printf( S_ERROR "LoadLibrary: couldn't load encrypted library %s\n", dllname ); + COM_PushLibraryError( va( "Library %s is encrypted, cannot load", hInst->shortPath ) ); + COM_FreeLibrary( hInst ); return NULL; } @@ -863,7 +466,11 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d if( !hInst->hInstance ) { - Con_Reportf( "LoadLibrary: Loading %s - failed\n", dllname ); + COM_PushLibraryError( GetLastErrorAsString() ); + + if ( GetLastError() == ERROR_MOD_NOT_FOUND ) + ListMissingModules( hInst ); + COM_FreeLibrary( hInst ); return NULL; } @@ -873,14 +480,12 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d { if( !LibraryLoadSymbols( hInst )) { - Con_Reportf( "LoadLibrary: Loading %s - failed\n", dllname ); + COM_PushLibraryError( va( "Failed to load library %s", dllname ) ); COM_FreeLibrary( hInst ); return NULL; } } - Con_Reportf( "LoadLibrary: Loading %s - ok\n", dllname ); - return hInst; } @@ -944,7 +549,7 @@ void *COM_FunctionFromName( void *hInstance, const char *pName ) if( !Q_strcmp( pName, hInst->names[i] )) { index = hInst->ordinals[i]; - return hInst->funcs[index] + hInst->funcBase; + return (void *)( hInst->funcs[index] + hInst->funcBase ); } } diff --git a/engine/platform/win32/lib_win.h b/engine/platform/win32/lib_win.h new file mode 100644 index 00000000..429dd4f8 --- /dev/null +++ b/engine/platform/win32/lib_win.h @@ -0,0 +1,24 @@ +/* +lib_win.h - common win32 dll definitions +Copyright (C) 2022 Flying With Gauss + +This program 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, either version 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "library.h" +#include +#include + +#define CALCULATE_ADDRESS( base, offset ) ( ( DWORD )( base ) + ( DWORD )( offset ) ) + +FARPROC MemoryGetProcAddress( void *module, const char *name ); +void MemoryFreeLibrary( void *hInstance ); +void *MemoryLoadLibrary( const char *name ); \ No newline at end of file diff --git a/engine/platform/win32/sys_win.c b/engine/platform/win32/sys_win.c index 78132f0a..8af51fd2 100644 --- a/engine/platform/win32/sys_win.c +++ b/engine/platform/win32/sys_win.c @@ -15,6 +15,7 @@ GNU General Public License for more details. #include "platform/platform.h" #include "menu_int.h" +#include "server.h" #include #if XASH_TIMER == TIMER_WIN32 @@ -53,6 +54,27 @@ void Platform_ShellExecute( const char *path, const char *parms ) ShellExecute( NULL, "open", path, parms, NULL, SW_SHOW ); } +void Platform_UpdateStatusLine( void ) +{ + int clientsCount; + char szStatus[128]; + static double lastTime; + + if( host.type != HOST_DEDICATED ) + return; + + // update only every 1/2 seconds + if(( sv.time - lastTime ) < 0.5f ) + return; + + clientsCount = SV_GetConnectedClientsCount( NULL ); + Q_snprintf( szStatus, sizeof( szStatus ) - 1, "%.1f fps %2i/%2i on %16s", 1.f / sv.frametime, clientsCount, svs.maxclients, host.game.levelName ); +#ifdef XASH_WIN32 + Wcon_SetStatus( szStatus ); +#endif + lastTime = sv.time; +} + #if XASH_MESSAGEBOX == MSGBOX_WIN32 void Platform_MessageBox( const char *title, const char *message, qboolean parentMainWindow ) { @@ -71,4 +93,4 @@ void Platform_Shutdown( void ) { Wcon_DestroyConsole(); } -#endif \ No newline at end of file +#endif diff --git a/engine/ref_api.h b/engine/ref_api.h index d17ab5a8..6b53c686 100644 --- a/engine/ref_api.h +++ b/engine/ref_api.h @@ -27,10 +27,14 @@ GNU General Public License for more details. #include "studio.h" #include "r_efx.h" #include "com_image.h" +#include "filesystem.h" #include "ref_vulkan.h" #include "ref_device.h" -#define REF_API_VERSION 1 +// RefAPI changelog: +// 1. Initial release +// 2. FS functions are removed, instead we have full fs_api_t +#define REF_API_VERSION 2 #define TF_SKY (TF_SKYSIDE|TF_NOMIPMAP) @@ -280,13 +284,13 @@ typedef struct ref_api_s void (*Cbuf_Execute)( void ); // logging - void (*Con_Printf)( const char *fmt, ... ); // typical console allowed messages - void (*Con_DPrintf)( const char *fmt, ... ); // -dev 1 - void (*Con_Reportf)( const char *fmt, ... ); // -dev 2 + void (*Con_Printf)( const char *fmt, ... ) _format( 1 ); // typical console allowed messages + void (*Con_DPrintf)( const char *fmt, ... ) _format( 1 ); // -dev 1 + void (*Con_Reportf)( const char *fmt, ... ) _format( 1 ); // -dev 2 // debug print - void (*Con_NPrintf)( int pos, const char *fmt, ... ); - void (*Con_NXPrintf)( struct con_nprint_s *info, const char *fmt, ... ); + void (*Con_NPrintf)( int pos, const char *fmt, ... ) _format( 2 ); + void (*Con_NXPrintf)( struct con_nprint_s *info, const char *fmt, ... ) _format( 2 ); void (*CL_CenterPrint)( const char *s, float y ); void (*Con_DrawStringLen)( const char *pText, int *length, int *height ); int (*Con_DrawString)( int x, int y, const char *string, rgba_t setColor ); @@ -338,7 +342,7 @@ typedef struct ref_api_s // utils void (*CL_ExtraUpdate)( void ); - void (*Host_Error)( const char *fmt, ... ); + void (*Host_Error)( const char *fmt, ... ) _format( 1 ); void (*COM_SetRandomSeed)( int lSeed ); float (*COM_RandomFloat)( float rmin, float rmax ); int (*COM_RandomLong)( int rmin, int rmax ); @@ -370,13 +374,6 @@ typedef struct ref_api_s void (*COM_FreeLibrary)( void *handle ); void *(*COM_GetProcAddress)( void *handle, const char *name ); - // filesystem - byte* (*COM_LoadFile)( const char *path, fs_offset_t *pLength, qboolean gamedironly ); - // use Mem_Free instead - // void (*COM_FreeFile)( void *buffer ); - int (*FS_FileExists)( const char *filename, int gamedironly ); - void (*FS_AllowDirectPaths)( qboolean enable ); - // video init // try to create window // will call GL_SetupAttributes in case of REF_GL @@ -434,6 +431,9 @@ typedef struct ref_api_s void (*pfnDrawTransparentTriangles)( void ); render_interface_t *drawFuncs; + // filesystem exports + fs_api_t *fsapi; + int (*XVK_GetInstanceExtensions)( unsigned int count, const char **pNames ); void *(*XVK_GetVkGetInstanceProcAddr)( void ); VkSurfaceKHR (*XVK_CreateSurface)( VkInstance instance ); diff --git a/engine/server/server.h b/engine/server/server.h index be144a9b..4775397f 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -325,6 +325,7 @@ typedef struct qboolean msg_started; // to avoid recursive included messages edict_t *msg_ent; // user message member entity vec3_t msg_org; // user message member origin + qboolean msg_trace; // trace this message void *hInstance; // pointer to game.dll qboolean config_executed; // should to execute config.cfg once time to restore FCVAR_ARCHIVE that specified in hl.dll @@ -431,6 +432,7 @@ extern convar_t sv_skyvec_z; extern convar_t sv_consistency; extern convar_t sv_password; extern convar_t sv_uploadmax; +extern convar_t sv_trace_messages; extern convar_t deathmatch; extern convar_t hostname; extern convar_t skill; @@ -468,6 +470,7 @@ void SV_SendResource( resource_t *pResource, sizebuf_t *msg ); void SV_SendResourceList( sv_client_t *cl ); void SV_AddToMaster( netadr_t from, sizebuf_t *msg ); qboolean SV_ProcessUserAgent( netadr_t from, const char *useragent ); +int SV_GetConnectedClientsCount( int *bots ); void Host_SetServerState( int state ); qboolean SV_IsSimulating( void ); void SV_FreeClients( void ); @@ -625,7 +628,6 @@ void SV_StartSound( edict_t *ent, int chan, const char *sample, float vol, float edict_t *SV_FindGlobalEntity( string_t classname, string_t globalname ); qboolean SV_CreateStaticEntity( struct sizebuf_s *msg, int index ); void SV_SendUserReg( sizebuf_t *msg, sv_user_message_t *user ); -edict_t* pfnPEntityOfEntIndex( int iEntIndex ); int pfnIndexOfEdict( const edict_t *pEdict ); void pfnWriteBytes( const byte *bytes, int count ); void SV_UpdateBaseVelocity( edict_t *ent ); diff --git a/engine/server/sv_client.c b/engine/server/sv_client.c index 64ebfbe4..97fd122f 100644 --- a/engine/server/sv_client.c +++ b/engine/server/sv_client.c @@ -733,7 +733,8 @@ sv_client_t *SV_ClientByName( const char *name ) sv_client_t *cl; int i; - ASSERT( name && *name ); + if( !COM_CheckString( name )) + return NULL; for( i = 0, cl = svs.clients; i < svgame.globals->maxClients; i++, cl++ ) { @@ -843,6 +844,8 @@ void SV_Info( netadr_t from ) if( svs.clients[i].state >= cs_connected ) count++; + // a1ba: send protocol version to distinguish old engine and new + Info_SetValueForKey( string, "p", va( "%i", PROTOCOL_VERSION ), MAX_INFO_STRING ); Info_SetValueForKey( string, "host", hostname.string, MAX_INFO_STRING ); Info_SetValueForKey( string, "map", sv.name, MAX_INFO_STRING ); Info_SetValueForKey( string, "dm", va( "%i", (int)svgame.globals->deathmatch ), MAX_INFO_STRING ); @@ -2263,6 +2266,17 @@ void SV_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) else if( !Q_strcmp( pcmd, "s" )) SV_AddToMaster( from, msg ); else if( !Q_strcmp( pcmd, "T" "Source" )) SV_TSourceEngineQuery( from ); else if( !Q_strcmp( pcmd, "i" )) NET_SendPacket( NS_SERVER, 5, "\xFF\xFF\xFF\xFFj", from ); // A2A_PING + else if (!Q_strcmp( pcmd, "c" )) + { + qboolean sv_nat = Cvar_VariableInteger( "sv_nat" ); + if( sv_nat ) + { + netadr_t to; + + if( NET_StringToAdr( Cmd_Argv( 1 ), &to ) && !NET_IsReservedAdr( to )) + SV_Info( to ); + } + } else if( svgame.dllFuncs.pfnConnectionlessPacket( &from, args, buf, &len )) { // user out of band message (must be handled in CL_ConnectionlessPacket) diff --git a/engine/server/sv_cmds.c b/engine/server/sv_cmds.c index 4d216f0e..571c4ffc 100644 --- a/engine/server/sv_cmds.c +++ b/engine/server/sv_cmds.c @@ -265,7 +265,7 @@ void SV_Maps_f( void ) Mem_Free( mapList ); - Msg( "%s\nDirectory: \"%s/maps\" - Maps listed: %d\n", separator, GI->basedir, nummaps ); + Msg( "%s\nDirectory: \"%s/maps\" - Maps listed: %d\n", separator, GI->gamefolder, nummaps ); } /* @@ -763,7 +763,7 @@ void SV_ServerInfo_f( void ) { Con_Printf( "Server info settings:\n" ); Info_Print( svs.serverinfo ); - Con_Printf( "Total %i symbols\n", Q_strlen( svs.serverinfo )); + Con_Printf( "Total %lu symbols\n", Q_strlen( svs.serverinfo )); return; } @@ -805,7 +805,7 @@ void SV_LocalInfo_f( void ) { Con_Printf( "Local info settings:\n" ); Info_Print( svs.localinfo ); - Con_Printf( "Total %i symbols\n", Q_strlen( svs.localinfo )); + Con_Printf( "Total %lu symbols\n", Q_strlen( svs.localinfo )); return; } diff --git a/engine/server/sv_custom.c b/engine/server/sv_custom.c index 112d74e1..ed65820e 100644 --- a/engine/server/sv_custom.c +++ b/engine/server/sv_custom.c @@ -533,7 +533,7 @@ void SV_BatchUploadRequest( sv_client_t *cl ) void SV_SendResource( resource_t *pResource, sizebuf_t *msg ) { - static byte nullrguc[36]; + static byte nullrguc[sizeof( pResource->rguc_reserved )]; MSG_WriteUBitLong( msg, pResource->type, 4 ); MSG_WriteString( msg, pResource->szFileName ); diff --git a/engine/server/sv_filter.c b/engine/server/sv_filter.c index b36908b2..fa7164a0 100644 --- a/engine/server/sv_filter.c +++ b/engine/server/sv_filter.c @@ -101,7 +101,7 @@ qboolean SV_CheckID( const char *id ) for( filter = cidfilter; filter; filter = filter->next ) { int len1 = Q_strlen( id ), len2 = Q_strlen( filter->id ); - int len = min( len1, len2 ); + int len = Q_min( len1, len2 ); while( filter->endTime && host.realtime > filter->endTime ) { diff --git a/engine/server/sv_game.c b/engine/server/sv_game.c index 494f1262..f3fe6e6e 100644 --- a/engine/server/sv_game.c +++ b/engine/server/sv_game.c @@ -60,6 +60,28 @@ qboolean SV_CheckEdict( const edict_t *e, const char *file, const int line ) } #endif +static edict_t *SV_PEntityOfEntIndex( const int iEntIndex, const qboolean allentities ) +{ + if( iEntIndex >= 0 && iEntIndex < GI->max_edicts ) + { + edict_t *pEdict = EDICT_NUM( iEntIndex ); + qboolean player = allentities ? iEntIndex <= svs.maxclients : iEntIndex < svs.maxclients; + + if( !iEntIndex || FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + return pEdict; // just get access to array + + if( SV_IsValidEdict( pEdict ) && pEdict->pvPrivateData ) + return pEdict; + + // g-cont: world and clients can be accessed even without private data + if( SV_IsValidEdict( pEdict ) && player ) + return pEdict; + } + + return NULL; +} + + /* ============= EntvarsDescription @@ -93,7 +115,7 @@ entavrs table for FindEntityByString */ TYPEDESCRIPTION *SV_GetEntvarsDescirption( int number ) { - if( number < 0 && number >= ENTVARS_COUNT ) + if( number < 0 || number >= ENTVARS_COUNT ) return NULL; return &gEntvarsDescription[number]; } @@ -586,7 +608,7 @@ void SV_RestartAmbientSounds( void ) continue; S_StopSound( si->entnum, si->channel, si->name ); - SV_StartSound( pfnPEntityOfEntIndex( si->entnum ), CHAN_STATIC, si->name, si->volume, si->attenuation, 0, si->pitch ); + SV_StartSound( SV_PEntityOfEntIndex( si->entnum, true ), CHAN_STATIC, si->name, si->volume, si->attenuation, 0, si->pitch ); } #if !XASH_DEDICATED // TODO: ??? @@ -640,10 +662,10 @@ void SV_RestartDecals( void ) for( i = 0; i < host.numdecals; i++ ) { entry = &host.decalList[i]; - modelIndex = pfnPEntityOfEntIndex( entry->entityIndex )->v.modelindex; + modelIndex = SV_PEntityOfEntIndex( entry->entityIndex, true )->v.modelindex; // game override - if( SV_RestoreCustomDecal( entry, pfnPEntityOfEntIndex( entry->entityIndex ), false )) + if( SV_RestoreCustomDecal( entry, SV_PEntityOfEntIndex( entry->entityIndex, true ), false )) continue; decalIndex = pfnDecalIndex( entry->name ); @@ -2608,6 +2630,13 @@ void GAME_EXPORT pfnMessageBegin( int msg_dest, int msg_num, const float *pOrigi svgame.msg_realsize = 0; svgame.msg_dest = msg_dest; svgame.msg_ent = ed; + + // enable message tracing + svgame.msg_trace = sv_trace_messages.value != 0 && + msg_num > svc_lastmsg && + Q_strcmp( svgame.msg_name, "ReqState" ); + + if( svgame.msg_trace ) Con_Printf( "^3%s( %i, %s )\n", __FUNCTION__, msg_dest, svgame.msg_name ); } /* @@ -2651,7 +2680,7 @@ void GAME_EXPORT pfnMessageEnd( void ) return; } - sv.multicast.pData[svgame.msg_size_index] = svgame.msg_realsize; + *(word *)&sv.multicast.pData[svgame.msg_size_index] = svgame.msg_realsize; } } else if( svgame.msg[svgame.msg_index].size != -1 ) @@ -2706,6 +2735,8 @@ void GAME_EXPORT pfnMessageEnd( void ) svgame.msg_dest = bound( MSG_BROADCAST, svgame.msg_dest, MSG_SPEC ); SV_Multicast( svgame.msg_dest, org, svgame.msg_ent, true, false ); + + if( svgame.msg_trace ) Con_Printf( "^3%s()\n", __FUNCTION__ ); } /* @@ -2718,6 +2749,7 @@ void GAME_EXPORT pfnWriteByte( int iValue ) { if( iValue == -1 ) iValue = 0xFF; // convert char to byte MSG_WriteByte( &sv.multicast, (byte)iValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); svgame.msg_realsize++; } @@ -2730,6 +2762,7 @@ pfnWriteChar void GAME_EXPORT pfnWriteChar( int iValue ) { MSG_WriteChar( &sv.multicast, (signed char)iValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); svgame.msg_realsize++; } @@ -2742,6 +2775,7 @@ pfnWriteShort void GAME_EXPORT pfnWriteShort( int iValue ) { MSG_WriteShort( &sv.multicast, (short)iValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); svgame.msg_realsize += 2; } @@ -2754,6 +2788,7 @@ pfnWriteLong void GAME_EXPORT pfnWriteLong( int iValue ) { MSG_WriteLong( &sv.multicast, iValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); svgame.msg_realsize += 4; } @@ -2769,6 +2804,7 @@ void GAME_EXPORT pfnWriteAngle( float flValue ) int iAngle = ((int)(( flValue ) * 256 / 360) & 255); MSG_WriteChar( &sv.multicast, iAngle ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %f )\n", __FUNCTION__, flValue ); svgame.msg_realsize += 1; } @@ -2781,6 +2817,7 @@ pfnWriteCoord void GAME_EXPORT pfnWriteCoord( float flValue ) { MSG_WriteCoord( &sv.multicast, flValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %f )\n", __FUNCTION__, flValue ); svgame.msg_realsize += 2; } @@ -2793,6 +2830,7 @@ pfnWriteBytes void pfnWriteBytes( const byte *bytes, int count ) { MSG_WriteBytes( &sv.multicast, bytes, count ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, count ); svgame.msg_realsize += count; } @@ -2854,6 +2892,7 @@ void GAME_EXPORT pfnWriteString( const char *src ) *dst = '\0'; // string end (not included in count) MSG_WriteString( &sv.multicast, string ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %s )\n", __FUNCTION__, string ); // NOTE: some messages with constant string length can be marked as known sized svgame.msg_realsize += len; @@ -2870,6 +2909,7 @@ void GAME_EXPORT pfnWriteEntity( int iValue ) if( iValue < 0 || iValue >= svgame.numEntities ) Host_Error( "MSG_WriteEntity: invalid entnumber %i\n", iValue ); MSG_WriteShort( &sv.multicast, (short)iValue ); + if( svgame.msg_trace ) Con_Printf( "\t^3%s( %i )\n", __FUNCTION__, iValue ); svgame.msg_realsize += 2; } @@ -3332,24 +3372,21 @@ pfnPEntityOfEntIndex ============= */ -edict_t *pfnPEntityOfEntIndex( int iEntIndex ) +static edict_t *pfnPEntityOfEntIndex( int iEntIndex ) { - if( iEntIndex >= 0 && iEntIndex < GI->max_edicts ) - { - edict_t *pEdict = EDICT_NUM( iEntIndex ); + // have to be bug-compatible with GoldSrc in this function + return SV_PEntityOfEntIndex( iEntIndex, false ); +} - if( !iEntIndex || FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) - return pEdict; // just get access to array +/* +============= +pfnPEntityOfEntIndexAllEntities - if( SV_IsValidEdict( pEdict ) && pEdict->pvPrivateData ) - return pEdict; - - // g-cont: world and clients can be acessed even without private data! - if( SV_IsValidEdict( pEdict ) && SV_IsPlayerIndex( iEntIndex )) - return pEdict; - } - - return NULL; +============= +*/ +static edict_t *pfnPEntityOfEntIndexAllEntities( int iEntIndex ) +{ + return SV_PEntityOfEntIndex( iEntIndex, true ); } /* @@ -3812,7 +3849,7 @@ char *pfnGetInfoKeyBuffer( edict_t *e ) if(( cl = SV_ClientFromEdict( e, false )) != NULL ) return cl->userinfo; - return ""; // assume error + return (char*)""; // assume error } /* @@ -3954,7 +3991,7 @@ void GAME_EXPORT SV_PlaybackEventFull( int flags, const edict_t *pInvoker, word return; // someone stupid joke // first check event for out of bounds - if( eventindex < 1 || eventindex > MAX_EVENTS ) + if( eventindex < 1 || eventindex >= MAX_EVENTS ) { Con_Printf( S_ERROR "EV_Playback: invalid eventindex %i\n", eventindex ); return; @@ -4150,13 +4187,13 @@ byte *pfnSetFatPVS( const float *org ) if( !sv.worldmodel->visdata || sv_novis->value || !org || CL_DisableVisibility( )) fullvis = true; - ASSERT( pfnGetCurrentPlayer() != -1 ); - // portals can't change viewpoint! if( !FBitSet( sv.hostflags, SVF_MERGE_VISIBILITY )) { vec3_t viewPos, offset; + ASSERT( pfnGetCurrentPlayer() != -1 ); + // see code from client.cpp for understanding: // org = pView->v.origin + pView->v.view_ofs; // if ( pView->v.flags & FL_DUCKING ) @@ -4200,13 +4237,13 @@ byte *pfnSetFatPAS( const float *org ) if( !sv.worldmodel->visdata || sv_novis->value || !org || CL_DisableVisibility( )) fullvis = true; - ASSERT( pfnGetCurrentPlayer() != -1 ); - // portals can't change viewpoint! if( !FBitSet( sv.hostflags, SVF_MERGE_VISIBILITY )) { vec3_t viewPos, offset; + ASSERT( pfnGetCurrentPlayer() != -1 ); + // see code from client.cpp for understanding: // org = pView->v.origin + pView->v.view_ofs; // if ( pView->v.flags & FL_DUCKING ) @@ -4722,6 +4759,7 @@ static enginefuncs_t gEngfuncs = pfnQueryClientCvarValue, pfnQueryClientCvarValue2, COM_CheckParm, + pfnPEntityOfEntIndexAllEntities, }; /* @@ -4776,7 +4814,7 @@ qboolean SV_ParseEdict( char **pfile, edict_t *ent ) if( !token[0] ) continue; // create keyvalue strings - pkvd[numpairs].szClassName = ""; // unknown at this moment + pkvd[numpairs].szClassName = (char*)""; // unknown at this moment pkvd[numpairs].szKeyName = copystring( keyname ); pkvd[numpairs].szValue = copystring( token ); pkvd[numpairs].fHandled = false; @@ -4814,8 +4852,8 @@ qboolean SV_ParseEdict( char **pfile, edict_t *ent ) { if( numpairs < 256 ) { - pkvd[numpairs].szClassName = "custom"; - pkvd[numpairs].szKeyName = "customclass"; + pkvd[numpairs].szClassName = (char*)"custom"; + pkvd[numpairs].szKeyName = (char*)"customclass"; pkvd[numpairs].szValue = classname; pkvd[numpairs].fHandled = false; numpairs++; @@ -4883,7 +4921,7 @@ qboolean SV_ParseEdict( char **pfile, edict_t *ent ) Mem_Free( pkvd[i].szValue ); } - if( classname && Mem_IsAllocatedExt( host.mempool, classname )) + if( Mem_IsAllocatedExt( host.mempool, classname )) Mem_Free( classname ); return true; diff --git a/engine/server/sv_init.c b/engine/server/sv_init.c index b045fdfd..8f55a25e 100644 --- a/engine/server/sv_init.c +++ b/engine/server/sv_init.c @@ -761,7 +761,7 @@ void SV_SetupClients( void ) Con_Reportf( "%s alloced by server packet entities\n", Q_memprint( sizeof( entity_state_t ) * svs.num_client_entities )); // init network stuff - NET_Config(( svs.maxclients > 1 )); + NET_Config(( svs.maxclients > 1 ), true ); svgame.numEntities = svs.maxclients + 1; // clients + world ClearBits( sv_maxclients->flags, FCVAR_CHANGED ); } diff --git a/engine/server/sv_log.c b/engine/server/sv_log.c index 4f6050c2..7c1c5d6b 100644 --- a/engine/server/sv_log.c +++ b/engine/server/sv_log.c @@ -136,7 +136,7 @@ void Log_Printf( const char *fmt, ... ) } } -static void Log_PrintServerCvar( const char *var_name, const char *var_value, void *unused2, void *unused3 ) +static void Log_PrintServerCvar( const char *var_name, const char *var_value, const void *unused2, void *unused3 ) { Log_Printf( "Server cvar \"%s\" = \"%s\"\n", var_name, var_value ); } diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 5639dd44..514a54d4 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -16,12 +16,14 @@ GNU General Public License for more details. #include "common.h" #include "server.h" #include "net_encode.h" +#include "platform/platform.h" -#define HEARTBEAT_SECONDS 300.0f // 300 seconds +#define HEARTBEAT_SECONDS ((sv_nat.value > 0.0f) ? 60.0f : 300.0f) // 1 or 5 minutes // server cvars CVAR_DEFINE_AUTO( sv_lan, "0", 0, "server is a lan server ( no heartbeat, no authentication, no non-class C addresses, 9999.0 rate, etc." ); CVAR_DEFINE_AUTO( sv_lan_rate, "20000.0", 0, "rate for lan server" ); +CVAR_DEFINE_AUTO( sv_nat, "0", 0, "enable NAT bypass for this server" ); CVAR_DEFINE_AUTO( sv_aim, "1", FCVAR_ARCHIVE|FCVAR_SERVER, "auto aiming option" ); CVAR_DEFINE_AUTO( sv_unlag, "1", 0, "allow lag compensation on server-side" ); CVAR_DEFINE_AUTO( sv_maxunlag, "0.5", 0, "max latency value which can be interpolated (by default ping should not exceed 500 units)" ); @@ -55,6 +57,7 @@ CVAR_DEFINE_AUTO( mp_logecho, "1", 0, "log multiplayer frags to server logfile" CVAR_DEFINE_AUTO( mp_logfile, "1", 0, "log multiplayer frags to console" ); CVAR_DEFINE_AUTO( sv_log_singleplayer, "0", FCVAR_ARCHIVE, "allows logging in singleplayer games" ); CVAR_DEFINE_AUTO( sv_log_onefile, "0", FCVAR_ARCHIVE, "logs server information to only one file" ); +CVAR_DEFINE_AUTO( sv_trace_messages, "0", FCVAR_LATCH, "enable server usermessages tracing (good for developers)" ); // game-related cvars CVAR_DEFINE_AUTO( mapcyclefile, "mapcycle.txt", 0, "name of multiplayer map cycle configuration file" ); @@ -152,6 +155,41 @@ qboolean SV_HasActivePlayers( void ) return false; } +/* +================ +SV_GetConnectedClientsCount + +returns connected clients count (and optionally bots count) +================ +*/ +int SV_GetConnectedClientsCount(int *bots) +{ + int index; + int clients; + + clients = 0; + if( svs.clients ) + { + if( bots ) + *bots = 0; + + for( index = 0; index < svs.maxclients; index++ ) + { + if( svs.clients[index].state >= cs_connected ) + { + if( FBitSet( svs.clients[index].flags, FCL_FAKECLIENT )) + { + if( bots ) + (*bots)++; + } + else + clients++; + } + } + } + return clients; +} + /* =================== SV_UpdateMovevars @@ -649,6 +687,9 @@ void Host_ServerFrame( void ) // clear edict flags for next frame SV_PrepWorldFrame (); + // update dedicated server status line in console + Platform_UpdateStatusLine (); + // send a heartbeat to the master if needed Master_Heartbeat (); } @@ -675,7 +716,7 @@ void Master_Add( void ) { netadr_t adr; - NET_Config( true ); // allow remote + NET_Config( true, false ); // allow remote if( !NET_StringToAdr( MASTERSERVER_ADR, &adr )) Con_Printf( "can't resolve adr: %s\n", MASTERSERVER_ADR ); @@ -718,7 +759,7 @@ void Master_Shutdown( void ) { netadr_t adr; - NET_Config( true ); // allow remote + NET_Config( true, false ); // allow remote if( !NET_StringToAdr( MASTERSERVER_ADR, &adr )) Con_Printf( "can't resolve addr: %s\n", MASTERSERVER_ADR ); @@ -737,22 +778,10 @@ void SV_AddToMaster( netadr_t from, sizebuf_t *msg ) { uint challenge; char s[MAX_INFO_STRING] = "0\n"; // skip 2 bytes of header - int clients = 0, bots = 0, index; + int clients = 0, bots = 0; int len = sizeof( s ); - if( svs.clients ) - { - for( index = 0; index < svs.maxclients; index++ ) - { - if( svs.clients[index].state >= cs_connected ) - { - if( FBitSet( svs.clients[index].flags, FCL_FAKECLIENT )) - bots++; - else clients++; - } - } - } - + clients = SV_GetConnectedClientsCount( &bots ); challenge = MSG_ReadUBitLong( msg, sizeof( uint ) << 3 ); Info_SetValueForKey( s, "protocol", va( "%d", PROTOCOL_VERSION ), len ); // protocol version @@ -770,6 +799,7 @@ void SV_AddToMaster( netadr_t from, sizebuf_t *msg ) Info_SetValueForKey( s, "version", va( "%s", XASH_VERSION ), len ); // server region. 255 -- all regions Info_SetValueForKey( s, "region", "255", len ); // server region. 255 -- all regions Info_SetValueForKey( s, "product", GI->gamefolder, len ); // product? Where is the difference with gamedir? + Info_SetValueForKey( s, "nat", sv_nat.string, sizeof(s) ); // Server running under NAT, use reverse connection NET_SendPacket( NS_SERVER, Q_strlen( s ), s, from ); } @@ -928,6 +958,7 @@ void SV_Init( void ) sv_hostmap = Cvar_Get( "hostmap", GI->startmap, 0, "keep name of last entered map" ); Cvar_RegisterVariable( &sv_password ); Cvar_RegisterVariable( &sv_lan ); + Cvar_RegisterVariable( &sv_nat ); Cvar_RegisterVariable( &violence_ablood ); Cvar_RegisterVariable( &violence_hblood ); Cvar_RegisterVariable( &violence_agibs ); @@ -946,6 +977,8 @@ void SV_Init( void ) Cvar_RegisterVariable( &listipcfgfile ); Cvar_RegisterVariable( &mapchangecfgfile ); + Cvar_RegisterVariable( &sv_trace_messages ); + sv_allow_joystick = Cvar_Get( "sv_allow_joystick", "1", FCVAR_ARCHIVE, "allow connect with joystick enabled" ); sv_allow_mouse = Cvar_Get( "sv_allow_mouse", "1", FCVAR_ARCHIVE, "allow connect with mouse" ); sv_allow_touch = Cvar_Get( "sv_allow_touch", "1", FCVAR_ARCHIVE, "allow connect with touch controls" ); @@ -1061,6 +1094,8 @@ void SV_Shutdown( const char *finalmsg ) // drop the client if want to load a new map if( CL_IsPlaybackDemo( )) CL_Drop(); + + SV_UnloadProgs (); return; } @@ -1076,7 +1111,7 @@ void SV_Shutdown( const char *finalmsg ) if( public_server->value && svs.maxclients != 1 ) Master_Shutdown(); - NET_Config( false ); + NET_Config( false, false ); SV_UnloadProgs (); CL_Drop(); diff --git a/engine/server/sv_move.c b/engine/server/sv_move.c index 491e0a89..48753f21 100644 --- a/engine/server/sv_move.c +++ b/engine/server/sv_move.c @@ -243,7 +243,7 @@ float SV_VecToYaw( const vec3_t src ) } else { - yaw = (int)( atan2( src[1], src[0] ) * 180.0f / M_PI_F ); + yaw = (int)( atan2( src[1], src[0] ) * 180.0 / M_PI ); if( yaw < 0 ) yaw += 360.0f; } return yaw; diff --git a/engine/server/sv_pmove.c b/engine/server/sv_pmove.c index 34008241..53337e80 100644 --- a/engine/server/sv_pmove.c +++ b/engine/server/sv_pmove.c @@ -455,6 +455,8 @@ static float GAME_EXPORT pfnTraceModel( physent_t *pe, float *start, float *end, matrix4x4 matrix; hull_t *hull; + PM_InitTrace( trace, end ); + old_usehull = svgame.pmove->usehull; svgame.pmove->usehull = 2; @@ -686,7 +688,6 @@ static void SV_SetupPMove( playermove_t *pmove, sv_client_t *cl, usercmd_t *ucmd pmove->flFallVelocity = clent->v.flFallVelocity; pmove->flSwimTime = clent->v.flSwimTime; VectorCopy( clent->v.punchangle, pmove->punchangle ); - pmove->flSwimTime = clent->v.flSwimTime; pmove->flNextPrimaryAttack = 0.0f; // not used by PM_ code pmove->effects = clent->v.effects; pmove->flags = clent->v.flags; diff --git a/engine/server/sv_save.c b/engine/server/sv_save.c index 2e754ce1..0fcf66e4 100644 --- a/engine/server/sv_save.c +++ b/engine/server/sv_save.c @@ -174,6 +174,7 @@ static TYPEDESCRIPTION gStaticEntry[] = DEFINE_FIELD( entity_state_t, framerate, FIELD_FLOAT ), DEFINE_FIELD( entity_state_t, mins, FIELD_VECTOR ), DEFINE_FIELD( entity_state_t, maxs, FIELD_VECTOR ), + DEFINE_FIELD( entity_state_t, startpos, FIELD_VECTOR ), DEFINE_FIELD( entity_state_t, rendermode, FIELD_INTEGER ), DEFINE_FIELD( entity_state_t, renderamt, FIELD_FLOAT ), DEFINE_ARRAY( entity_state_t, rendercolor, FIELD_CHARACTER, sizeof( color24 )), diff --git a/engine/server/sv_world.c b/engine/server/sv_world.c index ea823974..71079e65 100644 --- a/engine/server/sv_world.c +++ b/engine/server/sv_world.c @@ -871,10 +871,7 @@ void SV_ClipMoveToEntity( edict_t *ent, const vec3_t start, vec3_t mins, vec3_t qboolean rotated, transform_bbox; matrix4x4 matrix; - memset( trace, 0, sizeof( trace_t )); - VectorCopy( end, trace->endpos ); - trace->fraction = 1.0f; - trace->allsolid = 1; + PM_InitTrace( trace, end ); model = SV_ModelHandle( ent->v.modelindex ); @@ -945,10 +942,7 @@ void SV_ClipMoveToEntity( edict_t *ent, const vec3_t start, vec3_t mins, vec3_t for( i = 0; i < hullcount; i++ ) { - memset( &trace_hitbox, 0, sizeof( trace_t )); - VectorCopy( end, trace_hitbox.endpos ); - trace_hitbox.fraction = 1.0; - trace_hitbox.allsolid = 1; + PM_InitTrace( &trace_hitbox, end ); PM_RecursiveHullCheck( &hull[i], hull[i].firstclipnode, 0.0f, 1.0f, start_l, end_l, (pmtrace_t *)&trace_hitbox ); @@ -1114,10 +1108,7 @@ or custom physics implementation void SV_CustomClipMoveToEntity( edict_t *ent, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, trace_t *trace ) { // initialize custom trace - memset( trace, 0, sizeof( trace_t )); - VectorCopy( end, trace->endpos ); - trace->allsolid = true; - trace->fraction = 1.0f; + PM_InitTrace( trace, end ); if( svgame.physFuncs.ClipMoveToEntity != NULL ) { diff --git a/engine/vgui_api.h b/engine/vgui_api.h index 7f35d5d9..2ce89226 100644 --- a/engine/vgui_api.h +++ b/engine/vgui_api.h @@ -16,6 +16,8 @@ GNU General Public License for more details. #define VGUI_API_H #include "xash3d_types.h" +#include "key_modifiers.h" +#include "cursor_type.h" // VGUI generic vertex @@ -157,33 +159,10 @@ enum VGUI_MouseAction MA_WHEEL }; -enum VGUI_DefaultCursor -{ - dc_user, - dc_none, - dc_arrow, - dc_ibeam, - dc_hourglass, - dc_crosshair, - dc_up, - dc_sizenwse, - dc_sizenesw, - dc_sizewe, - dc_sizens, - dc_sizeall, - dc_no, - dc_hand, - dc_last -}; - - - - - - typedef struct vguiapi_s { qboolean initialized; + // called from vgui_support void (*DrawInit)( void ); void (*DrawShutdown)( void ); void (*SetupDrawingText)( int *pColor ); @@ -198,18 +177,23 @@ typedef struct vguiapi_s void (*GetTextureSizes)( int *width, int *height ); int (*GenerateTexture)( void ); void *(*EngineMalloc)( size_t size ); - void (*CursorSelect)( enum VGUI_DefaultCursor cursor ); + void (*CursorSelect)( VGUI_DefaultCursor cursor ); byte (*GetColor)( int i, int j ); qboolean (*IsInGame)( void ); - void (*SetVisible)( qboolean state ); + void (*Unused)( void ); void (*GetCursorPos)( int *x, int *y ); int (*ProcessUtfChar)( int ch ); + int (*GetClipboardText)( char *buffer, size_t bufferSize ); + void (*SetClipboardText)( const char *text ); + key_modifier_t (*GetKeyModifiers)( void ); + // called from engine side void (*Startup)( int width, int height ); void (*Shutdown)( void ); void *(*GetPanel)( void ); void (*Paint)( void ); - void (*Mouse)(enum VGUI_MouseAction action, int code ); - void (*Key)(enum VGUI_KeyAction action,enum VGUI_KeyCode code ); + void (*Mouse)( enum VGUI_MouseAction action, int code ); + void (*Key)( enum VGUI_KeyAction action, enum VGUI_KeyCode code ); void (*MouseMove)( int x, int y ); + void (*TextInput)( const char *text ); } vguiapi_t; #endif // VGUI_API_H diff --git a/engine/wscript b/engine/wscript index cf64f7b4..972d8804 100644 --- a/engine/wscript +++ b/engine/wscript @@ -32,6 +32,9 @@ def options(opt): grp.add_option('--enable-engine-tests', action = 'store_true', dest = 'ENGINE_TESTS', default = False, help = 'embed tests into the engine, jump into them by -runtests command line switch [default: %default]') + grp.add_option('--enable-engine-fuzz', action = 'store_true', dest = 'ENGINE_FUZZ', default = False, + help = 'add LLVM libFuzzer [default: %default]' ) + opt.load('sdl2') def configure(conf): @@ -87,6 +90,10 @@ def configure(conf): conf.env.ENGINE_TESTS = conf.options.ENGINE_TESTS + if conf.options.ENGINE_FUZZ: + conf.env.append_unique('CFLAGS', '-fsanitize=fuzzer-no-link') + conf.env.append_unique('LINKFLAGS', '-fsanitize=fuzzer') + conf.define_cond('XASH_ENGINE_TESTS', conf.env.ENGINE_TESTS) conf.define_cond('XASH_STATIC_LIBS', conf.env.STATIC_LINKING) conf.define_cond('XASH_CUSTOM_SWAP', conf.options.CUSTOM_SWAP) @@ -159,7 +166,7 @@ def build(bld): 'client/vgui/*.c', 'client/avi/*.c']) - includes = ['common', 'server', 'client', 'client/vgui', 'tests', '.', '../public', '../common', '../pm_shared' ] + includes = ['common', 'server', 'client', 'client/vgui', 'tests', '.', '../public', '../common', '../filesystem', '../pm_shared' ] if bld.env.SINGLE_BINARY: install_path = bld.env.BINDIR @@ -177,5 +184,6 @@ def build(bld): includes = includes, use = libs, install_path = install_path, - subsystem = bld.env.MSVC_SUBSYSTEM + subsystem = bld.env.MSVC_SUBSYSTEM, + rpath = '$ORIGIN' ) diff --git a/filesystem/VFileSystem009.cpp b/filesystem/VFileSystem009.cpp new file mode 100644 index 00000000..ffcffe40 --- /dev/null +++ b/filesystem/VFileSystem009.cpp @@ -0,0 +1,497 @@ +/* +VFileSystem009.h - C++ interface for filesystem_stdio +Copyright (C) 2022 Alibek Omarov + +This program 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, either version 3 of the License, or +(at your option) any later version. + +This program 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. +*/ +#include +#include +#include +#include +#include ALLOCA_H +#include "crtlib.h" +#include "filesystem.h" +#include "filesystem_internal.h" +#include "VFileSystem009.h" + +#if __cplusplus < 201103L +#define override +#endif + +// GoldSrc Directories and ID +// GAME gamedir +// GAMECONFIG gamedir (rodir integration?) +// GAMEDOWNLOAD gamedir_downloads (gamedir/downloads for us) +// GAME_FALLBACK liblist.gam's fallback_dir +// ROOT and BASE rootdir +// PLATFORM platform +// CONFIG platform/config + +static inline qboolean IsIdGamedir( const char *id ) +{ + return !Q_strcmp( id, "GAME" ) || + !Q_strcmp( id, "GAMECONFIG" ) || + !Q_strcmp( id, "GAMEDOWNLOAD" ); +} + +static inline const char* IdToDir( const char *id ) +{ + if( !Q_strcmp( id, "GAME" )) + return GI->gamefolder; + else if( !Q_strcmp( id, "GAMEDOWNLOAD" )) + return va( "%s/downloaded", GI->gamefolder ); + else if( !Q_strcmp( id, "GAMECONFIG" )) + return fs_writedir; // full path here so it's totally our write allowed directory + else if( !Q_strcmp( id, "PLATFORM" )) + return "platform"; // stub + else if( !Q_strcmp( id, "CONFIG" )) + return "platform/config"; // stub + else // ROOT || BASE + return fs_rootdir; // give at least root directory +} + +static inline void CopyAndFixSlashes( char *p, const char *in ) +{ + Q_strcpy( p, in ); + COM_FixSlashes( p ); +} + +class CXashFS : public IVFileSystem009 +{ +private: + class CSearchState + { + public: + CSearchState( CSearchState **head, search_t *search ) : + next( *head ), search( search ), index( 0 ) + { + if( *head ) + handle = (*head)->handle + 1; + else handle = 0; + + *head = this; + } + ~CSearchState() + { + Mem_Free( search ); + } + + CSearchState *next; + search_t *search; + int index; + FileFindHandle_t handle; + }; + + CSearchState *searchHead; + + CSearchState *GetSearchStateByHandle( FileFindHandle_t handle ) + { + for( CSearchState *state = searchHead; state; state = state->next ) + { + if( state->handle == handle ) + { + return state; + } + } + + Con_DPrintf( "Can't find search state by handle %d\n", handle ); + return NULL; + } + +public: + CXashFS() : searchHead( NULL ) + { + } + + void RemoveAllSearchPaths() override + { + FS_ClearSearchPath(); + } + + void AddSearchPath( const char *path, const char *id ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + + FS_AddGameDirectory( p, FS_CUSTOM_PATH ); + } + + void AddSearchPathNoWrite( const char *path, const char *id ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + + FS_AddGameDirectory( p, FS_NOWRITE_PATH | FS_CUSTOM_PATH ); + } + + bool RemoveSearchPath( const char *id ) override + { + // TODO: + return true; + } + + void RemoveFile( const char *path, const char *id ) override + { + FS_Delete( path ); // FS_Delete is aware of slashes + } + + void CreateDirHierarchy( const char *path, const char *id ) override + { + FS_CreatePath( va( "%s/%s", IdToDir( id ), path )); // FS_CreatePath is aware of slashes + } + + bool FileExists( const char *path ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + + return FS_FileExists( p, false ); + } + + bool IsDirectory( const char *path ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + + return FS_SysFolderExists( p ); + } + + FileHandle_t Open( const char *path, const char *mode, const char *id ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + + file_t *fd = FS_Open( p, mode, IsIdGamedir( id ) ); + + return fd; + } + + void Close( FileHandle_t handle ) override + { + FS_Close( (file_t *)handle ); + } + + void Seek( FileHandle_t handle, int offset, FileSystemSeek_t whence ) override + { + int whence_ = SEEK_SET; + switch( whence ) + { + case FILESYSTEM_SEEK_HEAD: whence_ = SEEK_SET; break; + case FILESYSTEM_SEEK_CURRENT: whence_ = SEEK_CUR; break; + case FILESYSTEM_SEEK_TAIL: whence_ = SEEK_END; break; + } + + FS_Seek( (file_t *)handle, offset, whence_ ); + } + + unsigned int Tell( FileHandle_t handle ) override + { + return FS_Tell( (file_t *)handle ); + } + + unsigned int Size( FileHandle_t handle ) override + { + file_t *fd = (file_t *)handle; + return fd->real_length; + } + + unsigned int Size( const char *path ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + return FS_FileSize( p, false ); + } + + long int GetFileTime( const char *path ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + return FS_FileTime( p, false ); + } + + void FileTimeToString( char *p, int size, long int time ) override + { + time_t curtime = time; + char *buf = ctime( &curtime ); + Q_strncpy( p, buf, size ); + } + + bool IsOk( FileHandle_t handle ) override + { + return !FS_Eof( (file_t *)handle ); + } + + void Flush( FileHandle_t handle ) override + { + FS_Flush( (file_t *)handle ); + } + + bool EndOfFile( FileHandle_t handle ) override + { + return FS_Eof( (file_t *)handle ); + } + + int Read( void *buf, int size, FileHandle_t handle ) override + { + return FS_Read( (file_t *)handle, buf, size ); + } + + int Write( const void *buf, int size, FileHandle_t handle ) override + { + return FS_Write( (file_t *)handle, buf, size ); + } + + char *ReadLine( char *buf, int size, FileHandle_t handle ) override + { + int c = FS_Gets( (file_t *)handle, (byte*)buf, size ); + + return c >= 0 ? buf : NULL; + } + + int FPrintf( FileHandle_t handle, char *fmt, ... ) override + { + va_list ap; + int ret; + + va_start( ap, fmt ); + ret = FS_VPrintf( (file_t *)handle, fmt, ap ); + va_end( ap ); + + return ret; + } + + void * GetReadBuffer(FileHandle_t, int *size, bool) override + { + // deprecated by Valve + *size = 0; + return NULL; + } + + void ReleaseReadBuffer(FileHandle_t, void *) override + { + // deprecated by Valve + return; + } + + const char *FindFirst(const char *pattern, FileFindHandle_t *handle, const char *id) override + { + if( !handle || !pattern ) + return NULL; + + char *p = (char *)alloca( Q_strlen( pattern ) + 1 ); + CopyAndFixSlashes( p, pattern ); + search_t *search = FS_Search( p, true, IsIdGamedir( id )); + + if( !search ) + return NULL; + + CSearchState *state = new CSearchState( &searchHead, search ); + + *handle = state->handle; + + return state->search->filenames[0]; + } + + const char *FindNext( FileFindHandle_t handle ) override + { + CSearchState *state = GetSearchStateByHandle( handle ); + + if( !state ) return NULL; + + if( state->index + 1 >= state->search->numfilenames ) + return NULL; + + return state->search->filenames[++state->index]; + } + + bool FindIsDirectory( FileFindHandle_t handle ) override + { + CSearchState *state = GetSearchStateByHandle( handle ); + + if( !state ) + return false; + + if( state->index >= state->search->numfilenames ) + return false; + + return IsDirectory( state->search->filenames[state->index] ); + } + + void FindClose( FileFindHandle_t handle ) override + { + for( CSearchState *state = searchHead, **prev = NULL; + state; + *prev = state, state = state->next ) + { + if( state->handle == handle ) + { + if( prev ) + (*prev)->next = state->next; + else searchHead = state->next; + + delete state; + + return; + } + } + + Con_DPrintf( "FindClose: Can't find search state by handle %d\n", handle ); + return; + } + + const char * GetLocalPath( const char *name, char *buf, int size ) override + { + if( !name ) return NULL; + + char *p = (char *)alloca( Q_strlen( name ) + 1 ); + CopyAndFixSlashes( p, name ); + +#if !XASH_WIN32 + if( p[0] == '/' ) +#else + if( Q_strchr( p, ':' )) +#endif + { + Q_strncpy( buf, p, size ); + + return buf; + } + + + const char *fullpath = FS_GetDiskPath( p, false ); + if( !fullpath ) + return NULL; + + Q_strncpy( buf, fullpath, size ); + return buf; + } + + char *ParseFile( char *buf, char *token, bool *quoted ) override + { + qboolean qquoted; + + char *p = COM_ParseFileSafe( buf, token, PFILE_FS_TOKEN_MAX_LENGTH, 0, NULL, &qquoted ); + if( quoted ) *quoted = qquoted; + + return p; + } + + bool FullPathToRelativePath( const char *path, char *out ) override + { + if( !COM_CheckString( path )) + { + *out = 0; + return false; + } + + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + searchpath_t *sp; + + for( sp = fs_searchpaths; sp; sp = sp->next ) + { + size_t splen = Q_strlen( sp->filename ); + + if( !Q_strnicmp( sp->filename, p, splen )) + { + Q_strcpy( out, p + splen + 1 ); + return true; + } + } + + Q_strcpy( out, p ); + return false; + } + + bool GetCurrentDirectory( char *p, int size ) override + { + Q_strncpy( p, fs_rootdir, size ); + + return true; + } + + void PrintOpenedFiles() override + { + // we don't track this yet + return; + } + + void SetWarningFunc(void (*)(const char *, ...)) override + { + // TODO: + return; + } + + void SetWarningLevel(FileWarningLevel_t) override + { + // TODO: + return; + } + + int SetVBuf( FileHandle_t handle, char *buf, int mode, long int size ) override + { + // TODO: + return 0; + } + + void GetInterfaceVersion(char *p, int size) override + { + Q_strncpy( p, "Stdio", size ); + } + + bool AddPackFile( const char *path, const char *id ) override + { + char *p = va( "%s/%s", IdToDir( id ), path ); + CopyAndFixSlashes( p, path ); + + return !!FS_AddPak_Fullpath( p, NULL, FS_CUSTOM_PATH ); + } + + FileHandle_t OpenFromCacheForRead( const char *path , const char *mode, const char *id ) override + { + char *p = (char *)alloca( Q_strlen( path ) + 1 ); + CopyAndFixSlashes( p, path ); + + return FS_OpenReadFile( p, mode, IsIdGamedir( id )); + } + + // stubs + void Mount() override {} + void Unmount() override {} + void GetLocalCopy(const char *) override {} + void LogLevelLoadStarted(const char *) override {} + void LogLevelLoadFinished(const char *) override {} + void CancelWaitForResources(WaitForResourcesHandle_t) override {} + int HintResourceNeed(const char *, int) override { return 0; } + WaitForResourcesHandle_t WaitForResources(const char *) override { return 0; } + int PauseResourcePreloading() override { return 0; } + int ResumeResourcePreloading() override { return 0; } + bool IsAppReadyForOfflinePlay(int) override { return true; } + bool IsFileImmediatelyAvailable(const char *) override { return true; } + bool GetWaitForResourcesProgress(WaitForResourcesHandle_t, float *pProgress, bool *pOverride) override + { + if( pProgress ) *pProgress = 0; + if( pOverride ) *pOverride = true; + return false; + } +} g_VFileSystem009; + +extern "C" void EXPORT *CreateInterface( const char *interface, int *retval ) +{ + if( !Q_strcmp( interface, "VFileSystem009" )) + { + if( retval ) *retval = 0; + return &g_VFileSystem009; + } + + if( retval ) *retval = 1; + return NULL; +} diff --git a/filesystem/VFileSystem009.h b/filesystem/VFileSystem009.h new file mode 100644 index 00000000..a0b73123 --- /dev/null +++ b/filesystem/VFileSystem009.h @@ -0,0 +1,153 @@ +/* +VFileSystem009.h - C++ interface for filesystem_stdio +Copyright (C) 2022 Alibek Omarov + +This program 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, either version 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#ifndef VFILESYSTEM009_H +#define VFILESYSTEM009_H + +// exported from dwarf +typedef enum { + FILESYSTEM_SEEK_HEAD = 0, + FILESYSTEM_SEEK_CURRENT = 1, + FILESYSTEM_SEEK_TAIL = 2, +} FileSystemSeek_t; /* size: 4 */ + +typedef enum { + FILESYSTEM_WARNING_QUIET = 0, + FILESYSTEM_WARNING_REPORTUNCLOSED = 1, + FILESYSTEM_WARNING_REPORTUSAGE = 2, + FILESYSTEM_WARNING_REPORTALLACCESSES = 3, +} FileWarningLevel_t; /* size: 4 */ + +typedef void * FileHandle_t; /* size: 4 */ +typedef int FileFindHandle_t; /* size: 4 */ +typedef int WaitForResourcesHandle_t; /* size: 4 */ + +class IBaseInterface +{ +public: + virtual ~IBaseInterface() {} +}; + +class IVFileSystem009 : public IBaseInterface +{ +public: + virtual void Mount() = 0; /* linkage=_ZN11IFileSystem5MountEv */ + + virtual void Unmount() = 0; /* linkage=_ZN11IFileSystem7UnmountEv */ + + virtual void RemoveAllSearchPaths() = 0; /* linkage=_ZN11IFileSystem20RemoveAllSearchPathsEv */ + + virtual void AddSearchPath(const char *, const char *) = 0; /* linkage=_ZN11IFileSystem13AddSearchPathEPKcS1_ */ + + virtual bool RemoveSearchPath(const char *) = 0; /* linkage=_ZN11IFileSystem16RemoveSearchPathEPKc */ + + virtual void RemoveFile(const char *, const char *) = 0; /* linkage=_ZN11IFileSystem10RemoveFileEPKcS1_ */ + + virtual void CreateDirHierarchy(const char *, const char *) = 0; /* linkage=_ZN11IFileSystem18CreateDirHierarchyEPKcS1_ */ + + virtual bool FileExists(const char *) = 0; /* linkage=_ZN11IFileSystem10FileExistsEPKc */ + + virtual bool IsDirectory(const char *) = 0; /* linkage=_ZN11IFileSystem11IsDirectoryEPKc */ + + virtual FileHandle_t Open(const char *, const char *, const char *) = 0; /* linkage=_ZN11IFileSystem4OpenEPKcS1_S1_ */ + + virtual void Close(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem5CloseEPv */ + + virtual void Seek(FileHandle_t, int, FileSystemSeek_t) = 0; /* linkage=_ZN11IFileSystem4SeekEPvi16FileSystemSeek_t */ + + virtual unsigned int Tell(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem4TellEPv */ + + virtual unsigned int Size(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem4SizeEPv */ + + virtual unsigned int Size(const char *) = 0; /* linkage=_ZN11IFileSystem4SizeEPKc */ + + virtual long int GetFileTime(const char *) = 0; /* linkage=_ZN11IFileSystem11GetFileTimeEPKc */ + + virtual void FileTimeToString(char *, int, long int) = 0; /* linkage=_ZN11IFileSystem16FileTimeToStringEPcil */ + + virtual bool IsOk(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem4IsOkEPv */ + + virtual void Flush(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem5FlushEPv */ + + virtual bool EndOfFile(FileHandle_t) = 0; /* linkage=_ZN11IFileSystem9EndOfFileEPv */ + + virtual int Read(void *, int, FileHandle_t) = 0; /* linkage=_ZN11IFileSystem4ReadEPviS0_ */ + + virtual int Write(const void *, int, FileHandle_t) = 0; /* linkage=_ZN11IFileSystem5WriteEPKviPv */ + + virtual char * ReadLine(char *, int, FileHandle_t) = 0; /* linkage=_ZN11IFileSystem8ReadLineEPciPv */ + + virtual int FPrintf(FileHandle_t, char *, ...) = 0; /* linkage=_ZN11IFileSystem7FPrintfEPvPcz */ + + virtual void * GetReadBuffer(FileHandle_t, int *, bool) = 0; /* linkage=_ZN11IFileSystem13GetReadBufferEPvPib */ + + virtual void ReleaseReadBuffer(FileHandle_t, void *) = 0; /* linkage=_ZN11IFileSystem17ReleaseReadBufferEPvS0_ */ + + virtual const char * FindFirst(const char *, FileFindHandle_t *, const char *) = 0; /* linkage=_ZN11IFileSystem9FindFirstEPKcPiS1_ */ + + virtual const char * FindNext(FileFindHandle_t) = 0; /* linkage=_ZN11IFileSystem8FindNextEi */ + + virtual bool FindIsDirectory(FileFindHandle_t) = 0; /* linkage=_ZN11IFileSystem15FindIsDirectoryEi */ + + virtual void FindClose(FileFindHandle_t) = 0; /* linkage=_ZN11IFileSystem9FindCloseEi */ + + virtual void GetLocalCopy(const char *) = 0; /* linkage=_ZN11IFileSystem12GetLocalCopyEPKc */ + + virtual const char * GetLocalPath(const char *, char *, int) = 0; /* linkage=_ZN11IFileSystem12GetLocalPathEPKcPci */ + + virtual char * ParseFile(char *, char *, bool *) = 0; /* linkage=_ZN11IFileSystem9ParseFileEPcS0_Pb */ + + virtual bool FullPathToRelativePath(const char *, char *) = 0; /* linkage=_ZN11IFileSystem22FullPathToRelativePathEPKcPc */ + + virtual bool GetCurrentDirectory(char *, int) = 0; /* linkage=_ZN11IFileSystem19GetCurrentDirectoryEPci */ + + virtual void PrintOpenedFiles() = 0; /* linkage=_ZN11IFileSystem16PrintOpenedFilesEv */ + + virtual void SetWarningFunc(void (*)(const char *, ...)) = 0; /* linkage=_ZN11IFileSystem14SetWarningFuncEPFvPKczE */ + + virtual void SetWarningLevel(FileWarningLevel_t) = 0; /* linkage=_ZN11IFileSystem15SetWarningLevelE18FileWarningLevel_t */ + + virtual void LogLevelLoadStarted(const char *) = 0; /* linkage=_ZN11IFileSystem19LogLevelLoadStartedEPKc */ + + virtual void LogLevelLoadFinished(const char *) = 0; /* linkage=_ZN11IFileSystem20LogLevelLoadFinishedEPKc */ + + virtual int HintResourceNeed(const char *, int) = 0; /* linkage=_ZN11IFileSystem16HintResourceNeedEPKci */ + + virtual int PauseResourcePreloading() = 0; /* linkage=_ZN11IFileSystem23PauseResourcePreloadingEv */ + + virtual int ResumeResourcePreloading() = 0; /* linkage=_ZN11IFileSystem24ResumeResourcePreloadingEv */ + + virtual int SetVBuf(FileHandle_t, char *, int, long int) = 0; /* linkage=_ZN11IFileSystem7SetVBufEPvPcil */ + + virtual void GetInterfaceVersion(char *, int) = 0; /* linkage=_ZN11IFileSystem19GetInterfaceVersionEPci */ + + virtual bool IsFileImmediatelyAvailable(const char *) = 0; /* linkage=_ZN11IFileSystem26IsFileImmediatelyAvailableEPKc */ + + virtual WaitForResourcesHandle_t WaitForResources(const char *) = 0; /* linkage=_ZN11IFileSystem16WaitForResourcesEPKc */ + + virtual bool GetWaitForResourcesProgress(WaitForResourcesHandle_t, float *, bool *) = 0; /* linkage=_ZN11IFileSystem27GetWaitForResourcesProgressEiPfPb */ + + virtual void CancelWaitForResources(WaitForResourcesHandle_t) = 0; /* linkage=_ZN11IFileSystem22CancelWaitForResourcesEi */ + + virtual bool IsAppReadyForOfflinePlay(int) = 0; /* linkage=_ZN11IFileSystem24IsAppReadyForOfflinePlayEi */ + + virtual bool AddPackFile(const char *, const char *) = 0; /* linkage=_ZN11IFileSystem11AddPackFileEPKcS1_ */ + + virtual FileHandle_t OpenFromCacheForRead(const char *, const char *, const char *) = 0; /* linkage=_ZN11IFileSystem20OpenFromCacheForReadEPKcS1_S1_ */ + + virtual void AddSearchPathNoWrite(const char *, const char *) = 0; /* linkage=_ZN11IFileSystem20AddSearchPathNoWriteEPKcS1_ */ +}; + +#endif // VFILESYSTEM009_H diff --git a/engine/common/filesystem.c b/filesystem/filesystem.c similarity index 56% rename from engine/common/filesystem.c rename to filesystem/filesystem.c index e15c6f1a..66a9b5ae 100644 --- a/engine/common/filesystem.c +++ b/filesystem/filesystem.c @@ -27,131 +27,31 @@ GNU General Public License for more details. #include #include #endif -#include "miniz.h" // header-only zlib replacement -#include "common.h" -#include "wadfile.h" +#include +#include +#include "port.h" +#include "const.h" +#include "crtlib.h" +#include "crclib.h" #include "filesystem.h" -#include "library.h" +#include "filesystem_internal.h" #include "xash3d_mathlib.h" -#include "protocol.h" +#include "common/com_strings.h" +#include "common/protocol.h" #define FILE_COPY_SIZE (1024 * 1024) -#define FILE_BUFF_SIZE (2048) -// PAK errors -#define PAK_LOAD_OK 0 -#define PAK_LOAD_COULDNT_OPEN 1 -#define PAK_LOAD_BAD_HEADER 2 -#define PAK_LOAD_BAD_FOLDERS 3 -#define PAK_LOAD_TOO_MANY_FILES 4 -#define PAK_LOAD_NO_FILES 5 -#define PAK_LOAD_CORRUPTED 6 +fs_globals_t FI; +qboolean fs_ext_path = false; // attempt to read\write from ./ or ../ pathes +poolhandle_t fs_mempool; +searchpath_t *fs_searchpaths = NULL; // chain +char fs_rodir[MAX_SYSPATH]; +char fs_rootdir[MAX_SYSPATH]; +char fs_writedir[MAX_SYSPATH]; // path that game allows to overwrite, delete and rename files (and create new of course) -// WAD errors -#define WAD_LOAD_OK 0 -#define WAD_LOAD_COULDNT_OPEN 1 -#define WAD_LOAD_BAD_HEADER 2 -#define WAD_LOAD_BAD_FOLDERS 3 -#define WAD_LOAD_TOO_MANY_FILES 4 -#define WAD_LOAD_NO_FILES 5 -#define WAD_LOAD_CORRUPTED 6 - -// ZIP errors -#define ZIP_LOAD_OK 0 -#define ZIP_LOAD_COULDNT_OPEN 1 -#define ZIP_LOAD_BAD_HEADER 2 -#define ZIP_LOAD_BAD_FOLDERS 3 -#define ZIP_LOAD_NO_FILES 5 -#define ZIP_LOAD_CORRUPTED 6 - -typedef struct stringlist_s -{ - // maxstrings changes as needed, causing reallocation of strings[] array - int maxstrings; - int numstrings; - char **strings; -} stringlist_t; - -typedef struct wadtype_s -{ - const char *ext; - signed char type; -} wadtype_t; - -struct file_s -{ - int handle; // file descriptor - int ungetc; // single stored character from ungetc, cleared to EOF when read - fs_offset_t real_length; // uncompressed file size (for files opened in "read" mode) - fs_offset_t position; // current position in the file - fs_offset_t offset; // offset into the package (0 if external file) - time_t filetime; // pak, wad or real filetime - // contents buffer - fs_offset_t buff_ind, buff_len; // buffer current index and length - byte buff[FILE_BUFF_SIZE]; // intermediate buffer -#ifdef XASH_REDUCE_FD - const char *backup_path; - fs_offset_t backup_position; - uint backup_options; -#endif -}; - -struct wfile_s -{ - string filename; - int infotableofs; - int numlumps; - poolhandle_t mempool; // W_ReadLump temp buffers - file_t *handle; - dlumpinfo_t *lumps; - time_t filetime; -}; - -typedef struct pack_s -{ - string filename; - int handle; - int numfiles; - time_t filetime; // common for all packed files - dpackfile_t *files; -} pack_t; - -typedef struct zipfile_s -{ - char name[MAX_SYSPATH]; - fs_offset_t offset; // offset of local file header - fs_offset_t size; //original file size - fs_offset_t compressed_size; // compressed file size - unsigned short flags; -} zipfile_t; - -typedef struct zip_s -{ - string filename; - int handle; - int numfiles; - time_t filetime; - zipfile_t *files; -} zip_t; - -typedef struct searchpath_s -{ - string filename; - pack_t *pack; - wfile_t *wad; - zip_t *zip; - int flags; - struct searchpath_s *next; -} searchpath_t; - -static poolhandle_t fs_mempool; -static searchpath_t *fs_searchpaths = NULL; // chain static searchpath_t fs_directpath; // static direct path static char fs_basedir[MAX_SYSPATH]; // base game directory static char fs_gamedir[MAX_SYSPATH]; // game current directory -static char fs_writedir[MAX_SYSPATH]; // path that game allows to overwrite, delete and rename files (and create new of course) - -static qboolean fs_ext_path = false; // attempt to read\write from ./ or ../ pathes #if !XASH_WIN32 static qboolean fs_caseinsensitive = true; // try to search missing files #endif @@ -159,7 +59,6 @@ static qboolean fs_caseinsensitive = true; // try to search missing files #ifdef XASH_REDUCE_FD static file_t *fs_last_readfile; static zip_t *fs_last_zip; -static pack_t *fs_last_pak; static void FS_EnsureOpenFile( file_t *file ) { @@ -183,21 +82,6 @@ static void FS_EnsureOpenFile( file_t *file ) } } -static void FS_EnsureOpenZip( zip_t *zip ) -{ - if( fs_last_zip == zip ) - return; - - if( fs_last_zip && (fs_last_zip->handle != -1) ) - { - close( fs_last_zip->handle ); - fs_last_zip->handle = -1; - } - fs_last_zip = zip; - if( zip && (zip->handle == -1) ) - zip->handle = open( zip->filename, O_RDONLY|O_BINARY ); -} - static void FS_BackupFileName( file_t *file, const char *path, uint options ) { if( path == NULL ) @@ -217,21 +101,10 @@ static void FS_BackupFileName( file_t *file, const char *path, uint options ) #else static void FS_EnsureOpenFile( file_t *file ) {} -static void FS_EnsureOpenZip( zip_t *zip ) {} static void FS_BackupFileName( file_t *file, const char *path, uint options ) {} #endif static void FS_InitMemory( void ); -static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedironly ); -static dlumpinfo_t *W_FindLump( wfile_t *wad, const char *name, const signed char matchtype ); -static dpackfile_t *FS_AddFileToPack( const char* name, pack_t *pack, fs_offset_t offset, fs_offset_t size ); -void Zip_Close( zip_t *zip ); -static byte *W_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ); -static wfile_t *W_Open( const char *filename, int *errorcode ); -static qboolean FS_SysFolderExists( const char *path ); -static int FS_SysFileTime( const char *filename ); -static signed char W_TypeFromExt( const char *lumpname ); -static const char *W_ExtFromType( signed char lumptype ); static void FS_Purge( file_t* file ); /* @@ -265,7 +138,7 @@ static void stringlistfreecontents( stringlist_t *list ) list->strings = NULL; } -static void stringlistappend( stringlist_t *list, char *text ) +void stringlistappend( stringlist_t *list, char *text ) { size_t textlen; @@ -374,7 +247,7 @@ FS_FixFileCase emulate WIN32 FS behaviour when opening local file ================== */ -static const char *FS_FixFileCase( const char *path ) +const char *FS_FixFileCase( const char *path ) { #if defined __DOS__ & !defined __WATCOM_LFN__ // not fix, but convert to 8.3 CAPS and rotate slashes @@ -406,7 +279,8 @@ static const char *FS_FixFileCase( const char *path ) out[i] = 0; return out; #elif !XASH_WIN32 && !XASH_IOS // assume case insensitive - DIR *dir; struct dirent *entry; + DIR *dir; + struct dirent *entry; char path2[PATH_MAX], *fname; if( !fs_caseinsensitive ) @@ -427,7 +301,7 @@ static const char *FS_FixFileCase( const char *path ) } /* android has too slow directory scanning, - so drop out some not useful cases */ + so drop out some not useful cases */ if( fname - path2 > 4 ) { char *point; @@ -471,7 +345,7 @@ FS_PathToWideChar Converts input UTF-8 string to wide char string. ==================== */ -const wchar_t *FS_PathToWideChar( const char *path ) +static const wchar_t *FS_PathToWideChar( const char *path ) { static wchar_t pathBuffer[MAX_PATH]; MultiByteToWideChar( CP_UTF8, 0, path, -1, pathBuffer, MAX_PATH ); @@ -479,49 +353,6 @@ const wchar_t *FS_PathToWideChar( const char *path ) } #endif -/* -==================== -FS_AddFileToPack - -Add a file to the list of files contained into a package -==================== -*/ -static dpackfile_t *FS_AddFileToPack( const char *name, pack_t *pack, fs_offset_t offset, fs_offset_t size ) -{ - int left, right, middle; - dpackfile_t *pfile; - - // look for the slot we should put that file into (binary search) - left = 0; - right = pack->numfiles - 1; - - while( left <= right ) - { - int diff; - - middle = (left + right) / 2; - diff = Q_stricmp( pack->files[middle].name, name ); - - // If we found the file, there's a problem - if( !diff ) Con_Reportf( S_WARN "package %s contains the file %s several times\n", pack->filename, name ); - - // If we're too far in the list - if( diff > 0 ) right = middle - 1; - else left = middle + 1; - } - - // We have to move the right of the list by one slot to free the one we need - pfile = &pack->files[left]; - memmove( pfile + 1, pfile, (pack->numfiles - left) * sizeof( *pfile )); - pack->numfiles++; - - Q_strncpy( pfile->name, name, sizeof( pfile->name )); - pfile->filepos = offset; - pfile->filelen = size; - - return pfile; -} - /* ============ FS_CreatePath @@ -546,650 +377,24 @@ void FS_CreatePath( char *path ) } } -/* -============ -FS_Path_f -debug info -============ -*/ -void FS_Path_f( void ) -{ - searchpath_t *s; - - Con_Printf( "Current search path:\n" ); - - for( s = fs_searchpaths; s; s = s->next ) - { - if( s->pack ) Con_Printf( "%s (%i files)", s->pack->filename, s->pack->numfiles ); - else if( s->wad ) Con_Printf( "%s (%i files)", s->wad->filename, s->wad->numlumps ); - else if( s->zip ) Con_Printf( "%s (%i files)", s->zip->filename, s->zip->numfiles ); - else Con_Printf( "%s", s->filename ); - - if( s->flags & FS_GAMERODIR_PATH ) Con_Printf( " ^2rodir^7" ); - if( s->flags & FS_GAMEDIR_PATH ) Con_Printf( " ^2gamedir^7" ); - if( s->flags & FS_CUSTOM_PATH ) Con_Printf( " ^2custom^7" ); - if( s->flags & FS_NOWRITE_PATH ) Con_Printf( " ^2nowrite^7" ); - if( s->flags & FS_STATIC_PATH ) Con_Printf( " ^2static^7" ); - - Con_Printf( "\n" ); - } -} - -/* -============ -FS_ClearPath_f - -only for debug targets -============ -*/ -void FS_ClearPaths_f( void ) -{ - FS_ClearSearchPath(); -} - -/* -================= -FS_LoadPackPAK - -Takes an explicit (not game tree related) path to a pak file. - -Loads the header and directory, adding the files at the beginning -of the list so they override previous pack files. -================= -*/ -pack_t *FS_LoadPackPAK( const char *packfile, int *error ) -{ - dpackheader_t header; - int packhandle; - int i, numpackfiles; - pack_t *pack; - dpackfile_t *info; - - packhandle = open( packfile, O_RDONLY|O_BINARY ); - -#if !XASH_WIN32 - if( packhandle < 0 ) - { - const char *fpackfile = FS_FixFileCase( packfile ); - if( fpackfile!= packfile ) - packhandle = open( fpackfile, O_RDONLY|O_BINARY ); - } -#endif - - if( packhandle < 0 ) - { - Con_Reportf( "%s couldn't open\n", packfile ); - if( error ) *error = PAK_LOAD_COULDNT_OPEN; - return NULL; - } - - read( packhandle, (void *)&header, sizeof( header )); - - if( header.ident != IDPACKV1HEADER ) - { - Con_Reportf( "%s is not a packfile. Ignored.\n", packfile ); - if( error ) *error = PAK_LOAD_BAD_HEADER; - close( packhandle ); - return NULL; - } - - if( header.dirlen % sizeof( dpackfile_t )) - { - Con_Reportf( S_ERROR "%s has an invalid directory size. Ignored.\n", packfile ); - if( error ) *error = PAK_LOAD_BAD_FOLDERS; - close( packhandle ); - return NULL; - } - - numpackfiles = header.dirlen / sizeof( dpackfile_t ); - - if( numpackfiles > MAX_FILES_IN_PACK ) - { - Con_Reportf( S_ERROR "%s has too many files ( %i ). Ignored.\n", packfile, numpackfiles ); - if( error ) *error = PAK_LOAD_TOO_MANY_FILES; - close( packhandle ); - return NULL; - } - - if( numpackfiles <= 0 ) - { - Con_Reportf( "%s has no files. Ignored.\n", packfile ); - if( error ) *error = PAK_LOAD_NO_FILES; - close( packhandle ); - return NULL; - } - - info = (dpackfile_t *)Mem_Malloc( fs_mempool, sizeof( *info ) * numpackfiles ); - lseek( packhandle, header.dirofs, SEEK_SET ); - - if( header.dirlen != read( packhandle, (void *)info, header.dirlen )) - { - Con_Reportf( "%s is an incomplete PAK, not loading\n", packfile ); - if( error ) *error = PAK_LOAD_CORRUPTED; - close( packhandle ); - Mem_Free( info ); - return NULL; - } - - pack = (pack_t *)Mem_Calloc( fs_mempool, sizeof( pack_t )); - Q_strncpy( pack->filename, packfile, sizeof( pack->filename )); - pack->files = (dpackfile_t *)Mem_Calloc( fs_mempool, numpackfiles * sizeof( dpackfile_t )); - pack->filetime = FS_SysFileTime( packfile ); - pack->handle = packhandle; - pack->numfiles = 0; - - // parse the directory - for( i = 0; i < numpackfiles; i++ ) - FS_AddFileToPack( info[i].name, pack, info[i].filepos, info[i].filelen ); - -#ifdef XASH_REDUCE_FD - // will reopen when needed - close(pack->handle); - pack->handle = -1; -#endif - - if( error ) *error = PAK_LOAD_OK; - Mem_Free( info ); - - return pack; -} - -/* -============ -FS_SortZip -============ -*/ -static int FS_SortZip( const void *a, const void *b ) -{ - return Q_stricmp( ( ( zipfile_t* )a )->name, ( ( zipfile_t* )b )->name ); -} - -/* -============ -FS_LoadZip -============ -*/ -static zip_t *FS_LoadZip( const char *zipfile, int *error ) -{ - int numpackfiles = 0, i; - zip_cdf_header_t header_cdf; - zip_header_eocd_t header_eocd; - uint signature; - fs_offset_t filepos = 0, length; - zipfile_t *info = NULL; - char filename_buffer[MAX_SYSPATH]; - zip_t *zip = (zip_t *)Mem_Calloc( fs_mempool, sizeof( zip_t ) ); - - zip->handle = open( zipfile, O_RDONLY|O_BINARY ); - -#if !XASH_WIN32 - if( zip->handle < 0 ) - { - const char *fzipfile = FS_FixFileCase( zipfile ); - if( fzipfile != zipfile ) - zip->handle = open( fzipfile, O_RDONLY|O_BINARY ); - } -#endif - - if( zip->handle < 0 ) - { - Con_Reportf( S_ERROR "%s couldn't open\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_COULDNT_OPEN; - - Zip_Close( zip ); - return NULL; - } - - length = lseek( zip->handle, 0, SEEK_END ); - - if( length > UINT_MAX ) - { - Con_Reportf( S_ERROR "%s bigger than 4GB.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_COULDNT_OPEN; - - Zip_Close( zip ); - return NULL; - } - - lseek( zip->handle, 0, SEEK_SET ); - - read( zip->handle, &signature, sizeof( signature ) ); - - if( signature == ZIP_HEADER_EOCD ) - { - Con_Reportf( S_WARN "%s has no files. Ignored.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_NO_FILES; - - Zip_Close( zip ); - return NULL; - } - - if( signature != ZIP_HEADER_LF ) - { - Con_Reportf( S_ERROR "%s is not a zip file. Ignored.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_BAD_HEADER; - - Zip_Close( zip ); - return NULL; - } - - // Find oecd - lseek( zip->handle, 0, SEEK_SET ); - filepos = length; - - while ( filepos > 0 ) - { - lseek( zip->handle, filepos, SEEK_SET ); - read( zip->handle, &signature, sizeof( signature ) ); - - if( signature == ZIP_HEADER_EOCD ) - break; - - filepos -= sizeof( char ); // step back one byte - } - - if( ZIP_HEADER_EOCD != signature ) - { - Con_Reportf( S_ERROR "cannot find EOCD in %s. Zip file corrupted.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_BAD_HEADER; - - Zip_Close( zip ); - return NULL; - } - - read( zip->handle, &header_eocd, sizeof( zip_header_eocd_t ) ); - - // Move to CDF start - lseek( zip->handle, header_eocd.central_directory_offset, SEEK_SET ); - - // Calc count of files in archive - info = (zipfile_t *)Mem_Calloc( fs_mempool, sizeof( zipfile_t ) * header_eocd.total_central_directory_record ); - - for( i = 0; i < header_eocd.total_central_directory_record; i++ ) - { - read( zip->handle, &header_cdf, sizeof( header_cdf ) ); - - if( header_cdf.signature != ZIP_HEADER_CDF ) - { - Con_Reportf( S_ERROR "CDF signature mismatch in %s. Zip file corrupted.\n", zipfile ); - - if( error ) - *error = ZIP_LOAD_BAD_HEADER; - - Mem_Free( info ); - Zip_Close( zip ); - return NULL; - } - - if( header_cdf.uncompressed_size && header_cdf.filename_len && ( header_cdf.filename_len < MAX_SYSPATH ) ) - { - memset( &filename_buffer, '\0', MAX_SYSPATH ); - read( zip->handle, &filename_buffer, header_cdf.filename_len ); - Q_strncpy( info[numpackfiles].name, filename_buffer, MAX_SYSPATH ); - - info[numpackfiles].size = header_cdf.uncompressed_size; - info[numpackfiles].compressed_size = header_cdf.compressed_size; - info[numpackfiles].offset = header_cdf.local_header_offset; - numpackfiles++; - } - else - lseek( zip->handle, header_cdf.filename_len, SEEK_CUR ); - - if( header_cdf.extrafield_len ) - lseek( zip->handle, header_cdf.extrafield_len, SEEK_CUR ); - - if( header_cdf.file_commentary_len ) - lseek( zip->handle, header_cdf.file_commentary_len, SEEK_CUR ); - } - - // recalculate offsets - for( i = 0; i < numpackfiles; i++ ) - { - zip_header_t header; - - lseek( zip->handle, info[i].offset, SEEK_SET ); - read( zip->handle, &header, sizeof( header ) ); - info[i].flags = header.compression_flags; - info[i].offset = info[i].offset + header.filename_len + header.extrafield_len + sizeof( header ); - } - - Q_strncpy( zip->filename, zipfile, sizeof( zip->filename ) ); - zip->filetime = FS_SysFileTime( zipfile ); - zip->numfiles = numpackfiles; - zip->files = info; - - qsort( zip->files, zip->numfiles, sizeof( zipfile_t ), FS_SortZip ); - -#ifdef XASH_REDUCE_FD - // will reopen when needed - close(zip->handle); - zip->handle = -1; -#endif - - if( error ) - *error = ZIP_LOAD_OK; - - return zip; -} - -void Zip_Close( zip_t *zip ) -{ - if( !zip ) - return; - - Mem_Free( zip->files ); - - FS_EnsureOpenZip( NULL ); - - if( zip->handle >= 0 ) - close( zip->handle ); - - Mem_Free( zip ); -} - -static byte *Zip_LoadFile( const char *path, fs_offset_t *sizeptr, qboolean gamedironly ) -{ - searchpath_t *search; - int index; - zipfile_t *file = NULL; - byte *compressed_buffer = NULL, *decompressed_buffer = NULL; - int zlib_result = 0; - dword test_crc, final_crc; - z_stream decompress_stream; - - if( sizeptr ) *sizeptr = 0; - - search = FS_FindFile( path, &index, gamedironly ); - - if( !search || !search->zip ) - return NULL; - - file = &search->zip->files[index]; - - FS_EnsureOpenZip( search->zip ); - - if( lseek( search->zip->handle, file->offset, SEEK_SET ) == -1 ) - return NULL; - - /*if( read( search->zip->handle, &header, sizeof( header ) ) < 0 ) - return NULL; - - if( header.signature != ZIP_HEADER_LF ) - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s signature error\n", file->name ); - return NULL; - }*/ - - if( file->flags == ZIP_COMPRESSION_NO_COMPRESSION ) - { - decompressed_buffer = Mem_Malloc( fs_mempool, file->size + 1 ); - decompressed_buffer[file->size] = '\0'; - - read( search->zip->handle, decompressed_buffer, file->size ); -#if 0 - CRC32_Init( &test_crc ); - CRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size ); - - final_crc = CRC32_Final( test_crc ); - - if( final_crc != file->crc32 ) - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s file crc32 mismatch\n", file->name ); - Mem_Free( decompressed_buffer ); - return NULL; - } -#endif - if( sizeptr ) *sizeptr = file->size; - - FS_EnsureOpenZip( NULL ); - return decompressed_buffer; - } - else if( file->flags == ZIP_COMPRESSION_DEFLATED ) - { - compressed_buffer = Mem_Malloc( fs_mempool, file->compressed_size + 1 ); - decompressed_buffer = Mem_Malloc( fs_mempool, file->size + 1 ); - decompressed_buffer[file->size] = '\0'; - - read( search->zip->handle, compressed_buffer, file->compressed_size ); - - memset( &decompress_stream, 0, sizeof( decompress_stream ) ); - - decompress_stream.total_in = decompress_stream.avail_in = file->compressed_size; - decompress_stream.next_in = (Bytef *)compressed_buffer; - decompress_stream.total_out = decompress_stream.avail_out = file->size; - decompress_stream.next_out = (Bytef *)decompressed_buffer; - - decompress_stream.zalloc = Z_NULL; - decompress_stream.zfree = Z_NULL; - decompress_stream.opaque = Z_NULL; - - if( inflateInit2( &decompress_stream, -MAX_WBITS ) != Z_OK ) - { - Con_Printf( S_ERROR "Zip_LoadFile: inflateInit2 failed\n" ); - Mem_Free( compressed_buffer ); - Mem_Free( decompressed_buffer ); - return NULL; - } - - zlib_result = inflate( &decompress_stream, Z_NO_FLUSH ); - inflateEnd( &decompress_stream ); - - if( zlib_result == Z_OK || zlib_result == Z_STREAM_END ) - { - Mem_Free( compressed_buffer ); // finaly free compressed buffer -#if 0 - CRC32_Init( &test_crc ); - CRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size ); - - final_crc = CRC32_Final( test_crc ); - - if( final_crc != file->crc32 ) - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s file crc32 mismatch\n", file->name ); - Mem_Free( decompressed_buffer ); - return NULL; - } -#endif - if( sizeptr ) *sizeptr = file->size; - - FS_EnsureOpenZip( NULL ); - return decompressed_buffer; - } - else - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s : error while file decompressing. Zlib return code %d.\n", file->name, zlib_result ); - Mem_Free( compressed_buffer ); - Mem_Free( decompressed_buffer ); - return NULL; - } - - } - else - { - Con_Reportf( S_ERROR "Zip_LoadFile: %s : file compressed with unknown algorithm.\n", file->name ); - return NULL; - } - - FS_EnsureOpenZip( NULL ); - return NULL; -} - -/* -==================== -FS_AddWad_Fullpath -==================== -*/ -static qboolean FS_AddWad_Fullpath( const char *wadfile, qboolean *already_loaded, int flags ) -{ - searchpath_t *search; - wfile_t *wad = NULL; - const char *ext = COM_FileExtension( wadfile ); - int errorcode = WAD_LOAD_COULDNT_OPEN; - - for( search = fs_searchpaths; search; search = search->next ) - { - if( search->wad && !Q_stricmp( search->wad->filename, wadfile )) - { - if( already_loaded ) *already_loaded = true; - return true; // already loaded - } - } - - if( already_loaded ) - *already_loaded = false; - - if( !Q_stricmp( ext, "wad" )) - wad = W_Open( wadfile, &errorcode ); - - if( wad ) - { - search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); - search->wad = wad; - search->next = fs_searchpaths; - search->flags |= flags; - fs_searchpaths = search; - - Con_Reportf( "Adding wadfile: %s (%i files)\n", wadfile, wad->numlumps ); - return true; - } - else - { - if( errorcode != WAD_LOAD_NO_FILES ) - Con_Reportf( S_ERROR "FS_AddWad_Fullpath: unable to load wad \"%s\"\n", wadfile ); - return false; - } -} /* ================ -FS_AddPak_Fullpath - -Adds the given pack to the search path. -The pack type is autodetected by the file extension. - -Returns true if the file was successfully added to the -search path or if it was already included. - -If keep_plain_dirs is set, the pack will be added AFTER the first sequence of -plain directories. +FS_AddArchive_Fullpath ================ */ -static qboolean FS_AddPak_Fullpath( const char *pakfile, qboolean *already_loaded, int flags ) +static qboolean FS_AddArchive_Fullpath( const char *file, qboolean *already_loaded, int flags ) { - searchpath_t *search; - pack_t *pak = NULL; - const char *ext = COM_FileExtension( pakfile ); - int i, errorcode = PAK_LOAD_COULDNT_OPEN; - - for( search = fs_searchpaths; search; search = search->next ) - { - if( search->pack && !Q_stricmp( search->pack->filename, pakfile )) - { - if( already_loaded ) *already_loaded = true; - return true; // already loaded - } - } - - if( already_loaded ) - *already_loaded = false; - - if( !Q_stricmp( ext, "pak" )) - pak = FS_LoadPackPAK( pakfile, &errorcode ); - - if( pak ) - { - string fullpath; - - search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); - search->pack = pak; - search->next = fs_searchpaths; - search->flags |= flags; - fs_searchpaths = search; - - Con_Reportf( "Adding pakfile: %s (%i files)\n", pakfile, pak->numfiles ); - - // time to add in search list all the wads that contains in current pakfile (if do) - for( i = 0; i < pak->numfiles; i++ ) - { - if( !Q_stricmp( COM_FileExtension( pak->files[i].name ), "wad" )) - { - Q_snprintf( fullpath, MAX_STRING, "%s/%s", pakfile, pak->files[i].name ); - FS_AddWad_Fullpath( fullpath, NULL, flags ); - } - } - - return true; - } - else - { - if( errorcode != PAK_LOAD_NO_FILES ) - Con_Reportf( S_ERROR "FS_AddPak_Fullpath: unable to load pak \"%s\"\n", pakfile ); - return false; - } -} - -qboolean FS_AddZip_Fullpath( const char *zipfile, qboolean *already_loaded, int flags ) -{ - searchpath_t *search; - zip_t *zip = NULL; - const char *ext = COM_FileExtension( zipfile ); - int errorcode = ZIP_LOAD_COULDNT_OPEN; - - for( search = fs_searchpaths; search; search = search->next ) - { - if( search->pack && !Q_stricmp( search->pack->filename, zipfile )) - { - if( already_loaded ) *already_loaded = true; - return true; // already loaded - } - } - - if( already_loaded ) *already_loaded = false; + const char *ext = COM_FileExtension( file ); if( !Q_stricmp( ext, "pk3" ) ) - zip = FS_LoadZip( zipfile, &errorcode ); + return FS_AddZip_Fullpath( file, already_loaded, flags ); + else if ( !Q_stricmp( ext, "pak" )) + return FS_AddPak_Fullpath( file, already_loaded, flags ); - if( zip ) - { - string fullpath; - int i; - - search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t ) ); - search->zip = zip; - search->next = fs_searchpaths; - search->flags |= flags; - fs_searchpaths = search; - - Con_Reportf( "Adding zipfile: %s (%i files)\n", zipfile, zip->numfiles ); - - // time to add in search list all the wads that contains in current pakfile (if do) - for( i = 0; i < zip->numfiles; i++ ) - { - if( !Q_stricmp( COM_FileExtension( zip->files[i].name ), "wad" )) - { - Q_snprintf( fullpath, MAX_STRING, "%s/%s", zipfile, zip->files[i].name ); - FS_AddWad_Fullpath( fullpath, NULL, flags ); - } - } - return true; - } - else - { - if( errorcode != ZIP_LOAD_NO_FILES ) - Con_Reportf( S_ERROR "FS_AddZip_Fullpath: unable to load zip \"%s\"\n", zipfile ); - return false; - } + // skip wads, this function only meant to be used for extras + return false; } /* @@ -1226,7 +431,7 @@ void FS_AddGameDirectory( const char *dir, uint flags ) // add any Zip package in the directory for( i = 0; i < list.numstrings; i++ ) { - if( !Q_stricmp( COM_FileExtension( list.strings[i] ), "zip" ) || !Q_stricmp( COM_FileExtension( list.strings[i] ), "pk3" )) + if( !Q_stricmp( COM_FileExtension( list.strings[i] ), "pk3" ) ) { Q_sprintf( fullpath, "%s%s", dir, list.strings[i] ); FS_AddZip_Fullpath( fullpath, NULL, flags ); @@ -1253,62 +458,11 @@ void FS_AddGameDirectory( const char *dir, uint flags ) search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); Q_strncpy( search->filename, dir, sizeof ( search->filename )); search->next = fs_searchpaths; + search->type = SEARCHPATH_PLAIN; search->flags = flags; fs_searchpaths = search; } -/* -================ -FS_AddGameHierarchy -================ -*/ -void FS_AddGameHierarchy( const char *dir, uint flags ) -{ - int i; - qboolean isGameDir = flags & FS_GAMEDIR_PATH; - - GI->added = true; - - if( !COM_CheckString( dir )) - return; - - // add the common game directory - - // recursive gamedirs - // for example, czeror->czero->cstrike->valve - for( i = 0; i < SI.numgames; i++ ) - { - if( !Q_strnicmp( SI.games[i]->gamefolder, dir, 64 )) - { - Con_Reportf( "FS_AddGameHierarchy: %d %s %s\n", i, SI.games[i]->gamefolder, SI.games[i]->basedir ); - if( !SI.games[i]->added && Q_stricmp( SI.games[i]->gamefolder, SI.games[i]->basedir ) ) - { - SI.games[i]->added = true; - FS_AddGameHierarchy( SI.games[i]->basedir, flags & (~FS_GAMEDIR_PATH) ); - } - break; - } - } - - if( COM_CheckStringEmpty( host.rodir ) ) - { - // append new flags to rodir, except FS_GAMEDIR_PATH and FS_CUSTOM_PATH - uint newFlags = FS_NOWRITE_PATH | (flags & (~FS_GAMEDIR_PATH|FS_CUSTOM_PATH)); - if( isGameDir ) - newFlags |= FS_GAMERODIR_PATH; - - FS_AllowDirectPaths( true ); - FS_AddGameDirectory( va( "%s/%s/", host.rodir, dir ), newFlags ); - FS_AllowDirectPaths( false ); - } - - if( isGameDir ) - FS_AddGameDirectory( va( "%s/downloaded/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); - FS_AddGameDirectory( va( "%s/", dir ), flags ); - if( isGameDir ) - FS_AddGameDirectory( va( "%s/custom/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); -} - /* ================ FS_ClearSearchPath @@ -1331,23 +485,19 @@ void FS_ClearSearchPath( void ) } else fs_searchpaths = search->next; - if( search->pack ) + switch( search->type ) { - if( search->pack->files ) - Mem_Free( search->pack->files ); - if( search->pack->handle >= 0 ) - close( search->pack->handle ); - Mem_Free( search->pack ); - } - - if( search->wad ) - { - W_Close( search->wad ); - } - - if( search->zip ) - { - Zip_Close(search->zip); + case SEARCHPATH_PAK: + FS_ClosePAK( search->pack ); + break; + case SEARCHPATH_WAD: + FS_CloseWAD( search->wad ); + break; + case SEARCHPATH_ZIP: + FS_CloseZIP( search->zip ); + break; + default: + break; } Mem_Free( search ); @@ -1357,19 +507,18 @@ void FS_ClearSearchPath( void ) /* ==================== FS_CheckNastyPath - Return true if the path should be rejected due to one of the following: 1: path elements that are non-portable 2: path elements that would allow access to files outside the game directory, - or are just not a good idea for a mod to be using. + or are just not a good idea for a mod to be using. ==================== */ -int FS_CheckNastyPath( const char *path, qboolean isgamedir ) +int FS_CheckNastyPath (const char *path, qboolean isgamedir) { // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless if( !COM_CheckString( path )) return 2; - if( fs_ext_path ) return 0; // allow any path + if( fs_ext_path ) return 0; // allow any path // Mac: don't allow Mac-only filenames - : is a directory separator // instead of /, but we rely on / working already, so there's no reason to @@ -1378,71 +527,23 @@ int FS_CheckNastyPath( const char *path, qboolean isgamedir ) if( Q_strchr( path, ':' )) return 1; // non-portable attempt to go to root of drive // Amiga: // is parent directory - if( Q_strstr( path, "//" )) return 1; // non-portable attempt to go to parent directory + if( Q_strstr( path, "//")) return 1; // non-portable attempt to go to parent directory // all: don't allow going to parent directory (../ or /../) - if( Q_strstr( path, ".." )) return 2; // attempt to go outside the game directory + if( Q_strstr( path, "..")) return 2; // attempt to go outside the game directory // Windows and UNIXes: don't allow absolute paths - if( path[0] == '/' ) return 2; // attempt to go outside the game directory + if( path[0] == '/') return 2; // attempt to go outside the game directory // all: forbid trailing slash on gamedir - if( isgamedir && path[Q_strlen( path )-1] == '/' ) return 2; + if( isgamedir && path[Q_strlen(path)-1] == '/' ) return 2; // all: forbid leading dot on any filename for any reason - if( Q_strstr( path, "/." )) return 2; // attempt to go outside the game directory + if( Q_strstr(path, "/.")) return 2; // attempt to go outside the game directory // after all these checks we're pretty sure it's a / separated filename // and won't do much if any harm - return 0; -} - -/* -================ -FS_Rescan -================ -*/ -void FS_Rescan( void ) -{ - const char *str; - const int extrasFlags = FS_NOWRITE_PATH | FS_CUSTOM_PATH; - Con_Reportf( "FS_Rescan( %s )\n", GI->title ); - - FS_ClearSearchPath(); - -#if XASH_IOS - { - FS_AddPak_Fullpath( va( "%sextras.pak", SDL_GetBasePath() ), NULL, extrasFlags ); - FS_AddPak_Fullpath( va( "%sextras_%s.pak", SDL_GetBasePath(), GI->gamefolder ), NULL, extrasFlags ); - } -#else - str = getenv( "XASH3D_EXTRAS_PAK1" ); - if( COM_CheckString( str ) ) - FS_AddPak_Fullpath( str, NULL, extrasFlags ); - str = getenv( "XASH3D_EXTRAS_PAK2" ); - if( COM_CheckString( str ) ) - FS_AddPak_Fullpath( str, NULL, extrasFlags ); -#endif - - if( Q_stricmp( GI->basedir, GI->gamefolder )) - FS_AddGameHierarchy( GI->basedir, 0 ); - if( Q_stricmp( GI->basedir, GI->falldir ) && Q_stricmp( GI->gamefolder, GI->falldir )) - FS_AddGameHierarchy( GI->falldir, 0 ); - FS_AddGameHierarchy( GI->gamefolder, FS_GAMEDIR_PATH ); - - if( FS_FileExists( va( "%s.rc", SI.basedirName ), false )) - Q_strncpy( SI.rcName, SI.basedirName, sizeof( SI.rcName )); // e.g. valve.rc - else Q_strncpy( SI.rcName, SI.exeName, sizeof( SI.rcName )); // e.g. quake.rc -} - -/* -================ -FS_Rescan_f -================ -*/ -void FS_Rescan_f( void ) -{ - FS_Rescan(); + return false; } /* @@ -1791,6 +892,11 @@ void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, const qbool pfile = COM_ParseFile( pfile, token, sizeof( token )); GameInfo->noskills = Q_atoi( token ); } + else if( !Q_stricmp( token, "render_picbutton_text" )) + { + pfile = COM_ParseFile( pfile, token, sizeof( token )); + GameInfo->render_picbutton_text = Q_atoi( token ); + } } } @@ -1809,7 +915,7 @@ void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, const qbool } // make sure what gamedir is really exist - if( !FS_SysFolderExists( va( "%s"PATH_SPLITTER"%s", host.rootdir, GameInfo->falldir ))) + if( !FS_SysFolderExists( va( "%s"PATH_SPLITTER"%s", fs_rootdir, GameInfo->falldir ))) GameInfo->falldir[0] = '\0'; } @@ -1934,15 +1040,15 @@ static qboolean FS_ParseGameInfo( const char *gamedir, gameinfo_t *GameInfo ) Q_snprintf( liblist_path, sizeof( liblist_path ), "%s/liblist.gam", gamedir ); // here goes some RoDir magic... - if( COM_CheckStringEmpty( host.rodir ) ) + if( COM_CheckStringEmpty( fs_rodir ) ) { string filepath_ro, liblist_ro; fs_offset_t roLibListTime, roGameInfoTime, rwGameInfoTime; FS_AllowDirectPaths( true ); - Q_snprintf( filepath_ro, sizeof( filepath_ro ), "%s/%s/gameinfo.txt", host.rodir, gamedir ); - Q_snprintf( liblist_ro, sizeof( liblist_ro ), "%s/%s/liblist.gam", host.rodir, gamedir ); + Q_snprintf( filepath_ro, sizeof( filepath_ro ), "%s/%s/gameinfo.txt", fs_rodir, gamedir ); + Q_snprintf( liblist_ro, sizeof( liblist_ro ), "%s/%s/liblist.gam", fs_rodir, gamedir ); roLibListTime = FS_SysFileTime( liblist_ro ); roGameInfoTime = FS_SysFileTime( filepath_ro ); @@ -1999,6 +1105,93 @@ static qboolean FS_ParseGameInfo( const char *gamedir, gameinfo_t *GameInfo ) return false; } +/* +================ +FS_AddGameHierarchy +================ +*/ +void FS_AddGameHierarchy( const char *dir, uint flags ) +{ + int i; + qboolean isGameDir = flags & FS_GAMEDIR_PATH; + + GI->added = true; + + if( !COM_CheckString( dir )) + return; + + // add the common game directory + + // recursive gamedirs + // for example, czeror->czero->cstrike->valve + for( i = 0; i < FI.numgames; i++ ) + { + if( !Q_strnicmp( FI.games[i]->gamefolder, dir, 64 )) + { + Con_Reportf( "FS_AddGameHierarchy: %d %s %s\n", i, FI.games[i]->gamefolder, FI.games[i]->basedir ); + if( !FI.games[i]->added && Q_stricmp( FI.games[i]->gamefolder, FI.games[i]->basedir )) + { + FI.games[i]->added = true; + FS_AddGameHierarchy( FI.games[i]->basedir, flags & (~FS_GAMEDIR_PATH) ); + } + break; + } + } + + if( COM_CheckStringEmpty( fs_rodir ) ) + { + // append new flags to rodir, except FS_GAMEDIR_PATH and FS_CUSTOM_PATH + uint newFlags = FS_NOWRITE_PATH | (flags & (~FS_GAMEDIR_PATH|FS_CUSTOM_PATH)); + if( isGameDir ) + newFlags |= FS_GAMERODIR_PATH; + + FS_AllowDirectPaths( true ); + FS_AddGameDirectory( va( "%s/%s/", fs_rodir, dir ), newFlags ); + FS_AllowDirectPaths( false ); + } + + if( isGameDir ) + FS_AddGameDirectory( va( "%s/downloaded/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); + FS_AddGameDirectory( va( "%s/", dir ), flags ); + if( isGameDir ) + FS_AddGameDirectory( va( "%s/custom/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); +} + +/* +================ +FS_Rescan +================ +*/ +void FS_Rescan( void ) +{ + const char *str; + const int extrasFlags = FS_NOWRITE_PATH | FS_CUSTOM_PATH; + Con_Reportf( "FS_Rescan( %s )\n", GI->title ); + + FS_ClearSearchPath(); + +#if XASH_IOS + { + FS_AddPak_Fullpath( va( "%sextras.pak", SDL_GetBasePath() ), NULL, extrasFlags ); + FS_AddPak_Fullpath( va( "%sextras_%s.pak", SDL_GetBasePath(), GI->gamefolder ), NULL, extrasFlags ); + } +#else + str = getenv( "XASH3D_EXTRAS_PAK1" ); + if( COM_CheckString( str )) + FS_AddArchive_Fullpath( str, NULL, extrasFlags ); + + str = getenv( "XASH3D_EXTRAS_PAK2" ); + if( COM_CheckString( str )) + FS_AddArchive_Fullpath( str, NULL, extrasFlags ); +#endif + + if( Q_stricmp( GI->basedir, GI->gamefolder )) + FS_AddGameHierarchy( GI->basedir, 0 ); + if( Q_stricmp( GI->basedir, GI->falldir ) && Q_stricmp( GI->gamefolder, GI->falldir )) + FS_AddGameHierarchy( GI->falldir, 0 ); + FS_AddGameHierarchy( GI->gamefolder, FS_GAMEDIR_PATH ); +} + /* ================ FS_LoadGameInfo @@ -2020,39 +1213,176 @@ void FS_LoadGameInfo( const char *rootfolder ) FS_ClearSearchPath(); // validate gamedir - for( i = 0; i < SI.numgames; i++ ) + for( i = 0; i < FI.numgames; i++ ) { - if( !Q_stricmp( SI.games[i]->gamefolder, fs_gamedir )) + if( !Q_stricmp( FI.games[i]->gamefolder, fs_gamedir )) break; } - if( i == SI.numgames ) + if( i == FI.numgames ) Sys_Error( "Couldn't find game directory '%s'\n", fs_gamedir ); - SI.GameInfo = SI.games[i]; - - if( !Sys_GetParmFromCmdLine( "-dll", SI.gamedll ) ) - { - SI.gamedll[0] = 0; - } - - if( !Sys_GetParmFromCmdLine( "-clientlib", SI.clientlib ) ) - { - SI.clientlib[0] = 0; - } + FI.GameInfo = FI.games[i]; FS_Rescan(); // create new filesystem - - Image_CheckPaletteQ1 (); - Host_InitDecals (); // reload decals } +/* +================== +FS_CheckForCrypt + +return true if library is crypted +================== +*/ +static qboolean FS_CheckForCrypt( const char *dllname ) +{ + file_t *f; + int key; + + f = FS_Open( dllname, "rb", false ); + if( !f ) return false; + + FS_Seek( f, 64, SEEK_SET ); // skip first 64 bytes + FS_Read( f, &key, sizeof( key )); + FS_Close( f ); + + return ( key == 0x12345678 ) ? true : false; +} + +/* +================== +FS_FindLibrary + +search for library, assume index is valid +================== +*/ +static qboolean FS_FindLibrary( const char *dllname, qboolean directpath, fs_dllinfo_t *dllInfo ) +{ + searchpath_t *search; + int index, start = 0, i, len; + + fs_ext_path = directpath; + + // check for bad exports + if( !COM_CheckString( dllname )) + return false; + + // HACKHACK remove absoulte path to valve folder + if( !Q_strnicmp( dllname, "..\\valve\\", 9 ) || !Q_strnicmp( dllname, "../valve/", 9 )) + start += 9; + + // replace all backward slashes + len = Q_strlen( dllname ); + + for( i = 0; i < len; i++ ) + { + if( dllname[i+start] == '\\' ) dllInfo->shortPath[i] = '/'; + else dllInfo->shortPath[i] = Q_tolower( dllname[i+start] ); + } + dllInfo->shortPath[i] = '\0'; + + COM_DefaultExtension( dllInfo->shortPath, "."OS_LIB_EXT ); // apply ext if forget + + search = FS_FindFile( dllInfo->shortPath, &index, false ); + + if( !search && !directpath ) + { + fs_ext_path = false; + + // trying check also 'bin' folder for indirect paths + Q_strncpy( dllInfo->shortPath, dllname, sizeof( dllInfo->shortPath )); + search = FS_FindFile( dllInfo->shortPath, &index, false ); + if( !search ) return false; // unable to find + } + + dllInfo->encrypted = FS_CheckForCrypt( dllInfo->shortPath ); + + if( index < 0 && !dllInfo->encrypted && search ) + { + Q_snprintf( dllInfo->fullPath, sizeof( dllInfo->fullPath ), + "%s%s", search->filename, dllInfo->shortPath ); + dllInfo->custom_loader = false; // we can loading from disk and use normal debugging + } + else + { + // NOTE: if search is NULL let the OS found library himself + Q_strncpy( dllInfo->fullPath, dllInfo->shortPath, sizeof( dllInfo->fullPath )); + + if( search && search->type != SEARCHPATH_PLAIN ) + { +#if XASH_WIN32 && XASH_X86 // a1ba: custom loader is non-portable (I just don't want to touch it) + Con_Printf( S_WARN "%s: loading libraries from packs is deprecated " + "and will be removed in the future\n", __FUNCTION__ ); + dllInfo->custom_loader = true; +#else + Con_Printf( S_WARN "%s: loading libraries from packs is unsupported on " + "this platform\n", __FUNCTION__ ); + dllInfo->custom_loader = false; +#endif + } + else + { + dllInfo->custom_loader = false; + } + } + fs_ext_path = false; // always reset direct paths + + return true; +} + +poolhandle_t _Mem_AllocPool( const char *name, const char *filename, int fileline ) +{ + return 0xDEADC0DE; +} + +void _Mem_FreePool( poolhandle_t *poolptr, const char *filename, int fileline ) +{ + // stub +} + +void* _Mem_Alloc( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline ) +{ + if( clear ) return calloc( 1, size ); + return malloc( size ); +} + +void* _Mem_Realloc( poolhandle_t poolptr, void *memptr, size_t size, qboolean clear, const char *filename, int fileline ) +{ + return realloc( memptr, size ); +} + +void _Mem_Free( void *data, const char *filename, int fileline ) +{ + free( data ); +} + +void _Con_Printf( const char *fmt, ... ) +{ + va_list ap; + + va_start( ap, fmt ); + vprintf( fmt, ap ); + va_end( ap ); +} + +void _Sys_Error( const char *fmt, ... ) +{ + va_list ap; + + va_start( ap, fmt ); + vfprintf( stderr, fmt, ap ); + va_end( ap ); + + exit( 1 ); +} + + /* ================ FS_Init ================ */ -void FS_Init( void ) +qboolean FS_InitStdio( qboolean caseinsensitive, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir ) { stringlist_t dirs; qboolean hasBaseDir = false; @@ -2060,63 +1390,39 @@ void FS_Init( void ) int i; FS_InitMemory(); - - Cmd_AddRestrictedCommand( "fs_rescan", FS_Rescan_f, "rescan filesystem search pathes" ); - Cmd_AddRestrictedCommand( "fs_path", FS_Path_f, "show filesystem search pathes" ); - Cmd_AddRestrictedCommand( "fs_clearpaths", FS_ClearPaths_f, "clear filesystem search pathes" ); - #if !XASH_WIN32 - if( Sys_CheckParm( "-casesensitive" ) ) - fs_caseinsensitive = false; - - if( !fs_caseinsensitive ) - { - if( COM_CheckStringEmpty( host.rodir ) && !Q_strcmp( host.rodir, host.rootdir ) ) - { - Sys_Error( "RoDir and default rootdir can't point to same directory!" ); - } - } - else + fs_caseinsensitive = caseinsensitive; #endif - { - if( COM_CheckStringEmpty( host.rodir ) && !Q_stricmp( host.rodir, host.rootdir ) ) - { - Sys_Error( "RoDir and default rootdir can't point to same directory!" ); - } - } - // ignore commandlineoption "-game" for other stuff - SI.numgames = 0; - - Q_strncpy( fs_basedir, SI.basedirName, sizeof( fs_basedir )); // default dir - - if( !Sys_GetParmFromCmdLine( "-game", fs_gamedir )) - Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir )); // gamedir == basedir - - if( FS_CheckNastyPath( fs_basedir, true )) - { - // this is completely fatal... - Sys_Error( "invalid base directory \"%s\"\n", fs_basedir ); - } - - if( FS_CheckNastyPath( fs_gamedir, true )) - { - Con_Printf( S_ERROR "invalid game directory \"%s\"\n", fs_gamedir ); - Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir )); // default dir - } + Q_strncpy( fs_rootdir, rootdir, sizeof( fs_rootdir )); + Q_strncpy( fs_gamedir, gamedir, sizeof( fs_gamedir )); + Q_strncpy( fs_basedir, basedir, sizeof( fs_basedir )); + Q_strncpy( fs_rodir, rodir, sizeof( fs_rodir )); // add readonly directories first - if( COM_CheckStringEmpty( host.rodir ) ) + if( COM_CheckStringEmpty( fs_rodir )) { + if( !Q_stricmp( fs_rodir, fs_rootdir )) + { + Sys_Error( "RoDir and default rootdir can't point to same directory!" ); + return false; + } + stringlistinit( &dirs ); - listdirectory( &dirs, host.rodir, false ); + listdirectory( &dirs, fs_rodir, false ); stringlistsort( &dirs ); for( i = 0; i < dirs.numstrings; i++ ) { - // no need to check folders here, FS_CreatePath will not fail if path exists - // and listdirectory returns only really existing directories - FS_CreatePath( va( "%s" PATH_SPLITTER "%s/", host.rootdir, dirs.strings[i] )); + char *roPath = va( "%s" PATH_SPLITTER "%s" PATH_SPLITTER, fs_rodir, dirs.strings[i] ); + char *rwPath = va( "%s" PATH_SPLITTER "%s" PATH_SPLITTER, fs_rootdir, dirs.strings[i] ); + + // check if it's a directory + if( !FS_SysFolderExists( roPath )) + continue; + + // no need to check folders here, FS_CreatePath will not fail + FS_CreatePath( rwPath ); } stringlistfreecontents( &dirs ); @@ -2150,15 +1456,17 @@ void FS_Init( void ) if( !FS_SysFolderExists( dirs.strings[i] ) || ( !Q_strcmp( dirs.strings[i], ".." ) && !fs_ext_path )) continue; - if( SI.games[SI.numgames] == NULL ) - SI.games[SI.numgames] = (gameinfo_t *)Mem_Calloc( fs_mempool, sizeof( gameinfo_t )); + if( FI.games[FI.numgames] == NULL ) + FI.games[FI.numgames] = (gameinfo_t *)Mem_Calloc( fs_mempool, sizeof( gameinfo_t )); - if( FS_ParseGameInfo( dirs.strings[i], SI.games[SI.numgames] )) - SI.numgames++; // added + if( FS_ParseGameInfo( dirs.strings[i], FI.games[FI.numgames] )) + FI.numgames++; // added } stringlistfreecontents( &dirs ); Con_Reportf( "FS_Init: done\n" ); + + return true; } void FS_AllowDirectPaths( qboolean enable ) @@ -2171,20 +1479,62 @@ void FS_AllowDirectPaths( qboolean enable ) FS_Shutdown ================ */ -void FS_Shutdown( void ) +void FS_ShutdownStdio( void ) { - int i; - + int i; // release gamedirs - for( i = 0; i < SI.numgames; i++ ) - if( SI.games[i] ) Mem_Free( SI.games[i] ); - - memset( &SI, 0, sizeof( sysinfo_t )); + for( i = 0; i < FI.numgames; i++ ) + if( FI.games[i] ) Mem_Free( FI.games[i] ); FS_ClearSearchPath(); // release all wad files too Mem_FreePool( &fs_mempool ); } +/* +============ +FS_Path_f + +debug info +============ +*/ +void FS_Path_f( void ) +{ + searchpath_t *s; + + Con_Printf( "Current search path:\n" ); + + for( s = fs_searchpaths; s; s = s->next ) + { + string info; + + switch( s->type ) + { + case SEARCHPATH_PAK: + FS_PrintPAKInfo( info, sizeof( info ), s->pack ); + break; + case SEARCHPATH_WAD: + FS_PrintWADInfo( info, sizeof( info ), s->wad ); + break; + case SEARCHPATH_ZIP: + FS_PrintZIPInfo( info, sizeof( info ), s->zip ); + break; + case SEARCHPATH_PLAIN: + Q_strncpy( info, s->filename, sizeof( info )); + break; + } + + Con_Printf( "%s", info ); + + if( s->flags & FS_GAMERODIR_PATH ) Con_Printf( " ^2rodir^7" ); + if( s->flags & FS_GAMEDIR_PATH ) Con_Printf( " ^2gamedir^7" ); + if( s->flags & FS_CUSTOM_PATH ) Con_Printf( " ^2custom^7" ); + if( s->flags & FS_NOWRITE_PATH ) Con_Printf( " ^2nowrite^7" ); + if( s->flags & FS_STATIC_PATH ) Con_Printf( " ^2static^7" ); + + Con_Printf( "\n" ); + } +} + /* ==================== FS_SysFileTime @@ -2192,7 +1542,7 @@ FS_SysFileTime Internal function used to determine filetime ==================== */ -static int FS_SysFileTime( const char *filename ) +int FS_SysFileTime( const char *filename ) { struct stat buf; @@ -2209,7 +1559,7 @@ FS_SysOpen Internal function used to create a file_t and open the relevant non-packed file on disk ==================== */ -static file_t *FS_SysOpen( const char *filepath, const char *mode ) +file_t *FS_SysOpen( const char *filepath, const char *mode ) { file_t *file; int mod, opt; @@ -2308,7 +1658,7 @@ static int FS_DuplicateHandle( const char *filename, int handle, fs_offset_t pos } */ -static file_t *FS_OpenHandle( const char *syspath, int handle, fs_offset_t offset, fs_offset_t len ) +file_t *FS_OpenHandle( const char *syspath, int handle, fs_offset_t offset, fs_offset_t len ) { file_t *file = (file_t *)Mem_Calloc( fs_mempool, sizeof( file_t )); #ifndef XASH_REDUCE_FD @@ -2339,44 +1689,6 @@ static file_t *FS_OpenHandle( const char *syspath, int handle, fs_offset_t offse return file; } -/* -=========== -FS_OpenPackedFile - -Open a packed file using its package file descriptor -=========== -*/ -file_t *FS_OpenPackedFile( pack_t *pack, int pack_ind ) -{ - dpackfile_t *pfile; - - pfile = &pack->files[pack_ind]; - - return FS_OpenHandle( pack->filename, pack->handle, pfile->filepos, pfile->filelen ); -} - -/* -=========== -FS_OpenZipFile - -Open a packed file using its package file descriptor -=========== -*/ -file_t *FS_OpenZipFile( zip_t *zip, int pack_ind ) -{ - zipfile_t *pfile; - pfile = &zip->files[pack_ind]; - - // compressed files handled in Zip_LoadFile - if( pfile->flags != ZIP_COMPRESSION_NO_COMPRESSION ) - { - Con_Printf( S_ERROR "%s: can't open compressed file %s\n", __FUNCTION__, pfile->name ); - return NULL; - } - - return FS_OpenHandle( zip->filename, zip->handle, pfile->offset, pfile->size ); -} - /* ================== FS_SysFileExists @@ -2394,7 +1706,7 @@ qboolean FS_SysFileExists( const char *path, qboolean caseinsensitive ) close( desc ); return true; -#else +#elif XASH_POSIX int ret; struct stat buf; @@ -2412,6 +1724,8 @@ qboolean FS_SysFileExists( const char *path, qboolean caseinsensitive ) return false; return S_ISREG( buf.st_mode ); +#else +#error #endif } @@ -2446,23 +1760,15 @@ qboolean FS_SysFolderExists( const char *path ) DWORD dwFlags = GetFileAttributes( path ); return ( dwFlags != -1 ) && ( dwFlags & FILE_ATTRIBUTE_DIRECTORY ); -#else - DIR *dir = opendir( path ); +#elif XASH_POSIX + struct stat buf; - if( dir ) - { - closedir( dir ); - return true; - } - else if( (errno == ENOENT) || (errno == ENOTDIR) ) - { + if( stat( path, &buf ) < 0 ) return false; - } - else - { - Con_Reportf( S_ERROR "FS_SysFolderExists: problem while opening dir: %s\n", strerror( errno ) ); - return false; - } + + return S_ISDIR( buf.st_mode ); +#else +#error #endif } @@ -2476,7 +1782,7 @@ Return the searchpath where the file was found (or NULL) and the file index in the package if relevant ==================== */ -static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedironly ) +searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedironly ) { searchpath_t *search; char *pEnvPath; @@ -2488,107 +1794,31 @@ static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedir continue; // is the element a pak file? - if( search->pack ) + if( search->type == SEARCHPATH_PAK ) { - int left, right, middle; - pack_t *pak; - - pak = search->pack; - - // look for the file (binary search) - left = 0; - right = pak->numfiles - 1; - while( left <= right ) + int pack_ind = FS_FindFilePAK( search->pack, name ); + if( pack_ind >= 0 ) { - int diff; - - middle = (left + right) / 2; - diff = Q_stricmp( pak->files[middle].name, name ); - - // Found it - if( !diff ) - { - if( index ) *index = middle; - return search; - } - - // if we're too far in the list - if( diff > 0 ) - right = middle - 1; - else left = middle + 1; - } - } - else if( search->wad ) - { - dlumpinfo_t *lump; - signed char type = W_TypeFromExt( name ); - qboolean anywadname = true; - string wadname, wadfolder; - string shortname; - - // quick reject by filetype - if( type == TYP_NONE ) continue; - COM_ExtractFilePath( name, wadname ); - wadfolder[0] = '\0'; - - if( COM_CheckStringEmpty( wadname ) ) - { - COM_FileBase( wadname, wadname ); - Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); - COM_DefaultExtension( wadname, ".wad" ); - anywadname = false; - } - - // make wadname from wad fullpath - COM_FileBase( search->wad->filename, shortname ); - COM_DefaultExtension( shortname, ".wad" ); - - // quick reject by wadname - if( !anywadname && Q_stricmp( wadname, shortname )) - continue; - - // NOTE: we can't using long names for wad, - // because we using original wad names[16]; - COM_FileBase( name, shortname ); - - lump = W_FindLump( search->wad, shortname, type ); - - if( lump ) - { - if( index ) - *index = lump - search->wad->lumps; + if( index ) *index = pack_ind; return search; } } - else if( search->zip ) + else if( search->type == SEARCHPATH_WAD ) { - int left, right, middle; - zip_t *zip; - - zip = search->zip; - - // look for the file (binary search) - left = 0; - right = zip->numfiles - 1; - - while( left <= right ) + int pack_ind = FS_FindFileWAD( search->wad, name ); + if( pack_ind >= 0 ) { - int diff; - - middle = (left + right) / 2; - diff = Q_stricmp( zip->files[middle].name, name ); - - // Found it - if( !diff ) - { - if( index ) *index = middle; - return search; - } - - // if we're too far in the list - if( diff > 0 ) - right = middle - 1; - else left = middle + 1; + if( index ) *index = pack_ind; + return search; + } + } + else if( search->type == SEARCHPATH_ZIP ) + { + int pack_ind = FS_FindFileZIP( search->zip, name ); + if( pack_ind >= 0 ) + { + if( index ) *index = pack_ind; + return search; } } else @@ -2614,7 +1844,7 @@ static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedir memset( search, 0, sizeof( searchpath_t )); // root folder has a more priority than netpath - Q_strncpy( search->filename, host.rootdir, sizeof( search->filename )); + Q_strncpy( search->filename, fs_rootdir, sizeof( search->filename )); Q_strcat( search->filename, PATH_SPLITTER ); Q_snprintf( netpath, MAX_SYSPATH, "%s%s", search->filename, name ); @@ -2624,26 +1854,6 @@ static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedir *index = -1; return search; } - -#if 0 - // search for environment path - while( ( pEnvPath = getenv( "Path" ) ) ) - { - char *end = Q_strchr( pEnvPath, ';' ); - if( !end ) break; - Q_strncpy( search->filename, pEnvPath, (end - pEnvPath) + 1 ); - Q_strcat( search->filename, PATH_SPLITTER ); - Q_snprintf( netpath, MAX_SYSPATH, "%s%s", search->filename, name ); - - if( FS_SysFileExists( netpath, !( search->flags & FS_CUSTOM_PATH ) )) - { - if( index != NULL ) - *index = -1; - return search; - } - pEnvPath += (end - pEnvPath) + 1; // move pointer - } -#endif // 0 } if( index != NULL ) @@ -2671,19 +1881,23 @@ file_t *FS_OpenReadFile( const char *filename, const char *mode, qboolean gamedi if( search == NULL ) return NULL; - if( search->pack ) - return FS_OpenPackedFile( search->pack, pack_ind ); - else if( search->wad ) - return NULL; // let W_LoadFile get lump correctly - else if( search->zip ) - return FS_OpenZipFile( search->zip, pack_ind ); - else if( pack_ind < 0 ) + switch( search->type ) { - char path [MAX_SYSPATH]; + case SEARCHPATH_PAK: + return FS_OpenPackedFile( search->pack, pack_ind ); + case SEARCHPATH_WAD: + return NULL; // let W_LoadFile get lump correctly + case SEARCHPATH_ZIP: + return FS_OpenZipFile( search->zip, pack_ind ); + default: + if( pack_ind < 0 ) + { + char path [MAX_SYSPATH]; - // found in the filesystem? - Q_sprintf( path, "%s%s", search->filename, filename ); - return FS_SysOpen( path, mode ); + // found in the filesystem? + Q_sprintf( path, "%s%s", search->filename, filename ); + return FS_SysOpen( path, mode ); + } } return NULL; @@ -2751,6 +1965,32 @@ int FS_Close( file_t *file ) return 0; } +/* +==================== +FS_Flush + +flushes written data to disk +==================== +*/ +int FS_Flush( file_t *file ) +{ + if( !file ) return 0; + + // purge cached data + FS_Purge( file ); + + // sync +#if XASH_POSIX + if( fsync( file->handle ) < 0 ) + return EOF; +#else + if( _commit( file->handle ) < 0 ) + return EOF; +#endif + + return 0; +} + /* ==================== FS_Write @@ -3078,7 +2318,7 @@ FS_Purge Erases any buffered input or output data ==================== */ -void FS_Purge( file_t *file ) +static void FS_Purge( file_t *file ) { file->buff_len = 0; file->buff_ind = 0; @@ -3112,10 +2352,10 @@ byte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamediro } else { - buf = W_LoadFile( path, &filesize, gamedironly ); + buf = FS_LoadWADFile( path, &filesize, gamedironly ); if( !buf ) - buf = Zip_LoadFile( path, &filesize, gamedironly ); + buf = FS_LoadZIPFile( path, &filesize, gamedironly ); } @@ -3134,7 +2374,6 @@ qboolean CRC32_File( dword *crcvalue, const char *filename ) f = FS_Open( filename, "rb", false ); if( !f ) return false; - Assert( crcvalue != NULL ); CRC32_Init( crcvalue ); while( 1 ) @@ -3293,122 +2532,6 @@ const char *FS_GetDiskPath( const char *name, qboolean gamedironly ) return NULL; } -/* -================== -FS_CheckForCrypt - -return true if library is crypted -================== -*/ -qboolean FS_CheckForCrypt( const char *dllname ) -{ - file_t *f; - int key; - - f = FS_Open( dllname, "rb", false ); - if( !f ) return false; - - FS_Seek( f, 64, SEEK_SET ); // skip first 64 bytes - FS_Read( f, &key, sizeof( key )); - FS_Close( f ); - - return ( key == 0x12345678 ) ? true : false; -} - -/* -================== -FS_FindLibrary - -search for library, assume index is valid -only for internal use -================== -*/ -dll_user_t *FS_FindLibrary( const char *dllname, qboolean directpath ) -{ - string dllpath; - searchpath_t *search; - dll_user_t *hInst; - int i, index; - int start = 0; - int len; - - // check for bad exports - if( !COM_CheckString( dllname )) - return NULL; - - fs_ext_path = directpath; - - // HACKHACK remove absoulte path to valve folder - if( !Q_strnicmp( dllname, "..\\valve\\", 9 ) || !Q_strnicmp( dllname, "../valve/", 9 )) - start += 9; - - // replace all backward slashes - len = Q_strlen( dllname ); - - for( i = 0; i < len; i++ ) - { - if( dllname[i+start] == '\\' ) dllpath[i] = '/'; - else dllpath[i] = Q_tolower( dllname[i+start] ); - } - dllpath[i] = '\0'; - - COM_DefaultExtension( dllpath, "."OS_LIB_EXT ); // apply ext if forget - search = FS_FindFile( dllpath, &index, false ); - - if( !search && !directpath ) - { - fs_ext_path = false; - - // trying check also 'bin' folder for indirect paths - Q_strncpy( dllpath, dllname, sizeof( dllpath )); - search = FS_FindFile( dllpath, &index, false ); - if( !search ) return NULL; // unable to find - } - - // NOTE: for libraries we not fail even if search is NULL - // let the OS find library himself - hInst = Mem_Calloc( host.mempool, sizeof( dll_user_t )); - - // save dllname for debug purposes - Q_strncpy( hInst->dllName, dllname, sizeof( hInst->dllName )); - - // shortPath is used for LibraryLoadSymbols only - Q_strncpy( hInst->shortPath, dllpath, sizeof( hInst->shortPath )); - - hInst->encrypted = FS_CheckForCrypt( dllpath ); - - if( index < 0 && !hInst->encrypted && search ) - { - Q_snprintf( hInst->fullPath, sizeof( hInst->fullPath ), "%s%s", search->filename, dllpath ); - hInst->custom_loader = false; // we can loading from disk and use normal debugging - } - else - { - // NOTE: if search is NULL let the OS found library himself - Q_strncpy( hInst->fullPath, dllpath, sizeof( hInst->fullPath )); - - if( search && ( search->wad || search->pack || search->zip ) ) - { -#if XASH_WIN32 && XASH_X86 // a1ba: custom loader is non-portable (I just don't want to touch it) - Con_Printf( S_WARN "%s: loading libraries from packs is deprecated " - "and will be removed in the future\n", __FUNCTION__ ); - hInst->custom_loader = true; -#else - Con_Printf( S_WARN "%s: loading libraries from packs is unsupported on " - "this platform\n", __FUNCTION__ ); - hInst->custom_loader = false; -#endif - } - else - { - hInst->custom_loader = false; - } - } - fs_ext_path = false; // always reset direct paths - - return hInst; -} - /* ================== FS_FileSize @@ -3462,19 +2585,23 @@ int FS_FileTime( const char *filename, qboolean gamedironly ) search = FS_FindFile( filename, &pack_ind, gamedironly ); if( !search ) return -1; // doesn't exist - if( search->pack ) // grab pack filetime - return search->pack->filetime; - else if( search->wad ) // grab wad filetime - return search->wad->filetime; - else if( search->zip ) - return search->zip->filetime; - else if( pack_ind < 0 ) + switch( search->type ) { - // found in the filesystem? - char path [MAX_SYSPATH]; + case SEARCHPATH_PAK: + return FS_FileTimePAK( search->pack ); + case SEARCHPATH_WAD: + return FS_FileTimeWAD( search->wad ); + case SEARCHPATH_ZIP: + return FS_FileTimeZIP( search->zip ); + default: + if( pack_ind < 0 ) + { + char path [MAX_SYSPATH]; - Q_sprintf( path, "%s%s", search->filename, filename ); - return FS_SysFileTime( path ); + // found in the filesystem? + Q_sprintf( path, "%s%s", search->filename, filename ); + return FS_SysFileTime( path ); + } } return -1; // doesn't exist @@ -3575,6 +2702,7 @@ search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ) searchpath_t *searchpath; pack_t *pak; wfile_t *wad; + zip_t *zip; int i, basepathlength, numfiles, numchars; int resultlistindex, dirlistindex; const char *slash, *backslash, *colon, *separator; @@ -3591,8 +2719,8 @@ search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ) slash = Q_strrchr( pattern, '/' ); backslash = Q_strrchr( pattern, '\\' ); colon = Q_strrchr( pattern, ':' ); - separator = max( slash, backslash ); - separator = max( separator, colon ); + separator = Q_max( slash, backslash ); + separator = Q_max( separator, colon ); basepathlength = separator ? (separator + 1 - pattern) : 0; basepath = Mem_Calloc( fs_mempool, basepathlength + 1 ); if( basepathlength ) memcpy( basepath, pattern, basepathlength ); @@ -3605,118 +2733,18 @@ search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ) continue; // is the element a pak file? - if( searchpath->pack ) + if( searchpath->type == SEARCHPATH_PAK ) { // look through all the pak file elements - pak = searchpath->pack; - for( i = 0; i < pak->numfiles; i++ ) - { - Q_strncpy( temp, pak->files[i].name, sizeof( temp )); - while( temp[0] ) - { - if( matchpattern( temp, (char *)pattern, true )) - { - for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) - { - if( !Q_strcmp( resultlist.strings[resultlistindex], temp )) - break; - } - - if( resultlistindex == resultlist.numstrings ) - stringlistappend( &resultlist, temp ); - } - - // strip off one path element at a time until empty - // this way directories are added to the listing if they match the pattern - slash = Q_strrchr( temp, '/' ); - backslash = Q_strrchr( temp, '\\' ); - colon = Q_strrchr( temp, ':' ); - separator = temp; - if( separator < slash ) - separator = slash; - if( separator < backslash ) - separator = backslash; - if( separator < colon ) - separator = colon; - *((char *)separator) = 0; - } - } + FS_SearchPAK( &resultlist, searchpath->pack, pattern ); } - else if( searchpath->wad ) + else if( searchpath->type == SEARCHPATH_ZIP ) { - string wadpattern, wadname, temp2; - signed char type = W_TypeFromExt( pattern ); - qboolean anywadname = true; - string wadfolder; - - // quick reject by filetype - if( type == TYP_NONE ) continue; - COM_ExtractFilePath( pattern, wadname ); - COM_FileBase( pattern, wadpattern ); - wadfolder[0] = '\0'; - - if( COM_CheckStringEmpty( wadname )) - { - COM_FileBase( wadname, wadname ); - Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); - COM_DefaultExtension( wadname, ".wad" ); - anywadname = false; - } - - // make wadname from wad fullpath - COM_FileBase( searchpath->wad->filename, temp2 ); - COM_DefaultExtension( temp2, ".wad" ); - - // quick reject by wadname - if( !anywadname && Q_stricmp( wadname, temp2 )) - continue; - - // look through all the wad file elements - wad = searchpath->wad; - - for( i = 0; i < wad->numlumps; i++ ) - { - // if type not matching, we already have no chance ... - if( type != TYP_ANY && wad->lumps[i].type != type ) - continue; - - // build the lumpname with image suffix (if present) - Q_strncpy( temp, wad->lumps[i].name, sizeof( temp )); - - while( temp[0] ) - { - if( matchpattern( temp, wadpattern, true )) - { - for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) - { - if( !Q_strcmp( resultlist.strings[resultlistindex], temp )) - break; - } - - if( resultlistindex == resultlist.numstrings ) - { - // build path: wadname/lumpname.ext - Q_snprintf( temp2, sizeof(temp2), "%s/%s", wadfolder, temp ); - COM_DefaultExtension( temp2, va(".%s", W_ExtFromType( wad->lumps[i].type ))); - stringlistappend( &resultlist, temp2 ); - } - } - - // strip off one path element at a time until empty - // this way directories are added to the listing if they match the pattern - slash = Q_strrchr( temp, '/' ); - backslash = Q_strrchr( temp, '\\' ); - colon = Q_strrchr( temp, ':' ); - separator = temp; - if( separator < slash ) - separator = slash; - if( separator < backslash ) - separator = backslash; - if( separator < colon ) - separator = colon; - *((char *)separator) = 0; - } - } + FS_SearchZIP( &resultlist, searchpath->zip, pattern ); + } + else if( searchpath->type == SEARCHPATH_WAD ) + { + FS_SearchWAD( &resultlist, searchpath->wad, pattern ); } else { @@ -3785,376 +2813,124 @@ void FS_InitMemory( void ) fs_searchpaths = NULL; } -/* -============================================================================= - -WADSYSTEM PRIVATE ROUTINES - -============================================================================= -*/ -// associate extension with wad type -static const wadtype_t wad_types[7] = +fs_interface_t g_engfuncs = { -{ "pal", TYP_PALETTE }, // palette -{ "dds", TYP_DDSTEX }, // DDS image -{ "lmp", TYP_GFXPIC }, // quake1, hl pic -{ "fnt", TYP_QFONT }, // hl qfonts -{ "mip", TYP_MIPTEX }, // hl/q1 mip -{ "txt", TYP_SCRIPT }, // scripts -{ NULL, TYP_NONE } + _Con_Printf, + _Con_Printf, + _Con_Printf, + _Sys_Error, + _Mem_AllocPool, + _Mem_FreePool, + _Mem_Alloc, + _Mem_Realloc, + _Mem_Free }; -/* -=========== -W_TypeFromExt - -Extracts file type from extension -=========== -*/ -static signed char W_TypeFromExt( const char *lumpname ) +static qboolean FS_InitInterface( int version, fs_interface_t *engfuncs ) { - const char *ext = COM_FileExtension( lumpname ); - const wadtype_t *type; - - // we not known about filetype, so match only by filename - if( !Q_strcmp( ext, "*" ) || !Q_strcmp( ext, "" )) - return TYP_ANY; - - for( type = wad_types; type->ext; type++ ) + // to be extended in future interface revisions + if( version != FS_API_VERSION ) { - if( !Q_stricmp( ext, type->ext )) - return type->type; + Con_Printf( S_ERROR "filesystem optional interface version mismatch: expected %d, got %d\n", + FS_API_VERSION, version ); + return false; } - return TYP_NONE; + + if( engfuncs->_Con_Printf ) + g_engfuncs._Con_Printf = engfuncs->_Con_Printf; + + if( engfuncs->_Con_DPrintf ) + g_engfuncs._Con_DPrintf = engfuncs->_Con_DPrintf; + + if( engfuncs->_Con_Reportf ) + g_engfuncs._Con_Reportf = engfuncs->_Con_Reportf; + + if( engfuncs->_Sys_Error ) + g_engfuncs._Sys_Error = engfuncs->_Sys_Error; + + if( engfuncs->_Mem_AllocPool && engfuncs->_Mem_FreePool ) + { + g_engfuncs._Mem_AllocPool = engfuncs->_Mem_AllocPool; + g_engfuncs._Mem_FreePool = engfuncs->_Mem_FreePool; + + Con_Reportf( "filesystem_stdio: custom pool allocation functions found\n" ); + } + + if( engfuncs->_Mem_Alloc && engfuncs->_Mem_Realloc && engfuncs->_Mem_Free ) + { + g_engfuncs._Mem_Alloc = engfuncs->_Mem_Alloc; + g_engfuncs._Mem_Realloc = engfuncs->_Mem_Realloc; + g_engfuncs._Mem_Free = engfuncs->_Mem_Free; + + Con_Reportf( "filesystem_stdio: custom memory allocation functions found\n" ); + } + + return true; } -/* -=========== -W_ExtFromType - -Convert type to extension -=========== -*/ -static const char *W_ExtFromType( signed char lumptype ) +static fs_api_t g_api = { - const wadtype_t *type; + FS_InitStdio, + FS_ShutdownStdio, - // we not known aboyt filetype, so match only by filename - if( lumptype == TYP_NONE || lumptype == TYP_ANY ) - return ""; + // search path utils + FS_Rescan, + FS_ClearSearchPath, + FS_AllowDirectPaths, + FS_AddGameDirectory, + FS_AddGameHierarchy, + FS_Search, + FS_SetCurrentDirectory, + FS_FindLibrary, + FS_Path_f, - for( type = wad_types; type->ext; type++ ) - { - if( lumptype == type->type ) - return type->ext; - } - return ""; -} + // gameinfo utils + FS_LoadGameInfo, -/* -=========== -W_FindLump + // file ops + FS_Open, + FS_Write, + FS_Read, + FS_Seek, + FS_Tell, + FS_Eof, + FS_Flush, + FS_Close, + FS_Gets, + FS_UnGetc, + FS_Getc, + FS_VPrintf, + FS_Printf, + FS_Print, + FS_FileLength, + FS_FileCopy, -Serach for already existed lump -=========== -*/ -static dlumpinfo_t *W_FindLump( wfile_t *wad, const char *name, const signed char matchtype ) + // file buffer ops + FS_LoadFile, + FS_LoadDirectFile, + FS_WriteFile, + + // file hashing + CRC32_File, + MD5_HashFile, + + // filesystem ops + FS_FileExists, + FS_FileTime, + FS_FileSize, + FS_Rename, + FS_Delete, + FS_SysFileExists, + FS_GetDiskPath, +}; + +int EXPORT GetFSAPI( int version, fs_api_t *api, fs_globals_t **globals, fs_interface_t *engfuncs ) { - int left, right; + if( !FS_InitInterface( version, engfuncs )) + return 0; - if( !wad || !wad->lumps || matchtype == TYP_NONE ) - return NULL; + memcpy( api, &g_api, sizeof( *api )); + *globals = &FI; - // look for the file (binary search) - left = 0; - right = wad->numlumps - 1; - - while( left <= right ) - { - int middle = (left + right) / 2; - int diff = Q_stricmp( wad->lumps[middle].name, name ); - - if( !diff ) - { - if(( matchtype == TYP_ANY ) || ( matchtype == wad->lumps[middle].type )) - return &wad->lumps[middle]; // found - else if( wad->lumps[middle].type < matchtype ) - diff = 1; - else if( wad->lumps[middle].type > matchtype ) - diff = -1; - else break; // not found - } - - // if we're too far in the list - if( diff > 0 ) right = middle - 1; - else left = middle + 1; - } - - return NULL; -} - -/* -==================== -W_AddFileToWad - -Add a file to the list of files contained into a package -and sort LAT in alpha-bethical order -==================== -*/ -static dlumpinfo_t *W_AddFileToWad( const char *name, wfile_t *wad, dlumpinfo_t *newlump ) -{ - int left, right; - dlumpinfo_t *plump; - - // look for the slot we should put that file into (binary search) - left = 0; - right = wad->numlumps - 1; - - while( left <= right ) - { - int middle = ( left + right ) / 2; - int diff = Q_stricmp( wad->lumps[middle].name, name ); - - if( !diff ) - { - if( wad->lumps[middle].type < newlump->type ) - diff = 1; - else if( wad->lumps[middle].type > newlump->type ) - diff = -1; - else Con_Reportf( S_WARN "Wad %s contains the file %s several times\n", wad->filename, name ); - } - - // If we're too far in the list - if( diff > 0 ) right = middle - 1; - else left = middle + 1; - } - - // we have to move the right of the list by one slot to free the one we need - plump = &wad->lumps[left]; - memmove( plump + 1, plump, ( wad->numlumps - left ) * sizeof( *plump )); - wad->numlumps++; - - *plump = *newlump; - memcpy( plump->name, name, sizeof( plump->name )); - - return plump; -} - -/* -=========== -W_ReadLump - -reading lump into temp buffer -=========== -*/ -byte *W_ReadLump( wfile_t *wad, dlumpinfo_t *lump, fs_offset_t *lumpsizeptr ) -{ - size_t oldpos, size = 0; - byte *buf; - - // assume error - if( lumpsizeptr ) *lumpsizeptr = 0; - - // no wads loaded - if( !wad || !lump ) return NULL; - - oldpos = FS_Tell( wad->handle ); // don't forget restore original position - - if( FS_Seek( wad->handle, lump->filepos, SEEK_SET ) == -1 ) - { - Con_Reportf( S_ERROR "W_ReadLump: %s is corrupted\n", lump->name ); - FS_Seek( wad->handle, oldpos, SEEK_SET ); - return NULL; - } - - buf = (byte *)Mem_Malloc( wad->mempool, lump->disksize ); - size = FS_Read( wad->handle, buf, lump->disksize ); - - if( size < lump->disksize ) - { - Con_Reportf( S_WARN "W_ReadLump: %s is probably corrupted\n", lump->name ); - FS_Seek( wad->handle, oldpos, SEEK_SET ); - Mem_Free( buf ); - return NULL; - } - - if( lumpsizeptr ) *lumpsizeptr = lump->disksize; - FS_Seek( wad->handle, oldpos, SEEK_SET ); - - return buf; -} - -/* -============================================================================= - -WADSYSTEM PUBLIC BASE FUNCTIONS - -============================================================================= -*/ -/* -=========== -W_Open - -open the wad for reading & writing -=========== -*/ -wfile_t *W_Open( const char *filename, int *error ) -{ - wfile_t *wad = (wfile_t *)Mem_Calloc( fs_mempool, sizeof( wfile_t )); - const char *basename; - int i, lumpcount; - dlumpinfo_t *srclumps; - size_t lat_size; - dwadinfo_t header; - - // NOTE: FS_Open is load wad file from the first pak in the list (while fs_ext_path is false) - if( fs_ext_path ) basename = filename; - else basename = COM_FileWithoutPath( filename ); - - wad->handle = FS_Open( basename, "rb", false ); - - // HACKHACK: try to open WAD by full path for RoDir, when searchpaths are not ready - if( COM_CheckStringEmpty( host.rodir ) && fs_ext_path && wad->handle == NULL ) - wad->handle = FS_SysOpen( filename, "rb" ); - - if( wad->handle == NULL ) - { - Con_Reportf( S_ERROR "W_Open: couldn't open %s\n", filename ); - if( error ) *error = WAD_LOAD_COULDNT_OPEN; - W_Close( wad ); - return NULL; - } - - // copy wad name - Q_strncpy( wad->filename, filename, sizeof( wad->filename )); - wad->filetime = FS_SysFileTime( filename ); - wad->mempool = Mem_AllocPool( filename ); - - if( FS_Read( wad->handle, &header, sizeof( dwadinfo_t )) != sizeof( dwadinfo_t )) - { - Con_Reportf( S_ERROR "W_Open: %s can't read header\n", filename ); - if( error ) *error = WAD_LOAD_BAD_HEADER; - W_Close( wad ); - return NULL; - } - - if( header.ident != IDWAD2HEADER && header.ident != IDWAD3HEADER ) - { - Con_Reportf( S_ERROR "W_Open: %s is not a WAD2 or WAD3 file\n", filename ); - if( error ) *error = WAD_LOAD_BAD_HEADER; - W_Close( wad ); - return NULL; - } - - lumpcount = header.numlumps; - - if( lumpcount >= MAX_FILES_IN_WAD ) - { - Con_Reportf( S_WARN "W_Open: %s is full (%i lumps)\n", filename, lumpcount ); - if( error ) *error = WAD_LOAD_TOO_MANY_FILES; - } - else if( lumpcount <= 0 ) - { - Con_Reportf( S_ERROR "W_Open: %s has no lumps\n", filename ); - if( error ) *error = WAD_LOAD_NO_FILES; - W_Close( wad ); - return NULL; - } - else if( error ) *error = WAD_LOAD_OK; - - wad->infotableofs = header.infotableofs; // save infotableofs position - - if( FS_Seek( wad->handle, wad->infotableofs, SEEK_SET ) == -1 ) - { - Con_Reportf( S_ERROR "W_Open: %s can't find lump allocation table\n", filename ); - if( error ) *error = WAD_LOAD_BAD_FOLDERS; - W_Close( wad ); - return NULL; - } - - lat_size = lumpcount * sizeof( dlumpinfo_t ); - - // NOTE: lumps table can be reallocated for O_APPEND mode - srclumps = (dlumpinfo_t *)Mem_Malloc( wad->mempool, lat_size ); - - if( FS_Read( wad->handle, srclumps, lat_size ) != lat_size ) - { - Con_Reportf( S_ERROR "W_ReadLumpTable: %s has corrupted lump allocation table\n", wad->filename ); - if( error ) *error = WAD_LOAD_CORRUPTED; - Mem_Free( srclumps ); - W_Close( wad ); - return NULL; - } - - // starting to add lumps - wad->lumps = (dlumpinfo_t *)Mem_Calloc( wad->mempool, lat_size ); - wad->numlumps = 0; - - // sort lumps for binary search - for( i = 0; i < lumpcount; i++ ) - { - char name[16]; - int k; - - // cleanup lumpname - Q_strnlwr( srclumps[i].name, name, sizeof( srclumps[i].name )); - - // check for '*' symbol issues (quake1) - k = Q_strlen( Q_strrchr( name, '*' )); - if( k ) name[Q_strlen( name ) - k] = '!'; - - // check for Quake 'conchars' issues (only lmp loader really allows to read this lame pic) - if( srclumps[i].type == 68 && !Q_stricmp( srclumps[i].name, "conchars" )) - srclumps[i].type = TYP_GFXPIC; - - W_AddFileToWad( name, wad, &srclumps[i] ); - } - - // release source lumps - Mem_Free( srclumps ); - - // and leave the file open - return wad; -} - -/* -=========== -W_Close - -finalize wad or just close -=========== -*/ -void W_Close( wfile_t *wad ) -{ - if( !wad ) return; - - Mem_FreePool( &wad->mempool ); - if( wad->handle != NULL ) - FS_Close( wad->handle ); - Mem_Free( wad ); // free himself -} - -/* -============================================================================= - -FILESYSTEM IMPLEMENTATION - -============================================================================= -*/ -/* -=========== -W_LoadFile - -loading lump into the tmp buffer -=========== -*/ -static byte *W_LoadFile( const char *path, fs_offset_t *lumpsizeptr, qboolean gamedironly ) -{ - searchpath_t *search; - int index; - - search = FS_FindFile( path, &index, gamedironly ); - if( search && search->wad ) - return W_ReadLump( search->wad, &search->wad->lumps[index], lumpsizeptr ); - return NULL; + return FS_API_VERSION; } diff --git a/filesystem/filesystem.h b/filesystem/filesystem.h new file mode 100644 index 00000000..6a7a1f4c --- /dev/null +++ b/filesystem/filesystem.h @@ -0,0 +1,202 @@ +/* +filesystem.h - engine FS +Copyright (C) 2007 Uncle Mike + +This program 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, either version 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#ifndef FILESYSTEM_H +#define FILESYSTEM_H + +#include +#include +#include +#include "xash3d_types.h" +#include "const.h" +#include "com_model.h" + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + +#define FS_API_VERSION 1 // not stable yet! + +// search path flags +enum +{ + FS_STATIC_PATH = BIT( 0 ), // FS_ClearSearchPath will be ignore this path + FS_NOWRITE_PATH = BIT( 1 ), // default behavior - last added gamedir set as writedir. This flag disables it + FS_GAMEDIR_PATH = BIT( 2 ), // just a marker for gamedir path + FS_CUSTOM_PATH = BIT( 3 ), // gamedir but with custom/mod data + FS_GAMERODIR_PATH = BIT( 4 ), // gamedir but read-only + + FS_GAMEDIRONLY_SEARCH_FLAGS = FS_GAMEDIR_PATH | FS_CUSTOM_PATH | FS_GAMERODIR_PATH +}; + +typedef struct +{ + int numfilenames; + char **filenames; + char *filenamesbuffer; +} search_t; + +typedef struct gameinfo_s +{ + // filesystem info + char gamefolder[MAX_QPATH]; // used for change game '-game x' + char basedir[MAX_QPATH]; // base game directory (like 'id1' for Quake or 'valve' for Half-Life) + char falldir[MAX_QPATH]; // used as second basedir + char startmap[MAX_QPATH];// map to start singleplayer game + char trainmap[MAX_QPATH];// map to start hazard course (if specified) + char title[64]; // Game Main Title + float version; // game version (optional) + + // .dll pathes + char dll_path[MAX_QPATH]; // e.g. "bin" or "cl_dlls" + char game_dll[MAX_QPATH]; // custom path for game.dll + + // .ico path + char iconpath[MAX_QPATH]; // "game.ico" by default + + // about mod info + string game_url; // link to a developer's site + string update_url; // link to updates page + char type[MAX_QPATH]; // single, toolkit, multiplayer etc + char date[MAX_QPATH]; + size_t size; + + int gamemode; + qboolean secure; // prevent to console acess + qboolean nomodels; // don't let player to choose model (use player.mdl always) + qboolean noskills; // disable skill menu selection + qboolean render_picbutton_text; // use font renderer to render WON buttons + + char sp_entity[32]; // e.g. info_player_start + char mp_entity[32]; // e.g. info_player_deathmatch + char mp_filter[32]; // filtering multiplayer-maps + + char ambientsound[NUM_AMBIENTS][MAX_QPATH]; // quake ambient sounds + + int max_edicts; // min edicts is 600, max edicts is 8196 + int max_tents; // min temp ents is 300, max is 2048 + int max_beams; // min beams is 64, max beams is 512 + int max_particles; // min particles is 4096, max particles is 32768 + + char game_dll_linux[64]; // custom path for game.dll + char game_dll_osx[64]; // custom path for game.dll + + qboolean added; +} gameinfo_t; + +typedef enum +{ + GAME_NORMAL, + GAME_SINGLEPLAYER_ONLY, + GAME_MULTIPLAYER_ONLY +} gametype_t; + +typedef struct fs_dllinfo_t +{ + string fullPath; + string shortPath; + qboolean encrypted; + qboolean custom_loader; +} fs_dllinfo_t; + +typedef struct fs_globals_t +{ + gameinfo_t *GameInfo; // current GameInfo + gameinfo_t *games[MAX_MODS]; // environment games (founded at each engine start) + int numgames; +} fs_globals_t; + +typedef struct fs_api_t +{ + qboolean (*InitStdio)( qboolean caseinsensitive, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir ); + void (*ShutdownStdio)( void ); + + // search path utils + void (*Rescan)( void ); + void (*ClearSearchPath)( void ); + void (*AllowDirectPaths)( qboolean enable ); + void (*AddGameDirectory)( const char *dir, uint flags ); + void (*AddGameHierarchy)( const char *dir, uint flags ); + search_t *(*Search)( const char *pattern, int caseinsensitive, int gamedironly ); + int (*SetCurrentDirectory)( const char *path ); + qboolean (*FindLibrary)( const char *dllname, qboolean directpath, fs_dllinfo_t *dllinfo ); + void (*Path_f)( void ); + + // gameinfo utils + void (*LoadGameInfo)( const char *rootfolder ); + + // file ops + file_t *(*Open)( const char *filepath, const char *mode, qboolean gamedironly ); + fs_offset_t (*Write)( file_t *file, const void *data, size_t datasize ); + fs_offset_t (*Read)( file_t *file, void *buffer, size_t buffersize ); + int (*Seek)( file_t *file, fs_offset_t offset, int whence ); + fs_offset_t (*Tell)( file_t *file ); + qboolean (*Eof)( file_t *file ); + int (*Flush)( file_t *file ); + int (*Close)( file_t *file ); + int (*Gets)( file_t *file, byte *string, size_t bufsize ); + int (*UnGetc)( file_t *file, byte c ); + int (*Getc)( file_t *file ); + int (*VPrintf)( file_t *file, const char *format, va_list ap ); + int (*Printf)( file_t *file, const char *format, ... ) _format( 2 ); + int (*Print)( file_t *file, const char *msg ); + fs_offset_t (*FileLength)( file_t *f ); + qboolean (*FileCopy)( file_t *pOutput, file_t *pInput, int fileSize ); + + // file buffer ops + byte *(*LoadFile)( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ); + byte *(*LoadDirectFile)( const char *path, fs_offset_t *filesizeptr ); + qboolean (*WriteFile)( const char *filename, const void *data, fs_offset_t len ); + + // file hashing + qboolean (*CRC32_File)( dword *crcvalue, const char *filename ); + qboolean (*MD5_HashFile)( byte digest[16], const char *pszFileName, uint seed[4] ); + + // filesystem ops + int (*FileExists)( const char *filename, int gamedironly ); + int (*FileTime)( const char *filename, qboolean gamedironly ); + fs_offset_t (*FileSize)( const char *filename, qboolean gamedironly ); + qboolean (*Rename)( const char *oldname, const char *newname ); + qboolean (*Delete)( const char *path ); + qboolean (*SysFileExists)( const char *path, qboolean casesensitive ); + const char *(*GetDiskPath)( const char *name, qboolean gamedironly ); +} fs_api_t; + +typedef struct fs_interface_t +{ + // logging + void (*_Con_Printf)( const char *fmt, ... ) _format( 1 ); // typical console allowed messages + void (*_Con_DPrintf)( const char *fmt, ... ) _format( 1 ); // -dev 1 + void (*_Con_Reportf)( const char *fmt, ... ) _format( 1 ); // -dev 2 + + void (*_Sys_Error)( const char *fmt, ... ) _format( 1 ); + + // memory + poolhandle_t (*_Mem_AllocPool)( const char *name, const char *filename, int fileline ); + void (*_Mem_FreePool)( poolhandle_t *poolptr, const char *filename, int fileline ); + void *(*_Mem_Alloc)( poolhandle_t poolptr, size_t size, qboolean clear, const char *filename, int fileline ); + void *(*_Mem_Realloc)( poolhandle_t poolptr, void *memptr, size_t size, qboolean clear, const char *filename, int fileline ); + void (*_Mem_Free)( void *data, const char *filename, int fileline ); +} fs_interface_t; + +typedef int (*FSAPI)( int version, fs_api_t *api, fs_globals_t **globals, fs_interface_t *interface ); +#define GET_FS_API "GetFSAPI" + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif//FILESYSTEM_H diff --git a/filesystem/filesystem_internal.h b/filesystem/filesystem_internal.h new file mode 100644 index 00000000..3cf5a462 --- /dev/null +++ b/filesystem/filesystem_internal.h @@ -0,0 +1,207 @@ +/* +filesystem.h - engine FS +Copyright (C) 2007 Uncle Mike + +This program 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, either version 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#ifndef FILESYSTEM_INTERNAL_H +#define FILESYSTEM_INTERNAL_H + +#include "xash3d_types.h" +#include "filesystem.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef struct zip_s zip_t; +typedef struct pack_s pack_t; +typedef struct wfile_s wfile_t; + +#define FILE_BUFF_SIZE (2048) + +struct file_s +{ + int handle; // file descriptor + int ungetc; // single stored character from ungetc, cleared to EOF when read + fs_offset_t real_length; // uncompressed file size (for files opened in "read" mode) + fs_offset_t position; // current position in the file + fs_offset_t offset; // offset into the package (0 if external file) + time_t filetime; // pak, wad or real filetime + // contents buffer + fs_offset_t buff_ind, buff_len; // buffer current index and length + byte buff[FILE_BUFF_SIZE]; // intermediate buffer +#ifdef XASH_REDUCE_FD + const char *backup_path; + fs_offset_t backup_position; + uint backup_options; +#endif +}; + +enum +{ + SEARCHPATH_PLAIN = 0, + SEARCHPATH_PAK, + SEARCHPATH_WAD, + SEARCHPATH_ZIP +}; + +typedef struct stringlist_s +{ + // maxstrings changes as needed, causing reallocation of strings[] array + int maxstrings; + int numstrings; + char **strings; +} stringlist_t; + +typedef struct searchpath_s +{ + string filename; + int type; + int flags; + union + { + pack_t *pack; + wfile_t *wad; + zip_t *zip; + }; + struct searchpath_s *next; +} searchpath_t; + +extern fs_globals_t FI; +extern searchpath_t *fs_searchpaths; +extern poolhandle_t fs_mempool; +extern fs_interface_t g_engfuncs; +extern qboolean fs_ext_path; +extern char fs_rodir[MAX_SYSPATH]; +extern char fs_rootdir[MAX_SYSPATH]; +extern char fs_writedir[MAX_SYSPATH]; + +#define GI FI.GameInfo + +#define Mem_Malloc( pool, size ) g_engfuncs._Mem_Alloc( pool, size, false, __FILE__, __LINE__ ) +#define Mem_Calloc( pool, size ) g_engfuncs._Mem_Alloc( pool, size, true, __FILE__, __LINE__ ) +#define Mem_Realloc( pool, ptr, size ) g_engfuncs._Mem_Realloc( pool, ptr, size, true, __FILE__, __LINE__ ) +#define Mem_Free( mem ) g_engfuncs._Mem_Free( mem, __FILE__, __LINE__ ) +#define Mem_AllocPool( name ) g_engfuncs._Mem_AllocPool( name, __FILE__, __LINE__ ) +#define Mem_FreePool( pool ) g_engfuncs._Mem_FreePool( pool, __FILE__, __LINE__ ) + +#define Con_Printf (*g_engfuncs._Con_Printf) +#define Con_DPrintf (*g_engfuncs._Con_DPrintf) +#define Con_Reportf (*g_engfuncs._Con_Reportf) +#define Sys_Error (*g_engfuncs._Sys_Error) + +// +// filesystem.c +// +qboolean FS_InitStdio( qboolean caseinsensitive, const char *rootdir, const char *basedir, const char *gamedir, const char *rodir ); +void FS_ShutdownStdio( void ); + +// search path utils +void FS_Rescan( void ); +void FS_ClearSearchPath( void ); +void FS_AllowDirectPaths( qboolean enable ); +void FS_AddGameDirectory( const char *dir, uint flags ); +void FS_AddGameHierarchy( const char *dir, uint flags ); +search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ); +int FS_SetCurrentDirectory( const char *path ); +void FS_Path_f( void ); + +// gameinfo utils +void FS_LoadGameInfo( const char *rootfolder ); + +// file ops +file_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly ); +fs_offset_t FS_Write( file_t *file, const void *data, size_t datasize ); +fs_offset_t FS_Read( file_t *file, void *buffer, size_t buffersize ); +int FS_Seek( file_t *file, fs_offset_t offset, int whence ); +fs_offset_t FS_Tell( file_t *file ); +qboolean FS_Eof( file_t *file ); +int FS_Flush( file_t *file ); +int FS_Close( file_t *file ); +int FS_Gets( file_t *file, byte *string, size_t bufsize ); +int FS_UnGetc( file_t *file, byte c ); +int FS_Getc( file_t *file ); +int FS_VPrintf( file_t *file, const char *format, va_list ap ); +int FS_Printf( file_t *file, const char *format, ... ) _format( 2 ); +int FS_Print( file_t *file, const char *msg ); +fs_offset_t FS_FileLength( file_t *f ); +qboolean FS_FileCopy( file_t *pOutput, file_t *pInput, int fileSize ); + +// file buffer ops +byte *FS_LoadFile( const char *path, fs_offset_t *filesizeptr, qboolean gamedironly ); +byte *FS_LoadDirectFile( const char *path, fs_offset_t *filesizeptr ); +qboolean FS_WriteFile( const char *filename, const void *data, fs_offset_t len ); + +// file hashing +qboolean CRC32_File( dword *crcvalue, const char *filename ); +qboolean MD5_HashFile( byte digest[16], const char *pszFileName, uint seed[4] ); + +// filesystem ops +int FS_FileExists( const char *filename, int gamedironly ); +int FS_FileTime( const char *filename, qboolean gamedironly ); +fs_offset_t FS_FileSize( const char *filename, qboolean gamedironly ); +qboolean FS_Rename( const char *oldname, const char *newname ); +qboolean FS_Delete( const char *path ); +qboolean FS_SysFileExists( const char *path, qboolean casesensitive ); +const char *FS_GetDiskPath( const char *name, qboolean gamedironly ); +void stringlistappend( stringlist_t *list, char *text ); +void FS_CreatePath( char *path ); +qboolean FS_SysFolderExists( const char *path ); +file_t *FS_OpenReadFile( const char *filename, const char *mode, qboolean gamedironly ); + +int FS_SysFileTime( const char *filename ); +file_t *FS_OpenHandle( const char *syspath, int handle, fs_offset_t offset, fs_offset_t len ); +file_t *FS_SysOpen( const char *filepath, const char *mode ); +const char *FS_FixFileCase( const char *path ); +searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedironly ); + +// +// pak.c +// +int FS_FileTimePAK( pack_t *pack ); +int FS_FindFilePAK( pack_t *pack, const char *name ); +void FS_PrintPAKInfo( char *dst, size_t size, pack_t *pack ); +void FS_ClosePAK( pack_t *pack ); +void FS_SearchPAK( stringlist_t *list, pack_t *pack, const char *pattern ); +file_t *FS_OpenPackedFile( pack_t *pack, int pack_ind ); +qboolean FS_AddPak_Fullpath( const char *pakfile, qboolean *already_loaded, int flags ); + +// +// wad.c +// +int FS_FileTimeWAD( wfile_t *wad ); +int FS_FindFileWAD( wfile_t *wad, const char *name ); +void FS_PrintWADInfo( char *dst, size_t size, wfile_t *wad ); +void FS_CloseWAD( wfile_t *wad ); +void FS_SearchWAD( stringlist_t *list, wfile_t *wad, const char *pattern ); +byte *FS_LoadWADFile( const char *path, fs_offset_t *sizeptr, qboolean gamedironly ); +qboolean FS_AddWad_Fullpath( const char *wadfile, qboolean *already_loaded, int flags ); + +// +// zip.c +// +int FS_FileTimeZIP( zip_t *zip ); +int FS_FindFileZIP( zip_t *zip, const char *name ); +void FS_PrintZIPInfo( char *dst, size_t size, zip_t *zip ); +void FS_CloseZIP( zip_t *zip ); +void FS_SearchZIP( stringlist_t *list, zip_t *zip, const char *pattern ); +byte *FS_LoadZIPFile( const char *path, fs_offset_t *sizeptr, qboolean gamedironly ); +file_t *FS_OpenZipFile( zip_t *zip, int pack_ind ); +qboolean FS_AddZip_Fullpath( const char *zipfile, qboolean *already_loaded, int flags ); + +#ifdef __cplusplus +} +#endif + +#endif // FILESYSTEM_INTERNAL_H diff --git a/filesystem/fscallback.h b/filesystem/fscallback.h new file mode 100644 index 00000000..bebfdd29 --- /dev/null +++ b/filesystem/fscallback.h @@ -0,0 +1,80 @@ +/* +fscallback.h - common filesystem callbacks +Copyright (C) 2022 Alibek Omarov + +This program 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, either version 3 of the License, or +(at your option) any later version. + +This program 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. +*/ +#ifndef FSCALLBACK_H +#define FSCALLBACK_H + +#include "filesystem.h" + +extern fs_api_t g_fsapi; +extern fs_globals_t *FI; + +#define GI FI->GameInfo +#define FS_Gamedir() GI->gamefolder +#define FS_Title() GI->title + +#define FS_InitStdio (*g_fsapi.InitStdio) +#define FS_ShutdownStdio (*g_fsapi.ShutdownStdio) + +// search path utils +#define FS_Rescan (*g_fsapi.Rescan) +#define FS_ClearSearchPath (*g_fsapi.ClearSearchPath) +#define FS_AllowDirectPaths (*g_fsapi.AllowDirectPaths) +#define FS_AddGameDirectory (*g_fsapi.AddGameDirectory) +#define FS_AddGameHierarchy (*g_fsapi.AddGameHierarchy) +#define FS_Search (*g_fsapi.Search) +#define FS_SetCurrentDirectory (*g_fsapi.SetCurrentDirectory) +#define FS_Path_f (*g_fsapi.Path_f) + +// gameinfo utils +#define FS_LoadGameInfo (*g_fsapi.LoadGameInfo) + +// file ops +#define FS_Open (*g_fsapi.Open) +#define FS_Write (*g_fsapi.Write) +#define FS_Read (*g_fsapi.Read) +#define FS_Seek (*g_fsapi.Seek) +#define FS_Tell (*g_fsapi.Tell) +#define FS_Eof (*g_fsapi.Eof) +#define FS_Flush (*g_fsapi.Flush) +#define FS_Close (*g_fsapi.Close) +#define FS_Gets (*g_fsapi.Gets) +#define FS_UnGetc (*g_fsapi.UnGetc) +#define FS_Getc (*g_fsapi.Getc) +#define FS_VPrintf (*g_fsapi.VPrintf) +#define FS_Printf (*g_fsapi.Printf) +#define FS_Print (*g_fsapi.Print) +#define FS_FileLength (*g_fsapi.FileLength) +#define FS_FileCopy (*g_fsapi.FileCopy) + +// file buffer ops +#define FS_LoadFile (*g_fsapi.LoadFile) +#define FS_LoadDirectFile (*g_fsapi.LoadDirectFile) +#define FS_WriteFile (*g_fsapi.WriteFile) + +// file hashing +#define CRC32_File (*g_fsapi.CRC32_File) +#define MD5_HashFile (*g_fsapi.MD5_HashFile) + +// filesystem ops +#define FS_FileExists (*g_fsapi.FileExists) +#define FS_FileTime (*g_fsapi.FileTime) +#define FS_FileSize (*g_fsapi.FileSize) +#define FS_Rename (*g_fsapi.Rename) +#define FS_Delete (*g_fsapi.Delete) +#define FS_SysFileExists (*g_fsapi.SysFileExists) +#define FS_GetDiskPath (*g_fsapi.GetDiskPath) + + +#endif // FSCALLBACK_H diff --git a/filesystem/pak.c b/filesystem/pak.c new file mode 100644 index 00000000..1cc0037a --- /dev/null +++ b/filesystem/pak.c @@ -0,0 +1,397 @@ +/* +pak.c - PAK support for filesystem +Copyright (C) 2007 Uncle Mike +Copyright (C) 2022 Alibek Omarov + +This program 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, either version 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include "build.h" +#include +#include +#include +#if XASH_POSIX +#include +#endif +#include +#include +#include "port.h" +#include "filesystem_internal.h" +#include "crtlib.h" +#include "common/com_strings.h" + +/* +======================================================================== +PAK FILES + +The .pak files are just a linear collapse of a directory tree +======================================================================== +*/ +// header +#define IDPACKV1HEADER (('K'<<24)+('C'<<16)+('A'<<8)+'P') // little-endian "PACK" + +#define MAX_FILES_IN_PACK 65536 // pak + +typedef struct +{ + int ident; + int dirofs; + int dirlen; +} dpackheader_t; + +typedef struct +{ + char name[56]; // total 64 bytes + int filepos; + int filelen; +} dpackfile_t; + +// PAK errors +#define PAK_LOAD_OK 0 +#define PAK_LOAD_COULDNT_OPEN 1 +#define PAK_LOAD_BAD_HEADER 2 +#define PAK_LOAD_BAD_FOLDERS 3 +#define PAK_LOAD_TOO_MANY_FILES 4 +#define PAK_LOAD_NO_FILES 5 +#define PAK_LOAD_CORRUPTED 6 + +struct pack_s +{ + string filename; + int handle; + int numfiles; + time_t filetime; // common for all packed files + dpackfile_t *files; +}; + +/* +==================== +FS_AddFileToPack + +Add a file to the list of files contained into a package +==================== +*/ +static dpackfile_t *FS_AddFileToPack( const char *name, pack_t *pack, fs_offset_t offset, fs_offset_t size ) +{ + int left, right, middle; + dpackfile_t *pfile; + + // look for the slot we should put that file into (binary search) + left = 0; + right = pack->numfiles - 1; + + while( left <= right ) + { + int diff; + + middle = (left + right) / 2; + diff = Q_stricmp( pack->files[middle].name, name ); + + // If we found the file, there's a problem + if( !diff ) Con_Reportf( S_WARN "package %s contains the file %s several times\n", pack->filename, name ); + + // If we're too far in the list + if( diff > 0 ) right = middle - 1; + else left = middle + 1; + } + + // We have to move the right of the list by one slot to free the one we need + pfile = &pack->files[left]; + memmove( pfile + 1, pfile, (pack->numfiles - left) * sizeof( *pfile )); + pack->numfiles++; + + Q_strncpy( pfile->name, name, sizeof( pfile->name )); + pfile->filepos = offset; + pfile->filelen = size; + + return pfile; +} + +/* +================= +FS_LoadPackPAK + +Takes an explicit (not game tree related) path to a pak file. + +Loads the header and directory, adding the files at the beginning +of the list so they override previous pack files. +================= +*/ +static pack_t *FS_LoadPackPAK( const char *packfile, int *error ) +{ + dpackheader_t header; + int packhandle; + int i, numpackfiles; + pack_t *pack; + dpackfile_t *info; + fs_size_t c; + + packhandle = open( packfile, O_RDONLY|O_BINARY ); + +#if !XASH_WIN32 + if( packhandle < 0 ) + { + const char *fpackfile = FS_FixFileCase( packfile ); + if( fpackfile != packfile ) + packhandle = open( fpackfile, O_RDONLY|O_BINARY ); + } +#endif + + if( packhandle < 0 ) + { + Con_Reportf( "%s couldn't open: %s\n", packfile, strerror( errno )); + if( error ) *error = PAK_LOAD_COULDNT_OPEN; + return NULL; + } + + c = read( packhandle, (void *)&header, sizeof( header )); + + if( c != sizeof( header ) || header.ident != IDPACKV1HEADER ) + { + Con_Reportf( "%s is not a packfile. Ignored.\n", packfile ); + if( error ) *error = PAK_LOAD_BAD_HEADER; + close( packhandle ); + return NULL; + } + + if( header.dirlen % sizeof( dpackfile_t )) + { + Con_Reportf( S_ERROR "%s has an invalid directory size. Ignored.\n", packfile ); + if( error ) *error = PAK_LOAD_BAD_FOLDERS; + close( packhandle ); + return NULL; + } + + numpackfiles = header.dirlen / sizeof( dpackfile_t ); + + if( numpackfiles > MAX_FILES_IN_PACK ) + { + Con_Reportf( S_ERROR "%s has too many files ( %i ). Ignored.\n", packfile, numpackfiles ); + if( error ) *error = PAK_LOAD_TOO_MANY_FILES; + close( packhandle ); + return NULL; + } + + if( numpackfiles <= 0 ) + { + Con_Reportf( "%s has no files. Ignored.\n", packfile ); + if( error ) *error = PAK_LOAD_NO_FILES; + close( packhandle ); + return NULL; + } + + info = (dpackfile_t *)Mem_Malloc( fs_mempool, sizeof( *info ) * numpackfiles ); + lseek( packhandle, header.dirofs, SEEK_SET ); + + if( header.dirlen != read( packhandle, (void *)info, header.dirlen )) + { + Con_Reportf( "%s is an incomplete PAK, not loading\n", packfile ); + if( error ) *error = PAK_LOAD_CORRUPTED; + close( packhandle ); + Mem_Free( info ); + return NULL; + } + + pack = (pack_t *)Mem_Calloc( fs_mempool, sizeof( pack_t )); + Q_strncpy( pack->filename, packfile, sizeof( pack->filename )); + pack->files = (dpackfile_t *)Mem_Calloc( fs_mempool, numpackfiles * sizeof( dpackfile_t )); + pack->filetime = FS_SysFileTime( packfile ); + pack->handle = packhandle; + pack->numfiles = 0; + + // parse the directory + for( i = 0; i < numpackfiles; i++ ) + FS_AddFileToPack( info[i].name, pack, info[i].filepos, info[i].filelen ); + +#ifdef XASH_REDUCE_FD + // will reopen when needed + close( pack->handle ); + pack->handle = -1; +#endif + + if( error ) *error = PAK_LOAD_OK; + Mem_Free( info ); + + return pack; +} + +/* +=========== +FS_OpenPackedFile + +Open a packed file using its package file descriptor +=========== +*/ +file_t *FS_OpenPackedFile( pack_t *pack, int pack_ind ) +{ + dpackfile_t *pfile; + + pfile = &pack->files[pack_ind]; + + return FS_OpenHandle( pack->filename, pack->handle, pfile->filepos, pfile->filelen ); +} + +/* +================ +FS_AddPak_Fullpath + +Adds the given pack to the search path. +The pack type is autodetected by the file extension. + +Returns true if the file was successfully added to the +search path or if it was already included. + +If keep_plain_dirs is set, the pack will be added AFTER the first sequence of +plain directories. +================ +*/ +qboolean FS_AddPak_Fullpath( const char *pakfile, qboolean *already_loaded, int flags ) +{ + searchpath_t *search; + pack_t *pak = NULL; + const char *ext = COM_FileExtension( pakfile ); + int i, errorcode = PAK_LOAD_COULDNT_OPEN; + + for( search = fs_searchpaths; search; search = search->next ) + { + if( search->type == SEARCHPATH_PAK && !Q_stricmp( search->pack->filename, pakfile )) + { + if( already_loaded ) *already_loaded = true; + return true; // already loaded + } + } + + if( already_loaded ) + *already_loaded = false; + + if( !Q_stricmp( ext, "pak" )) + pak = FS_LoadPackPAK( pakfile, &errorcode ); + + if( pak ) + { + string fullpath; + + search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); + search->pack = pak; + search->type = SEARCHPATH_PAK; + search->next = fs_searchpaths; + search->flags |= flags; + fs_searchpaths = search; + + Con_Reportf( "Adding pakfile: %s (%i files)\n", pakfile, pak->numfiles ); + + // time to add in search list all the wads that contains in current pakfile (if do) + for( i = 0; i < pak->numfiles; i++ ) + { + if( !Q_stricmp( COM_FileExtension( pak->files[i].name ), "wad" )) + { + Q_snprintf( fullpath, MAX_STRING, "%s/%s", pakfile, pak->files[i].name ); + FS_AddWad_Fullpath( fullpath, NULL, flags ); + } + } + + return true; + } + else + { + if( errorcode != PAK_LOAD_NO_FILES ) + Con_Reportf( S_ERROR "FS_AddPak_Fullpath: unable to load pak \"%s\"\n", pakfile ); + return false; + } +} + +int FS_FindFilePAK( pack_t *pack, const char *name ) +{ + int left, right, middle; + + // look for the file (binary search) + left = 0; + right = pack->numfiles - 1; + while( left <= right ) + { + int diff; + + middle = (left + right) / 2; + diff = Q_stricmp( pack->files[middle].name, name ); + + // Found it + if( !diff ) + { + return middle; + } + + // if we're too far in the list + if( diff > 0 ) + right = middle - 1; + else left = middle + 1; + } + + return -1; +} + +void FS_SearchPAK( stringlist_t *list, pack_t *pack, const char *pattern ) +{ + string temp; + const char *slash, *backslash, *colon, *separator; + int j, i; + + for( i = 0; i < pack->numfiles; i++ ) + { + Q_strncpy( temp, pack->files[i].name, sizeof( temp )); + while( temp[0] ) + { + if( matchpattern( temp, pattern, true )) + { + for( j = 0; j < list->numstrings; j++ ) + { + if( !Q_strcmp( list->strings[j], temp )) + break; + } + + if( j == list->numstrings ) + stringlistappend( list, temp ); + } + + // strip off one path element at a time until empty + // this way directories are added to the listing if they match the pattern + slash = Q_strrchr( temp, '/' ); + backslash = Q_strrchr( temp, '\\' ); + colon = Q_strrchr( temp, ':' ); + separator = temp; + if( separator < slash ) + separator = slash; + if( separator < backslash ) + separator = backslash; + if( separator < colon ) + separator = colon; + *((char *)separator) = 0; + } + } +} + +int FS_FileTimePAK( pack_t *pack ) +{ + return pack->filetime; +} + +void FS_PrintPAKInfo( char *dst, size_t size, pack_t *pack ) +{ + Q_snprintf( dst, size, "%s (%i files)", pack->filename, pack->numfiles ); +} + +void FS_ClosePAK( pack_t *pack ) +{ + if( pack->files ) + Mem_Free( pack->files ); + if( pack->handle >= 0 ) + close( pack->handle ); + Mem_Free( pack ); +} diff --git a/filesystem/wad.c b/filesystem/wad.c new file mode 100644 index 00000000..646476d3 --- /dev/null +++ b/filesystem/wad.c @@ -0,0 +1,636 @@ +/* +wad.c - WAD support for filesystem +Copyright (C) 2007 Uncle Mike +Copyright (C) 2022 Alibek Omarov + +This program 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, either version 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include +#include +#include +#if XASH_POSIX +#include +#endif +#include +#include +#include "port.h" +#include "filesystem_internal.h" +#include "crtlib.h" +#include "common/com_strings.h" +#include "wadfile.h" + +/* +======================================================================== +.WAD archive format (WhereAllData - WAD) + +List of compressed files, that can be identify only by TYPE_* + + +header: dwadinfo_t[dwadinfo_t] +file_1: byte[dwadinfo_t[num]->disksize] +file_2: byte[dwadinfo_t[num]->disksize] +file_3: byte[dwadinfo_t[num]->disksize] +... +file_n: byte[dwadinfo_t[num]->disksize] +infotable dlumpinfo_t[dwadinfo_t->numlumps] +======================================================================== +*/ +#define WAD3_NAMELEN 16 +#define HINT_NAMELEN 5 // e.g. _mask, _norm +#define MAX_FILES_IN_WAD 65535 // real limit as above <2Gb size not a lumpcount + +#include "const.h" + +typedef struct +{ + int ident; // should be WAD3 + int numlumps; // num files + int infotableofs; // LUT offset +} dwadinfo_t; + +typedef struct +{ + int filepos; // file offset in WAD + int disksize; // compressed or uncompressed + int size; // uncompressed + signed char type; // TYP_* + signed char attribs; // file attribs + signed char pad0; + signed char pad1; + char name[WAD3_NAMELEN]; // must be null terminated +} dlumpinfo_t; + +struct wfile_s +{ + string filename; + int infotableofs; + int numlumps; + poolhandle_t mempool; // W_ReadLump temp buffers + file_t *handle; + dlumpinfo_t *lumps; + time_t filetime; +}; + +// WAD errors +#define WAD_LOAD_OK 0 +#define WAD_LOAD_COULDNT_OPEN 1 +#define WAD_LOAD_BAD_HEADER 2 +#define WAD_LOAD_BAD_FOLDERS 3 +#define WAD_LOAD_TOO_MANY_FILES 4 +#define WAD_LOAD_NO_FILES 5 +#define WAD_LOAD_CORRUPTED 6 + +typedef struct wadtype_s +{ + const char *ext; + signed char type; +} wadtype_t; + +// associate extension with wad type +static const wadtype_t wad_types[7] = +{ +{ "pal", TYP_PALETTE }, // palette +{ "dds", TYP_DDSTEX }, // DDS image +{ "lmp", TYP_GFXPIC }, // quake1, hl pic +{ "fnt", TYP_QFONT }, // hl qfonts +{ "mip", TYP_MIPTEX }, // hl/q1 mip +{ "txt", TYP_SCRIPT }, // scripts +{ NULL, TYP_NONE } +}; + +/* +=========== +W_TypeFromExt + +Extracts file type from extension +=========== +*/ +static signed char W_TypeFromExt( const char *lumpname ) +{ + const char *ext = COM_FileExtension( lumpname ); + const wadtype_t *type; + + // we not known about filetype, so match only by filename + if( !Q_strcmp( ext, "*" ) || !Q_strcmp( ext, "" )) + return TYP_ANY; + + for( type = wad_types; type->ext; type++ ) + { + if( !Q_stricmp( ext, type->ext )) + return type->type; + } + return TYP_NONE; +} + +/* +=========== +W_ExtFromType + +Convert type to extension +=========== +*/ +static const char *W_ExtFromType( signed char lumptype ) +{ + const wadtype_t *type; + + // we not known aboyt filetype, so match only by filename + if( lumptype == TYP_NONE || lumptype == TYP_ANY ) + return ""; + + for( type = wad_types; type->ext; type++ ) + { + if( lumptype == type->type ) + return type->ext; + } + return ""; +} + +/* +==================== +W_AddFileToWad + +Add a file to the list of files contained into a package +and sort LAT in alpha-bethical order +==================== +*/ +static dlumpinfo_t *W_AddFileToWad( const char *name, wfile_t *wad, dlumpinfo_t *newlump ) +{ + int left, right; + dlumpinfo_t *plump; + + // look for the slot we should put that file into (binary search) + left = 0; + right = wad->numlumps - 1; + + while( left <= right ) + { + int middle = ( left + right ) / 2; + int diff = Q_stricmp( wad->lumps[middle].name, name ); + + if( !diff ) + { + if( wad->lumps[middle].type < newlump->type ) + diff = 1; + else if( wad->lumps[middle].type > newlump->type ) + diff = -1; + else Con_Reportf( S_WARN "Wad %s contains the file %s several times\n", wad->filename, name ); + } + + // If we're too far in the list + if( diff > 0 ) right = middle - 1; + else left = middle + 1; + } + + // we have to move the right of the list by one slot to free the one we need + plump = &wad->lumps[left]; + memmove( plump + 1, plump, ( wad->numlumps - left ) * sizeof( *plump )); + wad->numlumps++; + + *plump = *newlump; + memcpy( plump->name, name, sizeof( plump->name )); + + return plump; +} + +/* +=========== +FS_CloseWAD + +finalize wad or just close +=========== +*/ +void FS_CloseWAD( wfile_t *wad ) +{ + Mem_FreePool( &wad->mempool ); + if( wad->handle != NULL ) + FS_Close( wad->handle ); + Mem_Free( wad ); // free himself +} + +/* +=========== +W_Open + +open the wad for reading & writing +=========== +*/ +static wfile_t *W_Open( const char *filename, int *error ) +{ + wfile_t *wad = (wfile_t *)Mem_Calloc( fs_mempool, sizeof( wfile_t )); + const char *basename; + int i, lumpcount; + dlumpinfo_t *srclumps; + size_t lat_size; + dwadinfo_t header; + + // NOTE: FS_Open is load wad file from the first pak in the list (while fs_ext_path is false) + if( fs_ext_path ) basename = filename; + else basename = COM_FileWithoutPath( filename ); + + wad->handle = FS_Open( basename, "rb", false ); + + // HACKHACK: try to open WAD by full path for RoDir, when searchpaths are not ready + if( COM_CheckStringEmpty( fs_rodir ) && fs_ext_path && wad->handle == NULL ) + wad->handle = FS_SysOpen( filename, "rb" ); + + if( wad->handle == NULL ) + { + Con_Reportf( S_ERROR "W_Open: couldn't open %s\n", filename ); + if( error ) *error = WAD_LOAD_COULDNT_OPEN; + FS_CloseWAD( wad ); + return NULL; + } + + // copy wad name + Q_strncpy( wad->filename, filename, sizeof( wad->filename )); + wad->filetime = FS_SysFileTime( filename ); + wad->mempool = Mem_AllocPool( filename ); + + if( FS_Read( wad->handle, &header, sizeof( dwadinfo_t )) != sizeof( dwadinfo_t )) + { + Con_Reportf( S_ERROR "W_Open: %s can't read header\n", filename ); + if( error ) *error = WAD_LOAD_BAD_HEADER; + FS_CloseWAD( wad ); + return NULL; + } + + if( header.ident != IDWAD2HEADER && header.ident != IDWAD3HEADER ) + { + Con_Reportf( S_ERROR "W_Open: %s is not a WAD2 or WAD3 file\n", filename ); + if( error ) *error = WAD_LOAD_BAD_HEADER; + FS_CloseWAD( wad ); + return NULL; + } + + lumpcount = header.numlumps; + + if( lumpcount >= MAX_FILES_IN_WAD ) + { + Con_Reportf( S_WARN "W_Open: %s is full (%i lumps)\n", filename, lumpcount ); + if( error ) *error = WAD_LOAD_TOO_MANY_FILES; + } + else if( lumpcount <= 0 ) + { + Con_Reportf( S_ERROR "W_Open: %s has no lumps\n", filename ); + if( error ) *error = WAD_LOAD_NO_FILES; + FS_CloseWAD( wad ); + return NULL; + } + else if( error ) *error = WAD_LOAD_OK; + + wad->infotableofs = header.infotableofs; // save infotableofs position + + if( FS_Seek( wad->handle, wad->infotableofs, SEEK_SET ) == -1 ) + { + Con_Reportf( S_ERROR "W_Open: %s can't find lump allocation table\n", filename ); + if( error ) *error = WAD_LOAD_BAD_FOLDERS; + FS_CloseWAD( wad ); + return NULL; + } + + lat_size = lumpcount * sizeof( dlumpinfo_t ); + + // NOTE: lumps table can be reallocated for O_APPEND mode + srclumps = (dlumpinfo_t *)Mem_Malloc( wad->mempool, lat_size ); + + if( FS_Read( wad->handle, srclumps, lat_size ) != lat_size ) + { + Con_Reportf( S_ERROR "W_ReadLumpTable: %s has corrupted lump allocation table\n", wad->filename ); + if( error ) *error = WAD_LOAD_CORRUPTED; + Mem_Free( srclumps ); + FS_CloseWAD( wad ); + return NULL; + } + + // starting to add lumps + wad->lumps = (dlumpinfo_t *)Mem_Calloc( wad->mempool, lat_size ); + wad->numlumps = 0; + + // sort lumps for binary search + for( i = 0; i < lumpcount; i++ ) + { + char name[16]; + int k; + + // cleanup lumpname + Q_strnlwr( srclumps[i].name, name, sizeof( srclumps[i].name )); + + // check for '*' symbol issues (quake1) + k = Q_strlen( Q_strrchr( name, '*' )); + if( k ) name[Q_strlen( name ) - k] = '!'; + + // check for Quake 'conchars' issues (only lmp loader really allows to read this lame pic) + if( srclumps[i].type == 68 && !Q_stricmp( srclumps[i].name, "conchars" )) + srclumps[i].type = TYP_GFXPIC; + + W_AddFileToWad( name, wad, &srclumps[i] ); + } + + // release source lumps + Mem_Free( srclumps ); + + // and leave the file open + return wad; +} + +/* +==================== +FS_AddWad_Fullpath +==================== +*/ +qboolean FS_AddWad_Fullpath( const char *wadfile, qboolean *already_loaded, int flags ) +{ + searchpath_t *search; + wfile_t *wad = NULL; + const char *ext = COM_FileExtension( wadfile ); + int errorcode = WAD_LOAD_COULDNT_OPEN; + + for( search = fs_searchpaths; search; search = search->next ) + { + if( search->type == SEARCHPATH_WAD && !Q_stricmp( search->wad->filename, wadfile )) + { + if( already_loaded ) *already_loaded = true; + return true; // already loaded + } + } + + if( already_loaded ) + *already_loaded = false; + + if( !Q_stricmp( ext, "wad" )) + wad = W_Open( wadfile, &errorcode ); + + if( wad ) + { + search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t )); + search->wad = wad; + search->type = SEARCHPATH_WAD; + search->next = fs_searchpaths; + search->flags |= flags; + fs_searchpaths = search; + + Con_Reportf( "Adding wadfile: %s (%i files)\n", wadfile, wad->numlumps ); + return true; + } + else + { + if( errorcode != WAD_LOAD_NO_FILES ) + Con_Reportf( S_ERROR "FS_AddWad_Fullpath: unable to load wad \"%s\"\n", wadfile ); + return false; + } +} + +/* +============================================================================= + +WADSYSTEM PRIVATE ROUTINES + +============================================================================= +*/ + +/* +=========== +W_FindLump + +Serach for already existed lump +=========== +*/ +static dlumpinfo_t *W_FindLump( wfile_t *wad, const char *name, const signed char matchtype ) +{ + int left, right; + + if( !wad || !wad->lumps || matchtype == TYP_NONE ) + return NULL; + + // look for the file (binary search) + left = 0; + right = wad->numlumps - 1; + + while( left <= right ) + { + int middle = (left + right) / 2; + int diff = Q_stricmp( wad->lumps[middle].name, name ); + + if( !diff ) + { + if(( matchtype == TYP_ANY ) || ( matchtype == wad->lumps[middle].type )) + return &wad->lumps[middle]; // found + else if( wad->lumps[middle].type < matchtype ) + diff = 1; + else if( wad->lumps[middle].type > matchtype ) + diff = -1; + else break; // not found + } + + // if we're too far in the list + if( diff > 0 ) right = middle - 1; + else left = middle + 1; + } + + return NULL; +} + +/* +=========== +W_ReadLump + +reading lump into temp buffer +=========== +*/ +static byte *W_ReadLump( wfile_t *wad, dlumpinfo_t *lump, fs_offset_t *lumpsizeptr ) +{ + size_t oldpos, size = 0; + byte *buf; + + // assume error + if( lumpsizeptr ) *lumpsizeptr = 0; + + // no wads loaded + if( !wad || !lump ) return NULL; + + oldpos = FS_Tell( wad->handle ); // don't forget restore original position + + if( FS_Seek( wad->handle, lump->filepos, SEEK_SET ) == -1 ) + { + Con_Reportf( S_ERROR "W_ReadLump: %s is corrupted\n", lump->name ); + FS_Seek( wad->handle, oldpos, SEEK_SET ); + return NULL; + } + + buf = (byte *)Mem_Malloc( wad->mempool, lump->disksize ); + size = FS_Read( wad->handle, buf, lump->disksize ); + + if( size < lump->disksize ) + { + Con_Reportf( S_WARN "W_ReadLump: %s is probably corrupted\n", lump->name ); + FS_Seek( wad->handle, oldpos, SEEK_SET ); + Mem_Free( buf ); + return NULL; + } + + if( lumpsizeptr ) *lumpsizeptr = lump->disksize; + FS_Seek( wad->handle, oldpos, SEEK_SET ); + + return buf; +} + +/* +=========== +FS_LoadWADFile + +loading lump into the tmp buffer +=========== +*/ +byte *FS_LoadWADFile( const char *path, fs_offset_t *lumpsizeptr, qboolean gamedironly ) +{ + searchpath_t *search; + int index; + + search = FS_FindFile( path, &index, gamedironly ); + if( search && search->type == SEARCHPATH_WAD ) + return W_ReadLump( search->wad, &search->wad->lumps[index], lumpsizeptr ); + return NULL; +} + +int FS_FileTimeWAD( wfile_t *wad ) +{ + return wad->filetime; +} + +void FS_PrintWADInfo( char *dst, size_t size, wfile_t *wad ) +{ + Q_snprintf( dst, size, "%s (%i files)", wad->filename, wad->numlumps ); +} + +int FS_FindFileWAD( wfile_t *wad, const char *name ) +{ + dlumpinfo_t *lump; + signed char type = W_TypeFromExt( name ); + qboolean anywadname = true; + string wadname, wadfolder; + string shortname; + + // quick reject by filetype + if( type == TYP_NONE ) + return -1; + + COM_ExtractFilePath( name, wadname ); + wadfolder[0] = '\0'; + + if( COM_CheckStringEmpty( wadname ) ) + { + COM_FileBase( wadname, wadname ); + Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); + COM_DefaultExtension( wadname, ".wad" ); + anywadname = false; + } + + // make wadname from wad fullpath + COM_FileBase( wad->filename, shortname ); + COM_DefaultExtension( shortname, ".wad" ); + + // quick reject by wadname + if( !anywadname && Q_stricmp( wadname, shortname )) + return -1; + + // NOTE: we can't using long names for wad, + // because we using original wad names[16]; + COM_FileBase( name, shortname ); + + lump = W_FindLump( wad, shortname, type ); + + if( lump ) + { + return lump - wad->lumps; + } + + return -1; + +} + +void FS_SearchWAD( stringlist_t *list, wfile_t *wad, const char *pattern ) +{ + string wadpattern, wadname, temp2; + signed char type = W_TypeFromExt( pattern ); + qboolean anywadname = true; + string wadfolder, temp; + int j, i; + const char *slash, *backslash, *colon, *separator; + + // quick reject by filetype + if( type == TYP_NONE ) + return; + + COM_ExtractFilePath( pattern, wadname ); + COM_FileBase( pattern, wadpattern ); + wadfolder[0] = '\0'; + + if( COM_CheckStringEmpty( wadname )) + { + COM_FileBase( wadname, wadname ); + Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); + COM_DefaultExtension( wadname, ".wad" ); + anywadname = false; + } + + // make wadname from wad fullpath + COM_FileBase( wad->filename, temp2 ); + COM_DefaultExtension( temp2, ".wad" ); + + // quick reject by wadname + if( !anywadname && Q_stricmp( wadname, temp2 )) + return; + + for( i = 0; i < wad->numlumps; i++ ) + { + // if type not matching, we already have no chance ... + if( type != TYP_ANY && wad->lumps[i].type != type ) + continue; + + // build the lumpname with image suffix (if present) + Q_strncpy( temp, wad->lumps[i].name, sizeof( temp )); + + while( temp[0] ) + { + if( matchpattern( temp, wadpattern, true )) + { + for( j = 0; j < list->numstrings; j++ ) + { + if( !Q_strcmp( list->strings[j], temp )) + break; + } + + if( j == list->numstrings ) + { + // build path: wadname/lumpname.ext + Q_snprintf( temp2, sizeof(temp2), "%s/%s", wadfolder, temp ); + COM_DefaultExtension( temp2, va(".%s", W_ExtFromType( wad->lumps[i].type ))); + stringlistappend( list, temp2 ); + } + } + + // strip off one path element at a time until empty + // this way directories are added to the listing if they match the pattern + slash = Q_strrchr( temp, '/' ); + backslash = Q_strrchr( temp, '\\' ); + colon = Q_strrchr( temp, ':' ); + separator = temp; + if( separator < slash ) + separator = slash; + if( separator < backslash ) + separator = backslash; + if( separator < colon ) + separator = colon; + *((char *)separator) = 0; + } + } +} diff --git a/filesystem/wscript b/filesystem/wscript new file mode 100644 index 00000000..8a4e421a --- /dev/null +++ b/filesystem/wscript @@ -0,0 +1,24 @@ +#!/usr/bin/env python + +def options(opt): + pass + +def configure(conf): + nortti = { + 'msvc': ['/GR-'], + 'default': ['-fno-rtti', '-fno-exceptions'] + } + conf.env.append_unique('CXXFLAGS', conf.get_flags_by_compiler(nortti, conf.env.COMPILER_CC)) + + if conf.env.DEST_OS != 'android': + if conf.env.cxxshlib_PATTERN.startswith('lib'): + conf.env.cxxshlib_PATTERN = conf.env.cxxshlib_PATTERN[3:] + +def build(bld): + bld.shlib(target = 'filesystem_stdio', + features = 'cxx', + source = bld.path.ant_glob(['*.c', '*.cpp']), + includes = ['.', '../common', '../public', '../engine'], + use = ['public'], + install_path = bld.env.LIBDIR, + subsystem = bld.env.MSVC_SUBSYSTEM) diff --git a/filesystem/zip.c b/filesystem/zip.c new file mode 100644 index 00000000..829d1553 --- /dev/null +++ b/filesystem/zip.c @@ -0,0 +1,680 @@ +/* +zip.c - ZIP support for filesystem +Copyright (C) 2019 Mr0maks +Copyright (C) 2022 Alibek Omarov + +This program 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, either version 3 of the License, or +(at your option) any later version. + +This program 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. +*/ + +#include +#include +#include +#if XASH_POSIX +#include +#endif +#include +#include +#include STDINT_H +#include "port.h" +#include "filesystem_internal.h" +#include "crtlib.h" +#include "common/com_strings.h" +#include "miniz.h" + +#define ZIP_HEADER_LF (('K'<<8)+('P')+(0x03<<16)+(0x04<<24)) +#define ZIP_HEADER_SPANNED ((0x08<<24)+(0x07<<16)+('K'<<8)+'P') + +#define ZIP_HEADER_CDF ((0x02<<24)+(0x01<<16)+('K'<<8)+'P') +#define ZIP_HEADER_EOCD ((0x06<<24)+(0x05<<16)+('K'<<8)+'P') + +#define ZIP_COMPRESSION_NO_COMPRESSION 0 +#define ZIP_COMPRESSION_DEFLATED 8 + +#define ZIP_ZIP64 0xffffffff + +#pragma pack( push, 1 ) +typedef struct zip_header_s +{ + unsigned int signature; // little endian ZIP_HEADER + unsigned short version; // version of pkzip need to unpack + unsigned short flags; // flags (16 bits == 16 flags) + unsigned short compression_flags; // compression flags (bits) + unsigned int dos_date; // file modification time and file modification date + unsigned int crc32; //crc32 + unsigned int compressed_size; + unsigned int uncompressed_size; + unsigned short filename_len; + unsigned short extrafield_len; +} zip_header_t; + +/* + in zip64 comp and uncompr size == 0xffffffff remeber this + compressed and uncompress filesize stored in extra field +*/ + +typedef struct zip_header_extra_s +{ + unsigned int signature; // ZIP_HEADER_SPANNED + unsigned int crc32; + unsigned int compressed_size; + unsigned int uncompressed_size; +} zip_header_extra_t; + +typedef struct zip_cdf_header_s +{ + unsigned int signature; + unsigned short version; + unsigned short version_need; + unsigned short generalPurposeBitFlag; + unsigned short flags; + unsigned short modification_time; + unsigned short modification_date; + unsigned int crc32; + unsigned int compressed_size; + unsigned int uncompressed_size; + unsigned short filename_len; + unsigned short extrafield_len; + unsigned short file_commentary_len; + unsigned short disk_start; + unsigned short internal_attr; + unsigned int external_attr; + unsigned int local_header_offset; +} zip_cdf_header_t; + +typedef struct zip_header_eocd_s +{ + unsigned short disk_number; + unsigned short start_disk_number; + unsigned short number_central_directory_record; + unsigned short total_central_directory_record; + unsigned int size_of_central_directory; + unsigned int central_directory_offset; + unsigned short commentary_len; +} zip_header_eocd_t; +#pragma pack( pop ) + +// ZIP errors +#define ZIP_LOAD_OK 0 +#define ZIP_LOAD_COULDNT_OPEN 1 +#define ZIP_LOAD_BAD_HEADER 2 +#define ZIP_LOAD_BAD_FOLDERS 3 +#define ZIP_LOAD_NO_FILES 5 +#define ZIP_LOAD_CORRUPTED 6 + +typedef struct zipfile_s +{ + char name[MAX_SYSPATH]; + fs_offset_t offset; // offset of local file header + fs_offset_t size; //original file size + fs_offset_t compressed_size; // compressed file size + unsigned short flags; +} zipfile_t; + +struct zip_s +{ + string filename; + int handle; + int numfiles; + time_t filetime; + zipfile_t *files; +}; + +#ifdef XASH_REDUCE_FD +static void FS_EnsureOpenZip( zip_t *zip ) +{ + if( fs_last_zip == zip ) + return; + + if( fs_last_zip && (fs_last_zip->handle != -1) ) + { + close( fs_last_zip->handle ); + fs_last_zip->handle = -1; + } + fs_last_zip = zip; + if( zip && (zip->handle == -1) ) + zip->handle = open( zip->filename, O_RDONLY|O_BINARY ); +} +#else +static void FS_EnsureOpenZip( zip_t *zip ) {} +#endif + +void FS_CloseZIP( zip_t *zip ) +{ + if( zip->files ) + Mem_Free( zip->files ); + + FS_EnsureOpenZip( NULL ); + + if( zip->handle >= 0 ) + close( zip->handle ); + + Mem_Free( zip ); +} + +/* +============ +FS_SortZip +============ +*/ +static int FS_SortZip( const void *a, const void *b ) +{ + return Q_stricmp( ( ( zipfile_t* )a )->name, ( ( zipfile_t* )b )->name ); +} + +/* +============ +FS_LoadZip +============ +*/ +static zip_t *FS_LoadZip( const char *zipfile, int *error ) +{ + int numpackfiles = 0, i; + zip_cdf_header_t header_cdf; + zip_header_eocd_t header_eocd; + uint32_t signature; + fs_offset_t filepos = 0, length; + zipfile_t *info = NULL; + char filename_buffer[MAX_SYSPATH]; + zip_t *zip = (zip_t *)Mem_Calloc( fs_mempool, sizeof( *zip )); + fs_size_t c; + + zip->handle = open( zipfile, O_RDONLY|O_BINARY ); + +#if !XASH_WIN32 + if( zip->handle < 0 ) + { + const char *fzipfile = FS_FixFileCase( zipfile ); + if( fzipfile != zipfile ) + zip->handle = open( fzipfile, O_RDONLY|O_BINARY ); + } +#endif + + if( zip->handle < 0 ) + { + Con_Reportf( S_ERROR "%s couldn't open\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_COULDNT_OPEN; + + FS_CloseZIP( zip ); + return NULL; + } + + length = lseek( zip->handle, 0, SEEK_END ); + + if( length > UINT_MAX ) + { + Con_Reportf( S_ERROR "%s bigger than 4GB.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_COULDNT_OPEN; + + FS_CloseZIP( zip ); + return NULL; + } + + lseek( zip->handle, 0, SEEK_SET ); + + c = read( zip->handle, &signature, sizeof( signature ) ); + + if( c != sizeof( signature ) || signature == ZIP_HEADER_EOCD ) + { + Con_Reportf( S_WARN "%s has no files. Ignored.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_NO_FILES; + + FS_CloseZIP( zip ); + return NULL; + } + + if( signature != ZIP_HEADER_LF ) + { + Con_Reportf( S_ERROR "%s is not a zip file. Ignored.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_BAD_HEADER; + + FS_CloseZIP( zip ); + return NULL; + } + + // Find oecd + lseek( zip->handle, 0, SEEK_SET ); + filepos = length; + + while ( filepos > 0 ) + { + lseek( zip->handle, filepos, SEEK_SET ); + c = read( zip->handle, &signature, sizeof( signature ) ); + + if( c == sizeof( signature ) && signature == ZIP_HEADER_EOCD ) + break; + + filepos -= sizeof( char ); // step back one byte + } + + if( ZIP_HEADER_EOCD != signature ) + { + Con_Reportf( S_ERROR "cannot find EOCD in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_BAD_HEADER; + + FS_CloseZIP( zip ); + return NULL; + } + + c = read( zip->handle, &header_eocd, sizeof( header_eocd ) ); + + if( c != sizeof( header_eocd )) + { + Con_Reportf( S_ERROR "invalid EOCD header in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_BAD_HEADER; + + FS_CloseZIP( zip ); + return NULL; + } + + // Move to CDF start + lseek( zip->handle, header_eocd.central_directory_offset, SEEK_SET ); + + // Calc count of files in archive + info = (zipfile_t *)Mem_Calloc( fs_mempool, sizeof( *info ) * header_eocd.total_central_directory_record ); + + for( i = 0; i < header_eocd.total_central_directory_record; i++ ) + { + c = read( zip->handle, &header_cdf, sizeof( header_cdf ) ); + + if( c != sizeof( header_cdf ) || header_cdf.signature != ZIP_HEADER_CDF ) + { + Con_Reportf( S_ERROR "CDF signature mismatch in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_BAD_HEADER; + + Mem_Free( info ); + FS_CloseZIP( zip ); + return NULL; + } + + if( header_cdf.uncompressed_size && header_cdf.filename_len && ( header_cdf.filename_len < MAX_SYSPATH ) ) + { + memset( &filename_buffer, '\0', MAX_SYSPATH ); + c = read( zip->handle, &filename_buffer, header_cdf.filename_len ); + + if( c != header_cdf.filename_len ) + { + Con_Reportf( S_ERROR "filename length mismatch in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_CORRUPTED; + + Mem_Free( info ); + FS_CloseZIP( zip ); + return NULL; + } + + Q_strncpy( info[numpackfiles].name, filename_buffer, MAX_SYSPATH ); + + info[numpackfiles].size = header_cdf.uncompressed_size; + info[numpackfiles].compressed_size = header_cdf.compressed_size; + info[numpackfiles].offset = header_cdf.local_header_offset; + numpackfiles++; + } + else + lseek( zip->handle, header_cdf.filename_len, SEEK_CUR ); + + if( header_cdf.extrafield_len ) + lseek( zip->handle, header_cdf.extrafield_len, SEEK_CUR ); + + if( header_cdf.file_commentary_len ) + lseek( zip->handle, header_cdf.file_commentary_len, SEEK_CUR ); + } + + // recalculate offsets + for( i = 0; i < numpackfiles; i++ ) + { + zip_header_t header; + + lseek( zip->handle, info[i].offset, SEEK_SET ); + c = read( zip->handle, &header, sizeof( header ) ); + + if( c != sizeof( header )) + { + Con_Reportf( S_ERROR "header length mismatch in %s. Zip file corrupted.\n", zipfile ); + + if( error ) + *error = ZIP_LOAD_CORRUPTED; + + Mem_Free( info ); + FS_CloseZIP( zip ); + return NULL; + } + + info[i].flags = header.compression_flags; + info[i].offset = info[i].offset + header.filename_len + header.extrafield_len + sizeof( header ); + } + + Q_strncpy( zip->filename, zipfile, sizeof( zip->filename ) ); + zip->filetime = FS_SysFileTime( zipfile ); + zip->numfiles = numpackfiles; + zip->files = info; + + qsort( zip->files, zip->numfiles, sizeof( *zip->files ), FS_SortZip ); + +#ifdef XASH_REDUCE_FD + // will reopen when needed + close(zip->handle); + zip->handle = -1; +#endif + + if( error ) + *error = ZIP_LOAD_OK; + + return zip; +} + +/* +=========== +FS_OpenZipFile + +Open a packed file using its package file descriptor +=========== +*/ +file_t *FS_OpenZipFile( zip_t *zip, int pack_ind ) +{ + zipfile_t *pfile; + pfile = &zip->files[pack_ind]; + + // compressed files handled in Zip_LoadFile + if( pfile->flags != ZIP_COMPRESSION_NO_COMPRESSION ) + { + Con_Printf( S_ERROR "%s: can't open compressed file %s\n", __FUNCTION__, pfile->name ); + return NULL; + } + + return FS_OpenHandle( zip->filename, zip->handle, pfile->offset, pfile->size ); +} + +byte *FS_LoadZIPFile( const char *path, fs_offset_t *sizeptr, qboolean gamedironly ) +{ + searchpath_t *search; + int index; + zipfile_t *file = NULL; + byte *compressed_buffer = NULL, *decompressed_buffer = NULL; + int zlib_result = 0; + dword test_crc, final_crc; + z_stream decompress_stream; + size_t c; + + if( sizeptr ) *sizeptr = 0; + + search = FS_FindFile( path, &index, gamedironly ); + + if( !search || search->type != SEARCHPATH_ZIP ) + return NULL; + + file = &search->zip->files[index]; + + FS_EnsureOpenZip( search->zip ); + + if( lseek( search->zip->handle, file->offset, SEEK_SET ) == -1 ) + return NULL; + + /*if( read( search->zip->handle, &header, sizeof( header ) ) < 0 ) + return NULL; + + if( header.signature != ZIP_HEADER_LF ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s signature error\n", file->name ); + return NULL; + }*/ + + if( file->flags == ZIP_COMPRESSION_NO_COMPRESSION ) + { + decompressed_buffer = Mem_Malloc( fs_mempool, file->size + 1 ); + decompressed_buffer[file->size] = '\0'; + + c = read( search->zip->handle, decompressed_buffer, file->size ); + if( c != file->size ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s size doesn't match\n", file->name ); + return NULL; + } + +#if 0 + CRC32_Init( &test_crc ); + CRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size ); + + final_crc = CRC32_Final( test_crc ); + + if( final_crc != file->crc32 ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s file crc32 mismatch\n", file->name ); + Mem_Free( decompressed_buffer ); + return NULL; + } +#endif + if( sizeptr ) *sizeptr = file->size; + + FS_EnsureOpenZip( NULL ); + return decompressed_buffer; + } + else if( file->flags == ZIP_COMPRESSION_DEFLATED ) + { + compressed_buffer = Mem_Malloc( fs_mempool, file->compressed_size + 1 ); + decompressed_buffer = Mem_Malloc( fs_mempool, file->size + 1 ); + decompressed_buffer[file->size] = '\0'; + + c = read( search->zip->handle, compressed_buffer, file->compressed_size ); + if( c != file->compressed_size ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s compressed size doesn't match\n", file->name ); + return NULL; + } + + memset( &decompress_stream, 0, sizeof( decompress_stream ) ); + + decompress_stream.total_in = decompress_stream.avail_in = file->compressed_size; + decompress_stream.next_in = (Bytef *)compressed_buffer; + decompress_stream.total_out = decompress_stream.avail_out = file->size; + decompress_stream.next_out = (Bytef *)decompressed_buffer; + + decompress_stream.zalloc = Z_NULL; + decompress_stream.zfree = Z_NULL; + decompress_stream.opaque = Z_NULL; + + if( inflateInit2( &decompress_stream, -MAX_WBITS ) != Z_OK ) + { + Con_Printf( S_ERROR "Zip_LoadFile: inflateInit2 failed\n" ); + Mem_Free( compressed_buffer ); + Mem_Free( decompressed_buffer ); + return NULL; + } + + zlib_result = inflate( &decompress_stream, Z_NO_FLUSH ); + inflateEnd( &decompress_stream ); + + if( zlib_result == Z_OK || zlib_result == Z_STREAM_END ) + { + Mem_Free( compressed_buffer ); // finaly free compressed buffer +#if 0 + CRC32_Init( &test_crc ); + CRC32_ProcessBuffer( &test_crc, decompressed_buffer, file->size ); + + final_crc = CRC32_Final( test_crc ); + + if( final_crc != file->crc32 ) + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s file crc32 mismatch\n", file->name ); + Mem_Free( decompressed_buffer ); + return NULL; + } +#endif + if( sizeptr ) *sizeptr = file->size; + + FS_EnsureOpenZip( NULL ); + return decompressed_buffer; + } + else + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s : error while file decompressing. Zlib return code %d.\n", file->name, zlib_result ); + Mem_Free( compressed_buffer ); + Mem_Free( decompressed_buffer ); + return NULL; + } + + } + else + { + Con_Reportf( S_ERROR "Zip_LoadFile: %s : file compressed with unknown algorithm.\n", file->name ); + return NULL; + } + + FS_EnsureOpenZip( NULL ); + return NULL; +} + + +qboolean FS_AddZip_Fullpath( const char *zipfile, qboolean *already_loaded, int flags ) +{ + searchpath_t *search; + zip_t *zip = NULL; + const char *ext = COM_FileExtension( zipfile ); + int errorcode = ZIP_LOAD_COULDNT_OPEN; + + for( search = fs_searchpaths; search; search = search->next ) + { + if( search->type == SEARCHPATH_ZIP && !Q_stricmp( search->zip->filename, zipfile )) + { + if( already_loaded ) *already_loaded = true; + return true; // already loaded + } + } + + if( already_loaded ) *already_loaded = false; + + if( !Q_stricmp( ext, "pk3" ) ) + zip = FS_LoadZip( zipfile, &errorcode ); + + if( zip ) + { + string fullpath; + int i; + + search = (searchpath_t *)Mem_Calloc( fs_mempool, sizeof( searchpath_t ) ); + search->zip = zip; + search->type = SEARCHPATH_ZIP; + search->next = fs_searchpaths; + search->flags |= flags; + fs_searchpaths = search; + + Con_Reportf( "Adding zipfile: %s (%i files)\n", zipfile, zip->numfiles ); + + // time to add in search list all the wads that contains in current pakfile (if do) + for( i = 0; i < zip->numfiles; i++ ) + { + if( !Q_stricmp( COM_FileExtension( zip->files[i].name ), "wad" )) + { + Q_snprintf( fullpath, MAX_STRING, "%s/%s", zipfile, zip->files[i].name ); + FS_AddWad_Fullpath( fullpath, NULL, flags ); + } + } + return true; + } + else + { + if( errorcode != ZIP_LOAD_NO_FILES ) + Con_Reportf( S_ERROR "FS_AddZip_Fullpath: unable to load zip \"%s\"\n", zipfile ); + return false; + } +} + +int FS_FileTimeZIP( zip_t *zip ) +{ + return zip->filetime; +} + +void FS_PrintZIPInfo( char *dst, size_t size, zip_t *zip ) +{ + Q_snprintf( dst, size, "%s (%i files)", zip->filename, zip->numfiles ); +} + +int FS_FindFileZIP( zip_t *zip, const char *name ) +{ + int left, right, middle; + + // look for the file (binary search) + left = 0; + right = zip->numfiles - 1; + while( left <= right ) + { + int diff; + + middle = (left + right) / 2; + diff = Q_stricmp( zip->files[middle].name, name ); + + // Found it + if( !diff ) + return middle; + + // if we're too far in the list + if( diff > 0 ) + right = middle - 1; + else left = middle + 1; + } + + return -1; +} + +void FS_SearchZIP( stringlist_t *list, zip_t *zip, const char *pattern ) +{ + string temp; + const char *slash, *backslash, *colon, *separator; + int j, i; + + for( i = 0; i < zip->numfiles; i++ ) + { + Q_strncpy( temp, zip->files[i].name, sizeof( temp )); + while( temp[0] ) + { + if( matchpattern( temp, pattern, true )) + { + for( j = 0; j < list->numstrings; j++ ) + { + if( !Q_strcmp( list->strings[j], temp )) + break; + } + + if( j == list->numstrings ) + stringlistappend( list, temp ); + } + + // strip off one path element at a time until empty + // this way directories are added to the listing if they match the pattern + slash = Q_strrchr( temp, '/' ); + backslash = Q_strrchr( temp, '\\' ); + colon = Q_strrchr( temp, ':' ); + separator = temp; + if( separator < slash ) + separator = slash; + if( separator < backslash ) + separator = backslash; + if( separator < colon ) + separator = colon; + *((char *)separator) = 0; + } + } +} + diff --git a/game_launch/game.cpp b/game_launch/game.cpp index 7b637989..98389f9f 100644 --- a/game_launch/game.cpp +++ b/game_launch/game.cpp @@ -14,24 +14,24 @@ GNU General Public License for more details. */ #include "port.h" +#include "build.h" #include #include #include #include -#if defined(__APPLE__) || defined(__unix__) || defined(__HAIKU__) - #define XASHLIB "libxash." OS_LIB_EXT -#elif _WIN32 - #if !__MINGW32__ && _MSC_VER >= 1200 - #define USE_WINMAIN - #endif - #define XASHLIB "xash.dll" - #define dlerror() GetStringLastError() - #include // CommandLineToArgvW -#endif +#if XASH_POSIX +#define XASHLIB "libxash." OS_LIB_EXT +#define LoadLibrary( x ) dlopen( x, RTLD_NOW ) +#define GetProcAddress( x, y ) dlsym( x, y ) +#define FreeLibrary( x ) dlclose( x ) +#elif XASH_WIN32 +#include // CommandLineToArgvW +#define XASHLIB "xash.dll" +#define SDL2LIB "SDL2.dll" +#define dlerror() GetStringLastError() -#ifdef WIN32 extern "C" { // Enable NVIDIA High Performance Graphics while using Integrated Graphics. @@ -40,6 +40,8 @@ __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; // Enable AMD High Performance Graphics while using Integrated Graphics. __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; } +#else +#error // port me! #endif #define E_GAME "XASH3D_GAME" // default env dir to start from @@ -54,7 +56,7 @@ static pfnShutdown Xash_Shutdown = NULL; static char szGameDir[128]; // safe place to keep gamedir static int szArgc; static char **szArgv; -static HINSTANCE hEngine; +static HINSTANCE hEngine; static void Xash_Error( const char *szFmt, ... ) { @@ -70,6 +72,7 @@ static void Xash_Error( const char *szFmt, ... ) #else fprintf( stderr, "Xash Error: %s\n", buffer ); #endif + exit( 1 ); } @@ -88,6 +91,19 @@ static const char *GetStringLastError() static void Sys_LoadEngine( void ) { +#if XASH_WIN32 + HMODULE hSdl; + + if (( hSdl = LoadLibraryEx( SDL2LIB, NULL, LOAD_LIBRARY_AS_DATAFILE )) == NULL ) + { + Xash_Error("Unable to load the " SDL2LIB ": %s", dlerror() ); + } + else + { + FreeLibrary( hSdl ); + } +#endif + if(( hEngine = LoadLibrary( XASHLIB )) == NULL ) { Xash_Error("Unable to load the " XASHLIB ": %s", dlerror() ); @@ -107,12 +123,15 @@ static void Sys_UnloadEngine( void ) if( Xash_Shutdown ) Xash_Shutdown( ); if( hEngine ) FreeLibrary( hEngine ); + hEngine = NULL; Xash_Main = NULL; Xash_Shutdown = NULL; } static void Sys_ChangeGame( const char *progname ) { + // a1ba: may never be called within engine + // if platform supports execv() function if( !progname || !progname[0] ) Xash_Error( "Sys_ChangeGame: NULL gamedir" ); @@ -121,9 +140,8 @@ static void Sys_ChangeGame( const char *progname ) strncpy( szGameDir, progname, sizeof( szGameDir ) - 1 ); - Sys_UnloadEngine (); + Sys_UnloadEngine(); Sys_LoadEngine (); - Xash_Main( szArgc, szArgv, szGameDir, 1, Sys_ChangeGame ); } @@ -131,26 +149,26 @@ _inline int Sys_Start( void ) { int ret; pfnChangeGame changeGame = NULL; + const char *game = getenv( E_GAME ); + + if( !game ) + game = GAME_PATH; + + strncpy( szGameDir, game, sizeof( szGameDir ) - 1 ); Sys_LoadEngine(); -#ifndef XASH_DISABLE_MENU_CHANGEGAME if( Xash_Shutdown ) changeGame = Sys_ChangeGame; -#endif - const char *game = getenv( E_GAME ); - if( !game ) - game = GAME_PATH; - - ret = Xash_Main( szArgc, szArgv, game, 0, changeGame ); + ret = Xash_Main( szArgc, szArgv, szGameDir, 0, changeGame ); Sys_UnloadEngine(); return ret; } -#ifndef USE_WINMAIN +#if !XASH_WIN32 int main( int argc, char **argv ) { szArgc = argc; @@ -159,7 +177,6 @@ int main( int argc, char **argv ) return Sys_Start(); } #else -//#pragma comment(lib, "shell32.lib") int __stdcall WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdLine, int nShow ) { LPWSTR* lpArgv; @@ -170,8 +187,10 @@ int __stdcall WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdLine, int for( i = 0; i < szArgc; ++i ) { - int size = wcslen(lpArgv[i]) + 1; - szArgv[i] = ( char* )malloc( size ); + size_t size = wcslen(lpArgv[i]) + 1; + + // just in case, allocate some more memory + szArgv[i] = ( char * )malloc( size * sizeof( wchar_t )); wcstombs( szArgv[i], lpArgv[i], size ); } szArgv[szArgc] = 0; diff --git a/game_launch/wscript b/game_launch/wscript index 424d4ffc..d4dab31c 100644 --- a/game_launch/wscript +++ b/game_launch/wscript @@ -37,6 +37,7 @@ def build(bld): features = 'c cxx cxxprogram', includes = includes, use = libs, + rpath = '$ORIGIN', install_path = bld.env.BINDIR, subsystem = bld.env.MSVC_SUBSYSTEM ) diff --git a/mainui b/mainui index 330b16fb..97fcbf89 160000 --- a/mainui +++ b/mainui @@ -1 +1 @@ -Subproject commit 330b16fb29cfe0915047db9e2a374777e6b513ec +Subproject commit 97fcbf8979f22d774b1cc01cb5553743592d39d0 diff --git a/engine/common/build.c b/public/build.c similarity index 98% rename from engine/common/build.c rename to public/build.c index c4ddaeeb..b9662c53 100644 --- a/engine/common/build.c +++ b/public/build.c @@ -13,7 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ -#include "common.h" +#include "crtlib.h" static const char *date = __DATE__ ; static const char *mon[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; @@ -95,6 +95,8 @@ const char *Q_buildos( void ) osname = "DOS4GW"; #elif XASH_HAIKU osname = "haiku"; +#elif XASH_SERENITY + osname = "serenityos"; #else #error "Place your operating system name here! If this is a mistake, try to fix conditions above and report a bug" #endif diff --git a/public/build.h b/public/build.h index 6e1f326d..57a7735f 100644 --- a/public/build.h +++ b/public/build.h @@ -74,6 +74,7 @@ For more information, please refer to #undef XASH_RISCV_DOUBLEFP #undef XASH_RISCV_SINGLEFP #undef XASH_RISCV_SOFTFP +#undef XASH_SERENITY #undef XASH_WIN32 #undef XASH_WIN64 #undef XASH_X86 @@ -125,6 +126,9 @@ For more information, please refer to #elif defined __HAIKU__ #define XASH_HAIKU 1 #define XASH_POSIX 1 +#elif defined __serenity__ + #define XASH_SERENITY 1 + #define XASH_POSIX 1 #else #error "Place your operating system name here! If this is a mistake, try to fix conditions above and report a bug" #endif diff --git a/public/crtlib.c b/public/crtlib.c index 9aca2cf6..652ae917 100644 --- a/public/crtlib.c +++ b/public/crtlib.c @@ -21,6 +21,7 @@ GNU General Public License for more details. #include #include "stdio.h" #include "crtlib.h" +#include "xash3d_mathlib.h" void Q_strnupr( const char *in, char *out, size_t size_out ) { @@ -60,6 +61,16 @@ qboolean Q_isdigit( const char *str ) return false; } +qboolean Q_isspace( const char *str ) +{ + if( str && *str ) + { + while( isspace( *str ) ) str++; + if( !*str ) return true; + } + return false; +} + size_t Q_colorstr( const char *string ) { size_t len; @@ -306,94 +317,6 @@ void Q_atov( float *vec, const char *str, size_t siz ) } } -char *Q_strchr( const char *s, char c ) -{ - size_t len = Q_strlen( s ); - - while( len-- ) - { - if( *++s == c ) - return (char *)s; - } - return 0; -} - -char *Q_strrchr( const char *s, char c ) -{ - size_t len = Q_strlen( s ); - - s += len; - - while( len-- ) - { - if( *--s == c ) - return (char *)s; - } - return 0; -} - -int Q_strnicmp( const char *s1, const char *s2, int n ) -{ - int c1, c2; - - if( s1 == NULL ) - { - if( s2 == NULL ) - return 0; - else return -1; - } - else if( s2 == NULL ) - { - return 1; - } - - do { - c1 = *s1++; - c2 = *s2++; - - if( !n-- ) return 0; // strings are equal until end point - - if( c1 != c2 ) - { - if( c1 >= 'a' && c1 <= 'z' ) c1 -= ('a' - 'A'); - if( c2 >= 'a' && c2 <= 'z' ) c2 -= ('a' - 'A'); - if( c1 != c2 ) return c1 < c2 ? -1 : 1; - } - } while( c1 ); - - // strings are equal - return 0; -} - -int Q_strncmp( const char *s1, const char *s2, int n ) -{ - int c1, c2; - - if( s1 == NULL ) - { - if( s2 == NULL ) - return 0; - else return -1; - } - else if( s2 == NULL ) - { - return 1; - } - - do { - c1 = *s1++; - c2 = *s2++; - - // strings are equal until end point - if( !n-- ) return 0; - if( c1 != c2 ) return c1 < c2 ? -1 : 1; - - } while( c1 ); - - // strings are equal - return 0; -} - static qboolean Q_starcmp( const char *pattern, const char *text ) { char c, c1; @@ -485,31 +408,7 @@ const char* Q_timestamp( int format ) return timestamp; } -char *Q_strstr( const char *string, const char *string2 ) -{ - int c; - size_t len; - - if( !string || !string2 ) return NULL; - - c = *string2; - len = Q_strlen( string2 ); - - while( string ) - { - for( ; *string && *string != c; string++ ); - - if( *string ) - { - if( !Q_strncmp( string, string2, len )) - break; - string++; - } - else return NULL; - } - return (char *)string; -} - +#if !defined( HAVE_STRCASESTR ) char *Q_stristr( const char *string, const char *string2 ) { int c; @@ -534,6 +433,7 @@ char *Q_stristr( const char *string, const char *string2 ) } return (char *)string; } +#endif // !defined( HAVE_STRCASESTR ) int Q_vsnprintf( char *buffer, size_t buffersize, const char *format, va_list args ) { @@ -604,6 +504,17 @@ char *Q_strpbrk(const char *s, const char *accept) return NULL; } +void COM_StripColors( const char *in, char *out ) +{ + while ( *in ) + { + if ( IsColorString( in ) ) + in += 2; + else *out++ = *in++; + } + *out = '\0'; +} + uint Q_hashkey( const char *string, uint hashSize, qboolean caseinsensitive ) { uint i, hashKey = 0; @@ -651,7 +562,7 @@ char *Q_pretifymem( float value, int digitsafterdecimal ) else Q_sprintf( suffix, " bytes" ); // clamp to >= 0 - digitsafterdecimal = max( digitsafterdecimal, 0 ); + digitsafterdecimal = Q_max( digitsafterdecimal, 0 ); // if it's basically integral, don't do any decimals if( fabs( value - (int)value ) < 0.00001f ) @@ -908,6 +819,23 @@ void COM_RemoveLineFeed( char *str ) } } +/* +============ +COM_FixSlashes + +Changes all '/' characters into '\' characters, in place. +============ +*/ +void COM_FixSlashes( char *pname ) +{ + while( *pname ) + { + if( *pname == '\\' ) + *pname = '/'; + pname++; + } +} + /* ============ COM_PathSlashFix @@ -978,11 +906,14 @@ COM_ParseFile text parser ============== */ -char *_COM_ParseFileSafe( char *data, char *token, const int size, unsigned int flags, int *plen ) +char *COM_ParseFileSafe( char *data, char *token, const int size, unsigned int flags, int *plen, qboolean *quoted ) { int c, len = 0; qboolean overflow = false; + if( quoted ) + *quoted = false; + if( !token || !size ) { if( plen ) *plen = 0; @@ -1016,6 +947,9 @@ skipwhite: // handle quoted strings specially if( c == '\"' ) { + if( quoted ) + *quoted = true; + data++; while( 1 ) { diff --git a/public/crtlib.h b/public/crtlib.h index 338598db..84027ea6 100644 --- a/public/crtlib.h +++ b/public/crtlib.h @@ -16,16 +16,14 @@ GNU General Public License for more details. #ifndef STDLIB_H #define STDLIB_H -#include #include +#include #include "build.h" +#include "xash3d_types.h" -#ifdef __GNUC__ -#define _format(x) __attribute__((format(printf, x, x+1))) -#define NORETURN __attribute__((noreturn)) -#else -#define _format(x) -#define NORETURN +#ifdef __cplusplus +extern "C" +{ #endif // timestamp modes @@ -43,6 +41,17 @@ enum // exported APIs headers and will get nice warning in case of changing values #define PFILE_IGNOREBRACKET (1<<0) #define PFILE_HANDLECOLON (1<<1) +#define PFILE_TOKEN_MAX_LENGTH 1024 +#define PFILE_FS_TOKEN_MAX_LENGTH 512 + +// +// build.c +// +int Q_buildnum( void ); +int Q_buildnum_compat( void ); +const char *Q_buildos( void ); +const char *Q_buildarch( void ); +const char *Q_buildcommit( void ); // // crtlib.c @@ -61,24 +70,20 @@ size_t Q_strncat( char *dst, const char *src, size_t siz ); size_t Q_strncpy( char *dst, const char *src, size_t siz ); uint Q_hashkey( const char *string, uint hashSize, qboolean caseinsensitive ); qboolean Q_isdigit( const char *str ); +qboolean Q_isspace( const char *str ); int Q_atoi( const char *str ); float Q_atof( const char *str ); void Q_atov( float *vec, const char *str, size_t siz ); -char *Q_strchr( const char *s, char c ); -char *Q_strrchr( const char *s, char c ); -#define Q_stricmp( s1, s2 ) Q_strnicmp( s1, s2, 99999 ) -int Q_strnicmp( const char *s1, const char *s2, int n ); -#define Q_strcmp( s1, s2 ) Q_strncmp( s1, s2, 99999 ) -int Q_strncmp( const char *s1, const char *s2, int n ); +#define Q_strchr strchr +#define Q_strrchr strrchr qboolean Q_stricmpext( const char *s1, const char *s2 ); const char *Q_timestamp( int format ); -char *Q_stristr( const char *string, const char *string2 ); -char *Q_strstr( const char *string, const char *string2 ); #define Q_vsprintf( buffer, format, args ) Q_vsnprintf( buffer, 99999, format, args ) int Q_vsnprintf( char *buffer, size_t buffersize, const char *format, va_list args ); int Q_snprintf( char *buffer, size_t buffersize, const char *format, ... ) _format( 3 ); int Q_sprintf( char *buffer, const char *format, ... ) _format( 2 ); char *Q_strpbrk(const char *s, const char *accept); +void COM_StripColors( const char *in, char *out ); #define Q_memprint( val ) Q_pretifymem( val, 2 ) char *Q_pretifymem( float value, int digitsafterdecimal ); char *va( const char *format, ... ) _format( 1 ); @@ -90,15 +95,73 @@ void COM_ExtractFilePath( const char *path, char *dest ); const char *COM_FileWithoutPath( const char *in ); void COM_StripExtension( char *path ); void COM_RemoveLineFeed( char *str ); +void COM_FixSlashes( char *pname ); void COM_PathSlashFix( char *path ); char COM_Hex2Char( uint8_t hex ); void COM_Hex2String( uint8_t hex, char *str ); +// return 0 on empty or null string, 1 otherwise #define COM_CheckString( string ) ( ( !string || !*string ) ? 0 : 1 ) #define COM_CheckStringEmpty( string ) ( ( !*string ) ? 0 : 1 ) -char *_COM_ParseFileSafe( char *data, char *token, const int size, unsigned int flags, int *len ); -#define COM_ParseFile( data, token, size ) _COM_ParseFileSafe( data, token, size, 0, NULL ) -#define COM_ParseFileLegacy( data, token ) COM_ParseFileSafe( data, token, INT_MAX ) +char *COM_ParseFileSafe( char *data, char *token, const int size, unsigned int flags, int *len, qboolean *quoted ); +#define COM_ParseFile( data, token, size ) COM_ParseFileSafe( data, token, size, 0, NULL, NULL ) int matchpattern( const char *in, const char *pattern, qboolean caseinsensitive ); int matchpattern_with_separator( const char *in, const char *pattern, qboolean caseinsensitive, const char *separators, qboolean wildcard_least_one ); +// libc implementations +static inline int Q_strcmp( const char *s1, const char *s2 ) +{ + return unlikely(!s1) ? + ( !s2 ? 0 : -1 ) : + ( unlikely(!s2) ? 1 : strcmp( s1, s2 )); +} + +static inline int Q_strncmp( const char *s1, const char *s2, size_t n ) +{ + return unlikely(!s1) ? + ( !s2 ? 0 : -1 ) : + ( unlikely(!s2) ? 1 : strncmp( s1, s2, n )); +} + +static inline char *Q_strstr( const char *s1, const char *s2 ) +{ + return unlikely( !s1 || !s2 ) ? NULL : (char*)strstr( s1, s2 ); +} + +// libc extensions, be careful + +#if XASH_WIN32 +#define strcasecmp stricmp +#define strncasecmp strnicmp +#endif // XASH_WIN32 + +static inline int Q_stricmp( const char *s1, const char *s2 ) +{ + return unlikely(!s1) ? + ( !s2 ? 0 : -1 ) : + ( unlikely(!s2) ? 1 : strcasecmp( s1, s2 )); +} + +static inline int Q_strnicmp( const char *s1, const char *s2, size_t n ) +{ + return unlikely(!s1) ? + ( !s2 ? 0 : -1 ) : + ( unlikely(!s2) ? 1 : strncasecmp( s1, s2, n )); +} + +#if defined( HAVE_STRCASESTR ) +#if XASH_WIN32 +#define strcasestr stristr +#endif +static inline char *Q_stristr( const char *s1, const char *s2 ) +{ + return unlikely( !s1 || !s2 ) ? NULL : (char *)strcasestr( s1, s2 ); +} +#else // defined( HAVE_STRCASESTR ) +char *Q_stristr( const char *s1, const char *s2 ); +#endif // defined( HAVE_STRCASESTR ) + +#ifdef __cplusplus +} +#endif + #endif//STDLIB_H diff --git a/engine/common/miniz.h b/public/miniz.h similarity index 100% rename from engine/common/miniz.h rename to public/miniz.h diff --git a/public/xash3d_mathlib.h b/public/xash3d_mathlib.h index 4e37788b..363090d2 100644 --- a/public/xash3d_mathlib.h +++ b/public/xash3d_mathlib.h @@ -22,6 +22,7 @@ GNU General Public License for more details. #endif #include "build.h" +#include "com_model.h" #ifdef XASH_MSVC #pragma warning(disable : 4201) // nonstandard extension used @@ -33,18 +34,18 @@ GNU General Public License for more details. #define ROLL 2 #ifndef M_PI -#define M_PI (float)3.14159265358979323846 +#define M_PI (double)3.14159265358979323846 #endif #ifndef M_PI2 -#define M_PI2 ((float)(M_PI * 2)) +#define M_PI2 ((double)(M_PI * 2)) #endif #define M_PI_F ((float)(M_PI)) #define M_PI2_F ((float)(M_PI2)) -#define RAD2DEG( x ) ((float)(x) * (float)(180.f / M_PI_F)) -#define DEG2RAD( x ) ((float)(x) * (float)(M_PI_F / 180.f)) +#define RAD2DEG( x ) ((double)(x) * (double)(180.0 / M_PI)) +#define DEG2RAD( x ) ((double)(x) * (double)(M_PI / 180.0)) #define NUMVERTEXNORMALS 162 @@ -92,6 +93,7 @@ GNU General Public License for more details. #define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) #define DotProductAbs(x,y) (abs((x)[0]*(y)[0])+abs((x)[1]*(y)[1])+abs((x)[2]*(y)[2])) #define DotProductFabs(x,y) (fabs((x)[0]*(y)[0])+fabs((x)[1]*(y)[1])+fabs((x)[2]*(y)[2])) +#define DotProductPrecise(x,y) ((double)(x)[0]*(double)(y)[0]+(double)(x)[1]*(double)(y)[1]+(double)(x)[2]*(double)(y)[2]) #define CrossProduct(a,b,c) ((c)[0]=(a)[1]*(b)[2]-(a)[2]*(b)[1],(c)[1]=(a)[2]*(b)[0]-(a)[0]*(b)[2],(c)[2]=(a)[0]*(b)[1]-(a)[1]*(b)[0]) #define Vector2Subtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1]) #define VectorSubtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2]) @@ -104,7 +106,7 @@ GNU General Public License for more details. #define VectorScale(in, scale, out) ((out)[0] = (in)[0] * (scale),(out)[1] = (in)[1] * (scale),(out)[2] = (in)[2] * (scale)) #define VectorCompare(v1,v2) ((v1)[0]==(v2)[0] && (v1)[1]==(v2)[1] && (v1)[2]==(v2)[2]) #define VectorDivide( in, d, out ) VectorScale( in, (1.0f / (d)), out ) -#define VectorMax(a) ( max((a)[0], max((a)[1], (a)[2])) ) +#define VectorMax(a) ( Q_max((a)[0], Q_max((a)[1], (a)[2])) ) #define VectorAvg(a) ( ((a)[0] + (a)[1] + (a)[2]) / 3 ) #define VectorLength(a) ( sqrt( DotProduct( a, a ))) #define VectorLength2(a) (DotProduct( a, a )) diff --git a/ref_gl/gl-wes-v2 b/ref_gl/gl-wes-v2 index 11384d5c..7f567393 160000 --- a/ref_gl/gl-wes-v2 +++ b/ref_gl/gl-wes-v2 @@ -1 +1 @@ -Subproject commit 11384d5cf6de22de3a9a47f97f2e1a4f069d4cd4 +Subproject commit 7f567393d4273d8f33924d98968c9a96991aba3c diff --git a/ref_gl/gl4es b/ref_gl/gl4es index 4f2151a1..5c28420a 160000 --- a/ref_gl/gl4es +++ b/ref_gl/gl4es @@ -1 +1 @@ -Subproject commit 4f2151a104ac45bf5417a0063e96148204f8256c +Subproject commit 5c28420a384c93345a7a5d060a56a0de5f2ac871 diff --git a/ref_gl/gl_alias.c b/ref_gl/gl_alias.c index 77b27b77..d9766755 100644 --- a/ref_gl/gl_alias.c +++ b/ref_gl/gl_alias.c @@ -486,7 +486,7 @@ void *Mod_LoadSingleSkin( daliasskintype_t *pskintype, int skinnum, int size ) Q_snprintf( name, sizeof( name ), "%s:frame%i", loadmodel->name, skinnum ); Q_snprintf( lumaname, sizeof( lumaname ), "%s:luma%i", loadmodel->name, skinnum ); Q_snprintf( checkname, sizeof( checkname ), "%s_%i.tga", loadmodel->name, skinnum ); - if( !gEngfuncs.FS_FileExists( checkname, false ) || ( pic = gEngfuncs.FS_LoadImage( checkname, NULL, 0 )) == NULL ) + if( !gEngfuncs.fsapi->FileExists( checkname, false ) || ( pic = gEngfuncs.FS_LoadImage( checkname, NULL, 0 )) == NULL ) pic = Mod_CreateSkinData( loadmodel, (byte *)(pskintype + 1), m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); m_pAliasHeader->gl_texturenum[skinnum][0] = diff --git a/ref_gl/gl_backend.c b/ref_gl/gl_backend.c index ffc56ab0..da530405 100644 --- a/ref_gl/gl_backend.c +++ b/ref_gl/gl_backend.c @@ -300,7 +300,7 @@ GL_TexGen */ void GL_TexGen( GLenum coord, GLenum mode ) { - int tmu = min( glConfig.max_texture_coords, glState.activeTMU ); + int tmu = Q_min( glConfig.max_texture_coords, glState.activeTMU ); int bit, gen; switch( coord ) @@ -350,7 +350,7 @@ GL_SetTexCoordArrayMode */ void GL_SetTexCoordArrayMode( GLenum mode ) { - int tmu = min( glConfig.max_texture_coords, glState.activeTMU ); + int tmu = Q_min( glConfig.max_texture_coords, glState.activeTMU ); int bit, cmode = glState.texCoordArrayMode[tmu]; if( mode == GL_TEXTURE_COORD_ARRAY ) @@ -478,7 +478,7 @@ qboolean VID_ScreenShot( const char *filename, int shot_type ) case VID_SCREENSHOT: break; case VID_SNAPSHOT: - gEngfuncs.FS_AllowDirectPaths( true ); + gEngfuncs.fsapi->AllowDirectPaths( true ); break; case VID_LEVELSHOT: flags |= IMAGE_RESAMPLE; @@ -509,7 +509,7 @@ qboolean VID_ScreenShot( const char *filename, int shot_type ) // write image result = gEngfuncs.FS_SaveImage( filename, r_shot ); - gEngfuncs.FS_AllowDirectPaths( false ); // always reset after store screenshot + gEngfuncs.fsapi->AllowDirectPaths( false ); // always reset after store screenshot gEngfuncs.FS_FreeImage( r_shot ); return result; @@ -656,7 +656,7 @@ rebuild_page: if( i == MAX_TEXTURES && gl_showtextures->value != 1 ) { // bad case, rewind to one and try again - gEngfuncs.Cvar_SetValue( "r_showtextures", max( 1, gl_showtextures->value - 1 )); + gEngfuncs.Cvar_SetValue( "r_showtextures", Q_max( 1, gl_showtextures->value - 1 )); if( ++numTries < 2 ) goto rebuild_page; // to prevent infinite loop } diff --git a/ref_gl/gl_decals.c b/ref_gl/gl_decals.c index 2266df1e..c9e70983 100644 --- a/ref_gl/gl_decals.c +++ b/ref_gl/gl_decals.c @@ -476,8 +476,8 @@ static decal_t *R_DecalIntersect( decalinfo_t *decalinfo, msurface_t *surf, int // Now figure out the part of the projection that intersects pDecal's // clip box [0,0,1,1]. - Vector2Set( vUnionMin, max( vDecalMin[0], 0 ), max( vDecalMin[1], 0 )); - Vector2Set( vUnionMax, min( vDecalMax[0], 1 ), min( vDecalMax[1], 1 )); + Vector2Set( vUnionMin, Q_max( vDecalMin[0], 0 ), Q_max( vDecalMin[1], 0 )); + Vector2Set( vUnionMax, Q_min( vDecalMax[0], 1 ), Q_min( vDecalMax[1], 1 )); if( vUnionMin[0] < 1 && vUnionMin[1] < 1 && vUnionMax[0] > 0 && vUnionMax[1] > 0 ) { diff --git a/ref_gl/gl_export.h b/ref_gl/gl_export.h index 07d071d9..13b7b4d1 100644 --- a/ref_gl/gl_export.h +++ b/ref_gl/gl_export.h @@ -401,6 +401,10 @@ typedef float GLmatrix[16]; #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#define GL_COMPRESSED_RGBA_BPTC_UNORM_ARB 0x8E8C +#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB 0x8E8D +#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB 0x8E8E +#define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB 0x8E8F #define GL_COMPRESSED_RED_GREEN_RGTC2_EXT 0x8DBD #define GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI 0x8837 #define GL_COMPRESSED_ALPHA_ARB 0x84E9 diff --git a/ref_gl/gl_image.c b/ref_gl/gl_image.c index 643325d9..658b0240 100644 --- a/ref_gl/gl_image.c +++ b/ref_gl/gl_image.c @@ -22,6 +22,19 @@ static gl_texture_t gl_textures[MAX_TEXTURES]; static gl_texture_t* gl_texturesHashTable[TEXTURES_HASH_SIZE]; static uint gl_numTextures; +static byte dottexture[8][8] = +{ + {0,1,1,0,0,0,0,0}, + {1,1,1,1,0,0,0,0}, + {1,1,1,1,0,0,0,0}, + {0,1,1,0,0,0,0,0}, + {0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0}, +}; + + #define IsLightMap( tex ) ( FBitSet(( tex )->flags, TF_ATLAS_PAGE )) /* ================= @@ -373,6 +386,9 @@ static size_t GL_CalcImageSize( pixformat_t format, int width, int height, int d break; case PF_DXT3: case PF_DXT5: + case PF_BC6H_SIGNED: + case PF_BC6H_UNSIGNED: + case PF_BC7: case PF_ATI2: size = (((width + 3) >> 2) * ((height + 3) >> 2) * 16) * depth; break; @@ -404,6 +420,10 @@ static size_t GL_CalcTextureSize( GLenum format, int width, int height, int dept case GL_COMPRESSED_RED_GREEN_RGTC2_EXT: case GL_COMPRESSED_LUMINANCE_ALPHA_ARB: case GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI: + case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB: + case GL_COMPRESSED_RGBA_BPTC_UNORM_ARB: + case GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB: + case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB: size = (((width + 3) >> 2) * ((height + 3) >> 2) * 16) * depth; break; case GL_RGBA8: @@ -681,6 +701,9 @@ static void GL_SetTextureFormat( gl_texture_t *tex, pixformat_t format, int chan case PF_DXT1: tex->format = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; // never use DXT1 with 1-bit alpha case PF_DXT3: tex->format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; case PF_DXT5: tex->format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; + case PF_BC6H_SIGNED: tex->format = GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB; break; + case PF_BC6H_UNSIGNED: tex->format = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB; break; + case PF_BC7: tex->format = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; break; case PF_ATI2: if( glConfig.hardware_type == GLHW_RADEON ) tex->format = GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI; @@ -1153,6 +1176,15 @@ static qboolean GL_UploadTexture( gl_texture_t *tex, rgbdata_t *pic ) return false; } + if( pic->type == PF_BC6H_SIGNED || pic->type == PF_BC6H_UNSIGNED || pic->type == PF_BC7 ) + { + if( !GL_Support( GL_ARB_TEXTURE_COMPRESSION_BPTC )) + { + gEngfuncs.Con_DPrintf( S_ERROR "GL_UploadTexture: BC6H/BC7 compression formats is not supported by your hardware\n" ); + return false; + } + } + GL_SetTextureDimensions( tex, pic->width, pic->height, pic->depth ); GL_SetTextureFormat( tex, pic->type, pic->flags ); @@ -1620,7 +1652,7 @@ int GL_LoadTextureArray( const char **names, int flags ) mipsize = srcsize = dstsize = 0; - for( j = 0; j < max( 1, pic->numMips ); j++ ) + for( j = 0; j < Q_max( 1, pic->numMips ); j++ ) { int width = Q_max( 1, ( pic->width >> j )); int height = Q_max( 1, ( pic->height >> j )); @@ -1876,6 +1908,11 @@ void GL_ProcessTexture( int texnum, float gamma, int topColor, int bottomColor ) // all the operations makes over the image copy not an original pic = gEngfuncs.FS_CopyImage( image->original ); + + // we need to expand image into RGBA buffer + if( pic->type == PF_INDEXED_24 || pic->type == PF_INDEXED_32 ) + flags |= IMAGE_FORCE_RGBA; + gEngfuncs.Image_Process( &pic, topColor, bottomColor, flags, 0.0f ); GL_UploadTexture( image, pic ); @@ -1986,18 +2023,15 @@ static void GL_CreateInternalTextures( void ) tr.defaultTexture = GL_LoadTextureInternal( REF_DEFAULT_TEXTURE, pic, TF_COLORMAP ); // particle texture from quake1 - pic = GL_FakeImage( 16, 16, 1, IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA ); + pic = GL_FakeImage( 8, 8, 1, IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA ); - for( x = 0; x < 16; x++ ) + for( x = 0; x < 8; x++ ) { - dx2 = x - 8; - dx2 = dx2 * dx2; - - for( y = 0; y < 16; y++ ) + for( y = 0; y < 8; y++ ) { - dy = y - 8; - d = 255 - 35 * sqrt( dx2 + dy * dy ); - pic->buffer[( y * 16 + x ) * 4 + 3] = bound( 0, d, 255 ); + if( dottexture[x][y] ) + pic->buffer[( y * 8 + x ) * 4 + 3] = 255; + else pic->buffer[( y * 8 + x ) * 4 + 3] = 0; } } diff --git a/ref_gl/gl_local.h b/ref_gl/gl_local.h index 09e657c2..deac6e94 100644 --- a/ref_gl/gl_local.h +++ b/ref_gl/gl_local.h @@ -623,6 +623,7 @@ enum GL_ARB_VERTEX_BUFFER_OBJECT_EXT, GL_DRAW_RANGEELEMENTS_EXT, GL_TEXTURE_MULTISAMPLE, + GL_ARB_TEXTURE_COMPRESSION_BPTC, GL_EXTCOUNT, // must be last }; diff --git a/ref_gl/gl_opengl.c b/ref_gl/gl_opengl.c index 885ea5b3..f0a33d9c 100644 --- a/ref_gl/gl_opengl.c +++ b/ref_gl/gl_opengl.c @@ -273,8 +273,6 @@ static void APIENTRY GL_DebugOutput( GLuint source, GLuint type, GLuint id, GLui gEngfuncs.Con_Printf( S_OPENGL_ERROR "%s\n", message ); break; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB: - gEngfuncs.Con_Printf( S_OPENGL_WARN "%s\n", message ); - break; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB: gEngfuncs.Con_Printf( S_OPENGL_WARN "%s\n", message ); break; @@ -282,8 +280,6 @@ static void APIENTRY GL_DebugOutput( GLuint source, GLuint type, GLuint id, GLui gEngfuncs.Con_Reportf( S_OPENGL_WARN "%s\n", message ); break; case GL_DEBUG_TYPE_PERFORMANCE_ARB: - gEngfuncs.Con_Printf( S_OPENGL_NOTE "%s\n", message ); - break; case GL_DEBUG_TYPE_OTHER_ARB: default: gEngfuncs.Con_Printf( S_OPENGL_NOTE "%s\n", message ); @@ -498,10 +494,10 @@ void R_RenderInfo_f( void ) if( glConfig.wrapper == GLES_WRAPPER_GL4ES ) { - const char *vendor = pglGetString( GL_VENDOR | 0x10000 ); - const char *renderer = pglGetString( GL_RENDERER | 0x10000 ); - const char *version = pglGetString( GL_VERSION | 0x10000 ); - const char *extensions = pglGetString( GL_EXTENSIONS | 0x10000 ); + const char *vendor = (const char *)pglGetString( GL_VENDOR | 0x10000 ); + const char *renderer = (const char *)pglGetString( GL_RENDERER | 0x10000 ); + const char *version = (const char *)pglGetString( GL_VERSION | 0x10000 ); + const char *extensions = (const char *)pglGetString( GL_EXTENSIONS | 0x10000 ); if( vendor ) gEngfuncs.Con_Printf( "GL4ES_VENDOR: %s\n", vendor ); @@ -644,10 +640,10 @@ void GL_InitExtensionsBigGL( void ) // gl4es may be used system-wide if( Q_stristr( glConfig.renderer_string, "gl4es" )) { - const char *vendor = pglGetString( GL_VENDOR | 0x10000 ); - const char *renderer = pglGetString( GL_RENDERER | 0x10000 ); - const char *version = pglGetString( GL_VERSION | 0x10000 ); - const char *extensions = pglGetString( GL_EXTENSIONS | 0x10000 ); + const char *vendor = (const char *)pglGetString( GL_VENDOR | 0x10000 ); + const char *renderer = (const char *)pglGetString( GL_RENDERER | 0x10000 ); + const char *version = (const char *)pglGetString( GL_VERSION | 0x10000 ); + const char *extensions = (const char *)pglGetString( GL_EXTENSIONS | 0x10000 ); glConfig.wrapper = GLES_WRAPPER_GL4ES; } @@ -712,6 +708,7 @@ void GL_InitExtensionsBigGL( void ) GL_CheckExtension( "GL_EXT_gpu_shader4", NULL, NULL, GL_EXT_GPU_SHADER4 ); // don't confuse users GL_CheckExtension( "GL_ARB_vertex_buffer_object", vbofuncs, "gl_vertex_buffer_object", GL_ARB_VERTEX_BUFFER_OBJECT_EXT ); GL_CheckExtension( "GL_ARB_texture_multisample", multisampletexfuncs, "gl_texture_multisample", GL_TEXTURE_MULTISAMPLE ); + GL_CheckExtension( "GL_ARB_texture_compression_bptc", NULL, "gl_texture_bptc_compression", GL_ARB_TEXTURE_COMPRESSION_BPTC ); if( GL_CheckExtension( "GL_ARB_shading_language_100", NULL, NULL, GL_SHADER_GLSL100_EXT )) { pglGetIntegerv( GL_MAX_TEXTURE_COORDS_ARB, &glConfig.max_texture_coords ); @@ -874,7 +871,7 @@ register VBO cvars and get default value */ static void R_CheckVBO( void ) { - const char *def = "1"; + const char *def = "0"; const char *dlightmode = "1"; int flags = FCVAR_ARCHIVE; qboolean disable = false; @@ -901,12 +898,8 @@ static void R_CheckVBO( void ) def = "0"; } - r_vbo = gEngfuncs.Cvar_Get( "r_vbo", def, flags, "draw world using VBO" ); - r_vbo_dlightmode = gEngfuncs.Cvar_Get( "r_vbo_dlightmode", dlightmode, FCVAR_ARCHIVE, "vbo dlight rendering mode(0-1)" ); - - // check if enabled manually - if( CVAR_TO_BOOL(r_vbo) ) - r_vbo->flags |= FCVAR_ARCHIVE; + r_vbo = gEngfuncs.Cvar_Get( "gl_vbo", def, flags, "draw world using VBO (known to be glitchy)" ); + r_vbo_dlightmode = gEngfuncs.Cvar_Get( "gl_vbo_dlightmode", dlightmode, FCVAR_ARCHIVE, "vbo dlight rendering mode(0-1)" ); } /* diff --git a/ref_gl/gl_rlight.c b/ref_gl/gl_rlight.c index c0c0c5fc..89679989 100644 --- a/ref_gl/gl_rlight.c +++ b/ref_gl/gl_rlight.c @@ -153,7 +153,8 @@ void R_PushDlights( void ) tr.dlightframecount = tr.framecount; RI.currententity = gEngfuncs.GetEntityByIndex( 0 ); - RI.currentmodel = RI.currententity->model; + if( RI.currententity ) + RI.currentmodel = RI.currententity->model; for( i = 0; i < MAX_DLIGHTS; i++, l++ ) { @@ -165,7 +166,8 @@ void R_PushDlights( void ) if( GL_FrustumCullSphere( &RI.frustum, l->origin, l->radius, 15 )) continue; - R_MarkLights( l, 1<nodes ); + if( RI.currententity ) + R_MarkLights( l, 1<nodes ); } } diff --git a/ref_gl/gl_rmain.c b/ref_gl/gl_rmain.c index 664bbcc0..e8f93616 100644 --- a/ref_gl/gl_rmain.c +++ b/ref_gl/gl_rmain.c @@ -145,8 +145,6 @@ int R_WorldToScreen( const vec3_t point, vec3_t screen ) if( w < 0.001f ) { - screen[0] *= 100000; - screen[1] *= 100000; behind = true; } else @@ -379,7 +377,7 @@ static void R_SetupProjectionMatrix( matrix4x4 m ) RI.farClip = R_GetFarClip(); zNear = 4.0f; - zFar = max( 256.0f, RI.farClip ); + zFar = Q_max( 256.0f, RI.farClip ); yMax = zNear * tan( RI.fov_y * M_PI_F / 360.0f ); yMin = -yMax; diff --git a/ref_gl/gl_rmisc.c b/ref_gl/gl_rmisc.c index c402e337..230e66b9 100644 --- a/ref_gl/gl_rmisc.c +++ b/ref_gl/gl_rmisc.c @@ -29,7 +29,7 @@ static void R_ParseDetailTextures( const char *filename ) texture_t *tex; int i; - afile = gEngfuncs.COM_LoadFile( filename, NULL, false ); + afile = gEngfuncs.fsapi->LoadFile( filename, NULL, false ); if( !afile ) return; pfile = (char *)afile; diff --git a/ref_gl/gl_rsurf.c b/ref_gl/gl_rsurf.c index c720feff..c4a53edd 100644 --- a/ref_gl/gl_rsurf.c +++ b/ref_gl/gl_rsurf.c @@ -700,7 +700,7 @@ static void LM_UploadBlock( qboolean dynamic ) r_lightmap.size = r_lightmap.width * r_lightmap.height * 4; r_lightmap.flags = IMAGE_HAS_COLOR; r_lightmap.buffer = gl_lms.lightmap_buffer; - tr.lightmapTextures[i] = GL_LoadTextureInternal( lmName, &r_lightmap, TF_FONT|TF_ATLAS_PAGE ); + tr.lightmapTextures[i] = GL_LoadTextureInternal( lmName, &r_lightmap, TF_NOMIPMAP|TF_ATLAS_PAGE ); if( ++gl_lms.current_lightmap_texture == MAX_LIGHTMAPS ) gEngfuncs.Host_Error( "AllocBlock: full\n" ); @@ -1794,7 +1794,7 @@ void R_GenerateVBO( void ) // we do not want to write vbo code that does not use multitexture if( !GL_Support( GL_ARB_VERTEX_BUFFER_OBJECT_EXT ) || !GL_Support( GL_ARB_MULTITEXTURE ) || glConfig.max_texture_units < 2 ) { - gEngfuncs.Cvar_FullSet( "r_vbo", "0", FCVAR_READ_ONLY ); + gEngfuncs.Cvar_FullSet( "gl_vbo", "0", FCVAR_READ_ONLY ); return; } @@ -2934,8 +2934,8 @@ static qboolean R_CheckLightMap( msurface_t *fa ) } else { - smax = min( smax, 132 ); - tmax = min( tmax, 132 ); + smax = Q_min( smax, 132 ); + tmax = Q_min( tmax, 132 ); //Host_MapDesignError( "R_RenderBrushPoly: bad surface extents: %d %d", fa->extents[0], fa->extents[1] ); memset( temp, 255, sizeof( temp ) ); } @@ -3630,9 +3630,6 @@ void GL_InitRandomTable( void ) { int tu, tv; - // make random predictable - gEngfuncs.COM_SetRandomSeed( 255 ); - for( tu = 0; tu < MOD_FRAMES; tu++ ) { for( tv = 0; tv < MOD_FRAMES; tv++ ) diff --git a/ref_gl/gl_studio.c b/ref_gl/gl_studio.c index 5155ef00..c71630d9 100644 --- a/ref_gl/gl_studio.c +++ b/ref_gl/gl_studio.c @@ -34,7 +34,8 @@ typedef struct model_t *model; } player_model_t; -cvar_t r_shadows = { "r_shadows", "0", 0 }; +// never gonna change, just shut up const warning +cvar_t r_shadows = { (char *)"r_shadows", (char *)"0", 0 }; static vec3_t hullcolor[8] = { @@ -2698,7 +2699,7 @@ static model_t *R_StudioSetupPlayerModel( int index ) Q_snprintf( state->modelname, sizeof( state->modelname ), "models/player/%s/%s.mdl", info->model, info->model ); - if( gEngfuncs.FS_FileExists( state->modelname, false )) + if( gEngfuncs.fsapi->FileExists( state->modelname, false )) state->model = gEngfuncs.Mod_ForName( state->modelname, false, true ); else state->model = NULL; diff --git a/ref_gl/gl_warp.c b/ref_gl/gl_warp.c index 32359d89..72e77c1c 100644 --- a/ref_gl/gl_warp.c +++ b/ref_gl/gl_warp.c @@ -78,7 +78,7 @@ static int CheckSkybox( const char *name ) { // build side name sidename = va( "%s%s.%s", name, r_skyBoxSuffix[j], skybox_ext[i] ); - if( gEngfuncs.FS_FileExists( sidename, false )) + if( gEngfuncs.fsapi->FileExists( sidename, false )) num_checked_sides++; } @@ -90,7 +90,7 @@ static int CheckSkybox( const char *name ) { // build side name sidename = va( "%s_%s.%s", name, r_skyBoxSuffix[j], skybox_ext[i] ); - if( gEngfuncs.FS_FileExists( sidename, false )) + if( gEngfuncs.fsapi->FileExists( sidename, false )) num_checked_sides++; } diff --git a/ref_gl/wscript b/ref_gl/wscript index af60caea..207e7ec6 100644 --- a/ref_gl/wscript +++ b/ref_gl/wscript @@ -59,6 +59,7 @@ def build(bld): source = bld.path.ant_glob(['*.c']) includes = ['.', + '../filesystem', '../engine', '../engine/common', '../engine/server', diff --git a/ref_soft/r_decals.c b/ref_soft/r_decals.c index 04370cd8..49fa6111 100644 --- a/ref_soft/r_decals.c +++ b/ref_soft/r_decals.c @@ -480,8 +480,8 @@ static decal_t *R_DecalIntersect( decalinfo_t *decalinfo, msurface_t *surf, int // Now figure out the part of the projection that intersects pDecal's // clip box [0,0,1,1]. - Vector2Set( vUnionMin, max( vDecalMin[0], 0 ), max( vDecalMin[1], 0 )); - Vector2Set( vUnionMax, min( vDecalMax[0], 1 ), min( vDecalMax[1], 1 )); + Vector2Set( vUnionMin, Q_max( vDecalMin[0], 0 ), Q_max( vDecalMin[1], 0 )); + Vector2Set( vUnionMax, Q_min( vDecalMax[0], 1 ), Q_min( vDecalMax[1], 1 )); if( vUnionMin[0] < 1 && vUnionMin[1] < 1 && vUnionMax[0] > 0 && vUnionMax[1] > 0 ) { diff --git a/ref_soft/r_edge.c b/ref_soft/r_edge.c index 2a09ffb8..e5b8e571 100644 --- a/ref_soft/r_edge.c +++ b/ref_soft/r_edge.c @@ -651,7 +651,7 @@ void R_ScanEdges (void) surf_t *s; basespan_p = (espan_t *) - ((long)(basespans + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); + ((uintptr_t)(basespans + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); max_span_p = &basespan_p[MAXSPANS - RI.vrect.width]; span_p = basespan_p; diff --git a/ref_soft/r_glblit.c b/ref_soft/r_glblit.c index e0f25973..6f73b7f2 100644 --- a/ref_soft/r_glblit.c +++ b/ref_soft/r_glblit.c @@ -58,8 +58,6 @@ static void APIENTRY GL_DebugOutput( GLuint source, GLuint type, GLuint id, GLui gEngfuncs.Con_Printf( S_OPENGL_ERROR "%s\n", message ); break; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB: - gEngfuncs.Con_Printf( S_OPENGL_WARN "%s\n", message ); - break; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB: gEngfuncs.Con_Printf( S_OPENGL_WARN "%s\n", message ); break; @@ -67,8 +65,6 @@ static void APIENTRY GL_DebugOutput( GLuint source, GLuint type, GLuint id, GLui gEngfuncs.Con_Reportf( S_OPENGL_WARN "%s\n", message ); break; case GL_DEBUG_TYPE_PERFORMANCE_ARB: - gEngfuncs.Con_Printf( S_OPENGL_NOTE "%s\n", message ); - break; case GL_DEBUG_TYPE_OTHER_ARB: default: gEngfuncs.Con_Printf( S_OPENGL_NOTE "%s\n", message ); @@ -357,7 +353,7 @@ static qboolean R_CreateBuffer_GLES3( int width, int height, uint *stride, uint 1, 1, 0, 1, }; - int vbo, pbo, fbo, to; + GLuint vbo, pbo, fbo, to; // shitty fbo does not work without texture objects :( pglGenTextures( 1, &to ); diff --git a/ref_soft/r_image.c b/ref_soft/r_image.c index a374d401..247e2e1e 100644 --- a/ref_soft/r_image.c +++ b/ref_soft/r_image.c @@ -1186,6 +1186,11 @@ void GAME_EXPORT GL_ProcessTexture( int texnum, float gamma, int topColor, int b // all the operations makes over the image copy not an original pic = gEngfuncs.FS_CopyImage( image->original ); + + // we need to expand image into RGBA buffer + if( pic->type == PF_INDEXED_24 || pic->type == PF_INDEXED_32 ) + flags |= IMAGE_FORCE_RGBA; + gEngfuncs.Image_Process( &pic, topColor, bottomColor, flags, 0.0f ); GL_UploadTexture( image, pic ); diff --git a/ref_soft/r_light.c b/ref_soft/r_light.c index 8ca462de..1e84d77d 100644 --- a/ref_soft/r_light.c +++ b/ref_soft/r_light.c @@ -156,7 +156,8 @@ void R_PushDlights( void ) tr.dlightframecount = tr.framecount; RI.currententity = gEngfuncs.GetEntityByIndex( 0 ); - RI.currentmodel = RI.currententity->model; + if( RI.currententity ) + RI.currentmodel = RI.currententity->model; for( i = 0; i < MAX_DLIGHTS; i++, l++ ) { @@ -168,7 +169,8 @@ void R_PushDlights( void ) //if( GL_FrustumCullSphere( &RI.frustum, l->origin, l->radius, 15 )) //continue; - R_MarkLights( l, 1<nodes ); + if( RI.currententity ) + R_MarkLights( l, 1<nodes ); } } diff --git a/ref_soft/r_main.c b/ref_soft/r_main.c index 829304ef..44d2a383 100644 --- a/ref_soft/r_main.c +++ b/ref_soft/r_main.c @@ -253,8 +253,6 @@ int R_WorldToScreen( const vec3_t point, vec3_t screen ) if( w < 0.001f ) { - screen[0] *= 100000; - screen[1] *= 100000; behind = true; } else @@ -496,7 +494,7 @@ static void R_SetupProjectionMatrix( matrix4x4 m ) RI.farClip = R_GetFarClip(); zNear = 4.0f; - zFar = max( 256.0f, RI.farClip ); + zFar = Q_max( 256.0f, RI.farClip ); yMax = zNear * tan( RI.fov_y * M_PI_F / 360.0f ); yMin = -yMax; @@ -1260,13 +1258,12 @@ void R_DrawBrushModel(cl_entity_t *pent) else { r_edges = (edge_t *) - (((long)&ledges[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); + (((uintptr_t)&ledges[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); } if (r_surfsonstack) { - surfaces = (surf_t *) - (((long)&lsurfs[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); + surfaces = (surf_t *)(((uintptr_t)&lsurfs[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); surf_max = &surfaces[r_cnumsurfs]; // surface 0 doesn't really exist; it's just a dummy because index 0 // is used to indicate no edge attached to surface @@ -1414,17 +1411,18 @@ void R_EdgeDrawing (void) else { r_edges = (edge_t *) - (((long)&ledges[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); + (((uintptr_t)&ledges[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); } if (r_surfsonstack) { - surfaces = (surf_t *) - (((long)&lsurfs[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); + surfaces = (surf_t *)(((uintptr_t)&lsurfs + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1)); surf_max = &surfaces[r_cnumsurfs]; - // surface 0 doesn't really exist; it's just a dummy because index 0 - // is used to indicate no edge attached to surface - memset(&surfaces[0], 0, sizeof(surf_t)); + + // surface 0 doesn't really exist; it's just a dummy because index 0 + // is used to indicate no edge attached to surface + + memset(surfaces, 0, sizeof(surf_t)); surfaces--; R_SurfacePatch (); } diff --git a/ref_soft/r_rast.c b/ref_soft/r_rast.c index 70993daa..023f326e 100644 --- a/ref_soft/r_rast.c +++ b/ref_soft/r_rast.c @@ -521,7 +521,7 @@ void R_EmitCachedEdge (void) { edge_t *pedge_t; - pedge_t = (edge_t *)((unsigned long)r_edges + r_pedge->cachededgeoffset); + pedge_t = (edge_t *)((uintptr_t)r_edges + r_pedge->cachededgeoffset); if (!pedge_t->surfs[0]) pedge_t->surfs[0] = surface_p - surfaces; @@ -624,9 +624,9 @@ void R_RenderFace (msurface_t *fa, int clipflags) } else { - if ((((unsigned long)edge_p - (unsigned long)r_edges) > + if ((((uintptr_t)edge_p - (uintptr_t)r_edges) > r_pedge->cachededgeoffset) && - (((edge_t *)((unsigned long)r_edges + + (((edge_t *)((uintptr_t)r_edges + r_pedge->cachededgeoffset))->owner == r_pedge)) { R_EmitCachedEdge (); @@ -670,9 +670,9 @@ void R_RenderFace (msurface_t *fa, int clipflags) { // it's cached if the cached edge is valid and is owned // by this medge_t - if ((((unsigned long)edge_p - (unsigned long)r_edges) > + if ((((uintptr_t)edge_p - (uintptr_t)r_edges) > r_pedge->cachededgeoffset) && - (((edge_t *)((unsigned long)r_edges + + (((edge_t *)((uintptr_t)r_edges + r_pedge->cachededgeoffset))->owner == r_pedge)) { R_EmitCachedEdge (); diff --git a/ref_soft/r_scan.c b/ref_soft/r_scan.c index d096e4b7..28ed582b 100644 --- a/ref_soft/r_scan.c +++ b/ref_soft/r_scan.c @@ -1390,7 +1390,7 @@ void D_DrawZSpans (espan_t *pspan) // we count on FP exceptions being turned off to avoid range problems izi = (int)(zi * 0x8000 * 0x10000); - if ((long)pdest & 0x02) + if ((uintptr_t)pdest & 0x02) { *pdest++ = (short)(izi >> 16); izi += izistep; diff --git a/ref_soft/r_studio.c b/ref_soft/r_studio.c index fd4a3b1e..0a68035a 100644 --- a/ref_soft/r_studio.c +++ b/ref_soft/r_studio.c @@ -34,7 +34,7 @@ typedef struct model_t *model; } player_model_t; -cvar_t r_shadows = { "r_shadows", "0", 0 }; +cvar_t r_shadows = { (char *)"r_shadows", (char *)"0", 0 }; static vec3_t hullcolor[8] = { @@ -2462,7 +2462,7 @@ static model_t *R_StudioSetupPlayerModel( int index ) Q_snprintf( state->modelname, sizeof( state->modelname ), "models/player/%s/%s.mdl", info->model, info->model ); - if( gEngfuncs.FS_FileExists( state->modelname, false )) + if( gEngfuncs.fsapi->FileExists( state->modelname, false )) state->model = gEngfuncs.Mod_ForName( state->modelname, false, true ); else state->model = NULL; diff --git a/ref_soft/r_surf.c b/ref_soft/r_surf.c index d42db305..e1d44681 100644 --- a/ref_soft/r_surf.c +++ b/ref_soft/r_surf.c @@ -357,9 +357,6 @@ void R_InitRandomTable( void ) { int tu, tv; - // make random predictable - gEngfuncs.COM_SetRandomSeed( 255 ); - for( tu = 0; tu < MOD_FRAMES; tu++ ) { for( tv = 0; tv < MOD_FRAMES; tv++ ) diff --git a/ref_soft/r_triapi.c b/ref_soft/r_triapi.c index c98f717c..c1867083 100644 --- a/ref_soft/r_triapi.c +++ b/ref_soft/r_triapi.c @@ -356,17 +356,7 @@ convert world coordinates (x,y,z) into screen (x, y) */ int GAME_EXPORT TriWorldToScreen( const float *world, float *screen ) { - int retval; - - retval = R_WorldToScreen( world, screen ); - - screen[0] = 0.5f * screen[0] * (float)RI.viewport[2]; - screen[1] = -0.5f * screen[1] * (float)RI.viewport[3]; - screen[0] += 0.5f * (float)RI.viewport[2]; - screen[1] += 0.5f * (float)RI.viewport[3]; - - - return retval; + return R_WorldToScreen( world, screen ); } /* diff --git a/ref_soft/wscript b/ref_soft/wscript index 93933140..9504cd84 100644 --- a/ref_soft/wscript +++ b/ref_soft/wscript @@ -30,6 +30,7 @@ def build(bld): source = bld.path.ant_glob(['*.c']) includes = ['.', + '../filesystem', '../engine', '../engine/common', '../engine/server', diff --git a/scripts/continious_upload.sh b/scripts/continious_upload.sh index e29d964e..fb91611b 100755 --- a/scripts/continious_upload.sh +++ b/scripts/continious_upload.sh @@ -84,29 +84,59 @@ if [ ! -z "$UPLOADTOOL_SUFFIX" ] ; then fi else - # ,, is a bash-ism to convert variable to lower case - case $(tr '[:upper:]' '[:lower:]' <<< "$GIT_TAG") in - "") - # Do not use "latest" as it is reserved by GitHub - RELEASE_NAME="continuous" - RELEASE_TITLE="Continuous build" - if [ -z "$UPLOADTOOL_ISPRERELEASE" ] ; then + if [ "$GITHUB_ACTIONS" = "true" ]; then + if [ "$GITHUB_REF_TYPE" == "branch" ]; then + if [ "$GITHUB_REF_NAME" == "master" ]; then + RELEASE_NAME="continuous" + RELEASE_TITLE="Continuous build" + else + RELEASE_NAME="continuous-$GITHUB_REF_NAME" + RELEASE_TITLE="Continuous build ($GITHUB_REF_NAME)" + fi + if [ -z "$UPLOADTOOL_ISPRERELEASE" ]; then is_prerelease="false" else is_prerelease="true" fi - ;; - *-alpha*|*-beta*|*-rc*) - RELEASE_NAME="$GIT_TAG" - RELEASE_TITLE="Pre-release build ($GIT_TAG)" - is_prerelease="true" - ;; - *) - RELEASE_NAME="$GIT_TAG" - RELEASE_TITLE="Release build ($GIT_TAG)" - is_prerelease="false" - ;; - esac + elif [ "$GITHUB_REF_TYPE" == "tag" ]; then + case $(tr '[:upper:]' '[:lower:]' <<< "$GITHUB_REF_NAME") in + *-alpha*|*-beta*|*-rc*) + RELEASE_NAME="$GITHUB_REF_NAME" + RELEASE_TITLE="Pre-release build ($GITHUB_REF_NAME)" + is_prerelease="true" + ;; + *) + RELEASE_NAME="$GITHUB_REF_NAME" + RELEASE_TITLE="Release build ($GITHUB_REF_NAME)" + is_prerelease="false" + ;; + esac + fi + else + # ,, is a bash-ism to convert variable to lower case + case $(tr '[:upper:]' '[:lower:]' <<< "$GIT_TAG") in + "") + # Do not use "latest" as it is reserved by GitHub + RELEASE_NAME="continuous" + RELEASE_TITLE="Continuous build" + if [ -z "$UPLOADTOOL_ISPRERELEASE" ] ; then + is_prerelease="false" + else + is_prerelease="true" + fi + ;; + *-alpha*|*-beta*|*-rc*) + RELEASE_NAME="$GIT_TAG" + RELEASE_TITLE="Pre-release build ($GIT_TAG)" + is_prerelease="true" + ;; + *) + RELEASE_NAME="$GIT_TAG" + RELEASE_TITLE="Release build ($GIT_TAG)" + is_prerelease="false" + ;; + esac + fi fi # Do not upload non-master branch builds diff --git a/scripts/gha/build_android.sh b/scripts/gha/build_android.sh index 03b85d7b..a5f4df5c 100755 --- a/scripts/gha/build_android.sh +++ b/scripts/gha/build_android.sh @@ -12,15 +12,14 @@ elif [[ "$GH_CPU_ARCH" == "32&64" ]]; then export ARCHS="armeabi armeabi-v7a x86 aarch64 x86_64" fi -export API=21 -export TOOLCHAIN=host -export CC=clang-12 -export CXX=clang++-12 -export STRIP=llvm-strip-12 sh compile.sh release +popd + +mkdir -p artifacts/ + if [[ "$GH_CPU_ARCH" == "64" ]]; then - mv xashdroid.apk ../xashdroid-64.apk + mv android/xashdroid.apk artifacts/xashdroid-64.apk else - mv xashdroid.apk ../xashdroid-32.apk + mv android/xashdroid.apk artifacts/xashdroid-32.apk fi diff --git a/scripts/gha/build_linux.sh b/scripts/gha/build_linux.sh index 77910f98..a98afa32 100755 --- a/scripts/gha/build_linux.sh +++ b/scripts/gha/build_linux.sh @@ -67,7 +67,7 @@ build_appimage() cp SDL2_linux/lib/libSDL2-2.0.so.0 "$APPDIR/" if [ "$ARCH" = "i386" ]; then - cp vgui-dev/lib/vgui.so "$APPDIR/" + cp vgui_support/vgui-dev/lib/vgui.so "$APPDIR/" fi cat > "$APPDIR"/AppRun << 'EOF' @@ -80,7 +80,6 @@ echo "Xash3D FWGS installed as AppImage." echo "Base directory is $XASH3D_BASEDIR. Set XASH3D_BASEDIR environment variable to override this" export XASH3D_EXTRAS_PAK1="${APPDIR}"/extras.pak -export LD_LIBRARY_PATH="${APPDIR}":$LD_LIBRARY_PATH ${DEBUGGER} "${APPDIR}"/xash3d "$@" exit $? EOF @@ -104,11 +103,14 @@ EOF ./appimagetool.AppImage "$APPDIR" "$APPIMAGE" } +mkdir -p artifacts/ + rm -rf build # clean-up build directory build_engine dedicated -mv build/engine/xash xashds-linux-$ARCH +mv build/engine/xash artifacts/xashds-linux-$ARCH rm -rf build build_sdl2 build_engine full build_appimage +mv $APPIMAGE artifacts/ diff --git a/scripts/gha/build_motomagx.sh b/scripts/gha/build_motomagx.sh index 06c06e7d..c103bd42 100755 --- a/scripts/gha/build_motomagx.sh +++ b/scripts/gha/build_motomagx.sh @@ -8,9 +8,9 @@ cd $GITHUB_WORKSPACE mkdir -p Xash/valve/cl_dlls mkdir -p Xash/valve/dlls -cd hlsdk +pushd hlsdk ./waf configure -T fast --enable-magx --enable-simple-mod-hacks build install --destdir=../Xash || die -cd ../ +popd ./waf configure -T fast --enable-magx build install --destdir=Xash/ || die @@ -23,11 +23,14 @@ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$LIBDIR1:$LIBDIR2:$LIBDIR3 export HOME=$mypath export SDL_QT_INVERT_ROTATION=1 export SWAP_PATH=$HOME/xash.swap +export XASH3D_EXTRAS_PAK1=$HOME/extras.pak cd $mypath sleep 1 exec $mypath/xash -dev $@ EOF +python3 scripts/makepak.py xash-extras/ Xash/extras.pak -7z a -t7z $GITHUB_WORKSPACE/xash3d-fwgs-magx.7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on -r Xash/ +mkdir -p artifacts/ +7z a -t7z artifacts/xash3d-fwgs-magx.7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on -r Xash/ diff --git a/scripts/gha/build_win32.sh b/scripts/gha/build_win32.sh index 675efa4e..8faef732 100755 --- a/scripts/gha/build_win32.sh +++ b/scripts/gha/build_win32.sh @@ -14,17 +14,23 @@ fi # NOTE: to build with other version use --msvc_version during configuration # NOTE: sometimes you may need to add WinSDK to %PATH% -./waf.bat configure -s "SDL2_VC" -T "debug" --enable-utils --prefix=`pwd` $AMD64 || die +./waf.bat configure -s "SDL2_VC" -T "release" --enable-utils --prefix=`pwd` $AMD64 || die ./waf.bat build -v || die ./waf.bat install || die if [ "$ARCH" = "i386" ]; then cp SDL2_VC/lib/x86/SDL2.dll . # Install SDL2 - cp vgui-dev/lib/win32_vc6/vgui.dll . + cp vgui_support/vgui-dev/lib/win32_vc6/vgui.dll . elif [ "$ARCH" = "amd64" ]; then cp SDL2_VC/lib/x64/SDL2.dll . else die fi -7z a -t7z $BUILDDIR/xash3d-fwgs-win32-$ARCH.7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on *.dll *.exe *.pdb +mkdir valve/ +python3 scripts/makepak.py xash-extras/ valve/extras.pak + +mkdir -p artifacts/ +7z a -t7z artifacts/xash3d-fwgs-win32-$ARCH.7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on \ + *.dll *.exe *.pdb activities.txt \ + valve/ diff --git a/scripts/gha/deps_android.sh b/scripts/gha/deps_android.sh index e1809ec0..59b5c549 100755 --- a/scripts/gha/deps_android.sh +++ b/scripts/gha/deps_android.sh @@ -1,14 +1,5 @@ #!/bin/bash -echo "Install packages" -echo "deb https://apt.llvm.org/xenial/ llvm-toolchain-xenial-12 main" | sudo tee -a /etc/apt/sources.list -wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - -sudo apt update -sudo apt install clang-12 lld-12 llvm-12 llvm-12-tools p7zip-full -sudo rm /usr/bin/lld /usr/bin/ld.lld -sudo ln -s /usr/bin/lld-12 /usr/bin/lld -sudo ln -s /usr/bin/ld.lld-12 /usr/bin/ld.lld - echo "Download HLSDK" cd $GITHUB_WORKSPACE @@ -23,16 +14,10 @@ popd echo "Download all needed tools and NDK" yes | sdk/tools/bin/sdkmanager --licenses > /dev/null 2>/dev/null # who even reads licenses? :) -NDK_BUNDLE="ndk-bundle" -if [ "$ANDROID_NDK_VER" = "r10e" ]; then - NDK_BUNDLE="" -fi -sdk/tools/bin/sdkmanager --install build-tools\;29.0.1 platform-tools platforms\;android-29 $NDK_BUNDLE > /dev/null 2>/dev/null -if [ "$ANDROID_NDK_VER" = "r10e" ]; then - wget https://dl.google.com/android/repository/android-ndk-r10e-linux-x86_64.zip -qO ndk.zip > /dev/null || exit 1 - unzip -q ndk.zip || exit 1 - mv android-ndk-r10e sdk/ndk-bundle -fi +sdk/tools/bin/sdkmanager --install build-tools\;29.0.1 platform-tools platforms\;android-29 > /dev/null 2>/dev/null +wget https://dl.google.com/android/repository/android-ndk-r25-linux.zip -qO ndk.zip > /dev/null || exit 1 +unzip -q ndk.zip || exit 1 +mv android-ndk-r25 sdk/ndk-bundle || exit 1 echo "Download Xash3D FWGS Android source" git clone --depth 1 https://github.com/FWGS/xash3d-android-project -b waf android || exit 1 diff --git a/scripts/gha/deps_linux.sh b/scripts/gha/deps_linux.sh index 0575f6e1..abcba62c 100755 --- a/scripts/gha/deps_linux.sh +++ b/scripts/gha/deps_linux.sh @@ -13,6 +13,11 @@ elif [ "$GH_CPU_ARCH" == "amd64" ]; then sudo apt install libx11-dev libxext-dev x11-utils libgl1-mesa-dev libasound-dev libstdc++6 libfuse2 zlib1g wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" -O appimagetool.AppImage +elif [ "$GH_CPU_ARCH" == "aarch64" ]; then + sudo apt update + sudo apt install libx11-dev libxext-dev x11-utils libgl1-mesa-dev libasound-dev libstdc++6 libfuse2 zlib1g + + wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-aarch64.AppImage" -O appimagetool.AppImage else exit 1 fi diff --git a/scripts/waifulib/compiler_optimizations.py b/scripts/waifulib/compiler_optimizations.py index e964c812..3d653770 100644 --- a/scripts/waifulib/compiler_optimizations.py +++ b/scripts/waifulib/compiler_optimizations.py @@ -30,7 +30,7 @@ compiler_optimizations.CFLAGS['gottagofast'] = { } ''' -VALID_BUILD_TYPES = ['fastnative', 'fast', 'release', 'debug', 'nooptimize', 'sanitize', 'none'] +VALID_BUILD_TYPES = ['fastnative', 'fast', 'release', 'debug', 'sanitize', 'none'] LINKFLAGS = { 'common': { @@ -41,6 +41,7 @@ LINKFLAGS = { 'sanitize': { 'clang': ['-fsanitize=undefined', '-fsanitize=address', '-pthread'], 'gcc': ['-fsanitize=undefined', '-fsanitize=address', '-pthread'], + 'msvc': ['/SAFESEH:NO'] }, 'debug': { 'msvc': ['/INCREMENTAL', '/SAFESEH:NO'] @@ -50,7 +51,7 @@ LINKFLAGS = { CFLAGS = { 'common': { # disable thread-safe local static initialization for C++11 code, as it cause crashes on Windows XP - 'msvc': ['/D_USING_V110_SDK71_', '/FS', '/Zc:threadSafeInit-', '/MT'], + 'msvc': ['/D_USING_V110_SDK71_', '/FS', '/Zc:threadSafeInit-', '/MT', '/MP', '/Zc:__cplusplus'], 'clang': ['-g', '-gdwarf-2', '-fvisibility=hidden', '-fno-threadsafe-statics'], 'gcc': ['-g', '-fvisibility=hidden'], 'owcc': ['-fno-short-enum', '-ffloat-store', '-g3'] @@ -81,15 +82,11 @@ CFLAGS = { 'default': ['-O0'] }, 'sanitize': { - 'msvc': ['/Od', '/RTC1', '/Zi'], - 'gcc': ['-Og', '-fsanitize=undefined', '-fsanitize=address', '-pthread'], + 'msvc': ['/Od', '/RTC1', '/Zi', '/fsanitize=address'], + 'gcc': ['-O0', '-fsanitize=undefined', '-fsanitize=address', '-pthread'], 'clang': ['-O0', '-fsanitize=undefined', '-fsanitize=address', '-pthread'], 'default': ['-O0'] }, - 'nooptimize': { - 'msvc': ['/Od', '/Zi'], - 'default': ['-O0'] - } } LTO_CFLAGS = { diff --git a/scripts/waifulib/xcompile.py b/scripts/waifulib/xcompile.py index 5d7082b3..b9ce398f 100644 --- a/scripts/waifulib/xcompile.py +++ b/scripts/waifulib/xcompile.py @@ -20,12 +20,14 @@ import os import sys ANDROID_NDK_ENVVARS = ['ANDROID_NDK_HOME', 'ANDROID_NDK'] -ANDROID_NDK_SUPPORTED = [10, 19, 20, 23] +ANDROID_NDK_SUPPORTED = [10, 19, 20, 23, 25] ANDROID_NDK_HARDFP_MAX = 11 # latest version that supports hardfp ANDROID_NDK_GCC_MAX = 17 # latest NDK that ships with GCC ANDROID_NDK_UNIFIED_SYSROOT_MIN = 15 ANDROID_NDK_SYSROOT_FLAG_MAX = 19 # latest NDK that need --sysroot flag -ANDROID_NDK_API_MIN = { 10: 3, 19: 16, 20: 16, 23: 16 } # minimal API level ndk revision supports +ANDROID_NDK_API_MIN = { 10: 3, 19: 16, 20: 16, 23: 16, 25: 19 } # minimal API level ndk revision supports + +ANDROID_STPCPY_API_MIN = 21 # stpcpy() introduced in SDK 21 ANDROID_64BIT_API_MIN = 21 # minimal API level that supports 64-bit targets # This class does support ONLY r10e and r19c/r20 NDK @@ -266,47 +268,43 @@ class Android: '-isystem', '%s/usr/include/' % (self.sysroot()) ] - cflags += ['-I%s' % (self.system_stl()), '-DANDROID', '-D__ANDROID__'] + cflags += ['-I%s' % (self.system_stl())] + if not self.is_clang(): + cflags += ['-DANDROID', '-D__ANDROID__'] if cxx and not self.is_clang() and self.toolchain not in ['4.8','4.9']: cflags += ['-fno-sized-deallocation'] - def fixup_host_clang_with_old_ndk(): - cflags = [] - # Clang builtin redefine w/ different calling convention bug - # NOTE: I did not added complex.h functions here, despite - # that NDK devs forgot to put __NDK_FPABI_MATH__ for complex - # math functions - # I personally don't need complex numbers support, but if you want it - # just run sed to patch header - for f in ['strtod', 'strtof', 'strtold']: - cflags += ['-fno-builtin-%s' % f] - return cflags - + if self.is_clang(): + # stpcpy() isn't available in early Android versions + # disable it here so Clang won't use it + if self.api < ANDROID_STPCPY_API_MIN: + cflags += ['-fno-builtin-stpcpy'] if self.is_arm(): if self.arch == 'armeabi-v7a': # ARMv7 support - cflags += ['-mthumb', '-mfpu=neon', '-mcpu=cortex-a9', '-DHAVE_EFFICIENT_UNALIGNED_ACCESS', '-DVECTORIZE_SINCOS'] - - if not self.is_clang() and not self.is_host(): - cflags += [ '-mvectorize-with-neon-quad' ] - - if self.is_host() and self.ndk_rev <= ANDROID_NDK_HARDFP_MAX: - cflags += fixup_host_clang_with_old_ndk() + cflags += ['-mthumb', '-mfpu=neon', '-mcpu=cortex-a9'] if self.is_hardfp(): cflags += ['-D_NDK_MATH_NO_SOFTFP=1', '-mfloat-abi=hard', '-DLOAD_HARDFP', '-DSOFTFP_LINK'] + + if self.is_host(): + # Clang builtin redefine w/ different calling convention bug + # NOTE: I did not added complex.h functions here, despite + # that NDK devs forgot to put __NDK_FPABI_MATH__ for complex + # math functions + # I personally don't need complex numbers support, but if you want it + # just run sed to patch header + for f in ['strtod', 'strtof', 'strtold']: + cflags += ['-fno-builtin-%s' % f] else: cflags += ['-mfloat-abi=softfp'] else: - if self.is_host() and self.ndk_rev <= ANDROID_NDK_HARDFP_MAX: - cflags += fixup_host_clang_with_old_ndk() - # ARMv5 support cflags += ['-march=armv5te', '-msoft-float'] elif self.is_x86(): - cflags += ['-mtune=atom', '-march=atom', '-mssse3', '-mfpmath=sse', '-DVECTORIZE_SINCOS', '-DHAVE_EFFICIENT_UNALIGNED_ACCESS'] + cflags += ['-mtune=atom', '-march=atom', '-mssse3', '-mfpmath=sse'] return cflags # they go before object list @@ -322,8 +320,9 @@ class Android: if self.is_clang() or self.is_host(): linkflags += ['-fuse-ld=lld'] + else: linkflags += ['-no-canonical-prefixes'] - linkflags += ['-Wl,--hash-style=sysv', '-Wl,--no-undefined', '-no-canonical-prefixes'] + linkflags += ['-Wl,--hash-style=sysv', '-Wl,--no-undefined'] return linkflags def ldflags(self): @@ -332,10 +331,9 @@ class Android: if self.ndk_rev < 23: ldflags += ['-lgcc'] - ldflags += ['-no-canonical-prefixes'] - if self.is_clang() or self.is_host(): ldflags += ['-stdlib=libstdc++'] + else: ldflags += ['-no-canonical-prefixes'] if self.is_arm(): if self.arch == 'armeabi-v7a': diff --git a/utils/mdldec/mdldec.c b/utils/mdldec/mdldec.c index d902d400..c137be93 100644 --- a/utils/mdldec/mdldec.c +++ b/utils/mdldec/mdldec.c @@ -164,9 +164,9 @@ static qboolean LoadMDL( const char *modelname ) if( destdir[0] != '\0' ) { - if( !IsFileExists( destdir ) ) + if( !MakeDirectory( destdir ) ) { - fprintf( stderr, "ERROR: Couldn't find directory %s\n", destdir ); + fprintf( stderr, "ERROR: Couldn't create directory %s\n", destdir ); return false; } @@ -175,7 +175,7 @@ static qboolean LoadMDL( const char *modelname ) else COM_ExtractFilePath( modelname, destdir ); - len -= 4; // path length without extension + len -= ( sizeof( ".mdl" ) - 1 ); // path length without extension if( !model_hdr->numtextures ) { diff --git a/utils/mdldec/smd.c b/utils/mdldec/smd.c index 4a4f9884..4e80e3b0 100644 --- a/utils/mdldec/smd.c +++ b/utils/mdldec/smd.c @@ -262,8 +262,16 @@ static void WriteTriangleInfo( FILE *fp, mstudiomodel_t *model, mstudiotexture_t Matrix3x4_VectorRotate( bonetransform[bone_index], studionorms[norm_index], norm ); VectorNormalize( norm ); - u = ( triverts[indices[i]]->s + 1.0f ) * s; - v = 1.0f - triverts[indices[i]]->t * t; + if( texture->flags & STUDIO_NF_UV_COORDS ) + { + u = HalfToFloat( triverts[indices[i]]->s ); + v = -HalfToFloat( triverts[indices[i]]->t ); + } + else + { + u = ( triverts[indices[i]]->s + 1.0f ) * s; + v = 1.0f - triverts[indices[i]]->t * t; + } fprintf( fp, "%3i %f %f %f %f %f %f %f %f\n", bone_index, diff --git a/utils/mdldec/utils.c b/utils/mdldec/utils.c index 739390d9..d43fe266 100644 --- a/utils/mdldec/utils.c +++ b/utils/mdldec/utils.c @@ -17,24 +17,39 @@ GNU General Public License for more details. #include #include #include +#include #include "xash3d_types.h" +#include "port.h" #include "crtlib.h" #include "utils.h" /* ============ -IsFileExists +MakeDirectory ============ */ -qboolean IsFileExists( const char *filename ) +qboolean MakeDirectory( const char *path ) { - struct stat st; - int ret; + if( -1 == _mkdir( path )) + { + if( errno == EEXIST ) + { + // TODO: when filesystem library will be ready + // use FS_SysFolderExists here or replace this whole function + // with FS_CreatePath +#if XASH_WIN32 + DWORD dwFlags = GetFileAttributes( path ); - ret = stat( filename, &st ); + return ( dwFlags != -1 ) && ( dwFlags & FILE_ATTRIBUTE_DIRECTORY ); +#else + struct stat buf; - if( ret == -1 ) + if( !stat( path, &buf )) + return S_ISDIR( buf.st_mode ); +#endif + } return false; + } return true; } @@ -44,7 +59,7 @@ qboolean IsFileExists( const char *filename ) GetFileSize ============ */ -off_t GetFileSize( FILE *fp ) +off_t GetSizeOfFile( FILE *fp ) { struct stat st; int fd; @@ -71,7 +86,7 @@ byte *LoadFile( const char *filename ) if( !fp ) return NULL; - size = GetFileSize( fp ); + size = GetSizeOfFile( fp ); buf = malloc( size ); diff --git a/utils/mdldec/utils.h b/utils/mdldec/utils.h index b2e9958f..08d07202 100644 --- a/utils/mdldec/utils.h +++ b/utils/mdldec/utils.h @@ -16,8 +16,8 @@ GNU General Public License for more details. #ifndef UTILS_H #define UTILS_H -qboolean IsFileExists( const char *filename ); -off_t GetFileSize( FILE *fp ); +qboolean MakeDirectory( const char *path ); +off_t GetSizeOfFile( FILE *fp ); byte *LoadFile( const char *filename ); #endif // UTILS_H diff --git a/utils/run-fuzzer/run-fuzzer.c b/utils/run-fuzzer/run-fuzzer.c new file mode 100644 index 00000000..d0bfae0b --- /dev/null +++ b/utils/run-fuzzer/run-fuzzer.c @@ -0,0 +1,34 @@ +#include +#include +#include + +#if !defined LIB || !defined FUNC +#error +#endif + +typedef int (*FuzzFunc)(const char *Data, size_t Size); + +void *handle = NULL; +FuzzFunc f = NULL; + +int LLVMFuzzerTestOneInput( const char *Data, size_t Size ) +{ + if( !handle ) + handle = dlopen( LIB, RTLD_NOW ); + + if( handle ) + { + if( !f ) + f = dlsym( handle, FUNC ); + + if( f ) + { + return f( Data, Size ); + } + } + + fprintf( stderr, "Fail: %s\n", dlerror() ); + + abort(); + return 0; +} diff --git a/utils/run-fuzzer/wscript b/utils/run-fuzzer/wscript new file mode 100644 index 00000000..11174c8a --- /dev/null +++ b/utils/run-fuzzer/wscript @@ -0,0 +1,40 @@ +#! /usr/bin/env python +# encoding: utf-8 +# a1batross, mittorn, 2018 + +def options(opt): + pass + +def configure(conf): + if conf.options.BUILD_TYPE != 'sanitize': + conf.fatal('useless without -T sanitize') + + if conf.env.COMPILER_CC != 'clang': + conf.fatal('only clang is supported') + + conf.env.append_unique('CFLAGS', '-fsanitize=fuzzer') + conf.env.append_unique('LINKFLAGS', '-fsanitize=fuzzer') + +def add_runner_target(bld, lib, func): + source = bld.path.ant_glob('*.c') + includes = '.' + libs = [ 'DL' ] + + bld( + source = source, + target = 'run-fuzzer-' + func, + features = 'c cprogram', + includes = includes, + use = libs, + defines = ['FUNC="Fuzz_' + func + '"', 'LIB="' + lib + '"'], + install_path = bld.env.BINDIR, + subsystem = bld.env.CONSOLE_SUBSYSTEM + ) + +def build(bld): + add_runner_target(bld, 'libxash.so', 'Sound_LoadMPG') + add_runner_target(bld, 'libxash.so', 'Sound_LoadWAV') + add_runner_target(bld, 'libxash.so', 'Image_LoadBMP') + add_runner_target(bld, 'libxash.so', 'Image_LoadPNG') + add_runner_target(bld, 'libxash.so', 'Image_LoadDDS') + add_runner_target(bld, 'libxash.so', 'Image_LoadTGA') diff --git a/vgui-dev b/vgui-dev deleted file mode 160000 index 93573075..00000000 --- a/vgui-dev +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 93573075afe885618ea15831e72d44bdacd65bfb diff --git a/vgui_support b/vgui_support new file mode 160000 index 00000000..697de1ef --- /dev/null +++ b/vgui_support @@ -0,0 +1 @@ +Subproject commit 697de1efe2eea79d48118968d9e6a5a7e1117700 diff --git a/vgui_support/Android.mk b/vgui_support/Android.mk deleted file mode 100644 index 78c7db35..00000000 --- a/vgui_support/Android.mk +++ /dev/null @@ -1,23 +0,0 @@ -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_MODULE = vgui_support - -include $(XASH3D_CONFIG) - -LOCAL_CFLAGS = -fsigned-char -DVGUI_TOUCH_SCROLL -DNO_STL -DXASH_GLES -DINTERNAL_VGUI_SUPPORT -Wall -Wextra -Wno-unused-parameter -Wno-unused-variable - -LOCAL_CPPFLAGS = -frtti -fno-exceptions -Wno-write-strings -Wno-invalid-offsetof -std=gnu++98 - -LOCAL_C_INCLUDES := \ - $(LOCAL_PATH)/../common \ - $(LOCAL_PATH)/../engine \ - $(HLSDK_PATH)/utils/vgui/include \ - $(VGUI_DIR)/include - -LOCAL_SRC_FILES := vgui_clip.cpp vgui_font.cpp vgui_input.cpp vgui_int.cpp vgui_surf.cpp - -LOCAL_STATIC_LIBRARIES := vgui - -include $(BUILD_STATIC_LIBRARY) diff --git a/vgui_support/Makefile.linux b/vgui_support/Makefile.linux deleted file mode 100644 index 8031d116..00000000 --- a/vgui_support/Makefile.linux +++ /dev/null @@ -1,29 +0,0 @@ -CC ?= gcc -m32 -CXX ?= g++ -m32 -std=gnu++98 -CFLAGS ?= -O2 -ggdb -fPIC -TOPDIR = $(PWD)/.. -VGUI_DIR ?= ./vgui-dev -INCLUDES = -I../common -I../engine -I../engine/common -I../engine/client -I../engine/client/vgui -I../pm_shared -INCLUDES += -I$(VGUI_DIR)/include/ -DEFINES = -DNO_STL -%.o : %.cpp - $(CXX) $(CFLAGS) $(INCLUDES) $(DEFINES) -c $< -o $@ - -SRCS = $(wildcard *.cpp) -OBJS = $(SRCS:.cpp=.o) - -libvgui_support.so : $(OBJS) - $(CXX) $(LDFLAGS) -o libvgui_support.so -shared $(OBJS) vgui.so - -.PHONY: depend clean - -clean : - $(RM) $(OBJS) libvgui_support.so - -depend: Makefile.dep - -Makefile.dep: $(SRCS) - rm -f ./Makefile.dep - $(CC) $(CFLAGS) $(INCLUDES) $(DEFINES) -MM $^>>./Makefile.dep - -include Makefile.dep diff --git a/vgui_support/cl.bat b/vgui_support/cl.bat deleted file mode 100644 index c0f59c0d..00000000 --- a/vgui_support/cl.bat +++ /dev/null @@ -1,5 +0,0 @@ - set MSVCDir=Z:\path\to\msvc - set INCLUDE=%MSVCDir%\include - set LIB=%MSVCDir%\lib - set PATH=%MSVCDir%\bin;%PATH% -cl.exe %* \ No newline at end of file diff --git a/vgui_support/vgui_clip.cpp b/vgui_support/vgui_clip.cpp deleted file mode 100644 index 67085577..00000000 --- a/vgui_support/vgui_clip.cpp +++ /dev/null @@ -1,124 +0,0 @@ -/* -vgui_clip.cpp - clip in 2D space -Copyright (C) 2011 Uncle Mike - -This program 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, either version 3 of the License, or -(at your option) any later version. - -This program 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. - -In addition, as a special exception, the author gives permission -to link the code of this program with VGUI library developed by -Valve, L.L.C ("Valve"). You must obey the GNU General Public License -in all respects for all of the code used other than VGUI library. -If you modify this file, you may extend this exception to your -version of the file, but you are not obligated to do so. If -you do not wish to do so, delete this exception statement -from your version. - -*/ - -#include "vgui_main.h" -#include "wrect.h" - -//----------------------------------------------------------------------------- -// For simulated scissor tests... -//----------------------------------------------------------------------------- -static wrect_t g_ScissorRect; -static qboolean g_bScissor = false; -namespace vgui_support { -//----------------------------------------------------------------------------- -// Enable/disable scissoring... -//----------------------------------------------------------------------------- -void EnableScissor( qboolean enable ) -{ - g_bScissor = enable; -} - -void SetScissorRect( int left, int top, int right, int bottom ) -{ - // Check for a valid rectangle... - assert( left <= right ); - assert( top <= bottom ); - - g_ScissorRect.left = left; - g_ScissorRect.top = top; - g_ScissorRect.right = right; - g_ScissorRect.bottom = bottom; -} - -//----------------------------------------------------------------------------- -// Purpose: Used for clipping, produces an interpolated texture coordinate -//----------------------------------------------------------------------------- -inline float InterpTCoord( float val, float mins, float maxs, float tMin, float tMax ) -{ - float flPercent; - - if( mins != maxs ) - flPercent = (float)(val - mins) / (maxs - mins); - else flPercent = 0.5f; - - return tMin + (tMax - tMin) * flPercent; -} - -//----------------------------------------------------------------------------- -// Purpose: Does a scissor clip of the input rectangle. -// Returns false if it is completely clipped off. -//----------------------------------------------------------------------------- -qboolean ClipRect( const vpoint_t &inUL, const vpoint_t &inLR, vpoint_t *pOutUL, vpoint_t *pOutLR ) -{ - if( g_bScissor ) - { - // pick whichever left side is larger - if( g_ScissorRect.left > inUL.point[0] ) - pOutUL->point[0] = g_ScissorRect.left; - else - pOutUL->point[0] = inUL.point[0]; - - // pick whichever right side is smaller - if( g_ScissorRect.right <= inLR.point[0] ) - pOutLR->point[0] = g_ScissorRect.right; - else - pOutLR->point[0] = inLR.point[0]; - - // pick whichever top side is larger - if( g_ScissorRect.top > inUL.point[1] ) - pOutUL->point[1] = g_ScissorRect.top; - else - pOutUL->point[1] = inUL.point[1]; - - // pick whichever bottom side is smaller - if( g_ScissorRect.bottom <= inLR.point[1] ) - pOutLR->point[1] = g_ScissorRect.bottom; - else - pOutLR->point[1] = inLR.point[1]; - - // Check for non-intersecting - if(( pOutUL->point[0] > pOutLR->point[0] ) || ( pOutUL->point[1] > pOutLR->point[1] )) - { - return false; - } - - pOutUL->coord[0] = InterpTCoord(pOutUL->point[0], - inUL.point[0], inLR.point[0], inUL.coord[0], inLR.coord[0] ); - pOutLR->coord[0] = InterpTCoord(pOutLR->point[0], - inUL.point[0], inLR.point[0], inUL.coord[0], inLR.coord[0] ); - - pOutUL->coord[1] = InterpTCoord(pOutUL->point[1], - inUL.point[1], inLR.point[1], inUL.coord[1], inLR.coord[1] ); - pOutLR->coord[1] = InterpTCoord(pOutLR->point[1], - inUL.point[1], inLR.point[1], inUL.coord[1], inLR.coord[1] ); - } - else - { - *pOutUL = inUL; - *pOutLR = inLR; - } - return true; -} -} diff --git a/vgui_support/vgui_input.cpp b/vgui_support/vgui_input.cpp deleted file mode 100644 index 5280b5b7..00000000 --- a/vgui_support/vgui_input.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/* -vgui_input.cpp - handle kb & mouse -Copyright (C) 2011 Uncle Mike - -This program 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, either version 3 of the License, or -(at your option) any later version. - -This program 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. - -In addition, as a special exception, the author gives permission -to link the code of this program with VGUI library developed by -Valve, L.L.C ("Valve"). You must obey the GNU General Public License -in all respects for all of the code used other than VGUI library. -If you modify this file, you may extend this exception to your -version of the file, but you are not obligated to do so. If -you do not wish to do so, delete this exception statement -from your version. - -*/ - -#define OEMRESOURCE // for OCR_* cursor junk - -#include "vgui_main.h" - -namespace vgui_support { -void VGUI_Key(VGUI_KeyAction action, VGUI_KeyCode code) -{ - App *pApp = App::getInstance(); - if(!surface) - return; - switch( action ) - { - case KA_PRESSED: - pApp->internalKeyPressed( (KeyCode) code, surface ); - break; - case KA_RELEASED: - pApp->internalKeyReleased( (KeyCode) code, surface ); - break; - case KA_TYPED: - pApp->internalKeyTyped( (KeyCode) code, surface ); - break; - } - //fprintf(stdout,"vgui_support: VGUI key action %d %d\n", action, code); - //fflush(stdout); -} - -void VGUI_Mouse(VGUI_MouseAction action, int code) -{ - App *pApp = App::getInstance(); - if(!surface) - return; - switch( action ) - { - case MA_PRESSED: - pApp->internalMousePressed( (MouseCode) code, surface ); - break; - case MA_RELEASED: - pApp->internalMouseReleased( (MouseCode) code, surface ); - break; - case MA_DOUBLE: - pApp->internalMouseDoublePressed( (MouseCode) code, surface ); - break; - case MA_WHEEL: - //fprintf(stdout, "vgui_support: VGUI mouse wheeled %d %d\n", action, code); - pApp->internalMouseWheeled( code, surface ); - break; - } - //fprintf(stdout, "vgui_support: VGUI mouse action %d %d\n", action, code); - //fflush(stdout); -} - -void VGUI_MouseMove(int x, int y) -{ - App *pApp = App::getInstance(); - //fprintf(stdout, "vgui_support: VGUI mouse move %d %d %p\n", x, y, surface); - //fflush(stdout); - if(!surface) - return; - pApp->internalCursorMoved( x, y, surface ); -} -} diff --git a/vgui_support/vgui_int.cpp b/vgui_support/vgui_int.cpp deleted file mode 100644 index 777c53e3..00000000 --- a/vgui_support/vgui_int.cpp +++ /dev/null @@ -1,122 +0,0 @@ -/* -vgui_int.c - vgui dll interaction -Copyright (C) 2011 Uncle Mike - -This program 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, either version 3 of the License, or -(at your option) any later version. - -This program 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. - -In addition, as a special exception, the author gives permission -to link the code of this program with VGUI library developed by -Valve, L.L.C ("Valve"). You must obey the GNU General Public License -in all respects for all of the code used other than VGUI library. -If you modify this file, you may extend this exception to your -version of the file, but you are not obligated to do so. If -you do not wish to do so, delete this exception statement -from your version. - -*/ - -#include "vgui_main.h" -#include "xash3d_types.h" -namespace vgui_support { - -vguiapi_t *g_api; - -Panel *rootpanel = NULL; -CEngineSurface *surface = NULL; -CEngineApp staticApp; - -void VGui_Startup( int width, int height ) -{ - if( rootpanel ) - { - rootpanel->setSize( width, height ); - return; - } - - rootpanel = new Panel; - rootpanel->setSize( width, height ); - rootpanel->setPaintBorderEnabled( false ); - rootpanel->setPaintBackgroundEnabled( false ); - rootpanel->setVisible( true ); - rootpanel->setCursor( new Cursor( Cursor::dc_none )); - - staticApp.start(); - staticApp.setMinimumTickMillisInterval( 0 ); - - surface = new CEngineSurface( rootpanel ); - rootpanel->setSurfaceBaseTraverse( surface ); - - - //ASSERT( rootpanel->getApp() != NULL ); - //ASSERT( rootpanel->getSurfaceBase() != NULL ); - - g_api->DrawInit (); -} - -void VGui_Shutdown( void ) -{ - staticApp.stop(); - - delete rootpanel; - delete surface; - - rootpanel = NULL; - surface = NULL; -} - -void VGui_Paint( void ) -{ - int w, h; - - //if( cls.state != ca_active || !rootpanel ) - // return; - if( !g_api->IsInGame() || !rootpanel ) - return; - - // setup the base panel to cover the screen - Panel *pVPanel = surface->getEmbeddedPanel(); - if( !pVPanel ) return; - //SDL_GetWindowSize(host.hWnd, &w, &h); - //host.input_enabled = rootpanel->isVisible(); - rootpanel->getSize(w, h); - EnableScissor( true ); - - staticApp.externalTick (); - - pVPanel->setBounds( 0, 0, w, h ); - pVPanel->repaint(); - - // paint everything - pVPanel->paintTraverse(); - - EnableScissor( false ); -} -void *VGui_GetPanel( void ) -{ - return (void *)rootpanel; -} -} - -#ifdef INTERNAL_VGUI_SUPPORT -#define InitAPI InitVGUISupportAPI -#endif - -extern "C" EXPORT void InitAPI(vguiapi_t * api) -{ - g_api = api; - g_api->Startup = VGui_Startup; - g_api->Shutdown = VGui_Shutdown; - g_api->GetPanel = VGui_GetPanel; - g_api->Paint = VGui_Paint; - g_api->Mouse = VGUI_Mouse; - g_api->MouseMove = VGUI_MouseMove; - g_api->Key = VGUI_Key; -} diff --git a/vgui_support/vgui_main.h b/vgui_support/vgui_main.h deleted file mode 100644 index 454ecbae..00000000 --- a/vgui_support/vgui_main.h +++ /dev/null @@ -1,154 +0,0 @@ -/* -vgui_main.h - vgui main header -Copyright (C) 2011 Uncle Mike - -This program 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, either version 3 of the License, or -(at your option) any later version. - -This program 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. - -In addition, as a special exception, the author gives permission -to link the code of this program with VGUI library developed by -Valve, L.L.C ("Valve"). You must obey the GNU General Public License -in all respects for all of the code used other than VGUI library. -If you modify this file, you may extend this exception to your -version of the file, but you are not obligated to do so. If -you do not wish to do so, delete this exception statement -from your version. - -*/ -#ifndef VGUI_MAIN_H -#define VGUI_MAIN_H - -#ifdef _WIN32 -#include -#else -#include -#endif - -#include - -#include "vgui_api.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace vgui_support -{ -extern vguiapi_t *g_api; - -using namespace vgui; - -struct PaintStack -{ - Panel *m_pPanel; - int iTranslateX; - int iTranslateY; - int iScissorLeft; - int iScissorRight; - int iScissorTop; - int iScissorBottom; -}; - -class CEngineSurface : public SurfaceBase -{ -private: - - // point translation for current panel - int _translateX; - int _translateY; - - // the size of the window to draw into - int _surfaceExtents[4]; - - void SetupPaintState( const PaintStack *paintState ); - void InitVertex( vpoint_t &vertex, int x, int y, float u, float v ); -public: - CEngineSurface( Panel *embeddedPanel ); - ~CEngineSurface(); -public: - virtual Panel *getEmbeddedPanel( void ); - virtual bool setFullscreenMode( int wide, int tall, int bpp ); - virtual void setWindowedMode( void ); - virtual void setTitle( const char *title ) { } - virtual void createPopup( Panel* embeddedPanel ) { } - virtual bool isWithin( int x, int y ) { return true; } - virtual bool hasFocus( void ); - // now it's not abstract class, yay - virtual void GetMousePos(int &x, int &y) { - g_api->GetCursorPos(&x, &y); - } - void drawPrintChar(int x, int y, int wide, int tall, float s0, float t0, float s1, float t1, int color[]); -protected: - virtual int createNewTextureID( void ); - virtual void drawSetColor( int r, int g, int b, int a ); - virtual void drawSetTextColor( int r, int g, int b, int a ); - virtual void drawFilledRect( int x0, int y0, int x1, int y1 ); - virtual void drawOutlinedRect( int x0,int y0,int x1,int y1 ); - virtual void drawSetTextFont( Font *font ); - virtual void drawSetTextPos( int x, int y ); - virtual void drawPrintText( const char* text, int textLen ); - virtual void drawSetTextureRGBA( int id, const char* rgba, int wide, int tall ); - virtual void drawSetTexture( int id ); - virtual void drawTexturedRect( int x0, int y0, int x1, int y1 ); - virtual bool createPlat( void ) { return false; } - virtual bool recreateContext( void ) { return false; } - virtual void setCursor( Cursor* cursor ); - virtual void pushMakeCurrent( Panel* panel, bool useInsets ); - virtual void popMakeCurrent( Panel* panel ); - - // not used in engine instance - virtual void enableMouseCapture( bool state ) { } - virtual void invalidate( Panel *panel ) { } - virtual void setAsTopMost( bool state ) { } - virtual void applyChanges( void ) { } - virtual void swapBuffers( void ) { } -protected: - Cursor* _hCurrentCursor; - int _drawTextPos[2]; - int _drawColor[4]; - int _drawTextColor[4]; - friend class App; - friend class Panel; -}; - -// initialize VGUI::App as external (part of engine) -class CEngineApp : public App -{ -public: - CEngineApp( bool externalMain = true ) : App( externalMain ) { } - virtual void main( int argc, char* argv[] ) { } // stub -}; - -// -// vgui_input.cpp -// -void *VGui_GetPanel( void ); -void VGui_Paint( void ); -void VGUI_Mouse(VGUI_MouseAction action, int code); -void VGUI_Key(VGUI_KeyAction action, VGUI_KeyCode code); -void VGUI_MouseMove(int x, int y); -// -// vgui_clip.cpp -// -void EnableScissor( qboolean enable ); -void SetScissorRect( int left, int top, int right, int bottom ); -qboolean ClipRect( const vpoint_t &inUL, const vpoint_t &inLR, vpoint_t *pOutUL, vpoint_t *pOutLR ); - -extern CEngineSurface *surface; -extern Panel *root; -} -using namespace vgui_support; -#endif//VGUI_MAIN_H diff --git a/vgui_support/vgui_surf.cpp b/vgui_support/vgui_surf.cpp deleted file mode 100644 index e994021c..00000000 --- a/vgui_support/vgui_surf.cpp +++ /dev/null @@ -1,442 +0,0 @@ -/* -vgui_surf.cpp - main vgui layer -Copyright (C) 2011 Uncle Mike - -This program 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, either version 3 of the License, or -(at your option) any later version. - -This program 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. - -In addition, as a special exception, the author gives permission -to link the code of this program with VGUI library developed by -Valve, L.L.C ("Valve"). You must obey the GNU General Public License -in all respects for all of the code used other than VGUI library. -If you modify this file, you may extend this exception to your -version of the file, but you are not obligated to do so. If -you do not wish to do so, delete this exception statement -from your version. - -*/ - -#include -#include "vgui_main.h" - -#define MAX_PAINT_STACK 16 -#define FONT_SIZE 512 -#define FONT_PAGES 8 - -struct FontInfo -{ - int id; - int pageCount; - int pageForChar[256]; - int bindIndex[FONT_PAGES]; - float texCoord[256][FONT_PAGES]; - int contextCount; -}; - -static int staticContextCount = 0; -static char staticRGBA[FONT_SIZE * FONT_SIZE * 4]; -static Font* staticFont = NULL; -static FontInfo* staticFontInfo; -static Dar staticFontInfoDar; -static PaintStack paintStack[MAX_PAINT_STACK]; -static int staticPaintStackPos = 0; - -#define ColorIndex( c )((( c ) - '0' ) & 7 ) - -CEngineSurface :: CEngineSurface( Panel *embeddedPanel ):SurfaceBase( embeddedPanel ) -{ - _embeddedPanel = embeddedPanel; - _drawColor[0] = _drawColor[1] = _drawColor[2] = _drawColor[3] = 255; - _drawTextColor[0] = _drawTextColor[1] = _drawTextColor[2] = _drawTextColor[3] = 255; - - _surfaceExtents[0] = _surfaceExtents[1] = 0; - //_surfaceExtents[2] = menu.globals->scrWidth; - //_surfaceExtents[3] = menu.globals->scrHeight; - embeddedPanel->getSize(_surfaceExtents[2], _surfaceExtents[3]); - _drawTextPos[0] = _drawTextPos[1] = 0; - _hCurrentCursor = null; - - staticFont = NULL; - staticFontInfo = NULL; - staticFontInfoDar.setCount( 0 ); - staticPaintStackPos = 0; - staticContextCount++; - - _translateX = _translateY = 0; -} - -CEngineSurface :: ~CEngineSurface( void ) -{ - g_api->DrawShutdown (); -} - -Panel *CEngineSurface :: getEmbeddedPanel( void ) -{ - return _embeddedPanel; -} - -bool CEngineSurface :: hasFocus( void ) -{ - // What differs when window does not has focus? - //return host.state != HOST_NOFOCUS; - return true; -} - -void CEngineSurface :: setCursor( Cursor *cursor ) -{ - _currentCursor = cursor; - g_api->CursorSelect( (VGUI_DefaultCursor)cursor->getDefaultCursor() ); -} - -void CEngineSurface :: SetupPaintState( const PaintStack *paintState ) -{ - _translateX = paintState->iTranslateX; - _translateY = paintState->iTranslateY; - SetScissorRect( paintState->iScissorLeft, paintState->iScissorTop, - paintState->iScissorRight, paintState->iScissorBottom ); -} - -void CEngineSurface :: InitVertex( vpoint_t &vertex, int x, int y, float u, float v ) -{ - vertex.point[0] = x + _translateX; - vertex.point[1] = y + _translateY; - vertex.coord[0] = u; - vertex.coord[1] = v; -} - -int CEngineSurface :: createNewTextureID( void ) -{ - return g_api->GenerateTexture(); -} - -void CEngineSurface :: drawSetColor( int r, int g, int b, int a ) -{ - _drawColor[0] = r; - _drawColor[1] = g; - _drawColor[2] = b; - _drawColor[3] = a; -} - -void CEngineSurface :: drawSetTextColor( int r, int g, int b, int a ) -{ - _drawTextColor[0] = r; - _drawTextColor[1] = g; - _drawTextColor[2] = b; - _drawTextColor[3] = a; -} - -void CEngineSurface :: drawFilledRect( int x0, int y0, int x1, int y1 ) -{ - vpoint_t rect[2]; - vpoint_t clippedRect[2]; - - if( _drawColor[3] >= 255 ) return; - - InitVertex( rect[0], x0, y0, 0, 0 ); - InitVertex( rect[1], x1, y1, 0, 0 ); - - // fully clipped? - if( !ClipRect( rect[0], rect[1], &clippedRect[0], &clippedRect[1] )) - return; - - g_api->SetupDrawingRect( _drawColor ); - g_api->EnableTexture( false ); - g_api->DrawQuad( &clippedRect[0], &clippedRect[1] ); - g_api->EnableTexture( true ); -} - -void CEngineSurface :: drawOutlinedRect( int x0, int y0, int x1, int y1 ) -{ - if( _drawColor[3] >= 255 ) return; - - drawFilledRect( x0, y0, x1, y0 + 1 ); // top - drawFilledRect( x0, y1 - 1, x1, y1 ); // bottom - drawFilledRect( x0, y0 + 1, x0 + 1, y1 - 1 ); // left - drawFilledRect( x1 - 1, y0 + 1, x1, y1 - 1 ); // right -} - -void CEngineSurface :: drawSetTextFont( Font *font ) -{ - staticFont = font; - - if( font ) - { - bool buildFont = false; - - staticFontInfo = NULL; - - for( int i = 0; i < staticFontInfoDar.getCount(); i++ ) - { - if( staticFontInfoDar[i]->id == font->getId( )) - { - staticFontInfo = staticFontInfoDar[i]; - if( staticFontInfo->contextCount != staticContextCount ) - buildFont = true; - } - } - - if( !staticFontInfo || buildFont ) - { - staticFontInfo = new FontInfo; - staticFontInfo->id = 0; - staticFontInfo->pageCount = 0; - staticFontInfo->bindIndex[0] = 0; - staticFontInfo->bindIndex[1] = 0; - staticFontInfo->bindIndex[2] = 0; - staticFontInfo->bindIndex[3] = 0; - memset( staticFontInfo->pageForChar, 0, sizeof( staticFontInfo->pageForChar )); - staticFontInfo->contextCount = -1; - staticFontInfo->id = staticFont->getId(); - staticFontInfoDar.putElement( staticFontInfo ); - staticFontInfo->contextCount = staticContextCount; - - int currentPage = 0; - int x = 0, y = 0; - - memset( staticRGBA, 0, sizeof( staticRGBA )); - - for( int i = 0; i < 256; i++ ) - { - int abcA, abcB, abcC; - staticFont->getCharABCwide( i, abcA, abcB, abcC ); - - int wide = abcB; - - if( isspace( i )) continue; - - int tall = staticFont->getTall(); - - if( x + wide + 1 > FONT_SIZE ) - { - x = 0; - y += tall + 1; - } - - if( y + tall + 1 > FONT_SIZE ) - { - if( !staticFontInfo->bindIndex[currentPage] ) - staticFontInfo->bindIndex[currentPage] = createNewTextureID(); - drawSetTextureRGBA( staticFontInfo->bindIndex[currentPage], staticRGBA, FONT_SIZE, FONT_SIZE ); - currentPage++; - - if( currentPage == FONT_PAGES ) - break; - - memset( staticRGBA, 0, sizeof( staticRGBA )); - x = y = 0; - } - - staticFont->getCharRGBA( i, x, y, FONT_SIZE, FONT_SIZE, (byte *)staticRGBA ); - staticFontInfo->pageForChar[i] = currentPage; - staticFontInfo->texCoord[i][0] = (float)((double)x / (double)FONT_SIZE ); - staticFontInfo->texCoord[i][1] = (float)((double)y / (double)FONT_SIZE ); - staticFontInfo->texCoord[i][2] = (float)((double)(x + wide)/(double)FONT_SIZE ); - staticFontInfo->texCoord[i][3] = (float)((double)(y + tall)/(double)FONT_SIZE ); - x += wide + 1; - } - - if( currentPage != FONT_PAGES ) - { - if( !staticFontInfo->bindIndex[currentPage] ) - staticFontInfo->bindIndex[currentPage] = createNewTextureID(); - drawSetTextureRGBA( staticFontInfo->bindIndex[currentPage], staticRGBA, FONT_SIZE, FONT_SIZE ); - } - staticFontInfo->pageCount = currentPage + 1; - } - } -} - -void CEngineSurface :: drawSetTextPos( int x, int y ) -{ - _drawTextPos[0] = x; - _drawTextPos[1] = y; -} - -void CEngineSurface :: drawPrintChar( int x, int y, int wide, int tall, float s0, float t0, float s1, float t1, int color[4] ) -{ - vpoint_t ul, lr; - - ul.point[0] = x; - ul.point[1] = y; - lr.point[0] = x + wide; - lr.point[1] = y + tall; - - // gets at the texture coords for this character in its texture page - ul.coord[0] = s0; - ul.coord[1] = t0; - lr.coord[0] = s1; - lr.coord[1] = t1; - - vpoint_t clippedRect[2]; - - if( !ClipRect( ul, lr, &clippedRect[0], &clippedRect[1] )) - return; - - g_api->SetupDrawingImage( color ); - g_api->DrawQuad( &clippedRect[0], &clippedRect[1] ); // draw the letter -} - -void CEngineSurface :: drawPrintText( const char* text, int textLen ) -{ - //return; - static bool hasColor = 0; - static int numColor = 7; - - if( !text || !staticFont || _drawTextColor[3] >= 255 ) - return; - - int x = _drawTextPos[0] + _translateX; - int y = _drawTextPos[1] + _translateY; - - int tall = staticFont->getTall(); - - int j, iTotalWidth = 0; - int curTextColor[4]; - - // HACKHACK: allow color strings in VGUI - if( numColor != 7 ) - { - for( j = 0; j < 3; j++ ) // grab predefined color - curTextColor[j] = g_api->GetColor(numColor,j); - } - else - { - for( j = 0; j < 3; j++ ) // revert default color - curTextColor[j] = _drawTextColor[j]; - } - curTextColor[3] = _drawTextColor[3]; // copy alpha - - if( textLen == 1 ) - { - if( *text == '^' ) - { - hasColor = true; - return; // skip '^' - } - else if( hasColor && isdigit( *text )) - { - numColor = ColorIndex( *text ); - hasColor = false; // handled - return; // skip colornum - } - else hasColor = false; - } - for( int i = 0; i < textLen; i++ ) - { - int curCh = g_api->ProcessUtfChar( (unsigned char)text[i] ); - if( !curCh ) - { - continue; - } - - int abcA, abcB, abcC; - - staticFont->getCharABCwide( curCh, abcA, abcB, abcC ); - - float s0 = staticFontInfo->texCoord[curCh][0]; - float t0 = staticFontInfo->texCoord[curCh][1]; - float s1 = staticFontInfo->texCoord[curCh][2]; - float t1 = staticFontInfo->texCoord[curCh][3]; - int wide = abcB; - - iTotalWidth += abcA; - drawSetTexture( staticFontInfo->bindIndex[staticFontInfo->pageForChar[curCh]] ); - drawPrintChar( x + iTotalWidth, y, wide, tall, s0, t0, s1, t1, curTextColor ); - iTotalWidth += wide + abcC; - } - - _drawTextPos[0] += iTotalWidth; -} - -void CEngineSurface :: drawSetTextureRGBA( int id, const char* rgba, int wide, int tall ) -{ - g_api->UploadTexture( id, rgba, wide, tall ); -} - -void CEngineSurface :: drawSetTexture( int id ) -{ - g_api->BindTexture( id ); -} - -void CEngineSurface :: drawTexturedRect( int x0, int y0, int x1, int y1 ) -{ - vpoint_t rect[2]; - vpoint_t clippedRect[2]; - - InitVertex( rect[0], x0, y0, 0, 0 ); - InitVertex( rect[1], x1, y1, 1, 1 ); - - // fully clipped? - if( !ClipRect( rect[0], rect[1], &clippedRect[0], &clippedRect[1] )) - return; - - g_api->SetupDrawingImage( _drawColor ); - g_api->DrawQuad( &clippedRect[0], &clippedRect[1] ); -} - -void CEngineSurface :: pushMakeCurrent( Panel* panel, bool useInsets ) -{ - int insets[4] = { 0, 0, 0, 0 }; - int absExtents[4]; - int clipRect[4]; - - if( useInsets ) - panel->getInset( insets[0], insets[1], insets[2], insets[3] ); - panel->getAbsExtents( absExtents[0], absExtents[1], absExtents[2], absExtents[3] ); - panel->getClipRect( clipRect[0], clipRect[1], clipRect[2], clipRect[3] ); - - PaintStack *paintState = &paintStack[staticPaintStackPos]; - - assert( staticPaintStackPos < MAX_PAINT_STACK ); - - paintState->m_pPanel = panel; - - // determine corrected top left origin - paintState->iTranslateX = insets[0] + absExtents[0]; - paintState->iTranslateY = insets[1] + absExtents[1]; - // setup clipping rectangle for scissoring - paintState->iScissorLeft = clipRect[0]; - paintState->iScissorTop = clipRect[1]; - paintState->iScissorRight = clipRect[2]; - paintState->iScissorBottom = clipRect[3]; - - SetupPaintState( paintState ); - staticPaintStackPos++; -} - -void CEngineSurface :: popMakeCurrent( Panel *panel ) -{ - int top = staticPaintStackPos - 1; - - // more pops that pushes? - assert( top >= 0 ); - - // didn't pop in reverse order of push? - assert( paintStack[top].m_pPanel == panel ); - - staticPaintStackPos--; - - if( top > 0 ) SetupPaintState( &paintStack[top-1] ); -} - -bool CEngineSurface :: setFullscreenMode( int wide, int tall, int bpp ) -{ - // NOTE: Xash3D always working in 32-bit mode - // Skip it now. VGUI cannot change video modes - return false; -} - -void CEngineSurface :: setWindowedMode( void ) -{ - // Skip it now. VGUI cannot change video modes - /* - Cvar_SetFloat( "fullscreen", 0.0f ); - */ -} diff --git a/vgui_support/wscript b/vgui_support/wscript deleted file mode 100644 index cafab08d..00000000 --- a/vgui_support/wscript +++ /dev/null @@ -1,124 +0,0 @@ -#! /usr/bin/env python -# encoding: utf-8 -# mittorn, 2018 - -from waflib import Logs -import os - -top = '.' - -VGUI_SUPPORTED_OS = ['win32', 'darwin', 'linux'] - -def options(opt): - grp = opt.add_option_group('VGUI options') - grp.add_option('--vgui', action = 'store', dest = 'VGUI_DEV', default='vgui-dev', - help = 'path to vgui-dev repo [default: %default]') - - grp.add_option('--disable-vgui', action = 'store_true', dest = 'NO_VGUI', default = False, - help = 'disable vgui_support [default: %default]') - - grp.add_option('--skip-vgui-sanity-check', action = 'store_false', dest = 'VGUI_SANITY_CHECK', default=False, - help = 'skip checking VGUI sanity [default: %default]' ) - return - -def configure(conf): - conf.env.NO_VGUI = conf.options.NO_VGUI - if conf.options.NO_VGUI: - return - - conf.start_msg('Does this architecture support VGUI?') - - if conf.env.DEST_CPU != 'x86' and not (conf.env.DEST_CPU == 'x86_64' and not conf.options.ALLOW64): - conf.end_msg('no') - Logs.warn('vgui is not supported on this CPU: ' + str(conf.env.DEST_CPU)) - conf.env.NO_VGUI = True - return - else: - conf.end_msg('yes') - - conf.start_msg('Does this OS support VGUI?') - if conf.env.DEST_OS not in VGUI_SUPPORTED_OS: - conf.end_msg('no') - Logs.warn('vgui is not supported on this OS: ' + str(conf.env.DEST_OS)) - conf.env.NO_VGUI = True - return - else: - conf.end_msg('yes') - - conf.start_msg('Does this toolchain able to link VGUI?') - if conf.env.DEST_OS == 'win32' and conf.env.COMPILER_CXX == 'g++': - conf.end_msg('no') - # we have ABI incompatibility ONLY on MinGW - Logs.warn('vgui_support can\'t be built with MinGW') - conf.env.NO_VGUI = True - return - else: - conf.end_msg('yes') - - if conf.env.NO_VGUI: - return - - if conf.options.VGUI_DEV: - conf.start_msg('Configuring VGUI by provided path') - conf.env.VGUI_DEV = conf.options.VGUI_DEV - else: - conf.start_msg('Configuring VGUI by default path') - conf.env.VGUI_DEV = 'vgui-dev' - - if conf.env.DEST_OS == 'win32': - conf.env.LIB_VGUI = ['vgui'] - conf.env.LIBPATH_VGUI = [os.path.abspath(os.path.join(conf.env.VGUI_DEV, 'lib/win32_vc6/'))] - else: - libpath = os.path.abspath(os.path.join(conf.env.VGUI_DEV, 'lib')) - if conf.env.DEST_OS == 'linux': - conf.env.LIB_VGUI = [':vgui.so'] - conf.env.LIBPATH_VGUI = [libpath] - elif conf.env.DEST_OS == 'darwin': - conf.env.LDFLAGS_VGUI = [os.path.join(libpath, 'vgui.dylib')] - else: - conf.fatal('vgui is not supported on this OS: ' + conf.env.DEST_OS) - conf.env.INCLUDES_VGUI = [os.path.abspath(os.path.join(conf.env.VGUI_DEV, 'include'))] - - conf.env.HAVE_VGUI = 1 - conf.end_msg('yes: {0}, {1}, {2}'.format(conf.env.LIB_VGUI, conf.env.LIBPATH_VGUI, conf.env.INCLUDES_VGUI)) - - if conf.env.HAVE_VGUI and conf.options.VGUI_SANITY_CHECK: - try: - conf.check_cxx( - fragment=''' - #include - int main( int argc, char **argv ) - { - return 0; - }''', - msg = 'Checking for library VGUI sanity', - use = 'VGUI', - execute = False) - except conf.errors.ConfigurationError: - conf.fatal("Can't compile simple program. Check your path to vgui-dev repository.") - -def build(bld): - if bld.env.NO_VGUI: - return - - libs = [] - - # basic build: dedicated only, no dependencies - if bld.env.DEST_OS != 'win32': - libs += ['DL','M'] - - libs.append('VGUI') - - source = bld.path.ant_glob(['*.cpp']) - - includes = [ '.', 'miniutl/', '../common', '../engine' ] - - bld.shlib( - source = source, - target = 'vgui_support', - features = 'cxx', - includes = includes, - use = libs, - install_path = bld.env.LIBDIR, - subsystem = bld.env.MSVC_SUBSYSTEM - ) diff --git a/waf b/waf index dbbf6844..82ac5125 100755 --- a/waf +++ b/waf @@ -32,13 +32,13 @@ POSSIBILITY OF SUCH DAMAGE. import os, sys, inspect -VERSION="2.0.22" -REVISION="c7da3f216d29d06f37ce7acc9d571eeb" -GIT="51fa39db69ed5a58a1f1d256cbff0c35311064c3" +VERSION="2.0.24" +REVISION="99462435aaeec331fa35f40b2179472b" +GIT="c140c3f538c4a21f3d88bab9403b42c696759dcb" INSTALL='' -C1='#h' -C2='#D' -C3='#:' +C1='#5' +C2='#/' +C3='#+' cwd = os.getcwd() join = os.path.join @@ -171,5 +171,5 @@ if __name__ == '__main__': Scripting.waf_entry_point(cwd, VERSION, wafdir) #==> -#BZh91AY&SY} ?&_$e>V#:0c~-#h#:#:#:#:#:#:#:#:#:#:#:#:#:P#:#:#:#:#:#:#:#:#:#:#:#:#:#:#:#:UQWa;||{ޖh^ݯ=};gql—fmwnZZF{zNl{޼fm[n[3c`ۖ=]s)r:JL3[fm>mf7yEk޾J7_}n۵ikln}}heEyo\u4hw4QRI@.#:^=6ۨ ]{ϻ#:U #:#:n఻d R"VګUvh*0bUTf֕"[5S{#D瑶-.rca,<Y{S!QvIuG#D#:{= -oG#Ka*g{)3Bi@#:vʔE{/.℆u'ϻw|O]XR^OuJݟ=#:#:#:#:t#:#:͆4_}*}#:CmUyPy힎[s9vv+`#:(GAkYւ*Mخ6(JF#D#:#:PUPsaP)A]#:i@HUH#D:t#h;gvW}uNEhkƴhnTUJd }};M}鮔&;{>wϷ[:g]>k}}޾O_8_Nvw\7}muy̔죷:#:h۹EwQvdRۗnْVhwsT`A']}oGxukN#:+mêz7wJr^Ӯ]uoty[e;{G{ϗo_U7]]Bzz.{yy/m>ğ}ϝm֙۵smֽu饷g7[>6…齽.k˵Vֲ^ě=kXX7{_m794\Az5bṾ3t=쪅͚{ĮhժH9W*wT[sO#D99K4n;\.vvk;fa}s;{,v%H%z(ӂ/[ö}j+;zVxP#: ՂC:+J4m r##: Z@n dmZV#:Wh#D^c:5qKe D\es[޷*^MXKm3SiW{#:#:TY;w{ǖ#Ohwvնf:%}tgp.vrpvap#:]u|Op|sQj]9@_@n\>SuzT4 /"ٴ#:PMiU Z11wݫ6ۯdgjTfݵͶK<ġ\uKr;2mEnZzd#:kC\kj-:;"hS*݁@`VTh#D}#D)P#:#:=C졣ΰw:uނ#Dۻw;^f}]ku7wvۻYn\㹓݀ہE4:q-J npR$MvZڠA(#D ÜsjƦO]=#DX#:]m̠}Sor*u#DQ) G-C>Z% UN:,ulh^=Ͼo:|=rϖuI =7R[h\;p<]Xל4#: #:#@#:#:F#:hQPmMѧ z4C4jmOjS@@@@!`Pb D@* MJ2S##2!IKD$123lM)M ̖Kb45$ha2Idm,hmDl4((6H LM bō(4d$T00Tb!#D i+$R38& 4062e&d$vSl%6̳,63#DM`"M4iC3IU(f+14$eBHDTiVTFXYTXʼn614(1R1,SM1I,͑ie)DRBJJ,F#h@5&R)M2J!QE F&AcM6o#D+"1lj2A$iRI,S"+Ed#h214bhJ&mL#03#:d)4Id 4 fD2)ƌfmXcjJ`dYdb4a-$1#IJ(4 ,ƈ3,6(E,DԐB0IcA)6dDYLI6Fb[d#b0c6f#D1,dd6Jj#EF$-E%16 Z(ј&14II2d%5FZEEL-fe iJSd(&2آ*DVɥ-S,JҲkA2XM%,і2 ڊŪ#hā6Vi1RlT)U$EbYlmdSDU`TFbijHF65lXhj`-2hf"&!20+JiZ66[+HĄL)Y,ImUR٪Yl&J2%R@i62i-cH[f1P3$"&I!%b2A+21$&ADɵiL(,R(,$ie1D@2dD[6L6cCffHK 05M3ącAED-b4HbI#XĔK,,Z#:v%*SdZ2)66e&LdX)LcJm"XJ#RXhԥ&ieXQAd!*%(M@Hf$Ѣ̥LҦ[(a5Ae$Z!HQTl֌Lڃ@F%"[$Jҋ$DͰSId͓bL6hddkeAHfZM(ыfDT̆(!0hTlZ,b3-$fBi̳##DeQ`)AƢ(e%JDlEEĕ0K5F$X31(M%$ d5IB4iJ IJchمD[hMJcfj""ɠ,̡ZM4fĢ&YfDj3L݋%#hb%bحi#le#Di#l[)&Ɋ(RmUD%)Ħ c%̱%%"jC&fB-@CQ53iړlXR44FmZQFVYi4cmVѱjDKh5%)MȦI5"1(jfLRXRͬК*0QS(Z3lڋbmFkI(AbU++M*53fMjbP40+McE&TcFɪJfI%lI#DR"f ( M11dbbjX`)*y"4Z6 -4@4-* 4DC-lmMMV$1DHə6X1I"LFB)+QS-MSCsvܨKͤi\dߗYU͢d¦bE*}f};3û6,?mX`ã#DY%޿-/"mAO~oDlUme&{hcu4as^/0PEXxM8ԍr0?_\1||~\\k3heއ/۠'Jf,#Dx8\s̻{GOx?g[ϹV]#htp#D#hI k,?4kSWn$щԜai;\7pZJ6K_o#Dl-:u[vtlwD~֗"21c)g;I0ݖE&$6EF,ZV/&mt?jWb3JQQE_JQ?s"U{rMt7Irճʨ2P<L~koǍPƞ,s(PdHDo^g;vT f4I~H̐{3Fzh"Ä `2%((kT'v6C\DptDa1{%3ӫ*o G#hES G:>P2PS_,Q7+N(2If}(יa͞:}a=WxA͓&#DJ?M!%YI#((a mSJ/;T*㌞>MP(!jx˜#"%(I? ܥ%?͟e(aB܊e~n}וncr>F`_κ|TX3#he#hAUK޿&1E%ox-[|EC~ir8k#D0bG篃!٥_#huRIOdt!:fu8i}l5EDaZujtk#D09L64B5nr#h.9Z=~U13$zyGͬ+Q!C-kAMҳWltƴfgLw_{{tQd Δ>eNVspa2xnp{3F^L:+#htϞAʞ,)t}ID vlEhTM>?6C!x;'^aP*agŖp/XNbȔ1zAv_eehhBXӦ,8J-=u[;\9iUgJQ#h2t9gY$r<@_RDIUfO? v0g!|kM=}1#kMEJw`[[GE[hu˷L5f'娊Ҏ4'f,6:Ԩ<]A;;J| A*wԒC.Ʃ:/wu6 I? Z%ד," )Q-nG\"]H{txΣs;mp8>=R';1L]vO1L_ĤobZ_k%#7ؒq;1P&2[#hNJ9iAV;-nȴ!rySf0#h5hXB \P#D0Kor^ظ9LC9&'Xpl|]"'#r3PAeݝ$oۉGq @"3 $ܸulON?CYML˚gi2tgF|Nr!eҏ5 ]wpon{nbɻ-q#ДQs.xznggnSJc-0.cSs^2='G'Cc jXA^KXX=-ctҌD@9&#:pP֐@H@JAPG04  74zDJ2ydo k/e0ߕ:c#h4YߏvÆm=o*-nj24ͮW`F9m8฼S@T#DG|#Dyz-bRR ,;ƛ? 8=.xx6 ivJ#h9K6^Y9F9RO@7m =QJơs>"'ڈ&˄Tn#hSsi?s&zY@,#U .1{O^#:DFM~1/6hƍZW~x!ScX=zrst,4h`hݎ4L3mӿF3Ʉ|̒rynKPf<#R#hq2Ʌlѱάr~BjNn̲^aFfwpnCmϾy:dW̄k.T F=E=ur*î]zl#暕"$?(4 ʀr}n3mm㝬^SD$6_T#hHϥOc#D_q(}/:AĿ!|1$f}z6FC&-:ӏXP$EJ|rPs#D7X L}?%@O5vwg8ռbܧ?n"-<yħd;lٜ}e7C[aҵ.ot+M׮Jp c/7 kO`^>͙9?'b5R6f%"t@E>ޙ{ɨ%;(sxhvby=eM\a e=4H`c2AP7ㄣ4-!?X&bpn`wLJ6Ӛe.^7#k+!lnHr JbPZfSxm6#:qQ8-#hCWTa#DL ̃sE#hL}L9(YdMFZxT:Z_\[ӽ#h7ns%8s[CN"v#hS uW£_ܫ ?c(Ϯߥή;#vEot0pV$!#DА[ӌsl%BDڍ?]Ų~,tW jGTsPDb#=0fwUTxrgd.Ͱ1J W P#D^ VNqBfv8\r#h?32T)Iqo8HJߐ_cʽW;۶6N}kֳBCVAqQLC=?&=r].u6l"|FDz)Q }1wPtl{uG[ ʤ`$r#hKڪkZ#mi4cjb14Cͼ󇓠= 'oȾ]!¥A-t:vqiԷ9J#:HuD9'S|cUve\Kp~#hlyA KqS6e-X?ủܿ~v1#hR(XcPd@|=3y=]Zɺ'!O'3Tg&a9 n|"T5GmfF/ |}hd/q~?:#h:$q?S"I-LRpyXh-v>5lJ>UȤ3d5 NeBBrT97\ Tgww[0fG2̀0#h 8coN-3nr%Muч]SR541뭷(0RCs-`Z3j:O&iD%l>>uIKvvtzT,U?ԙ#D/E!}} :9PlDٿP,wD/ioEԋ!]^\hqs<BÿBxWJv:2$a$#DiBA }1wJX`VxpuآmPa0 'JA#|,v̱\SaXr{2" qBܜ|{F@C`2_|+>u-UڔR1F Al#De2F"HػBuFY2>i w+|w[`JǑA&kA'1RV4GvKw406^Kt˼WI3@ZD|չK#h^?}̺2YRheU"ف jk`V3YFp֋ 2$υs|"F:37u#h4_%C=υ$֤+um $T#Da&0%=d#ga$n$To5zzc8D5`(n +CS)RI3t/8遶Z:1.I̥FC1ف6I?J۾ɪeyY2*4Tog (R@(H$d>碑$_ж|n%=x^.{۬mq aYه]},pI ;YuyAEYD9WT^xFi<ЙU, (T][ !3q.}X9?N>z/m͑d!x}LrX33[]"8y%O?F$zUa=vq%iByoGطB=*[`%sl,s7QA!fIƮ|jS:ZTcJY 39x(Ƽg|@ގW\ݬ7 8U2$`LjbӠ~3 Rƿ7R\ڸ6Or|F3F#DhfLE\G^t;oWz]kO6gA}U6uX)bnY\]7sPS(IKb]U3gtgTz|aeU:}+'8s*-QpjVIɻa1Eɰm]aMD1VIj*uS-ꖀWJT9"G +wt*l~]W=Z((,LҋrlS~\m#hЏ#h>Xjtp}#DaiЄ=C8d;¿U;R248dૉ=veh}#3 O~o5BZ96;^_n#h#:DfS$]BJ=6r8N%>#:(B8 #:4ιq1ڱ,ڙ(`TRngn(:|Ӌ=kNhP"%EbM4LkO y1F+on&}B9Ą򱗆)硟=ϡL9N>*nDcfcLӘr;_!X#h?b[a^^enRTX/VuUUda\ar>mKd2# !*tI( [ǔ:BO6x$OHR&Ch;^v*RʵDni G"g7Z&Q4SȚxJ(#Ddi`GՐY45""ɐ#Dۡ+ }#hUt͸ZR#:Aa*OOi%4%?p,~u^!8rptNV ѓI%JI{;~L"uG*G6ܼqXqLR[+cѦQ~)YI,l*4nǸ$,_ְH讕[Dzs3ɧ$4ҪLB>RĩBj&U"ZQe*`ɜ`"kGyNm Qzy^o?8m1F{H,o_[y#:2#l 3qCRF`E{ t5(,pIsN+2whv=q2:}Gh0:q,#DNonx9j֣^zA˄#h%i#hGfUӸ`a:1.!3(303V(!HPAwfn\it?W~ްokrr4\Tn;/\g"S%H?6I52:gEEYԭQ#ht(=hݧz]R0Xq;q#6d2E?KTtjaMC>"jg}3!2CHkFQLPa郎o2`Pm)ܛH=#:Zj#F''ϘdU!ɧk̻d%?%gF`GA M4GB\f_Y*GL8_}s.50|FG/.#h ;Mٹj^e{ m94Ar2n=K)6:öWVng^*q߉Ȓ&XM$ިW##h*\xy2ldys7Knwq^u&<*~vNBuMQ}D~R3)ădh=:q*G֓lM=6|흱zO&D7_رZ=_^1X(CԪOdduƥ/;Lb&zM#h[sm)bz)?*C잟-.țtV_HD9`3*MF%G(j_҃688p{*ZلH #ht b 0ryB-F~Ί0LA$GmaFmWFrzA(t oDLKyZ65V>e&nàܢhKFȌ}a+!Dzy^孇|&a454kL5dXQ諲8e6A.((s>M1PLxj QW'khWJ}J4 x_{nԁ_glC\POcJ3-#h]05:6tEhvZeJJ 8U]#Dq<ml<rsjbn7AءaH yY>#D6I+c sTY*^Q8A!ֳN<":Z)<k0oSHMpݴ庪+/>}b:='v7p6"򲍼u4;x#h_bLܤZ. n_D%QhZJ.xYÔU";C̎L<$icxj3I%9%9tW }JXT#h z)B@rT6@.NvB#h,Ƕg'g lL)KVQ*OL')n;#h5˒ xAb#hZ 1"cqU8l#\wԛ7;Ч#DI2ڱu{iA_eqȸڞvsehTesc/Pr?[\>=p#1c#DCaQ78ɾ٩-}"/DC.!|6>9qf]Gĩ!wYDK {}q#?]=G#'RӹJh@}8fzݬ Nq3oeȋD+_љ>NzNŸ3G17p0Q#q`<3PGco*ſ`LGTL U4ϗ!!~?y1>Cy⓯~oO{=ף)<Ō\y(90)!  #ݭ2Ro̾#_Cv)G^]-scD9.7/t?>D1#hRD3#D4%!2<1'ȒUpQ+0?Seu#j(Ox^!3Ysܼ3 䔊 |׀4BYzuϢ?9Ո%W%#:ίXX @pOW(EƝ k|!ܫ5z̕iR#syW\u^v"K67UÕʝ5e!m4)me4cߟ :\c#h܏'h? UtQubK8hT!TUyC@?gqSB|k(5*|U#Hm2ud#:4HF7&:@:cuhaawc P?D Dȟ'?99ȅJ#o7)RѰ&Xz|- =(M!xk˶ϫ\=ghn/ fėAF./Yr;Gh+:ȗ#8k_|J =a7}M56%:d`ŇaCg9tq9?M@,#hڬP\bG'1QP]䙟s)8bʣO*xFCFmC绫?~#:\qKJHd@<4h,QӧMy,.Z7}iCN{V%GډC~o@c1$uH5:&g9] U\Ky,~1xC?3Fk$*c#D4:#Le|8k}[e>FO#DӁyֲ49u+d49XB3t+. d|Z$ =c|bHuS3YTY fSXQ|Bre #:Fӂ=_{՜?Hbt(ݜն~vJ+y%/Lr?W޹h#X-/U~w{y<>0KH4#D'k-O #DbJ '~$M)D%#hQ#:{?ٲJαy#D#D1Rh$S(0ox#hBWz-VE$~`9#:9HR `804l·&KTR3FT] {B8LԙHtbIqH8`:L&}M B6VqS\E1ksM Ӝ;4ʐA̳\b\U #:V #D*KU$zg87|`h4(Uΐ/#:؏#D08aJ=/zM5E,—w03!FZ8E% 6 Q OKCNf=3Qћ0-#Lc3ql9A񇭉҆;#Dd2kb9I՛jfΠŃbM.4Iq`EAVfZa _B&yZ#ḎƓn9ّi!;t78#fq+:~NhI"#D4!ڕbhSwAnrbqӒҝd4x3.vAD֏|մdYc(+<(&Fm.7U.ڀ, ,nWlҰb4 7z @tќf#DQ#{TAU\5&0kB1!3D١ IDXP9r82[qP[fC{a#+SSp\r*k*G/,B#:(MPٓZ@v 6Pn5`ZJO8C)LY;#h3Ԩ6WYӋVM.LbtŋT#Du|Rz}?'HW]#hwvA8L{kΔ^ʠvOFLʩg h+:S#hÞaAD3Cwo_=ߨ)q>s>*@PϚVN!mtΰ x^Mt\0HK]*Aԅ72y>:#DWC.^Iaj8NF%Y_ r CU\q֌@35ZaJA.ݷ޻8a;`[#,nqu`0':=ix,p¶b軞D90o9E~#DI؆]ԷbϮlZ<`:i6YӜ82r 䦶.{tf\_{JGccÅa,\FQn`Ĕ ,2U,KPDqPBF'ɮ4>@)Qbd! ᳚(z%7 U[~cGnO0_7FJMmNLt`}䤖#:Ըi_/VL}C?뜻nő+i5DbymTq]b2b8]UV_vѴa]3>GO_ ^dw֟&9Uh5#hAم OdŪxVZ}b8a|l] )86cQu#=QvGtNɋA5~#:~i}EvT,$Tk&d?cFug[byR W+"w9@x7Ly$ד[ҿx/z9"ώMek^ZwEQڿ0Qh4:2)s_Ni=PtI@y㙌#Dx)h&D`f.p$RwSgW_-Q|V=Md#DUC0Bddpg穛xILE"Ԕ*sr*"Ϥ!;δC5Q7D>5կY4fRS*%-M£q^zEN[Nz&AEojkߴ,\'|>q0yN9vo-|>7OMN\20ΰ3JVӍbPTڣ_ѵ@v\ˎSnps<}೦cA}BE'+%zL#:6J$`6@׏V4?x^7pL=yLfQے(PF(/Oa޴{Jhy̩w>')/ wHsI*a67l__0TWL6qWCcs\"$ q6_q#DD+r@Or\Uu:rhQ Fx(u`˞Jre9g1yb) #BE3#:Z${7&P[ܲۯ[l#hjPlِP<'yDV؄ټe>-kđ#D/+shPa|uG-uQ!#KîVBP4̨BU@v՚ 6]@%6;$G}CG2\FJ2#D_ջ1_j|FA`|{BQ+8/.@kL#h3q[j~V"Z(ҿMH/R9fA@R9sCEB[AĥX?'j#hرgn4"My." 8m7Bx;8TCU{n|]A頻kSʹGh#DOs6BZc&ZRvmR FTZ>0}\owC'3.`V~x&xǷ)~$~SHC<0pń6|4-ZMxg8H#DRЍE<.tPţˇX-R :$p!@o#:DHM' #D]C#hT#DݷϬm&^DeWY7Ď0.D:O]/JzJ ,Ub54(_jl[pjUr˟U3>fUK^+qduuQ:(bij&?tip8P'ޣOkf?wxFe\X۽f~?O"HJXTSs^3904\>m]F&g<|נNd"JZv.ó~5P=}@QE+r,;؏.>>4No)N90I0_V"cQꭌ^Ҋ@RW;5#h7?|#D獷,r/@lGd HasN#:1mQ=Kga4wڼ;0{ۙNaâ.#D<y3K!ɭ޽ʷ_~::npm6ocYܘ(nzFb϶#:/k a&௚ƚ6鼚E#Eog1fux[ؙ[䦿X7(HpʞSNgHƆIwz\\~fTrE=r\C_8fa `A.trrYϦQXL.knw/+{7KM'܀ U{ JM.qE`7#Dvr@Ǧt`u܋x_}, Epz`S/SvnyY먓ym-mog̓]z8Tܠ-B5QzU8c 7M$o$do^hqH$z֊@"t;'Qd @!VN[O/jk@j2#F^J"ƳE=#DQ!.)nݷ&+xdżXIbB"#D>s~YUKƄ>wJ^f0pAP$l9{.L `1ۚs[5{:)mg~Ja5b=(69<]>LmQ߻އ&it@o-A0ysYlu(~GX"^?I^ w@1Vl\!Z׫Je: %XahQc߹g&cdu/%ibTG=K#h V{w=!|y9kio_PoR5=6{"#h d1&?3s#hS:={d}]zZB0ڎAs ,:5Nv{M$`<_7+qf]4\U׀gF|)kpdo\b[HX/.Z;8??>BȣW뛓ǒv!`0! CyY}7}\ ?DGBI$!Ov?7nV}<0fŀQ!v#:$#:ʻz4[`e@Rc8}p.w6l,TaW *{qt Bi1s>poo~|SG͒(VI(z"'ӏ 4T?CxǺf"nln2"4cloqv9R"1"6=_EGVW#:l.n)(#DsNw..}#:ʔpu+_V]ȤcGL`u 8zsxoawz&(@C@]sBf#h#:QEm^}}#D#hioM 6?hǨ<)#hC~Ua$`X AC_DjaL_pSE5]d31D'nd1"=M 6itPYBoOTªI"&>njFw/lKZ#h#Dzti_7) M fwq/C")ic^Y0ZOo%91]7c͌  r]D>7䤠.;: UT)p x~8L#h-`c  ȦUAĂ{w HrsG!{$ 0#DV$Dٳ#hkt>f K+E #D@,Nw$,]_}>Ui9;v+ȯW-0r#:#:ɱ;@ڏu$ϡ2deZ(*Jx]t#:y`&l3mMQ QaEBqDD(GA2f.gHޜ@vͦ=sqUjlDC+q#hwA^B:: l]A !l;MmShؗd g#qw"icV#Jn/}qsxܘwI#:NJ $mcԁG#htx٨Xv vf/#hW2#D9mF̱"0PrI"GS似8GH?xAK|zjF4x)|:zN;UПl#DZmq!fQ3-fv)lVLΧ*JaY][gVUZQw%f'.Wyz̪{6y|{i`OxbibffCzW lD&#J/a)ߜ/~B: 捄jX ECǜ)~IcjFFt5zɋ[kAVh*U[m\]4o%Mw yL1p 8!>dLqnb58U,䦔fRSޅ3#hTClKѻ`։d?-9 aNo-kCesPYոcQ3^C+s[gP>k0vtxBۍo䗔rHb;i14.ʎ`Q=EᲹPydJ#:^&RQWILD]BF. lVlIK8TGRo9z'Ifng' e4-.R*(0}FLwSz?,)r<8K4'2_N#Dȑw$ÜoE!ݖ{_lϣ4l#:MKx?*11U`Էq FcFm`)Ӕ*J؁r|E񰥔uEd"[N'SW엹Ta#:F?R#:0drR˱s1psÍɄ.x!w\0@,w.Mz i%c$|/ ܗȺFl|zk%]+0/iRTe#hƁ#:od aE%Cbb7(>lC*;O #D3ZJvjVFS]^;d._3 #/U $ݎ IlKlm_n&vfqU 37ʹfd`I2sYMXpoԱ8ڴ>=\zU#hR!$oťʬ/rjߟ D O~eG{ڼ5bew r7Ys9V\RT'3z!R{,?qL*W<xnQ1߶trgEIEKr'+8NM>_w]#h#L8fA(?8?KLmHWÔ#☏v֑Pngl:' {t-H#9uPyw=SRL42L~ذcn\mdc3 ).hL#D(ZppՄe08Zfj5UxJjLbg_aW؇II!#5)x6}]a]Z95 +}1@g5Rw<|X~*19bA׺aSIkOGgA(%&9?vI.sh9C@-1|:Zttf$Ü̜,廫5d3OFٸИm s;Ɨ$,K>V #h,聓QNI)Y(? Fvn:B2!J#:&:o^t]a +^\l^#m6߰ܦqzLi[&nfhy77㊅ u&hP d4Π׈hoo#ūgc+7O53e9sx>{ j*&ΎذC5e{5 k^qL|L׃L Wt V b`2<#D4~X$n$YYaY:rAovR7rFDW[sapg#L92Vʺ> AAINن$ʰ%EW$TJ\!P];͞yw9cz qNTPsϻK<$Hfrvh.Yd 8hDF}R%6B>;C1#:lC˵nIvHǂ-*l#Dgn#6n*+ j\0hGCEMn'nҷN[=;aNi?R僖it"Җ.3SuGNBN #D#H@:""?xԿb/s@a t ϢٱݹDh5쁪%p9fB}cs)u+=0Q257DD33{BC"*1yc=3(P#hfu2{##D B#ho+(SyRGyՍgJ#hl*"'|j^K5@/#:9@1ta6bE-^3Y^7œOaFVğhprR~2 y\KGYS2S25+,Tb'@R(Yaea12 2u5@Y}I1m>VchcɄ}]P>iR#D޵H/8^aCW#h5"Nd)ʀBeI#12IM43#hF˧2Iu آM"eW3tQA[v0ɿ,{N ffHn8܇E07Wʞ{w5ʼnIؽʡ&' qK2g(BBWF_ )z^lqD#D)oRTd!9Sr<T% {$%<}BO*Rv`N^FwA\B#DH]3|,:Tq#h,@H[H8EE"b5Iz&[pY lCOQ|G9Gs/0(0A#h#hܔkzkLhw[T6$׀ͶG8]#;}M:#:NX5RࠅO?&ǵGmj8DK-VH@bUQ}pG-D{6/kDo%ଌ'.7R@0M m#hlSxw>gJЍrxea=DtPh.:|?-YsЮO̻0Rv7Cn;fFQ[봕w1ƐOF~c 4AU c#:D+:i-7#hdV(vB0kPY-dXj[-'#h]7!/PHXX5+Fe>^/nL7h\`׶^aX皅 X+c!F4aGvÍC?.D(HcAȩy{]S"`#hfҮqMc-kj#:6tggg#h[?_Dr߬%kkgZK:#hbq1QHô`cc,#D̎sB#Va7̧Mzf'OMVp`:O;Q'%=ٰ!Hjwy޸|N1C0};>'aqكǕu'NS ;6Lup |3a~l!:ʲi6I*#:eL3͜8[Đ")pqm$a5b[ 2sDQm{"Pz#h.]Ze3Mt{gZFTt6JD@$=.#D'qkZ`>ԴƟY9)>#Dj;cd4R1`>`[iʗMQ탤%I0]ouJfo\Vƭ5V5=wfږAr[#D2)KG>Dp`E4Uf;Ue.FWWT2vu&!B칙ZW0^Jׅ.pi\X`Ut(o= QhJG5&~0?Vq#D5XmZojknjF@$#D6#DNޫx[;WTa#ARFS9"4.s:V'T.6|Dt4}w߰?J 1+|*b܅ؼzZoCSrCȣv7$[EuGq'<L3o0{zsBAfәuJ~\iګ(i>:I0ǜJv"Xw [lnpSLQq4havרg<0nJ3/܀h?⮌i-7sv<6򙆝;g VJ.n#Dm׿Yt1Ѷ̛ &T)KT a8 7d dm/lw|"1}1aqmBA/+m;ng󽂼<2tKCTcꍝʒz(X#t!#h)p[ ABIS$J@oS56RܯLf;D^U[ds#8u}'3WQ$H[=v^wlt+f%<|Y97#:TAS]փ5~Ie$(Pao BM*&Ɔ;Y˯|°9r#hMt%c6cvy12. y*e BkJG1#DRH' Y#Db\U 6r2#D28oZl 7IDk{;4uՐJ0K)y}WFgdIoTvovM3ߘ9"<.DKgJQk^Tvd71ܸ5:\kaEq eb b$y,#DW8;3nѱ,^*7mrU>u8}&s#h:$$~O}}{w%v'NWmaOm睶oϦ&Tɓ rP{p{#ѕ>uTe٦Z(S#jşT@&|hvپ6pn޷2F.YeEo)cȩ<"ԓ[#h ! @zG/y7LRkdfxLWXHj{]8]wV3 ~އ6=nM9$*ANPaJp:P9 M:z/G禼rT^ګ|"#D#Dۥ$0/89#(EWVǝ؇ SG]MQxܧRW+Tqx6HP#h}- HAŠғ!!zftɲ6εn@0*OZ*e&:v'R2J mLfJgL610 ߔx&lA?y `0f5Ј#DWe#D hhCCY `(ܷ4(\+YxλӪ@@b-< EXo#hc|O/^]s 9.Az'*37#hlY DdUJa>X Nvx6NJ4ڣCH{ uM)ר*X.EZ^Eph(\^&vAM 90'nω'޼=gamf8;pꞧ4|$#D0vɬmhS(>א=;~ݦNیxSxvGY+͋#:0QB[>s*sC}L*ש-"d{)WsVzP]n9NHrLs'_iw]ֶyGkcU|]ەɥqw'eLRhJ!`IݠE⢑ jJF<!EM=i75+>#02C  3gC4=lT8/P2tzG\Z G$Sg: q'V#7 pgq'I0ܿϴzDpUJM#DllfPnp3`~=~=gО#hCG#:{UѢ*;G4AڶMCL˔%{s_>M5{Lĕs ·GS18w)4'uzɲͷ/qiP#;=M'#:x(k݀贙Fzst^ʌC#h:oEW 2tR'X/ԢHcQw14.FbOw%V /Xlj`PXM!T_ gns{*_`q]mWts_Dfytq)a,` &lnVzC?;SbY,gB nlj5#DDlLun!ι#h`'day5tԬbhZ0TQHzT ]xd mafj4y#D}ox; *4 x\'EE 8 %S40r6f@92t2Jd#:;Q#DjOCKiB%{9L b\Rמ__!1VaOavRL$HsciE8#:Ιm1/D v{[i?:CdOs߉|lZwv7e|,<|ϟ^#DW,m-X2f:f#,Q]Hx6HWmN6si_䐟yDzB#h;=߃h͛#D#i>8оɝVЇ͞7AfWIEe+xYigGV(1t|_$68#DAB6<=4O+4lF=1~;IJ I SmdD2" %̖#D鶟˛u>Xh@tmÞ̝bPi, ck5kmpC2 rɫqьA] KZshjFIY!sL=ң'r8 P-ZLH&8[H8 (dL!sSaJ[l&`^.!1M'9#DRcVw΅y/xt1{Lp.&v*L[g u$ NTRdPdK,a稵څb(Lg7e%׵토wJܓGeZ)S8Grٰ9/\ϑ"UQߡl,rWE B^Q3r5,Y+E+6b8dbRy1:*͙IZ2嘁xajH"cgal;jh7ӪCEuGߙЍhs f9l񡲘rpGuP9/~zfAm؀2yX%q(Jv+*`[UҾ=y}c{8z̑Q~?%UqBg`9_ڱԅ]"{2g1LUxb}ELEvjwā,wM#hSnŝ,]S5݌#W{q"BCq68c)emϾ!003:v$Yiw&+D3gwie2g{1׀ QFhpԢEm-^q[)+uumm(#DecݞK/'fa˱͔sTg+鍜oSfs"Dn:wmICcavxGޗas@ [AgDOgp˔%삗#:a ֧sx+kϣiaiнg|TS/4{75go(e3p+nAza<@>2*)±IvEh'ePKÙ#hR؃K]͗զZzw]lc9b贙 Zڊ&^f%bgJ<WC#:Cϧ~ DX,Rn]~U21Vn[%ъ#hVς0o0r0/]xrE*+$l1!S#:5#2CWc&#(DZ?cvg+PRPlТja3M0rdB7I"DV]f-W#m,;8и`B#hk984#DŞfz՝vO#hH(e٬Jtf0bu>fL#DoRNΑKWuѴ#Q5LS^$ְyvֲ#hƒ\"Z#DEj4<06;Z }/QTs#hV#k', >N)!U4;m\^/ ڋyrBl#h[et[ctIo=פ3ґ8UB>>!ř6owS|#D6[È [M<-4yky uE'!AgCW,E#DbJN,au#DJ(QE&e;;f^VT#:;!,@@5i>RDkJXѫ|C}#\ &T"6e"&S(}E!S13[;鹧6AB0aC&aaAu䦯^/{ey_J\F*r.C=Etν9ATc/CDDMbOqNRӸ)LV'#D!"Sm#h|r#XNyҜr7x>=4JgSE>.i~ޮ.u ns ΰ:?;1r~D{3#e3]Ax#D_п#:F{Ax$b05ֶ" œH#D"Z\C" _.R^Ь!F麽n;'뵜Ἇ#:Q~c7<)6HK2HAtu/xs8v5㿡3Dxy#:쮵?9qDe1oO2A4{$"=EҌ7ic J1dDJfYgPu]N5-x qTMʽ*>p}3Z.eiX٭-'PGW{묠KXII:(4N|#D6S9@SmfaG1Tg:Ym ;ia=!?nx<RF37O9Lvv?_&WS#Da#h1i=IǓn.3DpAn_rVmB:,ML9 `\h>1pvKsҋӛҹ.]RFu=V<[O*Sv7 ,jg^e)[䟓#w6ƙu=gBۻ[=vɄroj%kmZ;NJNZx=ѰgYTMsG1@s~+C#O1ajvV5,9{r؃~c0hJzèd=0>5`ka%zw,#:;jWn7$1HM?PoH#D@vEEDUTV 7RoaSnAC8ۿ31fJ0\е{Hq}Cmcfnt oą>xj])|,3nEr-E&ͧicކ#DG5DOWuU^-`~+,^*H);ao=`O _NlCY m lji*S<ü㮹f-#MK-Zʏ#D;.'%8BdQr7A+ǒCmM:B &j̡I_vG1"8Pm[.]HRI_VX\i*/9v3s?.is\gFj#h)}/OC7]abmե6REyÄf.fszE,\: Q#K%=lj!(T?0F /BFѮ_I7Sυʭ~ iT>]7ݻ?ɽ pcpmˀ##hEn%wvC2Od_rDȣ5ڂۍi",N5Z!6a4vjS߄*ny^cߋ[I]~M<=V#:EGO4V2@YK#D*g[=՟GQd#:q4y#:Ͳ5KԂcg"Kt3`kP #DDJ:ib{9:MMH$#hI=Xll:L Ї.N;wl03scUHq.l{AngZ3tJ\Y Y [nHv ƹ+$'(p,b-1h4F(,(D2.񰧓41ƫr#Dc (Onu6<#11m)\o^WXlMAq]Û?Jؖt8sj&~{~]]q2}avMpۉhs%wV1=s`ѳc804*kl]g;+dP<8s蔣8^x5sPdaEA,`D`4ډXBA5oF%՞R&BӃQyE, yd.x$n7WҨF7p鑠 WEiÝNQC!񵽖 Y~;sv$##:u2/zɜM5!ZFiͿ'-b<ˊLjnqX -Y,#hoS S*ײ0(67)S6:(Am]yY,OM%0U{zcq]J.B⯴&R\[Fj){hTc~wK[|xi3aU8EnshP)4T݋G>+ࣴEԛ2#Dm~ cPUXBUM#: k#h<.|F_4ʷՄEyOAI9ٜMDUsZ|1q$\AD2cA6P$BZa6Ď.Z(ZF4oY U3r>iUhtK 2X^s_o]b61h&K#D.k? c~{E^;QY ?nnU8w{6)NncyS#h{mǃCu9r$@û9@iJNu-%M/]4Ab#:H6!+ۦED:#4iا''ĈXMMFwV , 1L_O5I3'u2u>F_&0Y^l˕)/Pa2c/eÃclg!;@x' R4o;>w|ϖ/I;ixėU.U~r|8X=4m6OMA E!o:gRjTx^,mӓzǰA*dN2֠W`TIpIl[N,lk5*fA<Rϛ#:N\ACtwq5?!V#h/oHs )!ch@;y^Cb)Bq=, 3X;|Q >nglzZ}z1hu7Iz9o9CJhzslArᶶF~Y(Vծ,gC˶!-`Ɋ7BUWY2COl:F.m!a%GYYϡ aJ*<:0#hG#:H%u %o.u&GliaiNqZਝ#hbc9Vn*g&hq5~Rp"q9I9is>g,Ĉb9F2I0VKYgОZ\M؉A3!fzeYah"F$KLG_WŌR#D Z:ɪ\X^aeN/Cxڔ"cb AICy1yN:n}'hsZ;bui;/;bt*'w[DL(|r *_ 1mD4k<-;;##hk'dm-#veFٞƖtW4޶m֧6O3{eÑFx 1#Cxld^/g;яcbƣX5X$`q*|dJy:Ee*xuڍ{۶#h'>SmF4LO.!vvr#J.٘Src氫D&J&gwq#hԂ[4{k#D#DYIE*񋜲ζƋ`]?ra >< w'.xC^&0ἔ9,ׯ@yt(\J?#T+.A{ڨC iU#h9S)͠0d.K^~L^M\6X1.: L .t34Jr7e?ڰۼTDqIXQ#$(HtӧMa8咑*%Co*7}Gsk>W*6l^R!ЄmWַ7]N# IdV`_l;So[c.2CPf/PމBtmT:4K-t=>X g|o.Қ\rdN˧wiOOJ;8|)~tt3yLȄ̫^ATWWGK6WE|$|Q+״4C%b=C}s1Š#DpfC-Fb+"kغ`("%I,sǔNkקM=dՋC }*#D}auɒhb=g!$ <|D`ɳ-Wy$C#:쑝7h(ɭ#D悝q8:/O<-\#f#hȤdWBC/ !/zu{A]QCxḁ+>M3s!ZE! I#hۍ,!ޔ|v Z]uێ3&b'Q!ถbHmY%x^~=/c^q!߁#f;y5F %d-0e1eBB ;L;]@#:௠aDy5.UYǺc-nks(>ynfEW!ͰC\3)+a<=d\f[l\ #Dp2Gon: )dzkN1ŦJa;DĖ'bXXtR/r9\??;W0e])0H]Ii3:Uxspǻl=#DuV^MOM_ƃ|P4c3-(n/y$_$K@,BT]Q`n!t#D@.ۙEZ#D{ӺuoߒhZP$H(b瀇?҇>Kxq ߨ#DGWhԵ<%_sk=|9~s_ IW-(5>%hP8vxY R%=9!(Qy;SRh՘^똡}7w#,]ʜ3g {FVeC=Ρ=%gZC#h6=WkC=Oo\e&:n~wro{>Y}!uE|ne8l/km>Л "rM_JAѐG\a68wC"#]/fpTTa-F8I˝=w#:2s5"zLq9wcg<\e?5f-O6^(seJo #hQv`391붉 AELZ*h( 1GXZK2!Sy#:8ͅp{_ONOxva#FG(Ѿe#hR̈́ MLL%")LW s{|4'D0i<-F1#D1}#:hҌvb53C{S=6AoPތyƌ#hāb 33DzbAuP/L{9uM!mn=MǖCh[zxYr񈖱_R6:y1^5ξk3ӵ7`6aX2 !yNo}/;p1wx ًE*;GA!-P"9iiy$퟈{}{ #:8h59}Bsv|63#D4NrR[\zU1#: 9#h|G)`X)2ÏTE]C#:"E?E)'h?"{a?. Q8X#:a&I a?^ p}P2]K2e?#hq @9ؐG<RVgWs&Đ5(p4[pqsȲ/'GHhQ'Ѥ(w}(sI)0:8 0a$''5(8M0/wp*i*0gL܇.6sa'pYRtFw}&V\M]W(ŽL[&>)=T)g&|׌gTe$⬯ оM"#iMI#IRDfњlZ-6q(O(i"* gtSZu) Zxn?-Ew?σD|]<PQNw&B ctC>+S/#H(t%(9?Wo%`P)i+IL Lo6dHib?#RYJ6lᐗ%:AdSm۩sm톴1 %0@UU$:,Pa.y4wM2dmjcWŻ98o[P1loE#h&1"Zf"1rO\,eJ5!#o H8Pp8$auek4g3(FD VN*Pa㋽cю((_n#h$W\4H ҷ#/^5mhmRE)ȾY |o R𢤿{cאԛf*8,mK[mXLuT3Yz2KiʶVyɘك1R=i!ZE;L#hB1ZػflhoRm;bN#hҭ.RPyA#ew^k¹tшrA+l`Z܁ch퐤"=| dU!]wp衲R^oO?P~#D-H<ukP'f"XՠtSVrֽhi8A#DÖd<#:>ᯀ h#:#:2?FٹBq`RA}NS6Mb6/Jq_#:1\3K0)փߊ$1f7pݥS<&@y@0}H U?(x}!qpM%HM)c#:`}NMM_9X(%3\#S "_q)T]|IjtT8x##0PĞ9nxdgU،N"-B[0TJ׻ _q~me#l?UŢ͉:5'O( ,#Dō>= k|Lgn8K_O|7% XgmK ?4T]6܃s8[iax8S#D!Lҫ#DX܊k-xz/bJ]L[xfL~{D*?meHj⩙AQg.Aq)57]5]/}DhB&~7rC{3uͳb~יn6?O+zD#:<_0Z4lI)<("cŘds3?F}~,BANx#4y'nd?Gui&ޏ4ѻavsyr_s!O/䛮\CӺӾHWq~սD?NʆuNF8)zgPh q1MjJMI#dvNea)#:{PaB+PDUH #hT)LrVu"AD*RV)xMR ?"F؈#\A;#Dr JJR=c,bd!Bؙ5@OÊ>lÒ#ɂqNI2#:rڿg=s:nz>>4>ZgcVWߝO}:G}|e?>Ixp |]\VNN {-z RES绛.5h4'wOAl6£ϯaT4W-߷n89~R QY;oJjUyX_8ڳgzmMbqK#Dt!_!KՆSٺq^ê,rv?ۗ#DJ<}Z9a(;WǓ^/&C\m?ueϒ:w.O7^_t?G=rpw?ѲO`<ˮ={W/Dyڽ;UU'aݦaAz7r#Dz!t&L0on<=.Wu{5n_#DqG]<9r*+o^>]vO>73rz38f=w{Qi!.@͸"zO{[4y!˪lgkК=&؎_mRDʞPk\6y٨Wd.H1ٶ=];?>ϛ>:j&?~Z˟QrHkC`yFWm KzK/5ض2~wj{ ǘ]k#?;s?ɳӶFsv3?#D7^esjoݣKWDpPw3R=t "9uw"ywŔ9^V4?G>W\);ŐsmhN^wsv#D5y=?+<#DiGK?hFuzxt ϡ~z˭Dvh-|?ї[#D(|v|G]e%ɴe®jg#hep#hyn{K@F}}_U OtO#h#hB )v%%~Wv~&}i<_cvB3,8+~˳ޚ1A/׫>gfkp`!/<^/w3{/ܤUOD>]/;~x~)֨Av䨂F*T1O5QF =qP1KI3$֪`Lp#D_^ZXxrOr/pˌ׳Vjj?ˁ/1H4ٓdehvHR$:GD@Y*g;y%!+%_|cLoNy^<1c騇]G>}z%i=>vX@ӝ`TWyf+FI9^qs]6N^}0iw~̰-hp#ha ;ĥz@E:&hPn#DEBdi*3,À> i~Gcnչ)RC{)vBDhӜɺ9)vFtl-G+؃_v^n0Fͣk'b&m`''/闭׺:U_[< yNCSޫ9O( 9.3.W`(>vk%Q21 $W>w'>{gp#Dd:*.u?#(9GHTF 8Fl ^y2e.ϫmSw+]nS-əUћg)#'o??Gj:MǾ_yOxyx:1T{S4MON>z;2F"B1#D* v E]+cM*<_YO xj"ݨO9.[">$l*|do~#DDx7puDi/'=#Î:KZ'U;d #D8k\9:?+zoj=7w?QpM2Hdi$EjCI=ݛ"^CnvDgcDrAhp`#DZBA5IW[DHGWB2g*nYHF4a` jfKӱM&z˗j;*#DPR4/]#DF")*X18"X] ۵nQ;d): M(;znn~ٚ8&?P׌4%tz2]o]{D*dW7B?ox>7󻛂D0H)]F 7,bK+55rw}zS|]#:(P(z']~?/˕ɫ[]~OXdzS'舼m_-%)`os/g_?:z߷rZ~4xW9vo<=0M?!6kuwm1|\}/1;\B\ NbI#Do&RUS(?Wf&Ta4J" S_,B?r"!,ˡ[G9+zNctI۔Hp2&#A1##:XTŤ"'\9o]t)#he#D6^UqV[k42Xicَj(Sc#Pi(lf{ni 4#:qKt۩e[;EZ#h",ltwBd.3`7ˆEF RUEDwmۢ4 Ljk!ZL2K l*b8ze!'l6ڦ !#G+m,;#hT!d*z4E""#:I@6Q6eRꚩCM0PB/7^9o>R_?{cQ~g#h=l/Pz<#}i>>Cۡ~_nzK;Ѿr8 U 󎢛#h#8s ͎a᷃R>} 6HY7iFq CхO\zG>/e3݆ǣa:ֽpN3B`8VD_y٭z#DK&Fᯅ nYܘLTR =/oSk$4ծcp|KMa(!M8n}< 0D<-2Wqjw 4$wzMZt0p(֣?>мG7`|?N^"o\KN@j6A>S "FFaA8,"(FT-o7IM$Z˝:nK]h?Kr-#D%(Fܔ#D6)!jDʤBFh G]Ʉ^ ha H##D=H-0e30?\xȆ10۽ DC17ww#:]Q_~wܝ`}c48AIN#h=(,iK46 #qZ*X PѱQICWN]'urL]˧';7.qK#mEG<@a#r<>?ylhR4ݲ2XX0شEs;~o'swplW=;saQAƢcFޘ$V޹ڂŊ#h{+5\o3{feVgsЛVy(q𘳀#h6,}Ͷ.ŵ &F܁BD9[@١8lJIm<@ !$< H'3;`s,La1(yьLP7^UٙMFIeiL|uWFB;:A6W`t9Z2{\#:- Vgy)T Xn^1,J$0ynqx<+=rs #`sP\ĦVo)F0Pa2n[mvUa/M,Lx(D3\ɝ)c&@M#:A2= ߢd׌+`CJ8)}ASQ+@{ۿ^si$Lq5 >zL+,8rNE=ݡ5*\J"N':I'_ᝥ36Q{w'LԟӰw뗣8a2G P (:SC2k#Bf:h_,%FfOa\o|_:i[Wlᬾl,˛?gIUY,L k7#y7gNǀf[Xө7r2^g&Ѽ!Dƣ|yiC^e 6v#IM aKã.bbX)b*Y |b NVz'}c˖{Z.ϣ=_mߡxYo8Lة&Jg)Xk5$\JDfNr}xqǼT{RBG$s3Xv7{~}vԛ; 4"{&dMo4v-X*OyGG#h̸08qOCP~O&!ETHcVHPOX4#hQ&”sR@,.T-*e=^Ux5pC#hʑ[#DKulMt.*;W{#PV,}^\'~TQaTV .ޒH!{>A#h£FGbȚ@ˈs#:ryJvu+ Jū)usU3V9C Ѹ#:Ȏ#:]人iֿ޿|׻I0U-GRnlGyb;Z0H4&_65#Da(ܺĻLRR,0n&`#:$;#hS(%,Gn.I(b'ALQҔI>8AN~%G}=X#DEWpM;&*gQWBQh An|žc *Aw_4^e$'m.P!JUIhJ|O;=)ثžp\Gۏn.ݼ#fsO6ѻxc%1i9d02 GU ŌwhCH&=N7ςMC6g}PݔTܘs w ;Y?}X3n뼜>]`2q>wYOmI+Փu ,3?P@6\j,6#hm~E4%Vtb纂QPJ 1(4eX&d{Ly#{RvH C:r̢օ͘#.:K1 ?!|sLɗ- E7q`CLe2]r= Nf}KB19ĉ azT)`&C>o&W\Nv~hg)gl5'h?Z#hluCNVL#O; q ??}-0ssR'aݗIhVx;q#DTԣ_.ۓvZsW !an.:kEc'kh0-q[c1T<9TfͨqѭXFL]ϙO/Nyw0bI6^ ,Z#DMwsu-J +x:y"KH[N*ފc| [3Ǭy>;sS/"QziIaN27_߅FzӧH[6 ড়<30VB]/#h!V&Qc4)4|$fF3@#Dp$B*F֤`J"ȋMs,Hr/P{c$j2, duKTQ JفT[>#DIۣ{ #h+tzJ6}#h'Jo îzn* UXdr f5d^," ފe6}2T !#:(FYX q#DEtO)[zeseꫥEQV]8NuQXJ,(5RIV#np_Å$TC]M$8c|- #z%PSW(4\L ux|Ss;T=< nu42[p.N6IE)t8n0wPifҔsph=LGo[FeN 0ϑ^L|dXqN|Wtq`uپ{#D7(?&~[ UGF4Lx2:nό>LV-RwBU`{+)6Wdk=<g[#hivh+Z: \W#lJ“Wk}LyeZ^r'̴W}U`M|xSx]w@b "xg E@wVs$$+c#Ktur{F#h[MtzG*J0^hφ7*ra'[5}gMS?qZXXO]fjw V~cd4?pD_k#h{q~u({ _/S0d|X,4VP :SC>$<'Wj[}gFOZluCn&}V$nyö I#hie"E>ɗYNhr7I֢IΞFϰN)oϙj q)BiΧk( ۞DZQnڈ6%pN\RR/Maj};g+<)XnҌj(%%K<(B;4 ,rJɤ$5% ߯rZB|J[b8L#D#hg{mԕG{pm/ѽA#D$(罴RYOծB%|\XL0?ag(l朒bKaHl/^\+7% VOEڈ'{+%MWϕGg2}^ll2_VoF_?RU0ØlۊYy]k;. ܣT;uǜc~9TcٍwY;ݔV`5wjض'RlzG">4wM.2K#DAdõ#,is8]iメ.!QUbPDVjU~? ѮLvH?{R>ҏy}u:8wuSA{#h:E>}Y\͜g(ghhXC>pu6\7OmԬ'#DxAblϼ5.1ٲ[fuZ#D8eׂIbtc(kN'cX`3̗+|*DY\!}煍:n{بB8V42κr#D`8ܭ[C@w߱eGuU[;㪪pލ1F6#VLe悛Znr_& DM$@ԣUۣ<+}-_qֱ23&vΥ(ٶ.+@4MrKL Br.#DJɖ,<)u6\^%`s2t'HDaݖ,ҤNدG;}!L|9;[vz~9t(D(5s Qtf1;˛P,:{{:˷,G%Ɯwϗz&dž@'f^9/i3:GW<%ne5,#DنeW[↑o׀4M#P@<)h]"w;殜qJ#De5(E˹嘙"NL/q a++f,WT|#DJɮ!: wLݯCxtx~A#hk\G\H7 AM#:p)/]<` Ӄ8(&s,G%NCmwFôgG3 QCo<@+*|#fnuތoO8E9,\OҁRV!=g}&zb<{FvY6|LE[,&q˓GXQvR5W<^B#h.J%HYj]OA/dF[3ȍJ9 u*=WGxTa5wU< wj.]F$({Ů#Df<6*8kw :=ɽ]6vݖT [qc͝v^^/-3#h+>At9%,l7芈+=C{ |vV6E{s鱅䫝%7q3al 8615#b(?I;Ue0dOBFSߑj#x눹#D'QԉvS+XNIkZޝtp8\@:S#D[V_L^Ihm*]. '"r}Lwu3ְWXuSCQ1)Pƺծm*Ry]M o֐b[f@|fA uW:=;4k:SN3L8foR8{"x*Gm`]Ê*QA#hіo'z#D_(ϑvJgZSI~kY<#:䏀PCrXNHxVv |:l䔇N0)?Dg9:yj׺YxY(fS\>:>{wF-68kJ7޼9nӛ~pN gw8oȀ0Ɋ܇XG:L8G98Ӗ-T`#:)f,Ni'#DRLGMvVS3>j;U4TlUNw>55+1D@8>;A]yR7=j8Ih6DObd[{#h͍Ӈl@.y#DUPH,K}vH~#ȞO;D͝˜&F.Q..Ҋj.۵sjVҍKs^h]b6fC͹zkya""nh,it73 MAG9}vZJ]Dcvjib~ɧDtr$Pj#hnRk%5F@m6[ef-9`p1B% a'Ӟ#hNDYWپ-|g^YU;bUgBp5ծ @sx`G'IŸ)uws(ty#NDm;G_)F؊o\K{"}Rp@> N&/tm2JIXc19ip۲x(P3XƤZW S9=CLĪNvI?=fnڴ_z0ZQbdٴ:Ͱ'Z#^G=vF}|&Z49'$gG iN8I9Wv_cҪ}/'.;'f#b-&@l\ A̔+#:6+ؽ07}~BOo*U7wJprNA&& VnFEEu>io kk%B9q'܅|rhKmCh6rB예چ޳rZ|A-M7ÍŻa ִ&Oi30)geWqϳ9o\Qhw)v[#hDĘ~4QO>v=\x؆㯻7ρ[N>Mrz ;[Mo*xŸjU$%JŷTL~&Q%ƤnP8=`+Y%D7akn]ѱlx&wOnيq5/q3P.^)xx#:f 7ٳez3#:~_M8b[=l<W0C>#.{]xzo_?ٓ */PxAPi%ϭj7΋$#DʶNN _ &Abo8WzՁ$R_~"?u4;!sC:#hԣޔOeR &,?___fLl&>ɣw($?|ýy~@9=JR*CߨkCɷ^x|˟wDrD;v|\vD6iR/ C5hpZ l>յߧx?\4l}8>#:VJ0nߍz>Gy'YCT7$fFf"OQܒA'}yk5EwiDRj@s\ϩOlj9U#DE0#D_ 8<%eQ*R(J(z&@ޅ:#h!4$w wEZ#Zmfb+i5fֽ=nӌ.vvC0ǫ.#D:٭]ʿ.OzETU{{H 9@u= Ox͜}.@ @#DDm/]19exՄEWbS5\f=FmU3U;NO_ `UI^|ij$> +*MvمƇAgp|qѥyDG/9Ws>Sx於\uV˫U`Uo.59;܇DŽlbwonًG{#:zFNCK*J U]׬J@RM;0RwOۇGo@"?dtG^X`3_>뒐#DY%[5PMI׸4rSz}c\9O sJ HTOĄRA_L( sT?[F.l>U/oej!~ڧ޷ZOX[s^~=17&_GȎ3MOuZ*]bdqaSFZH[រM7#hY#DN9aM4މw 0W::˷b{rqiѐׇ]18Ոz2`L.u.j&כ'$^o iwVNhxG?]O#DBgCgw֗lUnK.n_ݘ5fh@R]č̱#:AC7w}SᦪJEv_}]߬Ps(~ \n%DBu,BZ}3kZB@('E]==|]+ZI"iZje=&cs}IC͡#:8r" j[x]5_2uٿo#:^6Lm5~\y]y)?=.&sf!*Tޯ)2 #h^͢#:c'Q#DPQFF( >,P~ .<=,?n#D@7p@((H0e+h#hU>zt07_Sܾ3G9[V!V`IYN'cYJ0 _Q#:ReĞ#:Iiߘ#Do[#hڞׯ4OXRm#Dq$x6899~0#X!CPD h^5cq|˻t>yOF/"ݼc[b!HL+O.NN[*U/#:/_-GDy|)Q"XX^żDG^.GN,x-P}AenD/pPu\Q3ݟ7@Wk{Y#D0Nʽ _07A!~ 갤ge^+ 0lhwSjGP֡_+Q4~˖{9 yt^gb"W.hS#Dݽ%$RPeE\nPױy5b'y3^cbf+:ntlH\yK|+^?06 M#:*or84$8^vY>,P~cڭq/s:sD%Fc~Dk#hZ="=}3a-kY9dϪf{xIS#hG3xLoG RG ==һ /L#hIn(wq&iޣJǝm[p>wY ۇ$#DtO}6"\C2H#ק`rZC-raDg"BwU]ù_ج<HZXi#hCO}xCxC.&vص763miyɊVxԢP9':t;A~K쬐UË&fvǔ#h\}?%`,s(ǝi#D~V6 <4SP>-q)uzy1'>um^=ǑS?/_iG^Xx}6Q7#Dѓ%~(n0<yAA24!iMvkfDuC]nl"^2rbh-aSI# (Bigb[Yʟ 1}Wٝo,ꗵ(`3:(w28~S3<2$ 'E#hUz]K4r!wF2:=A dO@ˎsμ:iY6#D}ռݠnGs@ǡb!owh<Ͼ_0'={)I:rpPI#Q Peq|@T(2Q@$q. 0şdGu#D.1Tc-P(-=xu#h/ͺۼwr1?MPsC_'a>\TQaP@!P#D+S (VG#se[$ܾ~zv ?rDB_rJjϛ[>QUU Q'Ȇ06JDF B -$GO+IAA" !ש&İփ~?֠s#v`^8ّu࿵'z$A'<́V; $LYGC_lXq?pz\R3i>,Ih㹳#:WP[aNFP{_9kRdX\};NQ#x&kz ERUQTq|3gFK#:`PPOl9yk`3?7A#DlQV!!sTv<lFmqp!DwFio8Gp ́27 HpG/`X]ԟ.8NRCw:ѻeGvZ)JBJ6O[^ڄEp"@~:G]9;:(9e&5WBr_fqҞ")%oW8VN.3͢eyL"Ŝ1j0Z+Ԍ Fr)#IDǿ<ݧТ#nlʧT)3Qo'|\brmS0#Dl[ޔl)Z9 nbK5nijJJ|Jci˾{i{|^sU ʗӝ:vj]u?/X0ՊF-^ #D7Z?as8z+f/JIO#hHe$cmBQA%_s;#:@_֭Ks=#htPG'8`)N2Xe[^gTYeV*(4A5TRbtW]fp:^Vp#:1H2,#:K#:t_uxCߓUQ&T<ZDx$UԨvgca֫947 #hnGL>3" DNvSzTy:=ƃң32eةSp!@h4zt:tBO01thh!]T ?\ i;#D/*B薒0)0]|m#ܣrv! pgNpyyp: y%Bdw&oؓMIRتI!D9җܠ,5(5r?f%TRfpT#h2y~*n~|R`ϭP82%Ī mi UѬa.S@>;9C=pk6lS {>_Orc!,^F3̮q3/⡣sM2iRNKw{r\k4~9'^eN </5ѹ40B02\b1 p'RҴƳ5`3\YlЛ{ҠeH5T6X2b8ɠemvq_k"erJʿ%\0-@X` Kܗ3&튃b,`aM/au) 3ՄL0U5b˪$$4fC(yK]Shz0}6͇hu(Ù4؆iQ.|@e9~,hE$!.r֞lQkI^mRq)D֏Jq mG&E'tb>|/(?&~}~2Ð 'WM[%$]l%3J*)?aX I m$>Lfs$*X|IΜqRq#h82j׎V#DvC\$NG»!ۼBGn#D 2$I99/i#DH|wl!>e2B,&58_]׮QYd*( .w"_1DA &#h:~ fm{020v@=$P5!!DK]xNv#hɞa7#ޒoq#D wٺB"P{(=L#DM^,$g(#:zG~q5%N,(@H{SK)48>>n)Nixϊ)q=!3պZkB܈f'TmpMuV|_9(N{#)}IǮó 3#>ys>Vt;X!>P{#@жfofn-k rĴj=ZRsMCBY9 j #DCa)%hL#2CP a+F%BmWw_96F#hb%`;p>ew8]=FA k$E(i6ou+/;6FLiѮDE1Nฐrx$X{N>m*b!_e@ׇbV=:s9=HҎ~x-Pĥ*̃FCKp,QRo~;dq:gi)~'CIgq476ɜVW 7&2%,LP. d(QwVw[< oYLl}|9!Mvp#hpu!rTHBE{IL<Ԯ:Nar]K|؈ f3h"<ڌƒ:$4 5K僙s5ݑ"HC߆yQw-It}; iv9:ͷ=ҞIls)6cwY1FH. M" ?2ޖc㓖TYu.#D'{ЈrF!  HDY<#DSe^URk2u hYh(1?"aK|,aLpS?ήМhh-#sMa1#h[>0"yo'Pwv޲P:i{we'o߾7$lM{䖯hCrtFq6 ت7$p40lH~Q86-[aPd!"H=t5='8Z&N96rDŽOG8]Go#h&@Nyw Dk<+M7OGd?,HpGx@;3J@c՛G]eE3WJ(0m#:VmTi{̉Koxyw#DFV.;UܑWsug @ݗ+s>rO#D i0f>qU9&繳`}!l%BWcۋo3+~Q`ؑAT='0~ hukuT#DgO#D+NELYW-f{T ؠ@9=E.XoA==.z@v_IyUO7/v#:hh|OvG>~a#DAOߒVśsܖٸWŇSӄ]=|5ę އ*mԗQEV$QI BCߛ$yY10qzm&U 4WϚ"@51ΐ}u5 B?8P9=pṇ0DD{eA;4{Wܕ\N$a;FC1Dz|}$yi|}}yɜy!I#ha#:&#ůMaLl:в@d08:uN؊G/Ya'A*{߿{;2|O͹]"R "hB&UHջE}:2¨r|H{Phv$_Y?~w,q ADfhV#D+Ek{}u?_ѽ 7*ٻ!pqT(fvvYK՟~R)7Zc",#h9+z; $#hRWO 2?3a}<3n|9@bQ@_~=#h#?W*u12sdx4_yZjNNGS}i]G$@HP~~>N] 4,>Ȫޢə4MP컙毝Vy8V?CGxw6\f~cpseN{>@N3B=!u=Fť˹_Q9K:'G}?,qk$ @MJ30&Dҫ!5afY &-5'Lպ'#6N3#:ϵáy>y0F>(<}|deSC;-v~_5&xbB b",dž`P1&i΃?Bw'ALN%R>BJP<"V'_ګJQ|GUpt.Q$#h'p?w;)D/|=)#:@#D%Or o'{ʮPCL87u 6B<%>x8(#hH^뜤9Bpm5D1< | ]*XT(A빡NЄU*?gC ;= !=qV39$ퟺD"'"#:փ6=\QP>{"= nE$ϧnO݅>>}TTHC /fcK"yao@O945Onߑ #h PSvC3Cü/eUSIV}cv!(C0D }ʉ;[l7;>Р|xf.{ ;c&wF tzTD,d~/a"(.?5,)滁mN `p-VWřl*̸n#DU,+̝pփzV,N C6mQ|oյf#hC `'U.[Fcl5s/#D}d:~)?*Xr]apΙס1#DH#:א%佃EL,qT#hbXܸY'&ڧ}qa:4#hvKoL`wr߷|SIMK#hxMs׫V[,ĄfGe}~]<#h5[_7U"MBi{?y8:+%yA/fb4Hr{}ېGz=WEjpgL#ڂPj#DCl].="6%f@"z0HK٤Rl.؍PLLĊm{kE7Ӓ183#:(;MZ0͈VrC6#D˸CŒ?a;)H ZEQ#hlIeB<`P2B \n0R2cW=iCɷwk9"/>'Tn#h%ax7mu9xJr#hxhFҚGw qr b$Hgl7Tup׈04cd췀V.6 #Pw˱Gܽm&0f##D"Pq#: 5#h7T]5LAQ^pե$T /nWڲ^aƳ(gcHPhB~b^3bSWE][=&"q&S")HfBԚKj8k^m~U#x|~U{ZgdBw se ڑJ^jlG,.4)9 hDy;wS2?OTwXA泟d_ 97CKwsřͼ?\PZu^DzgsFҹ0ܮ`:3M'y'tݓ3o{j:W^ؽfv4UWZ:enV7.e(M!'Z#D~#hao"lsbD;Nk2: F/|8$9*_5tC{` z#DϤU91;>317G|KzIOyCxI 2"f 372ym(^1lCq;JW^`X[۷ɀzl[C=9bi'w[]vV#:r~1UVML׎,1kQ a\ M*C<#D%xX5O6Ay`Yu[&^?)¥qVk)*fѷ{_ oMHE5֏eGw%X%9Q|M68lvɄ5{iL3tm']^7"5>}̌v'b8wq?#Dc?%j߷68jLs{?^:selNBo^j)ufvX t&c?5 hSN$St~K"}Cm۳b'v?oΟz:rTPǥ~dv#hid5޼tp?n^Jڃkse6 kcbu:͍mF1Kfc'&Lm^ܢZ-BG6ԆyU*&AQ KtעsW"ɍ;h*#: ڐV(kD2B,ty@3gh͏|scccHɃ&=ی6wT ɖ0&/C;!ikJ>,?yf#D\9}'2$RUM`nhgV]pE ->^ABiKXǡTXƫU#hP#hoE`!}Rq{w(#1ʑY2u+oHvOwZiwvf50p8?iŘ#51!=LW/*]f~o-խ@*;aSq;2|Wӭ|&+4ع|_RN+̢cJK01!H!\s@Bd>8'fjh$ ]iC2leIS0kxukć$A#-U?чrRfo!4d<5$4Q?+.PJo/^w|& ;o>(f"v-uh?_~8S|c_(*ёiCR~ Eyl[̵h<0Rks` "!/ ҃@Qr4q*&{1ȣ/Xh' ۢc IJW6zzK{nj5oIP%H>Ɉ@[pD#:r+bLIL#:}zB #:ҁJ#ϭ ZhӒQ$V#h(MC$ֿaM¨ " 8yo`49z#D9܄<js0uYp$8ٿ|#:9wmM&L+ScDj̀Ĝ_/xCCáO"wBUtCgLh*} ynPB΃@Q#hAd򫣏_~Xgi>g#:a'O`7*hd8@٣h,zxt"SI?JT ЖCU;$5QD(8'2GAC|2(^$?܊̹ӅfGk߈'U'Ps>|yZ (.NFf788f5YexΡ6; 1ËA%nfzXВ2axFicr@(o$#pAA>?UВ? PH '5%6.10WP:L#:Xfјᯣݮs纥kBJH#DI#hIDPa깟nly]srcf{ 2)ZP =%GJjfʚmvݫ;Ӌ*7I LEd}O>2ސXv#r3Z8bKXPA(*gpFH%9o-FU( I@yzq ":'xA"#:rG @ġ5%#hDk,#(bSՊҎI7#DR!SP#:u)D§DN7|XHRo#:h3rABu Īx!ELC#:L 0C]MP G_~4, UoYzjS8tFlT{~zp?.9,\Kؒ 2!}:8e՗PvpPVjЩn>IdRa7`Owcx;#D]O9#D$ D4eE4 *Bw؝~~b{gcd8uiӮ6]QmxrI"syP" G#:`hz!F1ϳ3K~_Y(}2Vp5glpR %Zj`\J\l?X9Ǿ;QmJ4*Di5pe)℉ĺ8-j+N. P[a79Q.$pGW.N[Z;Tun3s$ 8=T):ad>l6Y17͇;~nɆOXb"1a pIv`u}#D< o,σ)݋cimVQ 33X^)εvAh6%eu$cGt_OjVj c&!/*1-O_/(Ѫ:~5L$Vu!#,q㕜(S!pw7ݴSP>; B:!0yߎB/·LH9*3@Y'K8s_?ޠ2/{Dx'-#:O~_~=}v\pH[v3!poJZL\z?`dQ*,ku#D#;O^O0T7D^y % *a/ICOaB\SG#D|^.ULc"XG?/}P3_x1B }_歡i .cgײW:Zֻ!- BG4HzzR~\2AC$CYs#Dl{mXf21#۷7-{|GSǿ/&\<çda-_#v[ddcv=ͻ6#:#:0Ay5OO|Vhr30e#h(Wh㸰}]؟R#>%]0\rp?>4{X;pcIҏOhb[bH֪tpo#xfR \M'E|EA#:NIS?ؕFIhO>{ _j`D ߤC>9N,Sdn8X8Y2̛twӾC3`6RRHc?dwcЧ˪G'W!2 HFf\2?)|2$V#h$ j`d#h2,#DR`JLypf P A[\ ֘P9$dˀQƎStYC&yГÈ=enfۜq~ta#:ɾg*prV" BANU(@VOAJoAD%3}i_ßdtqujaѯIt2),k, jh#&! mRS#De˅|Z)UIb`fK ڿxzxQ{{n2QX'>VNiItcOv58#h:dbwyЉEy ooD ¡vU˦ru I&d"?O`I{+wfmg1m voP9}1(<κ̜TΔV`=h7Npq 4CMl0CPCK5Az5JP)69N cՍH>ϟQ&laŒb`%QB_roɃQjq+?>6֥?.@M0~_ht (0XK?R?=6\0OleѤgC ճRBw(`1sC/XYhI;`DT*W;--֧/8#hB$//X O|fdaBt]db@yQ,Pr '-5 t\%EKLjY˶YXXRD:(9Xx?f#:(d8^Z:95}|&+[lh}5_u}0qF\'y?wvS>G|y(vHXi9"=\`3tx0$lN@#hC5P(~7_Rﳨcq"5z6}wH~(zP} D=^pX(=u'W)5(7uh)ub0iB #D.;'O <*v!ӭ,hm*ZMkz[|f@#h'S0Y7ceУShS1jS#D@0$@bpQ-Te'p+Aw#hkO/.Ͱ$, wI$V.!q XyAdqCk#H@v  ŵ + L*S/C.'cnųݯ1#Dv#:FVQM~?|CF#h3”4?qYc?wp`d7`//CSTI徦lW2[UTDD#D.K=|PTYZ3Y?fm f#DKb3ijjbqǛ#:'A^;u{a9sL(\L9II=#h726B'/50 $]'")ȱ@;@h9c9 uAr"&2H@P%#] y+?yd#!?HiP=(v꾠prO?z ׳Z, vAm7#E&$l4bR -7,ɍ|_?HͼamVMb&X-@Ÿ.Fov |c_Y2Yy3'wqƣՔora֫a[4RpndqV8uaM6uI$7Q8&MN2Jv#hGкT.g˽Vrrzc#:؁6|#4 [=@!!*qWUo-hv5=^]4{b G=T9|!d#:>HT$5vxO\9i4 zv6ʞ̹+:`IQCcO_ "H+A7)$u~0 &#VA=_X{z?h _ lZkSč{X~$[ Ag#آ`rA >5?Wj*M0RѨAP&r>O}ȘWLCLh})]2AQ6 󠿩~tH%<^A.bJz$uE??Iaωa~!t_FiL3} s7kuqK#h$?R#:8I#:|9.yk $}Mn>ZЙ'8wyA#΢,0"D1真aUq" >Ԩ䉅2}czۃр$c'mO2tw9 n]Gѭ{ީV&":.-ݜiMM@laG#hUSQ #:פU;TN{;#hY`ވl""XPK9s춘*c-#:9!Yl'w>l-6يuXpiW)s@Ҵ#D~=(a#hȦ?bTp0QuB5\\QlKKGR}3$?4u>pPyPzI5NDI! N\§D6 w;79D|-O^"9b*#h#:(.ju5݂|tЙL3Lpk]rJC/ځ19/hS'F_ZeյMM%ƕ5rBRKT$i~u>+b~=G #hI0P'}c$Nf#DrAmUmr`>d1TbYf9v:b5؁̄gGn*QRv>?`iwN X&:T1}OS=]Vh ׂp6m!%t#(B[Bb{ls߂|#W(?q0xj4C䒁  ~_=Oϩ*!Ow{>=A/ȳ )$ +bDJ($e@?+LG<פxA$)|(u(U*$#:Pk6M2ꊝu3coO1H,o?|b CO{>ԄBx!9w&t$YboĆ5:|Y C`!65s~zt32d=7!2BwF$#h@iXn^Xo{3!$&6d'm*>u Ll/oAIEp־Ot:(FgKz_OuyC;y6A$|P쌉(ŒKd!ǹ]H=m` pg#D!&ĢDĊsWS>d[#:~g*QXLυ˼뾼 { # WWi {`ʰ@>!UURrv3@y0N$Ȉ)+j^GA0e4ta3ՍJ~ahU3%9f $&O`}zpKD`{$d!wiV<#D#D1B{?O;d;P\#:dk9Qsa`1^C@SRy'w(+@W_d2>=GC(P'>:sQ@QELOEu?`r7v[(ϏI ا9{GOlO=_[:#DG/rh2#DF^'#z4 (ij<줐Y)b4XM?EB\[g,pdCXdI{٪w: qU,gCEHPͨ?A=M:F*:qL)KL,mːqI/HaM$# 7ahFZW I6%Ye+gٕ)Lʁƶ“) (N'P? #D$5*Om;qxЯOb{!c=|Se]*B@Ni@<3ބ 3#gx| I!az;'d);^=?(k?j_R=s}:yCY|ȞazӘD;㌘$ZU$ *!l Ȅ.bK/\#<"8uMSO:, b:ɏt*AkSx֚m+-nU5.Zi#uIAGt4Dq~^\X5QeT?Jz0o˹U% ]&%kfd{^F„CǢV"2o#:DAO?O"my>@_7ongsIه  NA]>(((˗A6VOv&&]!H+R~~ҽo5%_ν |^$sAW#T #:6%  6şnvb׷ZIВ3Q-'@NԒXDg!{aI1&pA2-OT(Y#D$b,!ƔOQelG{|=#D\R}t^r`֋?G:brI}ќ}ƂI^BΚiz}#hENL3fhŘYU|*3>OC7 sp{ HuO]H$SHL#>k΀C3`_t0E'#D>8tpФ |>1NT)aadL @|hO05_VD/ IpjafnKeS0HOV}086*9.#:9c#DipĆ@eË`>? ߳Ό~?&g$Ԕ&$k$0eϦH0EjR3uPX7 #h$78=pA_`d>h}uP9*#:B"Iwvآ,Tjɸ5(9CQ$@>ߞ}R迲Cc`>#~v}mwe)s#h[ U5O89,α4;HfE `#:Gɼ%=*~gPЁۯ_Joq3*0IטaW >^']'ϧ#Df9LQ$#%'id7DjBdMj0#:aTD@]m<[9)!#:TAspw1T]oafI VQrO{!#D\#:h\X6}' 6@jk#:axI9E+^g{I~8`/?S??+]>5+7 c'>DJBPBTɠ)84H :c1<`USE` ҵŕH#Dq?&4RBd2bE u RlFM~]\g06d`J@dCުm#DTϗ,Ky{gj45hxfevKHY6!0u2EC/{ Z>px#:vqORaE#:Vn~R*9c0+2V3B=MsJd#hA* ;nVyqwPK uƿwk0Ȩ4(*е)Y=<^[59:\K!7E_'#:#h@qe()Yk+Oe]kk7^9s;71#)6 _8?ڡmղ)VG6l#hMNPzη8LӃ&|,4iڢq+ DA |YomUHdIa8.gգ}Y՛❝;Y[ɮICi9ԫO}ͣcԯ,CJJxC26|wQ9A /MQϬGsusmH"@(C%G>'i^Cpg{D7. y5 Vk#:Mz]Q(a~1-q宒Lt'@'u;v0#:GB׽ C$>q p7~|>I]9X(P7#:c󑋸ы:#DN Y)$;YkG43D]&"dShyxgIkWnlE%=(Q#h]~#:3AjH{q+?6hAF" k_eN+`r@G#OпY %ÂED?YHnmRQu|N4lhݲ6_=^, 0EDFsR{7adAVzϠtkŔ;.#91hC}ٙ NU3(Sl;C!7׉3g.ވvGv6aGv|9H7*y:N?ſ1袱}GUjw)ucyq= l"yvS/,P~XO.tq#DD-~oR!GP/rK7CuMuX7Yl̻%OJ&rY@9ʯ{F+K٤] d(IN$ФeV՞8n9G}'HN`v9F<:@#hPErhn"cڣŐfo){}́>9sz`2D  8/;+r"z~jӳ_xMMBLNJT$#:p>-#:2 {;] g<#:14Ng$s,H3xI#D;x LtYMZ;" jB#DaËˏC|,Be,qPB*E.5AgBb"UH#DÄ9$HT:TjX**^gA/4GoXJfڧO׃K]X3|9G YR Cg]z]s 9wg`B(/B| @w!JPt #:*`u@#:@HS@bQ Of'u?& >hpYA:MKۻ˄is! qb܎Lǃ@dmۃ:]9'")H#X$9'`n<^f׳8>L0pKGcb:$YK(9o Sz"\%1Kz GͿio#D6`ǶI%y&9Ch<ocqX.{#:,S:#D&4`rL=4ӆhĪipr}Τᕮ; d0[#HWXcISg1I'CXxe^=TX_edʡV<#:.Y#D踿|I9*e s^C*!D~O8ɃŤ*7$l7slh9o~#:di?YD-RrdUP#D\1Ela`\)XƛS-LJ`PJdU% w%@Fi&y1h{TZΒ4gW'z>I'yrOQTU.aU>[aؾUQUUO/.٘2!#:GV6#hh$*ȍ~}gps**DyGu?kc| C6سr~9f HYRhMǕ#hT"KjؖآSc@1e&MN@_o]C9H8!I$~"X{[~;#:ftbBzBᓫ-bïn'Bn>ݓ QJd"k0g DƏ3>o.ʇ&C*!׭VpO˨,0XSܑF8ۜ[m q L/AW~U2ı灂yx/ɝ|LJ$%5;t%Oa .a/^( 9܊ 3.;s8cX4y(im#h=:x7La0TP[DS_`˖ua\`Yᵟ ̙GܹG[fg5CY]^YY 5ˌ9kol`mZCl謑e #D#h 5 Uv#DgSvfn㜡!2 KX5պvGlplv]QJLOzM0'q9C!#:ͫz vρv|)!w]}!Wt,%CHKMΆfaЌ7MwC֟rBSvDD=>"##:>CQ4|78nU`˰Pn;J#:T&Q{! a49E|nR &t!]5I:@ċ]!UIyxl42#h7:COQ*țl d[kr68*jYw1#h\>=ǵxĹC"u=JITh73IC:T}DBꢱNr8J0ӃxY`STSТy1*PeT#hg>(Ikm;[ 'C58klU4F#D#D7^ {%iP SuPCV^Ryh)ADjw6I!Ps9";-=$qSt{|4=Qx4řh-k9$<&#:IphLD{/(bcdQ*X#:f}*gngEx=2b#<#hVRYWWbؾ?Ƅv$VbG}U9b! G(tin`%--wLߏMm*Hh&#D{x}O1wz,B4@4r`1)`z6o0pƂH_'@Աɓ#ƪ{#3޴U6'@ 0 Y,^;Q&L YLjP~3ZêpMc8c!I#h0w42lχɣYL ,a5!G@TZKܗT&TJ#Dt]c'7J-P+Ac; p=@#Dj#܏ƈ{B(?1!6}"LS#hj0;Yv#:t! *^xC"ڐ 9n1ӡRNS*kB#/90h/C\rN6 N#C80oAT5Fj`!m#hqݜV_Xi!:J<ȠgmO3zkEz42pdȪs8q 0O{>oD=NhhAQ׸'ZfVkMM뼙s#D#h/HhWohh2V|)6?q2lU|K֒HLX{b*ww&%x3JnBff^:wԽ#D*HէOS.X9lR̛'E0`XQnȳϗcۧrʼӒ"G _rDm)0+1 }E&B:Q)h^#hc5\T=q#]3/wYa~DOK )t195Uޓ\ ( 2$ ׶()5"c0ED M([nn]Zau#Db$ʇ1Ԝkg^= OB=gӔ;K >Ws'H@01@`>ob+AMs'y2A?Z0gm>UcA^!H(A0f|ס6!dLNL$zfyJ[{^Vhd=C (x #heGB"ESip#hg$<.E!-(#ho(@?+V05Xv[AJwwLjKِ{GHU$~ET: ]yC` é 2Qp!Z^|x!Nt:ȟHsC~!BD\vPLs LFk4b JE3L B!eOrz<*#:ݦ~ Htcz|ȁ˝4REUTtjQ}׋U] ? eM*ղM|#: 5+Ǯ)*ӷ9RP}ZD~?ZPe)T E @0MX|3^?&kܸ$].`Č?L+zltw& Br Ȝ#?Tt,>u9+HDśǃ׍xrvH):sórPO#D@@t}~O3OT1 OM.JC{pje8AjA5rh#DJBhD#:z$O3p WA?#h#hX8A{lPýna BH\*CsW-7Vmi P,Dzm`d- w LL&\S ^>TWnjpp>9cγo8H4oxPY'16G,:dWf.f(^"AE\ dDZ:1`CXv93 R]&#|?/Nwq yB-|<b…VAj2X!`%&_kɣlstjSizf %p,{tt*i虇3X/M\#D'#:l{Ő8B#)@"@`H8m xYg0fl gdDvh?iE|ᡡkieNd +ϟ¡Fv$hMJu _/dVB>^NB(JTebUw>XPW!샭E#:Dՠ&DWPJIZ)5WjڐZCAQt@ W`rK!(K %kC0FYܟA4 "z71[Q0Li*b:oDi3& o4d45f>`L!iIDKE,kqfYf`1iM3 L런D)#h!" MD#DP-E $to6Fv\EcVؕZ坅#:$H}rd4#JPRh!JQwa`+(C?و|@a<ös&jSHs(8AHQ{E$@5aUSNZtQ$ "&)=H{ \ۈ=_R~NF #h]kMj#:uLB lC ~ݢT#DfzWo`opނ Q@1?Cԕvb;+cg~EN|kӯW=%f'*# ~#:Td#D4wJoT]d L"QZi#g''ά/9A3Hp 9%\Y9 >}X]skT}EF mS?X6k#h>'_Ayrͬ3#8>5Xy3ۢI ο70KF vhaa (|.ʍ Zw:h.u_3|^Lqck4p)bBU#:_y\O)WZ #*Ѐ;7G-a`>pO㊈B ĔIon)>QCe *CdǷ#DLUndl>$>P/5#DGn7`NdmWF:0kIٽU!=#Jk#>YQ|qU0ҡC0pook G8+vI+d^)\5l|#on԰;\Ds#D1*$S`f&wݷ#DHL J#??؊a#u˧6'ΈW×4de')I'8~\݀>N;9Ķ/!@!@yV!h.#: #hBd#:Q|K偒p( {U(#D#DF ƜcQ"cE1#DPӡ& S{&nQKE33 B#D"j@=mdn:໧B3ZJ8F($,J5iYJLu "8j,le3z8]*Iyo~rǏ:x V*r"@0k:I&%1$#Dx~h,~<΍ Ȫ5&,;tN{ #D0PHBkIb",kb),jɣQiM!12ڍU&ڋf@٢6I5*f&̦hmi%+FґV%E6ّT%#hA)ih(Y0 (C~|]I3][ٷ-{ٕ#D'ھR%Y#:F((~ >?d\#:n4㉭.|p˯(dH2/2Oydmɧ-w3EÃpY-1#[`u<)2Yxa Mu0Ib"'x}8(SZP@c=LqkHpI~D R> <G7#hD&C@p~W4p#HaX?6#h䚼8S8lE&8ztTmCF96gGrsm&sea [ipppLx)o> 5W pd2o#:-38ŦLnŷG1b3ƃ;ә T*#h}K{m\%|ȉg}FB @@ԛM i6(TkW6-\jJ-TQu%#D#IgP-C@RdJPj4&"TEQ9PPԮ?"0!F׉@]@ ҁJ)Ɋ}>)K`֜#hp/=t[2QHh"P鋚rc1kو} V>>9%SU#7M~45*2eT,gX;?S\865PSl^0#h1Vw3/ˮŀa.D"xxI#:d"LVI`#?w4tU_* }~~|/#7gNr:"/oC\/$Wxʽ\ j*D#:ēɈ1R (Sꪟx<'eT> A%%hEC4AN5ƷL2iҌFֳΩPgcT4!4q7tIc8JQ#h|")1A)L=L'_o{|#> <=^=s(F_ ְ^!ߪ]i#0HMu~y{Oy@p.1^_X}?x}=8X/U1{.e?ץ~ 1M!qp)|fœ61'qa3>`_Pv9,y&ȅL8wYLf#<Phk;Notbt; }o*Jx%{oKzr iOͮ8LD4K%^ԃDwA5(DTP-gFɳ EGd:c9EC#h#D ڪ|at9<6ʨ]0vx>dyly9jhifͦaȷ\WsޛtySp\ƥ\) DiJ#DHF5Rϖ!sW#jrfѪ&'uYåȻL ORb.pȈ H)ůaO}k9.5%#D1r9߮5 <؀nO7&X#yT<|!̛,t-{힋ro4ڸvΣ,k{edTo2Aڣf5v ZcqTm A$R@tÁіB:pѭ rƳ|,r2s1/c{2u#37e2/ûܵG)}, 7 :ܝzuRVv#h!v<",nw(~??cI+G0$AdgP8-.C {ŻpݲF\hQ#D򰭨Cuhj v"fCijJffv͛RA^Ȗɯ7%:Y@ܝj.fwVhq@vtbtc=cv8"nQӑpk>ŇE(0obv)UR /_}7x_~&;&Y!u&W]G/̐CJ )`Q嫹|[bzC07Q+$RvO zT{#DP9]s:C365E;OB<|K$C5RU#hRB)u]"1X- =Z{<#De˟$d}jP!}jZʩAE'|rf,QWn7=M[Hr5$j<"/_њbD7D9;󤐑NDȒs~faAY)#h#hiH_(,\{:a:0!U Ald)!8n4&DTKA'Λ3iݭn] W+y]fJ;p`TFXMj.s43d\Guu6k$4ćud"J+bsyˣtKl2R Ģ"¥/.\%$*ɇA =mnr**!#hHCQ.QlUaf8L|-bEEbjM55fDڢE;#h H(IHb#DQnu_5vC}+5^>#DM(€1C"4bXꡥ"U0yJphJQ#q˽<>n]h!hCU vh:3#DIx SpuHC#h&̄A?E>훧BU#:x{8|>'vAt"PN#:OYRwԵ[]t+J]+/GM H8I3F9"f.S%$( -**TFYQM5˅_i#FKYGPH4P/wgٲ t#D1a@D x<ڽhdj L,AeM`eRl6FDŌd#DX[)PfDqc97R#sw[;&qܨa9;igY>QYT2igdaJnNs;RJ#:#h&!q5Pp>O)#-ekDpU|R%]I6Jr.0I,tYnfm& qޫg$'huf鄠Om(WlP}YO\9y,!1Yg- ԿHA}q©PИ@v鰀"M箅E1ݰ!<&gN+gza"& T] ?ϊ2X$#:8 ,^ ʒ"h`?i#2Ag`k;o6;ݣajȄR#hM*CVЎ.Hf%$}21Pc`>NL#D0VoMmMD2bA ڪ;Nf\DrK!?.\y5#hȄ&OCTp+4P!E'h\)tˀ}8i-:GD61ɎNCa$l`n mЩCY>F${UhC5%tGPpČ$^+NTe;9Ds_qwhȐi v^eBiat2< 0,{4B3(,~=~މK`9YҨ^]ڒV E'ְw!j:+T):dOY].~%{faZkC,$sF4LiPBasɆ1%\alX.#DBѭl1s 9Q:N=Z 5CV𯩙&XՅ#:>*4pqӤ S<ʮJDuۋ] ؿ=4pE}j!G'|A܄c AHzH,h3"0:\g ʛ7:WQvwNL=?!T@ã,+'*h#h/"F$;=;?#:,)+=nʽ~LM4Z2i5z<|Gn@ԅ1 r;(&Дђ\C"~n,6ḟ##h$Tc}!<#R,Cp7z|PHY'dI!|gI6M[0|?#D0YǛ]a#.%4@†1ʆ7hљh£`h`#h/[ ӒqzonQr3,̾N{#DCJcX&P1)$"DX"k~e"Z#:X;qbFf'FESJ;lL#D0 $-xNxb @5$E \Aܡ(|tw~.TQPkvHICSW\һtM@VU4mf5eCRmkd0c*r#hnB3XP0 !uPA$L9^)] h5]=v0#hsi(#:eG#:ʏM#:Lv%̑Ӏ#JyU#Dfr!BJÀ~C˽8vp=T_oQ} Gy2=۸{͘q2N΂C$+iyCLuf02TN#:v~|3P#h#DiB2Mhtj W{+bj`PJq҃ #<)Za^{feX-u6'xjnɰAbYP&i4P$̛iEX!m 2ل2ԁy"i#:oUhƣ+˕]VQomVj(OLIBVFdnj2KJbϛ؋IjM_b$}̈D&&cH#ITv) dqR#D#Dɕ&cU48MZ{aWZuk&sPfVjR>y}5@t !drP?+68G巫חyz4t3WHarNhDncP2A%;\ch)d i7RV5\2 j8XǺSWR)!!C ]d`al5#"DRe+R(dCTyfZ ]Y%Tkj9jݙ*;⥛T)%(#D45efT)#hT 6hi*RhMnEXI#hbsdTԛRW#DSDX4EҌc[#:ԕjK(٫(X/ςQ5q@q#hJi0s$p:fDt#%æhN,)@7u!R=4Pms""#Dzli\d/M(<1zVtSWk鼯F0*]&B :.0&uHmI,$p@j yTC/2xȩu/+J~JJb@efDB  N: @3 \2Ȩ`X*q ^r\p#%XȅD'2P)wP#:D6Ad@5IY̽ Э2)hh4Z(nJTԝXSH-#D.^==❊o95,yF JppCR&D1 0-FnfD`1^ C2#i>u.u|sΪLqŖy/Bg))S}Olj/l9y$FԎ!#h^w }pfB#h:.t  ,X]︽{xM`S#D#|as8 pj+ y?O4ԗq YY^ömd-H孛C >DM{:7#:6[($6K1]kb BC ~/NR#D>co*Q'$!#h'HLEu 5yF2z[ g g}!“׭D&,0j{ L)QZ7o+hO1c%L}AAnRc#h< #NH @s 5;<mc33T~?u;C#DFa@" I9e}ji]#h#Dp\!XF Mca1ZPz ta@$o%$Xi R fVtJHD4 o5Sabh4N5BI"LC<HF@0^:h[:d+9ָb?M-8'=LIA9>_#h}ΒNXg8IXƽ')E#:3L(z@CV#!w[7[fv1BbKKJfh,Orp Y3-{_A EomQ lX PGшiB"?#: -Nn(~l[mur/ky&L-]s$]ݎ;%uShFXmB9)HCB#:#Do#:2)tlYW*tVp% C%@gG|Y(G`#O'*ba5thkSf'Y<4Ʌ)0`|<{~7v{rm)AWY3cUW0crK|QijA=B1Ie2S,L_2DRDI1QI2+hFCJ)iMMA@4i;vӢbXMMX,IHfFLRDQE JlM/Ϻ(PiRMNf# ũ05J(L03IF!5)T#:%&Je6I3Am&R64/~s׷w=h#%ҾB3#D6:kшzmP]_lΗN;{C䈃KW8 `1PpLZX0]ѥ&f]Ľ.]'CEXm#D4^>30ɗ94;p0vX_0 J%#: 焁掕dhŋu6[?cr;ho3P{8`Ldk\C^ݮ(X9#ƆY5]V`j4c#D+.(7 h5`3 #DIq=Ez=S`[CR5r#D,IK1-#DE#DFnQsuN(ԣI#3fm2XT%i=`b'1164`>%Ddt6"agJνvsRƶCU0]C?ߴ樇<^&o#t{#:}1z#_/ncF=!dOpf`-PۉYNI@P] )9wٻN kxhh'ɘam&c1]Û1|A Wph4&)5MһG?Sn{?5ߌb{G*حf/fl%Z#h&ORv-$H!.a8\hy]βʝѨ!hxW'l0҂z-i|tImliPCɜe2f߸Ѯ+O<}8Nx9`|[6-2q3ѱ[놷Z<,t@+ٴW4Ar j2͐AaZ _bq3 ZCfD><69Q2k,!Iq0Pl, Gr}s.SN`2#:dNp5.uuVܝ#D'<bӀC/(HFZ3w>O_.fcڡR!(ǬM?SiCo.WzOaA N g*Ru{>3E|S Yx2Lkϧ~|؁$G1aX0vfTRo#:q&#:> +\fZ1aDi^WZ ;bh0Fsqr.szO/8#hQ@ 'Zz4u>`>ϳ>MXqZ>sQ/>!l{rmc>"zg$Sqj ܋4`bw܌c1\̽C†i>1Cb0[VEZko0NiR7+ysmq9q+rosY18֌$Děm6gaAel#oM3ՕFioRӳqWx#hu8E>d7vt{щܞ_wskJ5:t:y3l.TGNF3x#hbl#hnsKY[C!#h&0b/{k˖nAވlsy_j*3fM#h*]T'VHۊ͙Ey ]P;;j4q3oQqtI6Hj~ZʹAj2*uvh:#D!MT#e/dÒS]7Kan,n\:N/h}+$v+i)#;-T]-JӜqi97+NӢ0u $4#h\gu+aδʦ^6V*CmX,8zQw5CY)S08Q&/s#h͝w'¸ű#A&4u0Pt֌sAj0i Oe~Ttk#Ds DaE-Goѡ8v1)iÆվ$09 P@n*Rؘul!DPfm{~)԰<#&e4-95> 0#  D w*cSY HbX%p]E{p4XPgFSaò{V=O'#;΂c+^[v#0jIdo#D<$0;#h]\GX!“6zc.V'fmxCj1 5'XG~.kgqNTqpmI4wLV{t60qlG-6[bo!W6JGcVW8ؚ"1)Fӥ%u4N;V&: 3SD$waZTк[{yX긓BLKw~g4u/Y@jliѨtd#DY L C؁d#DbJF0F3{Q6EU]c}6/FT\SѮwR|W.[2@x㻗99:#i4#iz5caF5gٰ?8xz3A fI9@8RbW;;g@q}0bIO#:ޓ5!8 qf4z8@08@w #:nS &4sb*Xv02l\+q8?'uyS4*qK58YTgxٶ(#h*P^W4eڞ8Vn6ي6̒q%byyyZbNhH}u{pTd -ܺ<_RTJTDN0ݝ03tS:0҆$׉[O#NV}YE%Fs-JxJ; Ϋgw.8w*ӇL4E}|' 3d5D2!#DmҢjd)Fj"ʗiƓ.tѓ86a*L#h2 ֍`#hW$DnVҲ͉n0ۘ-߱9-& ̺qw=|H^UW0;_ct$@#c\ږsFY9D&Qjj`޷֕Vfg$a8]99s|WgSiG[\CcuFB2Kn WTYF*͝ I޻2\FPyٌ-8gӛ瓾KpMUJFGhfKZH#u7 0ouQ#hmpe*bV8#DzKa]罹E0+|-#Dp-N͞ڊ!0uR #D4,HR=Dà#Y:3iO&ɉ18|ofքcn0oi :.Mu0aGLC6ǚhM*eL%W ug״Z7;S&.86&L!e>#DWG X1bɿ)w O,4:߈mu- Bĕ8{K`i,5{rۇ+OlkSqeRSZe UT)dEEq 8Zj6j#tRXVȍ;m}bW#+X2g/f$ yz7q!LLd%4:”*0NL8)Q&i7`s{292j(;.^ָ=IH[;h[2ptxVNYs\BpuZ;qu]y\3`)6s6HC,ڊ"TB͔9t4Z6Y;fkJr3Ýx9PE<'j( %&U|AYj_tg.p'slcr*:څִ՝o_f#:HX'RL4 Ex2X9L]=zE#DiE/#D)Cܤ6gBI76{"2rT4lѣE6 `2&ەiA $g 2K/iA' Cn\I Ή!04~dwo(^uuw/Y6Rj7y%,N  I/Bd-2g<˖ Kqœ$AMPRrd;pR#D>J)P'bo#DO[ܝLβjx24ڈ[ xfx.BJ#DI{L0plФ*Z#hJQWJiQiGzP5dGrf2VFxck;]:ĉ}9cAd`5dRyc"3ȘmNӜQt/ e]gE>a`0MKrrSuF,,q]jH.VQcQQN[ebl;õm P0zJu5q|9N9u[su#hZ%r>RY#hЫ+#D5Lpō:6h9 iM#D Hp3հ4Ȭ  #:d##:@%d#: Qa2f#:`]_b\ݕ= .(%c#hi|-m#D'wl%R(PLnݭ$pl9yCYB "7rf,9~n t|!F+H`8f.|e0ܞ?-.sxQYq#h4&TzPm\;3UkJ٫E"H8{ƢS!u.ZmL<ݎRINJ1C4~ۖ/fsx6ʙ͒yޘ)#:88܃NwY{c۵a6Oc|ɇTƦ`z<&9D$2ӺOjPm>o$Om84K'eU>(t]m5=8)9zq)pE9)M-"m4`Qu RQKϿ9),BHYtA1%߲.(^',H{!y$ +TAKNp AG61t2ߗ]!R)9⋲B&h2yN_%TAx>&/*gJk]2nQch^9inGjfFfJ0:X'6#:$8e$fH|dhO")e'=a9{y4'23SM#h yz*Bɋ!>t: *C!0ؽ~^=jpS3D!p<#"?p!eR"b#D HadK}#f8Vb=RI"u< ;.DU@ "IVH'F,,LXnsuuA_O`Ds='4BG~U4&`z"Th's>O7L!{#D d>%)׮BjbRt,YW#D 5"!8cB}:Ԟr}xP:|yvjFZ#:Y5?5YI~^"HTr gWgE ^ӰD~Uw>ȤOS%\gЕ}9GC|9zsQ"+>dl+jM:y"6-յ"Mk.\fCڶa9 nm Ruˑgk aU"9]{5^ 7]CNƃocDS = t zCu\Br bz1;t-Pez~aº*4U-p}0yϝXF HESNv}w` H)DDJ!-H[hĘX`LͦV ]%!@#U(/;?3›搯0NُpX04 J>P d@|ï;}t7y̵UyfV#@CCdDQц0yTX,yT\55Z\_c6C`Ɠx%0sK DIb#D兕;!,v;  \Wu m#:G5y#*T4g:blgbyIbH 2D8#DtEB5cC>e&%Ihv9eYvHۑ$̇ketPnC#O~,Bby&`}iQ\B񊙊ˢGdw@R: d*yFUūX"sjW㫻5fUS݊(#ӑ3T%6!*F#DF1~ÚZ0#ܙ#bm i#DgGBkOFrmn[-mGsj5дw("Hz0Y4y#fM%Րip{-da1&x} ;@0Iu&'#h`WeJCx@yřBCd_i_<p(#dE1!8H"UV##`}a##hPR CFI#:PPLXf*ƒ=AqBT{ؓb=;@/O[LF\83~zans]*aґVD^1CbrpEpU<>?EK:dAP4F1y'awlI-٦(0m71Zar6-hVQ31* EVZ04>ȆI\<oFtCg aRxndgzo#:>m=Iw hO@d~lFlxu,C;d8ixq1$2+[?2*-;Ni܋R5i7،5e3-*68oc#D4iЈ |r4k Eu<:lBT;lɄc)[n3\MpG E@ \8uUA/RDbu.ӀN4YsXm<pnӱ\P1vgdz#t.!LJhF1۵z1lD T]#h*c,*gIt4oc06DKDE !k]R[r&ѹF6@̃A+=$?#TJAjsSB ,:_ rd2hZHsSκԃ^UДFr$By'N~P_`?FFy#hmij[HAUB&$QS?ɐ.#:.;/'[Iy\b6#h5m9QݑXa,rb*rCsqd|w'zjD=;u1'\h@Jp~Xđjww5 š]v[cM,D7R4C+K!s7x1NS|c5Z|dZ.nt~1VXt+T0Yxt㼷l;_.yDB M#hw:ÌR`:nٵ|#hӡv֢9qN,qţ&s^&#D=Zr]q^f\uX戣'5jg{-`ʹfn#hlPҮ̩#Nb޸sL)46$$;kh]!lqP$ ȁ\reT*yR[DeMI )F+K(t#}qD犃rʒJ.H*4%>Gp>d3Jyߝ՘n&g9ۨ9R!7n݀Kph]-toW# #D!b#h5VdZRZqI;rrmxVڇ8qj1k70ehf2m"<65:fBff͹E)i^]f,@ϖJCFґЇa]>M5r" !CgGMCt! ?=ڐ<CJJ4% *zm@x < .,jsXZeRܫ#:#:?!qUԀRd ($~n0V,3R0/S5a=I:`DhQVyDFP$4Y)hriq#:aȒhb8Pc?*,8mـ2 :H O9@p\#h$#h46ʴ 4=N7#D#:@Jc!JRDi [rgC 2C5If1GSl#&tŖ(V**SmZI#DJRKJ[A6Z53nFfDPId.>`}=9Jv#h#h)ũ[UM-@G%UCCCVff5~pБS::Oʕ1#:î"}q:\ARcOQ/=H·]΃ H*$IomlVh ELMbHi߿o#:{*ȘG0 ʂM5C?M#hDF?1mby _0ODE z3Ǻ#hBKb#hW<Ⱦ>Dq7 0ڹUV%F4;6y.iLWFM T%8O\dRlIy^5(m m?ס@l p ڒ5pm@(ax*]8xz`7d msoJ;L8A-x5BQ9T;ښ.qC#:M8kyR$#qA)%fJ 'tpH#:~iB/tLB^aJ;z>a'tOR(y:ɏо93aaVX28(|ds#:9v,jT#TgO=OmGb# P5F ruEk#hvf4*15tb!4L!(dӿpe{7/* 9(Q'xZ) PЀ_wu1CZ MkYL%@6Anԁ٧Ng_AJو*JAZ~a3Oɥ+5ؖi#hgHaԆW'Haw 1,&@K#o]&@귪#DDdA74޾.]zdu[#hZ@ݍ`fo? %H0Ĝ bz:G&MI7fչ=B{{R{;]*94)o72ll#:ɂ7Qwv)f. u.I3-lBn@el]cQNF(MMFKØ#:MrQY=kF.e&F:#:"+TTPdU6m]Mj!]QՀ@7Z,3)26N]{]<ҋm1b%%P ( $֍jS7*]k )dI1A;GV#DXw;.ޯo3"jE[+2F4N2,Xs#hͰaEr0 hW i #D&8-⸙It)BpRd e%qAn$2bPD`'[zMmq/Ϻג#he3(-yhCRaTsa\/ze{xɴжFnubl=WSϧBR8\"Us};! ( oёxp{xR7#h1,,#/1yD#Dp9v:VATRK_k#h,hi6ȉjfbB!I (S8)IHGraŌĤr^!!8KH"Rg=t*Rv#A b^Q!r .M1+,4f1MߺQd4ZR[e+UeK,[I,*SoaXҲI%dJEePCdYRIi0T#D3e3eeT6XbfIdhXdԥjSXƣ(c*Q4ҖK3XСIT+J#-cVm0 (PJddQQ%Dibfԙ$YUL)IJQDڪf" &m!Mm&BDfQ(#h)^$4JiA:9))Ih/iZZ%Q(x!{7ݬ䈛ѺgA? #Duw0dze>oP?ӎ#:LNS8;gHyLvZID4ĩ*'Ƨise(ݧa9RtZK=ô8 S~"]r~'65J6n4u5pbD.)wFM #D.#D( g>Q$#D`{U}a󾯮8h7Ӫs${5Jp|C}>t9ϯWF0 "!<~'y'_@!11L*I#:k$&h#:d>w95oCIr>Wl7p`acXύש׀2'L#D%#h̖TF''d/x6˦~LnD K<~6i$3~#Dܹ_#h.MhwHle\2Hs&LM|n8&},R/&aF$H3񊟌v#V7%ʉ8ĚQa1x43GE1~:ZT%y}ik%5/-_q>* +ph ~JPfJTQK/t4??AߤmO6(v~~+zcz`_6pk0Cj)*IO;d_@J4laANjc5>Ԩ\{8](zVY#:?&AIv2#hTG`iM h( .qc R(Ell$$1Y:K7ld]J#DFVeF6HiT]ɊM$o#:# ʔEJPVAo,1h"A*WwmSiH5jȍӧnZJ ^v ArwWkvtﻞ_;“Q/Z~]wܪiK:#:*85ݜ@L>E*.1ٿRi#:P!X$#D #h"#DI"P#h@?f#h3}P>T#DXۆLց鏞hR17(Si٦}Κb@&Bt\K2VCKtKJt5BẂb煮3gLk!&#DM9ŕ5C9CU#:DD~)iLwy[#hgL((2zPL퇄|V-`t C]pQL!#hJ 9%ÃేL4۽a1oڮ<㊱jV\4Dv"hm 0iII4{f hnQ" Q j6:8m*"ŒcA #D!T4E&< ,k[JLQPk#iZ yS̊Y_l-|>Mqw',mp#DDj:7wмˑBQ4*#>3A e\O9FJ0ܫLQ*_`s7Z8ن. #:!nV咻ļ1aTJIJcx9ֳB5%4p Pc5hbj^Վ_vOy#h,q!B36Ln%:ni2҈mm U:Wh6GZIX GV`#:*:(vQEkrQShTv[[*_#htlLD=`OS*pg7| %3I#Dh<;ut$.y~=Z9@򪘪Q񞃳' Y߭(Tb4?<Ö9<狸U˷[3XZƗSus]#DïU&@z({=򫲡FHʼn+C=W[ q=Է5DPA:gtI2D #:epjq`$Tq:_D8r=}1*PR4BPIIY;#h 2j%D@#hd㔅Db1-!FSWd@Ph#4der('pFs"|!_=r-}YordA1Ai.w[S߮ge2 PIBdZ0*Q6Қ.kJhf^m;N '`WʽJy{j#Dp{'anHn#-,'^N۬O3͂$)#hw1[ȄѾ#D#:v̋%&L}U-U4:k)#:T8.;C)=>Ĩl;*fy$}^R/.F' SzƊT<֗,Xt $FЄ_\ʯWX[O7I#::uDQjϡApp_$CC@E  .2٣Y*R۶᫻\2hKF&4H%'πwUAآll5(&Q9W.F_Iޚo(kLH7#h̙cI͏IFPc1S#FY&w"&mɭ!5a& &t h]#SNUi%1 fiw]KFT%R=OU"~}xEq{ڏPب+(/g>=b4Ig)#hMVօhU@)BŃ0ҩM?Wk!Aه7ppY% ,ˆΩDf#nkascokӺ__,Y'FDk1Z.p#h!]ss$MQŸ6Y`M.E"WD\msdf/6AL_zD7?@cD!CjokmjJE6$%mkT٤ұW5!륑hhֿ~+݊*5E@0#p䚁#P0WPZ#hG<1'#h#:BF񰰬* i=V z6&@#h0'tY2@Ig#:$?eI!] ,F;S>]tL4#D!_LA4̖Hd'4UuH1 6Z*!31SV"^CK(1S,?TQ)z1)^!@#Br y;n#D`Jtٮ"wI*Bb)F1'# ̨Z[lm+rslE\hTkZlcAZZ&ƨFke#s,jQQW1hj5mr.QF9ڋXWN\6Tl"14bƱDmJlQT-֒cQZk Qh65IF4mclTHUj*JXhrj54Qb+,#h#:0U#~\~YCP8 j-Zm#IrW v%n \}?J(&"%iRmQP,0ֽQ#:?GN.tS(ScܟXj콜:M4L "bZ_n܂f(.Er+X37<Eȱp[uJG h0y1UQ,@#Dü9C8r`>SIP¨b5F0TFJF#h`ĚQbJT̆R-+,mh#h< tI]"BZ71c!t #:$<6Yך=: `#D7gSߦL!{\FZF"e#D2ÄIn=!Q?@g>q`=Gq |?ߋSx~ӵ|W=EJT[Byc|/B PH;:?\gS#n85}!RE5A€p['"{'FIEa H˲"Lh$@(Ig23)_RDŌw׽uOS[[@@ "@ &@*jQV 1W! j MH)HJ#h\)E4iրZmIo/}D.A\CoDQDV-دA6B뇰%Hh);ꓑLclXT%a!yr,8e"!臻QP#DID}|>(ל<#:מás0}^OW&T(W!zh*7D#:I:Y|fp?fDdkkź.ªRMuIP@upAHw&C8,K*<_H,͉П&;tu`Cfu4z~Kouz=-E8DϘ`iK#d{49l rQc&ĨkCW8(AVd`Z˨FiIwNB,.\,@͓Ё|}ݐXI_{W9Qs$֜5XsY6NaFZBતYl5f0)2wkaHl7d`Vcmq#h3}my qQsn\dCBn #Dw#h2?Q*#DPK5.F3Sn"`P!Ih0Η$SF:hÍJ#DGX`6MmXnX$ilMAFL+"GY"4ѕ(03M6#V(8AeSdEa9ߑW"`%HO"`)9#ԁ3;J#:DŸ#DɶZ:!wԡdԣBM,]#Rġ)zHddqƬYDGLw%q(FFUE27lQr¸0E`KުQհ,i; |ͮ\%o0wu&5ÉP&B혜U:mM"4܂p;Zu"c hfʋbޱ{K`lYd5q0CY%L5ߛ^ ?<'#:׼͘K{Mc!ZݿK}p!h'%'7A'l%.Q'|̜@?Y$DXe%p[@4O @E?ҚNA4u!k+*n`$KB4.!Lo#:0X-u;bZvI2`ܚN#0$Ą~ Dij]&җW+|nϱ~DŽ#hySk=+B5+ 6ν?Uonpd%i~>պ(`NA$2s,d$騡UWFa;`T"Kmb~!%ݘn7*X~MDP8trR$CW]"o5MTwx44#DXTuÑS{3?q]?吤dS2'^CD<:՜~ ?n=_P``~7$T[0Aỏ$gqdٵ}/7H,)iJVѢث_0t'$#:O耠Rz{)i~]Fa%@jT3'̝yQ9#D4)1^8\j@R/Lug@0B}˥#: 7c;9||m+s`Awb?nUe$d'@K,[]Gf]䤿dzXQk!sPL'F(TL:S|#6#YMSlÉ뾙 ibo 3u؝i 6}G;J=\hs*SX1 p㒉G3vBڍ#Df7s#DoSȕ4BaZ1 >0̠c2"BQLBHiP1$[H.G}-Nb de{A!0ʋ'ϟQ00H\#:~G`LADR A`!M!9aV'n=g^AU8@:ǗyU@j M.!q#"̹R=bCpBHr8QrRL%%ê$̄I I:41d[%7FjB#_ҜC[l5h;Yn۷SʽM=dBrF;#h"LB {K@ǯ9y;vv@1_-e"##:+,Ij#Dؚٟ:Mv봞@v*00l"b?OOQ6@"g#:s`CIyI̞ s,̞~Jcy99A@?KU2SPL?H'03#:j)F^ɕRɮ_mI`aJF2Xd#llmY4cX&I5--15lZ6$+QQF6MBQ)B-fͩ+#DDڌS_+1d5+5#KKJR[vAfV[-b#hY5"i5i4D#hD2J$i[-lաLl&X,"3Ѧ"0Rm$EY2jj6ڢ#hYt{ZSDLCcaZs\CLñq0MB< '@(egSRWϚ*jŭ !#:U BedRf#h_ЇD ؊-RWf$1 {/wGܠm#D$ZmkFsizQ%H J}#Dt(.TR͝t51Bj ˛D֡꛶ZҦmm&)?J2BWxd L9E+IH5!4^MKOOr\w$х[wIH'y@E`܇3^SRO*0AH?.R!(@_"||N#D8#hE_`^/wZ54IX*J#$kQU%rؿ~0ү6 M&z;mWDN,4r(k#h(j@2pbF~MS;'5gq&'WkC}4d=aNN'΄Ԁ$x!l}8#:ɊTޞ9h] f6sT8cR*3@ֺeL|tkCo!CTI$ غ 'Pn|#DS#h@I `m#:^I(>#:<#fRuKWǹbN_4@#D}oG<\(Q&5",5#:ƙ/s(Cʾ'o/SO.R,F#%@h۶O0#D$88V3oR$+V0#MӶ+$_fo02=ϭ|ar2I~]uwҥ )b0FC!螑K'xjdi}Bv*0Ix4hcL.L?vZIpm`#hp~0*Ò(Kϯ:6`#D}eDj5D! VX8@nՠ d[9a@zE/)#h(Jځ2”5T,ABmzB)̨`*2qҌmKSASEA|^5vtݔbAJ>oԞ??p^Ϯ!W8QYRlFF46]nM=,Ue`f%uXEl-'[&.$Yӣ77)R[2 t*d&X}ҠLᢦmX4Y:z'r(IeK͕HwEzvTQ}p´ m #NHAZ1j*j%$_BD7'Wnc&M ӊf%e!@AJchQBU+>}ѕ?z1Ds6#h5uYA.YSő!̀(#DlHj#D] UqG\1b,-p- :J"}]Mj4ҒtrȥP Ÿfaâp$S:"/^IHN+I wfޓJ)p1J YX;5ΓM$Ys,.GC"Hb" јʚRB4H(h4a#DqfiD}>I%+swlȥ$&"#h1G@y5MSr8*0H"8{o:88؇G^`U8A͔LKGԏ#_pI"GH+OX5WPz%I)7B."Pwfd0iQG#݆?̆2`1#QKMBkras"M0 #cmxjK&RR!yOigxڍ[%.\,[ۮlmQRd%x@x#hM_"!ʆPuwxfz-{3ɣL`ىP8!C}3eJLZrm8/ncc̱f'& N3&R14ZiepB d,n@gg$oId?qN_={"=TK6iHB&9jTO NRv5n#DjRՃ鉌ݰD[ǍԜ#ƍ8#W1AǪ Reٌ#h4*yZmĸ@΢xN YE\x$=#DZLcY-U_k,7~ CJLCSLd_HI&-nvsJ +b踤5-+68uiT=ݓ$(a˦lqA>51 %2 Tc#D c<1[6{h7TJ#Dݡeb hA'4i(yx`k38ė+iD̂LC"u#:遊-a7*LWضiy&uASUj~/#h#:w V !Qo@E/?ޝ|oك&7Ár(- L@d4kJE"$8DPLbh%А ADA"D!u`!P|x+>*/L({eUB(P=?+gXu4g Q0zGڌk:Ixxg2k' {v|=]s_r@U%ZZRa%):'؃׭h#hwwHBb-E{{"r:V-jS%l5"r}㬅(ԦSjtTG!?\-陂VPoD7ȡb $'{58DXg1ns{+ڋzX,D@JD&xxUcmӅ{ޢ?_u)!&Vݺe\VMwuZҍ4ʖVTDD*3LI2M(&@#:c`@+]~WqӠӾL#DJ:#DCq2Rlnidj47d5enH/`$W)aYJе.DH#V!XTR%F _6N^BѶ6ڎg&+6a =+F#DY0ACOѵ|q'8-95pA4Ib4"ڄSo-יmJp#h&5ձZI Ivف%D30ѹCmH Ás KzBd.aƱƕiCtC 8cTƤ)@u0l32Ir2 ԹO0ژRLx0lX45+h"]s!ذ%3%#:GG#LL81Bpn+#DbbDtX7hݵ2eh).nbKR?u,IZSf60.#:B=#:2= WY#D;11ft$BW\%aGDbE=TL80j|;]=y Β45"#:Zlr 9`wYN8bK,7+H! :=?_AAz=h#h%!2#:(q#D%U)j\Jf]sb&REc_ܵJ7LUZ<ݑCńNf.<(Qm뻔ns%sg]\κ}nlQM#D)J99S2skSi Z6!_$#:HJGDtK1@ggѯ(-OWr8=ǻ#:#D$~Zӆ[]&TzRPbmH nsmϟ́ٯ.=gw|el1U-HT)~07 o`ޫ#:_/UK>-gM7ܛ#,aCؔwm8*J9=#93wi{~-\HٰXp z|#:'`YSp(<μVkP6l#hY۴=@#DD,dYx=S.+= LS<V 0 O;\F.gTG\V}#D=t)aѴ%- CbHDKm*]u?0~F)89B1؋[wc7}j?$<^W?3"O0>#DXAƏ_ix+NыcgBِa&CCv |)LQRƴlhȴc#hi.ϰ| Ls*p rd*,CzṁV"X'6~n" 9waP綸<^ZL7jj~\T8d1 tߴ0PT:b<bd*nQ0 PKg΁)88%`lqYip\?#Dp`^CD#:wNCKĉDu#DDIٳ% :#hţLyhi_wL]ok*ʋ]$`>Ivy( 6ԤB=M1tD#hSr''gtPO<\jCY!Q<;$rp e]xf#D9Pr\ІRh5=gW>SM#@D@+)칼pÃǪ: 9Sag<,I<_OP.b{?OWy$ڞqPS>= NB!O^!T֣{;} ɩ'#3sqh20`S/L. 9("l F&ڧA:3.hXMFٙ]Z#DR6ɰ6,UȚ LX8&#DN8=4ͭjΦ `6>" 1a?v^dȚ)Za?NVXNIcrbaQrj#:preK- !r4&Cb{1t9+K&N>R'wcq(^ԬdࡲfC%iVB<pJl6I7¦Q?`pCmb*"#:КH2<S!y!!Gb븬&G%h XJ'O,y<^>|x2qÛ'NC}\C0J={BN #h8W}WQ yyLFX$7$ >=+TwBO^)T)ݻ#h@$@02#h9Ngd}Ț`@c( [G{Az/'_w=Qbڋhbjmg~^{)+wͦITk۔2#D@}$ AJ)mbsmThs"7qTҰKJsSE(j@Af@-x.uA%dn4F3[fdfwNhԂ3vi[17,mCWq0`'{↊UXQ,(4<$jb0UCkz9=kt!"h`G|IGr t'R6~PÚ$@% "@jϘ?f!TP adFHلiy!pa6ŚhivzHN*3{c`bPfaP,P)!;ƃpĮgBCa#hI(Q99u$,BppdrP*=d4M P9pJ "К\04x&^!K#D%.ߍD8nڱll#DH{AkPg׶IkrvogIoDgZB՘6"+Q#hJh!M^d9[x)#*4#h]>і4%޽$r }obʤՖbP"#DEE(zɐG_RKq)etN#'FdZ1“/ȝbKݡr&tb'q2!#D! wJaLjٙ)>80gg0C) gwW7k's4l>fcu'*FxCꒉgAȝ~ہW:#]Ϲ{g>h~cSC1cXn-+aw3~ #D5{ԛ⡣ btDhy.6#D#D#h#h#hy-RBCoJM3VDn|Ȫ}P:H%`4Xf,5[KlO{1^ZĆ']ĻmzzSwnݞ^E׽w.XaatRQv$D"c"&-taA6sBy[Lw˂3MDcP2]XBYz: FDݽ9:cJ5ckcd7IpJ,4pI?G:84kZP[xRP4?V#D 302.rf5VaN=)F<%VZRZz>s8ƙ@驈N١d($ǰo$PLAV0ĭ!$ʨƚ@9Y d ^J#hqc6m#DM4dD1ifYb;PE"@ ojQȈV F ']-![I#81ꐋR7` F6-B#hH ȗAhk<3\MLk!n(dOT1`Ԉ;×c6mvhBQq*q2Ehޖ4Jq$' L&\RiӻW{PYP޽o|Tv}InܒA>t5kJثtj2(d7x@eq5%Mh5vſu#h&*k0P#N=*7iņ-Ù:B&vNN44sʳpTm5#yoJebg^ܮa;6eZj7')dp941=C.eT0YtP#hQ Ɠd6^8F6MQ*lb #:"&JY4g\K"ż4$I$d|ʹel5l/ehGy9qJـ8ȸ~SV_{<{wǿ#DX:2Ʉ)0l ,`DSZC94qɎͼFtĜV:OD`6: 7lUE+SC1T;`\yaܼdo#:EV}{4Wero}c!3BhB%ZR `0Q#h `p p.M$<&;MPQXц2 O,F&+|fm e<*|SV bCrǿ-WL|XJHPXGj%Ѣ" hY&6lfPan#j{~VtQX_4,aet2EteZlȳXf&ޠ}x@a5T"Uݰ#Dg~tĤ" kW;ۯ2S%z=)?ռ2a;ij 5#MHNۇzs4c6zLɆW v&F{3C%ɐn͏ViZMZS8g5#hp$Qd5Y;\99Ut62{.&6"2]6Qm 8h0PB+r= \LoM-)jI06prk֐xyB'6jb-M%H#DiB~31:rMJ&[i !DI~uKF6K ;8Cz鐲ZM^V|90Q`?@}Ɣ"o%ry #:hR% %N`p#D}# 8"j1HRX[X`L (ܬeǘ<u6#:%"p 8)Xd?\sRȷ]?)(#h@PiM(ߗUz;@;S`#:w d#:)K,R#ݑ촉Ǟٜ93cBOa-b@F*j0& @)#h+ Aba;#:(9H҅$dbF]ut{(T<2e{sr=yO64Y|tTW6wQYeTiB#DHHahH#h#DJ#ㆬ!ۣ(M͕+0s"11ܧBs`(;D`U9%֫Kq4K,ȏf060 <$`$@9ŭ۪]㊃4p˶髹E+r*5yZ|Yl5QJc iF2Dʿ[H}iA "%R3M),[FQhBѢV5eFL$l,4RM$ښ25EZmtá#D",}~%DH,tjB ,#dB}>bKIB, ,6")ocWцMXj1Y-Z&c5]nbze<$iG*3)dpr˺aaU#:#h"P"!0Pѣ#:ȡ R @nH~hU)L2A#h0Lc(PC&]Ha1 #D6A@9#D6bƓ FjEcY6%Q2R %Ro5ޢpZI{GM'B .^WIfV &>+#$Gk[ HU'Hva1)9MHhIpFR\N@_nj&93HLNM&-K% ={`ND@'8K j<ʵM$+GQ餠)LadbY\h^C(R(햑&:0|BՓ}GF2aQHg52GiO+3slmcDiXm~J5BNyvrHl&5QF6Rk٭tc8zԆhn!Hz䝝\F&.#DG^nCX.LRλ;Cn^/ <,3;ê}Gl0I#zx%\/葕"1ڨ6TV)ieLpf'*7)ĞSr&(V5Immk-VN@eѨы _O@}C|H3yi:ubk!׾v1Դ%].2ZJ5FlbDWVثf9[ۏ<`cyhj[i6!Z)bZ#[ޮjCUٵ&֜Ɣ5fb!!U%Up]6~r.(,bdqHXE Ck͗X*'nqP$Mh*@D"TyNQD!2Z-*5ѭN0֭&K[i%[DskcNmD;VZ"rGyW*3+ە)`꟩>#:8XBrSO6oqFt#h45#&g{݁PQܛv2\u!:GO/#:ÙEP}fb*QTKv 7`~!}?s3.Ґ \6#:X-LϼK"Aе%dY,s&OA.0<4uwI)7KH~plD+<0jRKh>Ƶ?,-*՜ !ܐJ{݃)'ԠDRo^ 5N[Š)e#h;=Nxf;@Ў%7qBwm#:冭t逵T:EJkbW@f8 N0΅ѬX_VD9ڛ0iFfxTBJs;uJ9qAQ(٧Һ1Tdwo 8hPTMeG $y"Ꮉ+D3d۫aqlw@ /0kAL xa6kUҰKl4"iFeH:Ճi9~\mlhb4??#hiSe]J`z5@ ٵkAz'E94'?ۆW' WaeSt_^q6,.RB$$*c$Pg#ii\j66ZTn둽uޮL\s^W]׽s1wm.Oe|T2ܲe2Sfa4a*TmԑcEvltnn66^{y:ūh#huF`##:YX:{s׶ջUsUwu29JԖԥη.NQzMݍM-wNkmjTj &@Qb#:HHUmIT `@ N"P P+X` vA(JU }O C>p<}H_P?d y"B8PBP>g*jeOZA}@AP>SE#G.BYd!@#hQ1GDCr#:!f'{ M~~B?J#y#:,gFw=k?[WA<"#:)RDNq>ML@BHB=COkPǕtjص^I56u(qNxk{}pPTd$@( }?jJ$HhmƵIEZTmch֊RR#hAlXrE܇us^XqWtk׺U2eaPa(tdxf] #hG`D 0`4ۖ2 X=6R^7Nv9nG#D.$>dN 2@c8"L!5:)lmi"Jj xЙR/tɨyZ'Z "Q\X6G!,)rS^F) %=ռ Ή%E6C"C#:#:DH rw ,)bD;(ɤp ;v@`,bO"#M)ZoV#?C}IH<:@R#:;"ѓpa#D1iXNE8 ^`FP2*JQ" ē$jyIG; Q=&MU*SM*6)mjTRԖZhHKǐSJ#DJ}Cz~2B-*Ң!r0*2JzmM55iYضf@wf ,|6bf;D?~^GI *bbT;( 0zѓ6eb Q@0 4L):=bןgt2eq'ty/28ХS߳HOzw>V#(Q)U ˼Ѕ0ǜ;ϢY`O|˹H`F`Vh*!"GL@@<q-n'4v&C  7-+$ƃ0T4m""f5[y^)Vj/z'#:"P$lMFlF52n#:k^p}c"+0#:T+lVfŚPU1#hImAֺ=T"EuҳH!Qa2MVb R5RqYAT T!(hk+"@Ơʜ k%1:skBB!Kr)kwwESPFTWpA"Р4ҁC0p8sE#GPda-Sч* &;Ҕi|UƋQlk﷮UF#D(6#h⹗Xn1 ( [|T+$,YvQXF B_;tG;6b(iRV0m6%&"vdYݨ1!*1/JT`5#?1FSy |X x!%K#DqQAH0`ƙFP*|F}sIys.4T91`AK#D:6M-EB1<(*+NI:֫Z*& G ڞ' 3)1;ÿں>|?0]yABDC4Q@ ;te*Str4'.13"SHPYsΞOfhD@;O*:Vb2)ܺu7͹T0Ha9 Z4ZQ~dJIST)B.#hD2B#h*T%{_LL?{03TֶU~k$o~=[sQA rA2W6IYUeyS8p2Ee#hn&^ľZy^bJ}%y!d9a~`Ȍ2C܋*?A?JMYD$)E1I|0S'`D5#h#D"Jx!]1 2pHR `sd-# 4p?JBfSYl50'LY5ߕ$QU4)ӯvxe?#:#hjI.`tOPP$\#hvhJɒ2٘"dA}‡UQ7~a!I-B4_@isb3k9Lˬf5$e\J>^9JTiXiXfMs *U~DU-~OJcLh"ʔ飯'(/#j1;0?ڴp#D'<>#DRQeJ%** .ŨY&~X0RPpۑ ɪWT :1920@z}?ZC}Œe=_QH<00~úheL~9$X?#:QA##AL糍qh,VgrY ua62޻Ds4[5 L! lL\gg@Ǫ!ց'F\d[\$c:RfV9fEظs2ف8kx$({yLiMY#:D:*)[,ƮTIa얐ٌs-ɛS4RqE'/Lp̑2#D)h=(H(_-tEQ9h$ b`cX呏F$#h#DyVJqÜ81.8mǢM)5#D/#셉u|<"iT%ĶqFtV7HʬQ6H鍢κlѿm4V_u8 M0x2HV~*S2ieћax#G-H#:_Hj(!u?/o#:N2`$Ld_ NXwM^PM8_+K#:cccƎ̑d n}+\MMVͪ*ף986)bv![d`6ߖ#DN9qU;qEۂƏn{̡#}̰0~u_[m` :'pf~GGc @"P9ؿ#:1#:zH^8C4'd71vd)"`Sb`M#v$snQЩ#hcH O,\0մ'l`P?.@1EdQQT<*=8֡?m=\ّ+8!5]$qG}7I8vF}xSeM7r0za?^G׵D |_&ήNCu?TH ߌO<V$ϫ`Ã+#Dv%MhAdoP412)^$7qroXDXL7%lrJ|^3kzf7Y~/)4R3E[N{,`dAj[c\1Z+孕2LuѮ0n} i =#h,0gm=n \rU A%#hH4v(J :+UX!鍯DM|8B[`fxs]q\x\0w3Db CU&I1SdA㢧7*/5`.Y5*կ[1e֭ڡYS'#: B_Kb6c`PvSģ@rC#: `}jTaBfbFe ~ /UH@ه`2N#h6CJ"QEF,[%!8.-nƜPR}Dg=N3Fv 4>.d!=z:*Sw?|^>w=:M[dE N$PG|}mߓSl_}tO_<󓤊5I!jDAcԝ<g/aAOkXsObR0$EVhSQHJ"**Vt^}M7ǒ>$R>R(qbp]#D/"G!1h<_!x -@(P2A#h"#:4!&2D%  43h32F4RU⁼zŇϘq9I*6T0'?n͆Y*EA/  7Qv@7ݥR#D,@&ePƧyAȜM1j%I&&5R ` v=H=o&Gt<0~͢ qi0:9o碓81d2Nu ѣ5tdV[BqrO`U85t(15דAl>m#(#:APrZOL RjwDH8bsԾޢhlDNM|qF9(7X) փOx>+ >JUDaBB GN"L)7'}a:Fߍ* X|{85`BD:Pc#DI"8B f8@P1b"(BF~#:⏫f,ܴ*1 pAJ3|d,;Br_-]7p\ W;枝(k/5hMb5\nkڤʒʁ!@0#D״ƪ'MP;`>^击ۻ٭y rFbFiD\E2RnIZPH^wd%Gsa06&fb(2DZ"C(ɢXh$f?z[=╈Y)[.F#h/ٗ'}$ HC+vm+[cZzZ[ R@-5=L0]J1p+g`+,ѶSrwG01T8"_ߋ=Fx!F OPA#DdAgmv7uҦy0+c/s̟pAI=L4T<Ț<C)OHJD qx16@v%WWgT}+"a7Za5+jCim+X:6d5J0l?S7T1]9(u1k\ڝ7Fyi" 6ۂI#:h#hY*R@Ha R[S%XiΈEbAAuvtwIV_^v.4JcFcC:HpŰi6KP#hDxqi[#D0d3P(1A &C,U"+#D<14Q|E9$Mϙ9A;|&I3@Z5!(fs>wAg'#hMu2rFff_VQ[bƚd6䚣_s.{|[aoA8չv`n1׋#hP#Dl^ѽfɡ(H)zLTkC)J1 )Rn񖤘ӾJ尕vsrmcIv˶kr0޹˂ˀI(ff0hdO9fRݻ/}ݴjq+gE91BFJц =yNJiTX( Lqe+ֳ1MEa!hMzdY 3,u:4{jʶ"mU8#DJ6Tr!*²۱RZ#hYap4aX+IJTtihh$ɀr𱓭ǁ4ԖB9s3BIr(H֙04:0zk+x3Pӳ$ef(tOwRʐ0U5HB0]XÐi#D*"\hM3.Pt2d2Fm1#FzPT 7amMZ PsE0rT4rf*;08,ͬVx5E1aFC\&ZvOw1f=#9Q0Ɠ"$#DdFF1ybMQKF%#J #hvE#0rKZLULLx,T+y(  B!Ƃd֙&oYHiC*4D_Ԓ8#:jqȠSF4Udܭk1otyʹO19xh82sqE-\ۖf{^k'*^F#hcFd](lL4FmTm7<ws w2Uӄ!parrfEZ10 d2#:9$h tso5BT1NpLlhQѢ~!rՎb1h0#DQw1wr`yӊ bE14Φ,L6(@K#h#hTpr@ڶ YMlp`7t JCpL&X81}$;?s`JT\]ntŨS7? ّY`2v)x#:_ (B&mb*6*Ѱ&Vj*+cU2#D) #B!J(@@ERD@xHm4+!!:D'0O$#D<"·!B¤Jc* 2L .:_]@z1h0@Y"(&0kU˻eWK_TMIi}&&{7?$m8nRƳ"DfH/ӄZ}7'#:hd  E 13ZDm-&ā@Q#:7NO;77Rљη1kzj/t1$p(B#h;Z1̥ވێ dNT bt8deʐ#hz@hp_ZO%bP4b燂qr<3ka~3haZa&-#hLG##hMCV 4nQ"ݞE]7Gm&BSbٙ(*{9f*F(*bK~\`jFZgo~TvPq`O&h[ʲD6̤hz{y&ߔE0'/"/L#h4F ӗB'"ݣ)?EP"ž(d:V+Oibd,d4lHhth[ѡ\dD+I5LV4f$`5N҃kO|?~x(ۧL#0? ~F5yX*>vߧ#:{N}ѕ'+NSрcwf<%'΍!:dzJa= Ձ:#:T~zaݴȼ?136}#hnDh\:w5nPY a.&}=@jr#j!弳#hjZbP#:C&u)5M ~T)&Za nMf "$ l#P=-D:70(^hOC7omG"UBEcC2}!Is#hpMfAյY2098 R*#9uGO\ˇp',wTۋ˺Sr!Bq.@$JQcgXչz;]ҙ\QFbMB3'n[JkrUIj)uhrUJnV%0PL X\S%b Ƞ~>;N4ԓPC' 6Jl3⾆#>@0%͝dJH ~<n", C!Tv=pG#:?_ASJ'b!1NHe!$ፅ!p9h "/&LQ#:1(CS)ITQ ==o#%D@!-U|%[B0l`#6*1Q~ҢKayjYDLUhYr`I"$'jOX 3+(i/gQԎHa&=0C9) Va P5B4 #:$/Cth6#OzE$UzC5!!?lT![ZlckHD)BҪ1bXY&k#:4(9='vT_㶑C~qT>_XkYdav!Kn5&#d`=Y,p#DָP>%!`#h @M/չJ:C(%!$# '=<Ӑ;%򪑠)#>]Ӽ`'#:R@P@-VPM"{mWT͛SIfL*LPK&&#D(ڍJRd&)XZ*تUMS`ZKRJJ4PH6w;>BU n9 YnUT6H1޻owysw{[)1 D(Z!XLqc14VZ#D^;o/MtfIe{0MoNae̅h##cƱզD#׼w#h#iqt!;h˻'tܻ|f9]ڽ^^QM:1f#:8gC-tZYUSNƴ:Zm[R#:)|NPMÒ[J!̰s12A2D" `#h_a$P*_F -Ie0f9ӕcKhv{]JP;ނ,Z҂@AI#:Ɓg 0,Uz}1rd#D@HwøK@_#h.6ΗqG@H E^}[GְY*}.bN%!phpR#D/5$N\֩{\Ea5j1V#hN!Oˡ*'6>v@3#D,?7u-_8@I'Im4*iQwAА 'SQk 2JCDwX^C$YZ3ݦw=9Әfq'EX9^kf|ZLLdL^W-UFjFE!1t%8s_c? -_4@}u4Rwy{e~ ew7Uo1h+{cFBJn{|O,ocut/i'O2jS$h> t:?g&P#2~j8ZSӔ9`O=^s 522iޣABҘf;pPŨEDʮc_k3IAa&]>^u8qu|3_4s aFS}ݪ]~v泉gɼ=nc-q/C!XN&_)KFBnr}\f+\Wйʻ7oHLY,qt2!Z2XMg-׏~98X&<3J6y2V#~Ĭ;t1SڮPmQ싗QjRt͎Q!;L#h,!y='I#hl1V|].hk]y';vV]Yu"['2멕Vѿkx~ϻlc)7s)qմ#hvZqI4arySݯ*I˾YeW&"fg܄wm,̦-N) "FG!Q^I9>T>mZ2wme\h|le8k"#DћۨjX|WXz~iDRcm/ozciu9eɻ.:G݌oMcNTn%f^e?|ҏ.&OQW~U,zsܕw}2~BwjX!kklf]8Z 6IJd;kdBbDñ%"edv0c6t%Bnwq6r澼0ɦ-13RrsvBkEV'PcX))aA|!oQTڜx7kpI>.sNCְ|c ods <7n\KKCW|Tyģʘ[sq&n:yA׈Pim#hdb`v?"gsGKx\bwC.<Қe5-<ռٲl?`*MyHH_cL:*2[Ŏ1cv1$>j懜D? ^AL&idky0Cw :%|yO^ xAEsH%TD6;HK2eAڮl9{ u)kP:)f2fiLҠz=P87~3Bj&3FݷǠ8_u"Rcƈ;k?JLE!֛glbefeLJdylO*%ThEUT5a5cնwg&(]cny9s`a}<#NU:g%p3[iPB1TL<޼qMkOSR;Ój;(t-9jEU(&d<˟lkTI6#S}bI]X֖dTH%#cT)#|Mn]30 ì%ڛ#hsQ ¥K[=bIni2b®eCjG: *C9b*a8 jyƍ/\"Rn>|lJ>ITi-^ VٜQ#h)ɍ{jU^5[3<tUBu{6(ͅ={>Y߭#Dﵜx4-LJ DHѝxo3!!,i[AN\ZmB&b1j>U ;UmOmbvXKlsʺ^xk(&>]voEәtӝOaɍ;e@;RY.|UI{}1w2 Xآ#D#:'9dڪkƣcfZ^@^#֗-㎁=>Ӵ#: 37Cr`q㝘xn*O/>[ȸYbYǧWӭ;ylfQ<}:wgq#6#D;7I rI D42p be<XT ;Xț@ D5k϶1ƓA`9I\@$+=,Sר SP#D#:`8=-㦼=!8pm$ȗc3+?}cTz2PC i*cdNu׷J$Eț ˿M݇y ~#:9d(i '("{w"gM#h)bTVU>^qhݗ;#njòeQDpJ<2hrUw5\s͆Lfi26F/Y9wۻ!ӹF[PbVSU=>#D)͐8䷡vddgt:ٕCĕr8i4Yalĭ?~ ߻ #c)LNT ::gLj@31/vixBԠՈ(vD#cXkZEQdzV&D;St́1*u#hx׋*Ӟ{d y->WjZ72"8=M8Ɩ樅V Mu+/}u|x* ?8}^K`ɥĐ1PЙX9vVMüQR|*ɌTXx17sj Na$#hqtd\#$*b 7w1K`쇸% d8eޑ-n@F&>yÞ_#:#; tOyh#(u^r jV#2ALsKHƤlS@pFn*G*S@EgmAD$\J!x[0x0῟ [{;=ǛD>Thrf"a& bO'a>qogOWkO $qWJj<$#Dx|) qqZi;.c_(v68 E]:Y@A+rd{nj#h IѴϑ"J9=IBh ċ{@0? }_=|>ɊScз}NF$>w蠫jбL%e#h>4QL1dE {)VD촋V!BF4u2v-;tyL G2:@6`qxpRVQ׹k$^]fsv,vQj#D@a,F3Z6@mmiYZJV@emXԆQDHa op@Њ/+GY6~N.d:@۠\N5ׯ5ŕ+!|T;A} ͊;)*Jqʊ$N]}a˸Wwpμ{~$yW(&TEFPPĔ!ؑmaaj;λs'2PF5itIw|Y.9\w#hC!0KUt_w8:FҊ-ő#D&)aX؞8,)%PxՐla/]wD).uᘛ5A.U ܕh.K6HY2W2nı' AHyRBAʣMPs`:5: ZS#DbzQ?l̊ƨ-u?O*]ڑۧAG]-֔ɕ6W]  TSB:4DmO0x:1Q4;nWedZ0Q\#hf%/ڽ{#:}yᇰä'N$5.C }Q%1Bw u6a#D(hͿ?d*)nP21OPPwZ'bЕ߉BOP_fH~>#"9UC},ĢC;%r&}BpCZM2pŒz5f3 3Ë́0#:tgcI42DmsAэԚ=K)dmeT0+9p#D}_1FK9 :kObO`='c!gdnQ#DΆDc͘YWvt31$4t7Є<9:=/НfjRªXEoE{#hfMWͺ>VnbjK[/-6(4-I*0M;-muRY1ֹq<ծVZk^e^\#DtӫܱQjnvtb6E'"}[k؜Yҧ|aAW*U ,%(Ij-kYXXQ@MD5-0lm#D6[i Z)Q&X٥4٩ME("I4R4*1DlDbj2eJY%6&(!P\jAXw':?~2"S3>x!~osؒdpNLy{ctڡ_G!\9c5#: sF#D/<r1zAG=S*T8qG"#hwC㚡pUK #h w%~Vɰ&ffyաٖ2DۘK}gCJW S tDvdIMM5uݩ0Яߝr(e5WgLj&x6C{˥0Y2ś/8a(1d(/+e ) 0ak3#:1(aK*b$k&e%b2ɱLӍuWwM] \q,ZMT DLb,EbFMF3. #h0hTD(ey'}\YFLPM@F[moh.f24)Bd~x#:Щ4}5ZPC܏R#D6*\:HsRV>kk|VԙVbč&#:›?wN$bEgg#zBٹqLdoR,ȖpmC01,7ISIN#h!A%xݭ2.06<(ݥP[74B`pn+zeZ(߃4騵UZA5p9dXKcQ@#h5#Djt۠vwubq5%0O%$d$KNUf?o<,2{t;.L`X#Aڄqk0ܮy"34 dc"a(?w``z|/#>gl،k踒Wj,=0Mҁ̂}O&&NP)N^QA\3>Uf@WsWP#h)BPiM0NjKG,q̜ 0  ?2"~}Osh)WzצhjL`r,ߘ> g/5Jת{F']yu>29y>t#he~w6./)sxD(seT{!%æ:Kt#'Dw"Mw_Ox^DA$?LKH{z?U4a ,#'xY#:@:;}M{0Q}uUYBw[3"l RfebU"&CМ_y/C^7+6<d@;s!jD@l1<~|Å#2Jb`xT+6dbf#DBwF>d#:]N+U)ӽI@4h!ҬN1*d(8i`a+}<.&Nj<~#h'04?H4+%Tu:AC}za{#D/2@f9~OTq=wέ2[hSpJuNO}kZ}O=’ F501;藧OhoͿL%*愒&~K$24nvMq0\T4%I&LS`TwL3pmm9{XXr^&&mdغÏ띳zu8.$G3 u#D>#:뤜HdưwmI0;틴Ҕxb8]5&{"͎u> owafxĖwlE)B> 8nVw%'S̽q&#9 F-:79XT6 f#h@1Y767uhqsi80+]8}[6ɪF9[uˎ&m̲t)Z ߍp\Nf%+; (D:aLkkstdXUiv0HkgrgwYO+Pc\8a`ػqg^4w]ZF cCGipѶ$w6}bռalyv#:$V5wJv6LLL@-6tۈƇѬ#:Et&5¬fZ㶓RD#hsT,72Ȑvbu^Mh>&(Sm 1[UZeAf*v=o$̰f&9ax25vKHS>CN(! j2Fv$xgF0vl`kLVB'UH(V+dyAM+[i2u-4CrƝNM1:̅L@-fc/35>-;ͩK\ȷ3 ϐ\F .#h\ޚaO%4`TiMhd P^W#hv#D-4VdZJ!r ɁBB(=i5UN=)ۧYl1ڋL8Orִp_o8C}q/x#hf7?r>8&I9YAXԎ9"l #D! u95@IR'5Lk:riP\,=(łOS 䪝ޱKF)zjGiY-\$"#h:f"逘9(xn1#8`ʂ\K cfA>Q6PAOVsm.z2od U~[oRRX15E4',y+6BM#&,aN,Ϋ6wb2םaB:kp7 z<2LۧPqĉS7fu/6yaC u~F4D`xs"#Dg|D9A+:߭^\; 8luX,IRvÅ!ߊSÿ=(ƝEc ^`6a0, 0 dcEaƆ5cn@a»=e"6ATP+FDC|T2l(tK~zeBE<¯*q8IG,lUhU.PI1dCcYR%-;18.\كp=b҃'C7,H#%la$ˠHk̳a>\љGp<d96xüц(HfTеOvÙ-#:A#::`K0ޢ^æ5rآTqP(mɊo1b*:dr#D66ؠJtuR"H\#hled)8QK:!)dAA4d:vaC!#bAq3\Sttx7lQXSGch;z ci8nZ7r#h6˽.Ԙ@SD#hyi#g 2W8LaSO&DIq$8HPb}l#h6YڵW5JdsG6^堃@#D9 % #$3#hiĿ~<.(Cȇl\5|>VM9 5(%zQX'vN ^;v5X4$#:5Gihkއ|MbıJF8Q:B#:#:ăp~%TI# a3&cf㥡vXf[4(_1tgZBi88f9ȥ TRrO`ڀl%0,PQ6ne X% #:m~\H (j/5ߩV^Ldדc,B/z 8#:w!#h~~:tf V?hX5F|AcG_mJ=qdo,mtnv=*Rk,+mbXcro7={4vDMqXA#h#D@_n#DIoךS%ܠ;|Q'CB4_#Qp dQM5"~hO~萀F@gW~YEQU'n#*yEܡm9.zz**'HWvtݹӻּۭGOa{-ӂ4#:i& K5h$Y1_;]w||WKF]ͷ/y>uN:bݛqQ\_2闖-`m{.w]UuI1(|@Ռo0NbTS 307l$7mIUa7#h2BrARCSDá4L*A)q&DrS"`/jMIbņypV/xk q܃ʟXF|5s>%vF*ֲL/[wׄap1+36ZV`o3]hduF#h h9IR3f#:C#{tPP 6z,b.f){$!E}=4v2I\ZFym{W8(5mg&0<7Bg;٩X#Dhޜиȸ8S9jh5#D20pCg ^57z+#:mփ]@XM0fXC,"E9SP~C;$gM.DIO+f.ԙSc/d|HMse$fcNlܺ#hHO;s;/C&{ZVmnBŇ=ĉ$l) Ur2[CsI1m#h'92N63pn˺;-zl f]j)s,M&5M}+Y4i>ZÓ9@mmDz%vB"{_+ =kg_FS_{Q?&[мj fecUg'E).a黇#h*3"ӧNC*i(`$jpf2]x` z eB@~3`CI"@aN( cKCE̴-2껥]$mDjլ0'6JD @$J(.TزB brʭJמUhtI 2J$RT}8H&@ R*fbRҊe+@.@#: JQPJ?\*Qq_AT^$~=>5x9#Q,sMҧ@hg4~CaapH%#:5J"~]G"QIF>V@=4RHD#:Q^|نro /C]= r>9ה!Puy12e@#:ӹM6bTXΰ S@EJ8GQ&3qwycnx˸l ;O&2'Ѣ|N)[չjIOkQL4g$S˵ 4D浢k[s_ؠbNPrԝ|ZDs7.6VGkmʙ-)V18#h;(/TO=_kLzLTz8% bSؗo5;vI\FN81+`µ-01ad(f4!-hf1#`&m22d+81,X$.#D!p"lP4li10}j^kF 6\|׹^WgCQaIq8%F9L{Dmrvb*BNAbP#DX,QD'#@Gxq#:.ҥV2So;w`Y<'Qz*:ˠMyt:dٞvQT]Vax0~<}H%2A_1Q1 A{֑C9TmsSIL0Iڄ]#DC$B(R} 4sn%M@4*SlVՍcV45R h>f#D@װ(#:Y#:vLQNM7UfG]n6f@QB F'ۘ'>Yܮoy7yݝa^2"@QxtY+:{F!F٭.S_NApǽ|nǡGm#D̒ʸV^l'g99 Ł@ն f$ղ!r"MF/-.SaJi6Bk81H "ZAh5b4$KBc\#S)kP#h$#h#D6vDL'RϯۿRG֟"?-D)HhXKlY5%-,ʖ)miىm"jY"MW[zрhѐAF a/GxԜP= ?P#:#hCp6>⊉{+Xtnc.k淅JWxwsXj}s$c6%3[oZYKB*Щڄ>(Ftպfʂ#D(XN6H#D}* <'"%LGAla2Jr\`n+<#*LPMq͍O峝9\+unur^*FE7c "NnZ1x6K,덉*!"gЙl6c#De2o3StI#(‘J̎#ha|,X'0<2d^ivX 3 @`Rd)ѥ-~}6L@#`puiTRT>.:F7#3F #:AWH&C+;#Dk&cC5'Rn)bul<[L6Ck~81aSڔn% :M*#:/_n4V^ѩBH ª+0A2+2qT;'tRz2A#ZH{׾eU6N@HA㔬2'6ŭE@K#DkrTL2$J֝ϤFH#:Sf=Z*߯k(QlHͿ/]u`iH!٣DS!KYs L"̍B'Q ]Cy^AW 1U""GE[3;AA|CLHbc3GO/(RM#hsS`:?g{y?6& %֨GsHN[#T=?7۶@G 3Wߛ>'#K?%~r|M3bf8Ph#:- TX t*$#:i)hOR)uICQfݬU #:#:oddN](~]rQJD%ٝ{_6v#D{=TȐF!F4iJ1'ލ|i\#H!(QFľw5cRQ#f[QkE]yۓLjDHdì#:WbpD|_?Ȓ=AH?Dxֿ>CSD?$?5i#:4:|G#:*j5Z5kTZ̄"($-#Dkjg(hopQ_i4Ј%h)Ed6+#Q[)5шtGĈ}#(E6FQ*"ƤQdfU3Z[ֻ?i*4D60R1!P;l_ZG#T*HBO^'} Gq$#DM_{_ŃWI(cl-fֈزfVJ(mhK-Tf/JAA&!$L`@9~?;'-~NK=bpB}{̝3_Իܺ#hdQ#:?YA,# j .BNp#h|TÌBñi-#h})I|%kJ'}'P~Ӡ{ c1sqTdK0R#hhb+HQHjOYδA4qq1; Ha0HEw 72*%8ϬWD#D; h~*!J@:> A()p Ҫǿ(6uL0O挓sM5bpݰ׺ZVfZʹJ4Z$#DB:u%+0=_X)yȞA'O6GW2WNT~h?TХ1˞‚im#:Q,Dz3T#h#U._1 i2MPGh2x@xsW9KXZюGRS#hj%)Õ˒Y?#hM66-co`#WU_2jK+][PKTSM]O&-NJN*`4@P&N)N8a3*j5wj%Y&2wm(IwuwvJM*Jj̮Rt楆8 5,qƵA.P:s hH)eYX$31sHic 02C7XlkIdhF 0#D?.ER/P|r`H=,32yMI&}(#:wHi@$.驖8pϨ.C\m #`j`!#D^Qq O5S4nnXpb\Uf}:#hXW#9Xo_Y^1o$ Z1 U4PLI8i (iGIC6f䐴jcX~a)~0ү4NR; :{,R*]Tu F =30be¿jI׼pU~4I8: 'Sdxh#WL#`ɶ#:7.eÐ6243~'& 6[+B<[C!~E>#hw*HIP o㋃F-^8FA1tw^mA.]u뻞K#hԴdFF.򮴍&Ih#DA K&bӳ.5@mQ !eDȊ@hhyZb`#hX/ǃua S> +ܳ\qj)SXrp%DdC~P5>i׉3EBp#TTmNP)dU([Vh9R w[NkD"ԥhTgj^j^eg:ž a+(HlgȎۜƏiùI 4/O=krJ(C&D < `b #DG({:GKx] #:0T*V\:#:C{}T֕/9""4yσV#hP9Je'P ew*,`X"#hCM!iC4}d]$QNp LD#CC#D-cjss)$~4DD7eBmnN'XT6G #:ɤTST0U;'nqgN#L^(q~>rh"#D< #>g1$^Gy&k]>f:p0و1#:?~ERĬI1G@u^d).Oт=#hu@']&$D&YLMRiJ@fg??՗o7?WoNG}!?8_gz%b_̂fsv8Qп#:DZ@Nh?D@Cy'Ӽuz=dBȊ;F*J#:O t_CUe5Ʋ`I#h0 8}OjӸN4CW){t~/#DFfrgDowޜ)(j/F&~oQ^,4@RL/(x#h0\\!B(1DXXPכ4`K7Y*ZҩiX( 1CDd C h&T557EHRBf#h1Ah ʹ}&ĮXQ662l9_紕#Jvn먜ӝlߦ׶t9m/ j#Ckѩ#h4GkњnFV+X%ĆcKmñaYMG.xv]Ӳ1u#DOC{Xh+Bv(芦Rܸ#Dj+`齷9DfO$|r6+"JTJ#fM&]]mF9μSi E#D)5 +&M-#:V h)+)#D("V#:2;#e6,Æ#D@p:zE6$bGG8 4a塀eAáσrG ކ 530.%~=WgKY.3S':m@>_=&!6>~_O0` rfr#D?W~ʼ{ߌVԾD>Nb'QTe0`'C'〠!JC (P!hU=B2B2XDfA& 1D1 SKLH^ޡ_S{щ~kkP% cP chWR  ӑ?N?NHA#:ab1[S=:JJv3 !azyJwM"^{`aEߧQ#:HKi)*jH@"Rszjc ʂ#(e?K~p4 O>yv]ޭn]D;λ|s){ܟXO{'Y/|a;psBs;c񩮿:/fJ]!aAO^ L:lI4W{B;>!P=K_F+?'#D&ڔiWs)~W 50>?ӧm3} ?ǭe/!i8{Ķ#D-荟Q3˦#}S1碃,t=gMz*$(k?ǿd%x\<,>#:Jp AyӐTnj2_#hU`7<_?/={byNK[D^ z*!1a#:?L=?#D7QsK/(jR?N $"(H>#: +#BZh91AY&SYoY?&W$m~V#+0c>z@#+#+#+#+#+#+#+#+#+#+#+#+#+#+P#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+ᄉ[Ju{޷}wI^w(wݲ|^ûx-r)6vlVƝwYjޗn`Zey}9yʶk;X=<{wnԳuu=`ͧ{M׸wAT|}66oW޷Tt4wmh/[I(KqvNEIEPA^үwqw>#+b#+#5w[ڻ4ʢ(fR:ji;ˊ)UbmRe#/x7RӸݕ.R2(:VT(H#+I{dVTTIܛ}#/}im7ݺTg -r8#+#+#+#+#+>@>x!][#+*%$],Pփ\rth5]"v#5n9GZPu\@#5#/;aR#5{*@#+P#+@ NI#5@׾ϼ"lwvDn@64 Pm2b |>VtToyKgohs}nͽ竷o=/oC}kۜsv6޷i>Ӿumz}|;nƝܓ|#5=p$3WWF JP.Mۋڴwawv=Ol'-7_Ǡ:w07ڪm>Q_b{/{mct;yM̯uפ^u>>Zw}e6픝﷾wۓo@;k}{nڽs˛O'#5ּqoYVtmek=si[kwOvMw1qnl+ju}ϾUܧo޹QfOuøv-tܰwNk[csjQݮs==:k;75nu{zؾ/kG ٻl[k.\Yݱ>:zۚrC͸Dxw9L!׵){{@.`P^ׇKW;:|.>"T>VZ#+VeT@-R=#5Bm@[]k6]v7rEPҁ{kq ݷmWj۰#moyz#5JURs$ AGDs;eϻo#+tP#+ϻ#+#+]9o]͢sNk7Sly=#55רcdtt#+ե{zLz\skw#/\[Eې n溋EvyǀHɬRνY^#5iv:#/(}7U磽qC'Q풄=l@vsWtۧsg6ã{k[p`ç{f)oqw1m:{_Y=#/5I#+(%+Bom:zO{^{L#+Pf%V,]6#+q}woi#+#+(#5'6KX,#+OMġSWf7@qy$8}o2xLˮZ9۵ns@[;`r=/p#+'l(l=۶#+^]:Q#5js#+5]rۭv6UM(#+V9޻=#+@P#+#+#+vQvr}۝ۗ{})]Uҁ@[jK]]8l@ wJ4]briJow#+zhR#+[mR#+6TW`{sew;M˵ڷ#w\#5@ks5ۙRI#+&uL)''(`הH>yu4r|~o?w¯?}]/>Yt)w7+4U$/ѭM]gГF'OhkI< M}3PqOeK2v[$<;'O9Vv !@oslP/+~Lo2Y4RFǙE .EDZV&mt?Ѫc^ !)EEU|aDmؔ3nӠ'3oF#5@`a1o9ą@J>to3@y#Qӫ-߃i>I՘4 4)NűSm<0.\rK w8a̦$DGY5'8tAZC$'J%P((kT'Kb,U#/=%ԓ{p4'8:^4D,}䝓ZBho,?4d9Fah^O$=l~֎lr+m%#Ty5B2V~7M\LQ׹'u[yTS*uぺZEVس8) p-&)hTa.Y'|(VF9kl6qpFS$ЭV93skpu\Ζqaf66f]aI-9߱\ ͉ 3UJl#/1?Bd>7gޝܱjE+*E>Q hg;Gɘ#/Z#4_eFm\XpΕt#׏Ǐi_ S.fWI{N{|d?}A>T?^'eBP/z#*E~с#/>ҋفeH MCԑLs1[rO˜;>F( [Lw0r@oUuͭ2VaXT|p{Z1IEwpxAhOMjPRlZC1OwvèwmP䆈^(X~(0 I}i#5,Y;DC1zQAu_UehHBXӞ,8%-=*vBqЪv*#5RJ`,"2d" QAڊ9/ʩ"$hUU3?0ݘ(y)%⃭֏nlGGk)`iT2nl khq8qw'b=3PJiqd=T6bw͎*'|I-}M !mI)D;B<*R` e]xȲ(`ϱ%8BuR+ILq%Rp#.KYPPa#+Q<٦8ie Y:aN;kÈԇac%;9l a:XN6ʴ"_Lh~Xئ@y#5e."#5#5lnj>Ɋss㿲pO#+HJP+ 9ۈ 3ZkC U\*U'2f,RyAh#/Y2IQ;WhO7S_Q:өQJ{Cj{o~Mɍ#5Qd{9g%Fo6yQ9nߋzZ\(W%J-֢5Xۭ|ko3Mcl,IЌ;'4*G90f@:-1(Hlu=z}b,s6\Lq֩sJƈƷ6)ͭ-"tk3 Sm (Pz7PAeݜoׄwG1b40re* y`\gv|>%ї*Xp:5cu q u(><\HzYBS%ຢ!qK)&&)&ďwBRG/]ѨY#/a~Zq#5$_hgIzI8hOY8jV>^gWQYxgSR%<̔M!"ŞLF'͢ajGwURcx w=_T49wEJ|rPod&8PaE p ]cQT)8--~<$E2bCpc#Y8H/[Q&v Ԛ^Rzs{x~,_(Ug{2Z1֛èg:YҺqA#/$0g1 U^Qھ#/Uy!Q䃫6c#/4,IPbPV"zzrIoδZNl7}f\8ჴY\|q[6oync]xRE :e2^Sǐubw>&>*랎:ᄍŒĎh8íM%[Xg.5SJJoaeF3vM3YrE}t$}W0ken#5PX(W=g zڠ (8˖9q#5/i1F(#:BSL7?ZŸgNY8O޻Y'O;YH~n?ۦl,eȵsxVn3 n^\$H؃2}TnY7YNP̎_@ҡ&l|Oϙ&ͽf5U6.LJup&lKkWuNQw];> #z*/[j?-ښ#5V&-%qo73FWI "D:AȂiBt9iyN-⿁񰵭ub-#5CWTa#/L ̃sU`'>SVsxY5f-kzLppd%;_kpqp72\C>;A5;'jx02^p\*=qcTW*X67bka >Ǻ?]IGef`t$,/ cq6O'fu8>N=olGҠ)HQfUU)!D N%s6B=#5m>fW?fKD99/Vfe/HRp6ʕ!J(R^񬠆}~f'T fBBh@Մߧp׮qcec<o6x1tfPÚ6m-TָO.ֻ!CGBnCjH̥r`/7) "(W,Zf#/KJl뿟!S޵"yƔ8>}"ZRN#/k֩cWT#ofKVO+[DDgjMZIis۱TGRd4W6 TpM=f&)x:)j2>\n'w?"vĢC˭yFÎ8%J_Pe't5ٶV  (A/7NIVl#5Ïn1D I[ۡ@QRJ'?6Mg\09Ol|!nN>'20#//6*@PtڔR1~ PO꾟= PIE3T"0r9ݒj#/8L#/:M\סY3@pnuNe^ǔOt>3.!,˩ 貄QT0$#/MzY 10lE-L+ͼ`ʥ 9H(ӱ|g˄K{u#5)HVZ`H*ͻ{M~}M>O\&P#/td@D%"y2$֡P+CpH(d0vO'|wNMМ[#5@4fPT{#+δ!cGF%"XY>hhf1Vf2һ6ja}^LJ*4Tog (R@(H$d>碑$_ж|n%=x^.{I,чu*|sw4[kW~Br\@_LK~IJ"{řNw&(/,9sR/_}KZ#5,iX+!g0מs(Ub;fgʳv]f^R>  HԝwKb~Ux}b$Hi3F#/hfLE4yE {jӇazg^%3i*:d,7Q:I:ir|j՜*ZK㵿_L>W"߲r:XUX+\9.Xjtp}#/aiЄ=Ca˼+_õ!,)CFA^=#5329~s郞#C߫^֢ɵH8#5݃tPИ$H{4"OI"2T9큵Ñv})Ӎ(߇Sv{;(f 0ξm)ֵaJZ_/2@{OBΑ}-BEĘ*gO0(D*`VN7-9bT5#5-Z@ɜ`"kGyKa,}~Qq+8bM{M(*tڥbv 3qCRF`E{ t5rW_> .`WOb)(Ywvo=#wg&VsU#/|k؆u4л!sqe5EkQ׿aS0,'W_րxwtwji|ڇc;Q=Nt4j:^+ohkmܡ8M(՞PZ6k#+Q*AhٔSS#p4YQn0tY.-ׅoHWeˡAG8YZ蔇ÏN)ۉ)dMSѩ=*:Ԭ 4awKnUR#/pg}3!2CHkFQ#/tCe)Jgs#/w9#+*j/8?g|99N<9),q߅؞m7{?'7w?}mU)vqW){,D[yIL|\d/wZGbl֕E{{#5_ Wi,?Fm8um{T%EI1i$TK⫑H޸f#5(\r/n&#Qx@9+5Rcu4Sqjyҩ:sq 'NJo(Fo$֓lM=v|흱zO`ldުg#+ŏٍVhI믱-H`J>eֳ 26()mʹu Tr)q B|#ܷ ;\bhveۑ7V_HEŁ#r}i2]:t;Nʖa Bs¼]%1Qr#ip (tT F.d!_5"ժt oDLKyz65V)=&ZS1ȉ!6D dF>6Hq~/|iģ;>Ӵ8aD=0Y5,XŏUA#5\\W?~Z^>p{Ω-#/CTvW!G#5OWgOa;3o 2d<^0bokxm::Lmw+BJ#5Υ u#<~L#/NbD`Z>&ݾ֙qA> U}T?GXhodixӓWm[3u#/5zs{oDbGӋ],kw|ơ'^~7G+>]/(SYp Yg|":Z)<k0oSHMpݸ庪+/m>~4Sv;(<^|<WVqs/]QnR-OTd7/Z07t,mwȠ\V{2:2>ls乍8 $>[Ks0]8jZ;2C+T(H"Ҁ`Q7lcl-xh9g=[9<#8xcbeWaJZ=HͱUYP]N{R+~tJh-(Ik/H'ĉĒ^#/daǑfz#5L4ybJ}"#/Z}x%SZ(\ꢵ(ܻ0F\5`h#5B(o%Kˤq}eSa~!Ey^,]BmKY}śpg+Vue/]덈*w/~#+z@GNr) Ѐp|+vbT1;qͼ >?vq݃r#5Js|'O?աɿ>\ɂrX}fj7US~!GN#+;Bdd<8*#/gpSp?,_x`b:HKx%4#8M(jP"¶拗OYPrHaRE#+B-F7k~L+T4w;/qQ:|}P#+|K#釘OO2)"AFDѾwF'ȒUpQ2XFQQR=nzfrϳA JEgg#+#5GdȞ-?~[T#TL8M> E9sjaSAyC5(s'we#?]u`U@.:GpK.! 넡#5i,T(r*ɣxs!3=SI i K,"Rd3j jL+;9G+m)i3jSriǷ>:\c#5܏. }Gh?$al^|>#/#+P#5d1gNE.^g'I ^L"DC {cRZiZr)OD#/eV2 0,d_$iM_^rH.Y|.[HS$)-xAywF+袊`8#/+(?̯6h0|]'w;w(7b_\xQ""7ylv*~Uqşip׷k3_g၎iEчYg[5ͣ=>|VO?I V ;}'ǟzL_c{kHB#+bx=?|#/8~jLHV EaD?5xfH;`Lc~26R<эɀC(Xp]l*XXb]4$ZA<&"T]/Y;>#/״&@}y8= r&M罓C YԐn4sX{7Qz ZCKחmWz]٘3EԓQ'd͉8t#/gASϣ)nWO8"\(W!ωO8zdpSfN#/JKo0t@U{OkgqTbo^i5?=E\~'XdJgFً?Wh'U+zvn>Q #* ,VV{ {'5j$/$&|oW=]/㏢Xu TC%AdB:o{eqt#+L̓#+FTJ TUo*Eh|m9ߏ,O\}xr#+aKaa<'_.4=i~#+ٷ~ QŻ8[|C\p_߅R![J`=\燅c@8C@P4ߔa!ÌcW׋z0a;,D)>p+diQ0@Pp@ڬ&C&pX#5Ɛ8yheG-0p8NfDhC$ g} A޻ {9ظ G+>PMyHC[ZA%(GY?֣?\[B}Σ W Q]0Qxˍ\Ό3enOq|`zkx8U:"#57Jg0htʥ6z]H|6c7V\#+nW?:gkQ#`#Ms &ދlzjShA۶uOsqoTWdz13%,@ݢH1fRL=)MTkWд#j KENqhoxsk|͋D$G<{ eHHI,`eh7~k8;X s'bΣB3-cK6#Cf#B!TϹk;QDRPFj&b#Ⱦ +NwhM6䡖֗!  74c1$)#/C7oMBD10*+' a`% #/#/EIBfrnF b* ^ꗉm&as1&.iIk>az9R,!$UښԞ--Z8iRz,gbXXL9.}*8IHZhwA7tfݖ(G:80v3Ӑ#/,>`7/#5z!F<"hȳ.$c7^Ntc 'ieiFyI03 R0o9E}\ #+~GPrl?g5xPJPn8w<Ǥǒ>a'۽z9X @F4aWSAl+Dpy]LȎÎʎ#/Y}TGܴԚ"1]<`asñ1.X+}O/»֭7tmhv7 2oGO_ ^dw֟y}QGw |}%>XمyJZj#+ drYOZ;vL]#/^{^'|IBxA4[7eBI:k}H١ֲ`vMc;-g_~ay?mNrc<~˼}tQ#550orũ(SGYÕĎ#WkF~~2c|l/r]|FuAxEvIdԛ\KŠ[uQbŊsI/ V%\=͘({#/8\5IIy {ЁR<*OC#/ #-:@2r)@򁔞lhF >o~MHej]-H;"U(m_0QhS#/lfCv!%ff0fbw='#-DȀ$Zp]W"#5ڢJ{$Ʉ9#/5DL2@]#4Rfj{{S #+?RMS[/"}) >)ayU*Owvr5TvJG3mP:T?e됈jfKP_Zl*9_wq_ 1($͠Tn~Ł(CC7-7io x!;>3~rTMx#i?N?'I4'wՉL2Ky%2Rl*;N(!ƩމwQoZ -7L]UHiw3#8bLҹXݏ^4tQ3EʛX 9>YxYx|>¡"ቯar?&J3#+m~I?*lV4?x^7pL=yLfQے(PF(/Oa޴{Jhy̩w>')/wHsI*a676/G0/Cܙtl㷆cu;^EIBA#+m[qYx#5-L8V媁x$x7[-uUK˧g5缤E,8 L9 W`2xfg֛+@Ff#+Ix>nM稠e_$зG('h*06ٳ!DyOFH+l2&[g5H8kϯ:(s!(i fTmx!Tc۪;j.J#ƾoܡʙrPq`%/rݘύ/P|5W>#[*{;F7]u+i}:kˆ:D#/kXis(㦍jexJXk4J6"HWMHi˕IH΢]q ny#`+bŝЋֽ4z撇<,%#5@QQUNH;B3=M%Ϡ@ïsgF./sqA]Ey+Wo #+a#5RZG 0y\( 燑X-Ү0:$p2j>Н(.wB/rAǮz'"'GU/&FoC^-j}ڼSol̿}AԶ#wt.W,m޳?/[$%xN^)Ĺկe. ۮ٣Yr3nf>'ȲNXp%-;L7KM(*T!7{_^E#5A|xpj6-I)5X@o0Cyxhg"<  ;;9׵j₩Sml❾#/A$鼩fkQQGSs')aJYQx'B5 tJ檋W\t pwtnߏC-湑@HF-I08w#56bSA.S<*"(Pw#=bb.KPO G쨶7uDM @ׁt1^r}'"ÿ-l`"#/Dxpvt᳓<>3 AZohh~+kzgA6y(jO版iN 8uVZ΢9lJz0Wv~1{au:60;cG哴c#+>l/{[ToRdF$|WZNcG|s#.5٧4ܞqܫu3k\E`NBd.}C=L:\ \~T]*9n"b!{uF\] 3}c7ZsS<}4@ds]s": GrZo68ǯ^NW8aGrH5ҿwSs @Xjmp}h\*j0w"#=7+`Hb+r q~g`.)^fNqimnԟ/|W;>")8EףA-US:#5:}̈́d>-4t$m#/N)׏l<56ǷGH8`d=.bImX"::dm촢,k4S٠ݭe L"^\aq1]K&-zK޲}iT̻2_;64!bWPw$;-4xtD?Җ/[)+8#/'9AX繞i-2(H%0qllk#5F.h&oCit@o-A0y㎳F6PDN +`b21cj͋0ZiUv,UdY -#51{,wdLlE,@ʖHx@0Ajx/}~_(f#So'QеPc!o/wIċ;' +>yt_RFGL9&[<1ʉDՙxz=.9ppT`\kI~p tE Y$גFL-ʏ#pQrk9aeg { ơ!#/f-bӟRQзI쐵%t q#5mGH wR;dK0pIV~ 8cc.\*k@3XTxN 0 xr6#/7%ؖh\P@g4*1APÞ>08`.b[Ne>P>?܇_R#5Du/T,9!#5n7gv!`0! CyY}7}\ ?DGBI$!Ov?}瓼!7>,30[aFDž$$2ޠ&#/)X2YP(.5n0x <4w8M #+U*+Fʶ]2j/pϷs߃k/h9E*i2ZSd࣢yzq"A Q0_#*uXaM\@c &"Eѣcx답9ʖC-ۃ ȁ+ǀ{נr ÎϽ R:x" 8r?u㮝;s2""p"*eԀ#1EgpC>'9zgq$QI0)X10cq0ȲMz{kǷ4?qM3۠tAk [,9֮=G^287#Лjyg2,#5#5hr#nD!'Iz Fyy.Q9)( W#5QY;UE2S{<3larBf=n)KgGcT]&%˄5O?6ۘs5a6VHǜ#VBPL,v:rUèї"%iYjia"Od-%S@m%}ZD &`{xC '$^b-L)܌i.39o#+A*(hOl#/Zmq!fQ3-fft˓?Ӻx?Y[iL++lє*]*#_@d/\Ofԯ1/}, 4XuXu(ɥ҅K-y#5wmwC߇|!GO9aؠnwvߦdڵ+Q*e^bPU3bUVW6qzWMShkBow g)\'B#s5ϡ[XNK9)0as+sH8;ͦĽhLS`6 z#/6W5#5e[55s|%nkl4fno]=qmIawM8lKrOr}ylT6Y'2GW˜msy L]?AحCWW5I*#c_Iߒva͹e/.gg7Jꍣ_ۦ;=hufF%w̸VUjK#+2E+9R=/6#O`u?pP`[^&*,52.5$SHwͬ0`ָZr"WGzZ#/E6kP:23ύׯ(z5ܝ8ÇdփĶ!ˊgar89A>$Y`n-]seӺQ+yuQ7*9-yuCBݻ#/zb|z8C$#/!LnMoڈ?P[ ~4Q"dwmvG]͞F:?#/pvx(ɩr@H3Pj릝c^5. _R8HT Õp/,r/}]Bq<❀y`PEchgӵ9$-ta'Qm}ٓx}c7 WN_ 9|W#/p6r}9 G)(-E[\ m~ч=xBSxx/Nzq.>W엹Ta#+}]ig"1a#5bc\.B_pcm#+"aQx?;Y\ӀJe[H={`76_28xs."$(7tf,JWCkT`_l, ))T&#/KoйT14;l$0F&(zgAe6B񍃷+̤|PAkj*٪5YNeMuxOʎi7m[;W f1&pNw7e0{Y /a>x5do <)UPD7HEƞ&#5Dnmj3qםP"*o=mf>!rf#X0CdOt %նItIRl'|T*ֲf' QBN_3kH(ډ{Axlghp榓:&!=nbCBqo`n'd]}y^ڋsh/C+Snę]'8Xu*>8Y)dLa#l hL6X9Kt+#5>blGo#5@DJDݻ[*i R#+ ׵p..e"kBJj>A"bcu53ʦMwf6-CSZf4i;T^zv&#_̌f8x Z{f?,>]9{; k^ FM LqN&~ln+.ը7>8 }X1 &K+Nݺf V be#+xhHI%|L²t &oQ#/e_)Ֆ=ǡ8h\JAJ;$_;e]{keX "A+*%YHQ~G$2qN6P󅨥pU{Zn1ڢBqQ@ NX_uW n/Bv$;}Ǧ>rkyq`"g\CV<\/C=Bd&%B$ #vcji}jYW:W FL>x1G;MT-Zzlb#+v4R=!Lf.'|ܛpCŤ6v113J9[81ik]}{FQL#/Ud) y#5n-gyu Ry_Vӎ'I۷ȻLrꅋf~ƍ/K!K`BF ue, VWǭ 8rKXp2lC˵nIvHǂ-* 6k3nPFL𛥊BsB?w*htp>y_Bc"RW[E]rz\`^ky/Ud)Sӏ4gYQ߫#/"*1yc=s,9?y^G{##/ B#5o+(SyRGyՍgJ#5l*"'|j^K[2St1wO6n*^:2Cs2il)}CU򯏍LU,jv^* 9oU zLĝ|۹X¯ pB; wlQ1&ft`y~F];֟5G=#+r+^`Q`(S'I֙hUumӎIm0pG^wPQZ /wl@ $+%[#5m~8dH!_݅hx+#6D7׿H M m#5lSxw>gJЍrxea=DtPh.:|?-YsЮOva#/JGn݊-^v̍䢷봕w1ƐOF~c 4AU c#+D+:i)f(Py ÈB7f)G>3߫C>oH|B/s.gL+/*"`Ԯ5^X0nѢl4eVq\{YZ#561 8=4C3S C~5#+lh9//~kۺu<$LcAQUX.!o*}`>|$-c/Iv9Sol_5ֵ@-%A18a01c,#/|Q a#+'Ѯ1J\BpL_I$ k㏇6?N3{/޲2(fx'wG99}0`]e99r䍼8fs!3$#+R!&nLW|Kko) <˃lF#5CV%!pw4Eײ%>Hh^#5^@ڰeS;.dgOn}eGJSns*]RN>ֵ!r}i>xFՅnݲvwVml~lܛoG\gު$ûZhĬraJU%Jk9B@ W¸ #5xr n;X#/h>*TPv]j)1PeCܮF'$}gI[bK~!կK8,kS|==[y#/1PVVu'YI+VϕPI{nWů0P  &"l1r2)U?E0Y4NqeK&.T3Q7[]VI@`;3[ mKE -)KG>Dp`E4UfOUű_=طslCJ!zmt66r[^67ѵ5|75# Uq'oUؼ-AsDKlEưM_ Kig)lǑCIÚwm@~9+Vgey|h>"ur>o%q>C1nB^=Im-^7꩹G!Q;Kl-x}f1=+\3o0{|#+ԨG}(Mq[jT@x$BSu+pE'f*1_nbcvub=Gkg٠vD8sC#/DD|,A."qߜ=˼w"/#5U33 sS].dOuS7׌9w$0Z*09^.[t+]te~RY`oS=5Kӕ *0c{$P.NZݪbyZGuM[0@TZrۿ2c?gW1ߎmH,/f1-IHQYm7[Y-f s#1hB#/~gƑAed{=dVO΁c'1l:!XʃhB"pyFLYfvJ>@c6}Tf=pǟK|_:ht>Ly* O3]փ5f3^n)tђlhh/(+|`¬@D ':!h2#5MszyцS#/+ f~OtU1l#pk$kjAk*oG Ź\WRj7PsQM#/Xiip=ӝŚrj#/ A#/Hf ^x}81s N*~GldЃ>. @:#5p/}=P|#8ِrr<Wwf\2ŠQGl=g3nѱ,^*7mrU>8}&s#5:$"Ae#/v3t_^1 °aߦ9>N1BBU &rypކƖZ /2 chvپVpn׏2F>YeEoR/GiSyE'?bܶ<P&Bժ"B렶 ;㋵3<&+,u$ 5=z..;DAhˊ @S"nAAb`3 G7\cDUYH)C0|5)NX*r:G!pS O_%׎BTꙫUt9ֲA!RC JͲ2UuRP Es<"qlT=[PRW+Tqx6HP#5}- HAŠғ!!zftɲ6εn@0*OZ*e&:v'R2J mL$sGH.qx=#+DPks@T j$t: ACc,B4{f2_/+MO߾'CCY `(ܷ4(\+YxλӪ@@-}޽G'y荒X+g3}⹶HDBgR Kj9& ˳JNba:a(p9=B÷KB(v,_K1HߝZ·$DxHB[MD'{JGtM/oP*c܃Ռbq:IFb.<5vx`)"]\W!$)"ۿ|_}.%Q@~#e\o!{"YyxsSYQ;9piS*Yh#56"#Dm5#/AqBt׀S{[ [\_'.2F!Cenm]A3BFpՖ6er`;)jC:g'CjD{;m`jvnY3B}-\*ҜwP~#/6l68E4/gj$U!/gDw<=];>WoX񯌳+F0PV 6Ow&&(ͭ lCoSGPO##5#/S^L;(;(# g!2-M#+4k{2X4Bj05q zQݙ>#/ygFjI3gfq3Y-Tjd۹V0OnԍNCy<=='pgpxaWS sۆDn#.l`A0D#3>e;&_ eG(Sfv80lջygBۼc:=5A>~Є!/#5#5&uv QN. {^woCʓH2-N4Hȓmakq-+#5QܘnKs[#/@%ҵU ##/5lfP ~66E=yo(ǧmAG#/9BlLܮMmJ#+mJ͘$1#YjLNfRAVf x3ZR#}Ym3v#+]wQ7t#k+٠n5+L$ w;}-r|M'ģd8@o; 0G*=K{M:Y]!2GEWư ۋgא#55hXȨNXF4jՓ9H.St1w[ vjwā,w7sc 0v` 0]]_aC/Z1GM+(>VO۟"#/} pS3c ϭoL^]r8WqB6tZoBWA}`޶P+&~Sُ=x$#5(#5)QR_})J6otoҶ{lշ%7A?^<]A.Nj[6QRoVu_  %֤LK VL+/m*r;#5͗)V#T\f#5\o0oZWQ\wztrm9#L2-:öpxGE=@g熪զ&N#5n{d1^iAdipTVh&]EE6XV:> Xȭd xs!XԶ fsetO>ԋQiX&Dx:tVyvYXR:A!i׿rЇhDX,Rn]~nu5C\fd7 T-Bj#+m+/0o0raI{^;&79苵+$l1!S#+r4$8NKo0ӿ}񒇛,\MF[܏PU6JboŰJ}#/\vP4.$}m}Z==u*#ws{.t0RJxlO`'#<+2Ǿ5YJ "M:W#bl뭣ibF0OjOIKA)ankxp7%#/D#3hxam(v$1/Q*[9e}W+zQ >N)!U4;m\^/ ڋ6٧?Ǩy;=Ca#/ixo=:פ3ґ8UB>^!ř6oYOqݹfӅD@LT;Ֆ2iU$SO(>[[)> 5U#/\R5)8Ym+) #5PMˢuwֳf^VT#+;!,@@5i>RDkJXѫ|C~ #\ &T"6e"&S(}E!S13[;鹧6AB0aC&abQM_Q_w)O9~U!鞢:C^ X1!y&ѧ^X؈Ƨe)iq&+gK|)6{ nB(%|^t2{Gޮ<M}m bG*߷KX:|7D9gXtsMw˷!v P^+W$6//~.񡶓ߨkNg}@z>IY #/ÕovX-X=,7q=M:R=-]B;\\_;$]'S57_yxn OA`#/qYFt$5#5)NmA{|Gx[޷@6^NgCmnFLtm>}@ѱۥຏ|66|jFAy2>0HV<ȃ!㋔+s|h;$`yѤzn['d=v7#5/׬fw%8&S42r0)fI:h S[u_7ås>{0l~Qd1voyJboY;sgEߩͦ/al6;I(:7[$>V!7WiE@2%~%Flij#/ck'p8bCeTq҃*u6+tgna\jEp9x9?9F}&+އ,FcYXc_7xK!Qr䃷~c0hJ{Cd=ݷ(/[S{Ocu#+|ǩet$m1ɣ^Gze(^#+bLseeo0s #5 3&ϓyw#/2&Fy"زa )k4`K10 vUpw^SvsR6ԅ&q zǴi',j7Vc*ZԢp 2:3W9 W")T2pRӨS楘{֍i/Cѐqiv<:f>#+un\s|p]U#+RU ~N ߌM`>HM?ơ6*1H싣8;%T0o3 ܂qgkb("\е{Hq}Cmcfntz~$/!?[RKav+1mz()0Fm=KK6o]6PLd}s[Kew[\ةQ0DAb|q!#/H%׭*@cmY[cU6|e=bN66jb;+2Txop<A)oUW$v(&i^<OKjm̗]GDNp/!0Ve#5J9‡mjQp襎RGo:N#5$<8 ڰΑ{c3P]!RD%#+]6۲(:k?m6Ng:9:orzGS^.&GzP<#tvf\<]7pcЛ}&, uOE{~&voFs)*@dȯ/+ }h E)Vmݷ;Oyriͧx1cG1g5th֠q,!x=/go]}WXoX=Gup)Dk>fp::Yޑns xsnnRbO[*ZJ0+F /|#Hh/Dv_auՇJWAbA!xY)N##+&PhB@z}.#+*?BKR;1`B/nu|n$r0LE^F2)i9#5jBӛ tF|.*m1TFǍ`|d)aLPIKØD/$]L#xxCTlZxY,OU+->ZKPZLWR 8$ ԗ8#5^xb__LEUN[;YWQ:<8ɧF_J<-Suv56l5O2AWS G73#+w߳P~|} IӌeXVTaA`EE)^zDIS}MlR (#/ 3cP#/<6mGyH=(ZF4oY U3r>iUhtK 2X^s_o]b61h&Ky6S5 ɟi1=_͝XޟIng7H*Y;n=}7 >nk:スxhӁH`jR/z08 B-0)X!!$z]54PCEUTE)KEDYĆ!К+1 ^s(|899=5$FJjm0le^D&8Ig#+0%JfXSf[#5ksBT4k#/ߊȣgnǶ;#/Iǧ+vݩ%39O!uilOI3\*wdm‚)2hoY{޻5B `}03QHb\傑bY#+>E{JϦ*|2LXnr>gN09;AyS|zNtY`5z/=K#5*U83 G0@83[D0:piQr|8X=4m6OMB=oJgRjTx_)MQb,hZ%o(bw[ܛ)(x(RD󝮝vciM6lZ4\1Dl{nw{[+zn';p~mh:?R]Gc׳s>o8*bBtc6Du]WiɽcX Mv#5#5. 9qŔ-f=4$cu!Գ%`3~`MqH{Û3Ǟ%4m3iro3r1g*EbDVo 3H}05׵\c{HZORQ-u"9@iMORtomH>.kmg璎kXZ|D:mjN .pD*U|ɗ*:|DHa1u7b H|]eg >ZT.\%+++l)%  29Pof]z+cNw;#/#L2uϝU#5ߝp(9}V3ufҦraOf'^|G+*'%~fV`zumknn#6#<+\ZZT9GlXK~Nxܾ8pP-&|ZLjS*^c"xJfr; Э C#/H֎B &5vBJ#/uP"F5>9ON;ℴ7gsӯ<]8~usXxeW5<~Ds8#/;wYNqG1#lh}j&Q#5ǝ|jY&sF^尳:֨9U贈dP۫I0V&Se2I7t2:3Zʲ+< ’T!Rv70HNUg2]}s[EK<%qj:o~2YeV걅 um7c6T:V >_uYuoo^}uQZϳ_{\ F`ȱ\lR}1|\ϺY1"#QLuRjYt'x2&v"xLHY{FXZ:*QcC:`֭)Ԩ/F0첧ڔcb AICzb1yN:n}'hsZ;bui;/;bt*gw[D쵎8Ub㨪OvUnZ63j&WXEUH]#5=ˌZ0Q#/-#veFٞƖtW4l۹O|mU5f˧#9Œ#/bG͏⇎ɬ^WxiٚōGIαtkIf3>8UhTȕAst:4UvGm.-LN|u3G [ηv'm 9)[1fߞ¬wp6#olڐ KCfR#j}w@JK"5#564XO`#Q) "TUh!j0zfNchʡi}:Ykqh4 6KabH;1񾳎`oZjSpls"v]>Jz|5(YPJ㤎ɝbf6D&eZ+Iu{t{faIE[Ջ~*q 5bZ:.{)#5?oujZ.XtBz~u=)2明}p֮#/XjҚìjQv!AX05D(S#/ۃ{WJeK0A&U9WLvqpf=NmkeaشH%~ݫ՛\1r"; ;o^KGǰzx?m"cN,r>e|d[38kT !VA:#5#+#5(7%0~MԶYdn~OkvR("%I,sǙm+ko]&2Dl1 bU:d8*a, ,x `d#5Eh0do=^w}m;s"W/QST9NzBt,_Ug,88H>LdK2}MU:Xc;X )bI= "I|/sbtƒ)7UO0ud֒kB$C#+쑝7h(ɭ#/悝q=MǓ_5AciD6ս^JC ht7GF5W}P$Pq,6z/*p3#/ +H#Cbd![q58_62kV7ż뭴η>)KB"J*<y$1L#f;y5F %d-0e1*!.o;~KκP $zؿ#[=(DUg錵̠֭Pk~xÿaԇɰ|fS߱W DN!\ɸC'̷ظdF.tS78׾2cLъ\v^$7>Wx8L_Bv q{jJqW0.o`'9HـG*Kiu"'u~~Jon>+ ڏUmw;7F;L1R҆KXn6KչQ`TX8[HE4CP,}kdU^8\vw_ 6JHI 瀇?̇>Kxq ߨ#/GWhԵ<%_sݻj 9l@@0{HD@XUr5|JM[tnЖ#+F #5xY R%=9#m@z*SD'jx*M ڎٻ_8]Rey<ӗcuOtif_>3z]֯n祸W~q~.*ͮla{g?ءpVW;Ypm8(hU¹ɿw|{9c9B(f;|ـ健}8Boh3Ċ=ʮq7/~ă 8w_miq#/7rpU;z3MfOnτkW[).'.`W+ 1GqŴvF]chBP?oI7#/ "g_0s`b)W'%m<@SM W^%q=\'j]x_b t.HDDĬP@U*YUJyz]i\7RAdoV Jje"bR"tvO^s״T7dAH,Do406XZ*"i趧ч|p6Aۀ&3%B XP]y ތS@Zu]Ym#+d,hBF&<6{.ٟkU#aӧC\},d|KGh~* 7φQ80ۙ#5#5AAIrF!#/Ic|>_%<]]{1pGp_.G~ziU7Ъlv|g79'lK/偂0ۣ>þߡWZ;>U'9Djru* ?t)? ? O#+k?~SxzM]Z>@\!HȻyIHO>h*.#+8XI{%|ɯ-/I@#+@+_u,r(xGRgo:J&w_Th{[B̾aPu?Dygu:m#tU|xY_s%|_#/[FڍA8'2v&/&!Q'Ҋ(҃BQEC)+/HHz J:c/ ץXơyi?eW D~#+НI=~8wAp!(PPa5CgDHOGf#+#dۻs#5NR7w ⫪#j^Rw2I 9IrCOZ7Ϛ[q5w\2liati7䃤ҾdBC)?6vde RC늲/fj EA X)$)%R%(IJ"#Qy#5Aޑ3#/P{J.|.r߮]ZtL#/w[íXrƀ>o<=~ΛmyLw }1?L#+sה's+~Kt u[oU'ݿakuo/!#+GH_C&%.#/L\bRJd_:!F^#5:cT_o/#@QпZ|څvۉ4)LRpSO?{z=g6fpi0HܩuwPy哲Fv*K`aSY$_aaKM1]Z@BJG_ڴfIm@ǦQ*HHi?:%QF0hi 112 Nɀ`&6mO`A#"EAFnuQj3.F:BԾI!h&iV0qr 񗫌\+Ik!Z(1PEj=EIgG F"G{Rv.Nl5HhV46pܰUlnZъ 1wG昢/6[rS#խQ_M*ޤ9n vd[9j\8n(FQ3MBc)zs#/ʈ$5Ȥ6[X[~ *vI~_pJ!B^o/MCRLV`fӻqνٰ;5hիj O-@?,GT<|O?;2_zYx)?k>*`()jYcM`Je(HD|Ŕ#+c~Xf#+SHñc8IGv8~0y{|˄r(vQB,#/F@?R= 1EJ"(L$?T6v##0P%Sʒ%PU[mlSpcN$^8F VYw+D.?jtkFOr(x\6ܺ0$fvӄx|P@45u3 >4T;Fvӧ?ïXGa)h,{DŘ109hF?rR Z~_>!0_rbJ$O'Vp%f@cM)| X7z b=+#tTaoN_QPִ,=f_P8LCoԥ/:[~W?ح/1zb5lZS=*o1FtOG$%{R#5o[} |K}%,3>#+G.~X^M*㫗v#+9:J'r zf$9^NE?NmQ{6ݐU?Mnd<]9О&eC =fAgx#/kYcSHepp#+<r.dnA9nn-_)ÐnzgUYJnEn5\<=ڱڽx|1nh5yLg@>b-0R27v9)S2ң!U7AϘ\*\Sjn[*j+/_ߊЄM#5x<] ZUO۠o:Q(=ޥS@eS{H*=(!cb ׿u>pR:x ] 1^j ).;-6=kN$wF4q)ewȃ/pOx(0L6~Nl>Bo_SSyHjRch$s2)F*JXIVY(g0eQm#+DNB3".]w|!]yl7y_siۅͪ=.C-8slFEw'Ξk{MD=z&9@OwutH0w'9#+O9Asag=s:nz>>4>ZgcVWߝO}:G}|e?>Ixp |]\/eZ;LG9N^#/jN+]z 5zwfq\#/"wnsGgN|q 6£ϯa<4'W-߷n89~>w|yuoU;!;caZ===~>Hàr뗏xu+w~up٫hgv!x2=IMy1?g91w·U;2sW=z=ۋ#JKB哸K۟Kf\nͫ;v}.'_#5Ի.07L]ܾei\mXe8<}c×G'\|.ugVY$ÃշO¤|r\#^uѥ#/Ӑ^%D!}KFcv-Z?qoGsu'6zvݨ|0Nwo=/}W6&޾J9Ի|TGs1e#>-Op-@Z kxcW~)|?mޞ}qncp_2''Cܮ^<4t>]xl3̶6N;p#8].} |>mh#@ki~oEWu&ӭ[~#59))/$kw:0C˵e|~? 9?Ӟ?L3Di$523xj!uռ|?!~>mz%YsRW~g4Ǚ`Ǯ^t~X~kh!ۏ8m|OviSʐHK$:ojVOa%9KF0w'{φmXٷyk#5j4Oѯg'Z|:i;WUӷx}:{;?w^ je|=:ǓUs6Bo539cS]|۫#+8?T|f [Pûٱ5K48Y8Roܣv'?mDNj=X!W7Z\:c<\#+=#5>½#5DK{7F~#+n?p}DD$:C>.1yO" ycyy`wx#;p>ʢIkbOJuA.Y\J)&"*)z1&du:U bN!+^+QKI^%NyqjmMP0ze)v2qs,m *DS0_蜨t4eS`Lcz##d~5Q^{F7mÔ}q$ΐÁ$ I;pj7DyY3CY,Nʋ:5|Y3Ud=dCG9"S)^H'=R#/>?#/:d]]՟W&ڦV2ܦ[ 3v6SFOv'~0`3x#+F cd{%>}w`s0D=zbɢozu!cIِ'1ylP\/@dP|"*[iWOAP@%(E;:)_=GX{yO\[Uj)~n^AL|\/Kj#ѿ+ Ht<qYkD'l|wՁz?R#&ي=oßIc=75qD-'ս61hTJ\L6$MbqX؃AsfX9'!RFSG1i1K\9p1΀8.VҘ*$I8Xb64Lij<uqޞt 'h#/1E;w80Չa0#+f1G*vݫvҌTOۆ`+!Oapdbn=#_Szڹg2mo C5ˏ'Mo{]B `PPo|DI CI0Qѡ;'e%A$$4UY)a(ADNVϿRhៗ~KEeխ?'[WY2v)sD^6/-%)`os/g_?Ɲ^=w~-NS[G@w׫w7b_WJLސGX5e~:.{R_.<"(aYJjӎkpl#Ϸ[b2av,vH?G#5?w>??ʉ/~9ȊjY#/{}xfV#/Pt#+OwKY9<ƛ ]&ZC*(?O*/6&UTZD*k3G"*ܺr1(0#51saq-7-Dӈfb3D'vM#+ksoV^$LQDkG2bKF-jCBYd+CM4ו\fÍ 1X6cզP4FQ%Q`*J693>\0-cqq.`%bp(oI!s7Bh#V)#+pd[lYxG#+7'yrIL2K l*b8+H6r1$60pbEu,E<'Pv&p+hȈp$ lX`[YJg8R6Y#/6)DxHh_7 /h^ j?vyXu g>7֞=g.F窑D=W-߈t$}s|㨦ˆNU{xfnɀ)GJ>T$bi[#8CaǮ=_廏La$Q:FԼ,}P (#s&"AvlCϥx#}…ݷ,L)75BjRT1>%߇&xjx=N;JS zw#T=p@x#,GkaUj+st,Cű~~R<:s5P1Mo|4m*xҙ"gK8bj9͘3<5<ɯģB) #/.E:zČg/t8Ln21:Ann{+D۩(FIƤ9%5sTnKqE}0v9i4T0|ݹ$#/(a؈doylhR4ݲ1QX0شEއ#+qmZ>E4nε1Qžf&ڌM` q)F#4!C!;=D@IO(#*:%nFL@'Lf&rL۔wlk!c9_vX1C`4n~ȢKć0Zj'H#/0d AcΟ-~ ^n]߲0 w"yC#+"7Q1xx)(xޣii5a##5Tm?͇s\g#SS #57 eᙢ5;(v!;%ؤ$ lSmڼy =*Os!]$Տ7.N4Rjtivj.CtfH2"߆a2MV0p!6c;m M@FF3F3#5w׉ָϐqmAxnRo!h5p靯EX@ya];&8ʋ3ɹF hMAĔQLYuqE>Nbڄkln# mrz@Ump39!↷<(8lJ#Im~yjBIx|Og~w9|X bD {K|Ջզ6?Q62m&5][=6w1P݄%d!zx ?O!+x[#/پP?\j#/Fܞ*Q&{s*D#H}a2Bn)9#5M5)}-gnH\=4t_hp_+O#X3]-)EvH&)7٫QYϧ0x -6I @b+U#+~ k0&v`K$\x91T3(zVPSʏi7t?% 3O-a Z.mz}]!YD0wi$(jթx#5\7+d,5F{ ,k{^߿aOhXWW<`Aa#-[#/Ku16_"陿jf#+ ”J pEۋCH1_P(&OzӟQ+r,2'$w#m1S=u#5C/5.$Q!&0i`HN/m.P!JUI4Vg;sݠ2@nc,\ ED@,Wjϯ#QvۣKowo5هrNi7v0 d0-9C0g,2zZhvxxDǻIa۟j0ꔛ5޻d|V1Gĉ"|w8ǺwjEH{|pSX8̟+Iz+۹i%{2nKex _ I.OZ5S֖^k62π*vC~5UMsA(%z2β#/rL`=կhj]NA|N|s(sfˎBFLY&rdt"#5h0@JzT|Yh ;/uDdmB}C`|N&"w#y*`&C>o&\Nv~hg)Y[cgxːX/,wrc=qF kwQpL@-^kr6uuK\0=IhV<5ɭ{1\t9r Opڶ9Zl4[Ki{(!:^{C~d r|%SFALj 0RQjGDTlwwr\uwS|o[P^M#5fl9xn-=hP67\n`6o_/ ׺$Rc׃ vds:iLa5&'Ж['**-Id};g4H[61#Ze|L>O<&o[[I}b,a0*/ᑂk{ņS(dʡ8k8\⑅> :&`~YW}β `AaPZׁoTYu#/}]}U<3D_^֠JOV+PҬLbsJq](mM)9CwZR'c'}<ۯ7;^g=}\yHHѼ_)P9"3] ah@",L76 j{r~Q拗|v:I oT氰! ٟIwl(⦥ܛKOQ3wYpZ. =APjt:ohSwK Q7ѭXF򘻕멟Cs<1$um-#MG2-#Z;sEܼf"m<5;Ym@y7N],#o]{,֛ݦE`llv2R=Kt7sw L]3՛͆^Bwpj⊺c}YLAζgx#5;N~;X߹F%%%;?%o["#5mOMȑ;8l 9%J]i)9;ffmZ~]='dÉUߚPrp6/Yn5LAJM N $ʻdz[0}U&H"^I>\/8a8g[Nԭ;塢8vPbէZcNK-Ζf stӦ Q6#uG\]Ԏ5ZN޳tex0^ifJ%y5g|̝ǚwBBxٲxga7z*L M2ԁC-B!u,[j MNMq7(w&uo[rDQ0ܙMuᴎN?yq#/vC;b"e̹N߈,1[:,nVuK֫ka8E枀j/v({?9_gQd1:&.>esY\կP;Z;=wvj;8>WN`o>j×M0[tdMwct&"=/,,n{{>Tc3nQIF]']M^c\c25FvI-z9Vj{,`ĥunͺaNo]< wD`(8ZG[;$Цt򊈑#+\6IXxzZe+8>W'3CƗQ>geC=p-G: BA6Aê8 #/ pmFW2~:ȷF:;Q>潻crF6x?jrѴo-҂~%& d5G7Nhϭ 1±Xb^$Aƈߛ)aEyi nxޏϸw p'u0@-`W.`UU Ʉ_Hҭُ5vH0ࡧ#/6.Մv˳UEd,2xMdbD0}3fսԅ}tpk퟿*EumYE`]vïؚ p #/WeFzYgx2[?t-Lg:NW}m8J"}Exr9$mX=v1tn_d۷9} ڕN-,Gmw~aQbkFϻ W:S îzn* UXdr f5dؼYE,md5_,B#+P/LC%,AomxLV-RwB[{)6W²Q]yt5š]#5֎n75rd'.{Zm!Yd/{9֧{z8YuQ?諭YɶBO#5o˱[B)O} Tpug9BORI ±6?;8tG]Lw(^~״`uG}L0.2##/Um{2){żw].5]˻IfbcY*#+̬9Ӥ4{ߢq{(!4žra_J?Ɨ0d}9!h7|Őtɣ|I5(qk-~:iP:PZBQZ#+^ul!>y)-4#/<{%ΔϦ "ږ,q8!y\Y?*YmFTx^zIm;nд=|VPAx$_2[)>-^_j$QkDl /tjZR6|VpKJLܕ5N>״$H?u=]a~sݣnauz>Cl>>uT’}n{ Um;=yXMvd#QA)*YBɥ`W1'S1j$ӊz/}ֺN*nCwBY0?3[s%ysGK4oPCL(9<<_]~"g:4Ax8F8rXap\l_l)#/ܡa;+fWAӄJ|Q=S/v~%dY=YH;OFeZwVS.xt^<fa70UY23#Rlw]ADX:[/xqة|ʹhѭ/1vƻn+w0y[)~6m=zP\;f&xZ% ckFY#/!3pk< C\B12<23$3.|KWV1;u8?S;Io,ހҏy}u:8wuSA{#5:E>}Y\͜g(ghhXC>p6\7OmԬ'#/xAblϼ5.1ٲ[fuZ#/8eׂIbtc(kN'cX`#// ^:WRgejF}j0Z<թe>eG IC]xwMP7=UlTXСC+QUwSg]EnŰtVVnk c̯N-o{x9Wv>TjɑSrнݫWK{ݜbڔutxǐu-_mƔCءt%IMZA}CɢXIb)Br.#/Jɖ,<)u6\^%`s2t'HDaݔqsJk;bV3Hw-1w0mڗ=(X YPPY/F#/Ǩ3.n9@K.~b9/N4㾀x|F\+MVb;UX|n#+ƞ2]xd#+?)|*j#+BAC#WB H(B8ê8WH:y3=RE#/%e6(Us12E4 ,^@)0;i~pʀ9t‰rUrtΙönv j**Xw]+,$G#5zUO~HwU9@r.x1pnu5"y]Tne>Z]9N(V4qœ=WRsy*k#5`k9 BȢ_SSs|C_9F0=&ٖskPƺծm*Ry]M o֐b[f@|fA uW:=;4k !^uR 8E3 b#5Gw7;O8EQ5Y4AYwwBPXOۦk괜'eRP>>{k-vB9ޮ 0O@#\'6^,U9%!ӆ/wԈ੶7rYUtzt00;MBϗ[NJ@4Ҝ.b'p @ۿ|r6mAZU9vspL;%~DLPE(~=E> Ժd99&j#+şh`9\btkI?.zTK$/>j;U4TlUNw>55+1D@8>;A]yR7=j8Ih6DO'cAWjt#yHȎ6lï P1"pi\@`w=lb8B}(b^6m(mQjE6WACUw9F#/Lan:ۂ9o3w\cci}hjwJ]Dcjib~9If#T'H5u`Jjqmm}͜ZrxM^hSu&/{dЎā3]YYS-Y1~{q۳+|`3cCw*F*lt|jB ij^p}qd\#}zӶQN&Q";7Rd$xxIdËIP1!;R*vPBw)R2OaI-t,J Ȟ6b٪`Jx$6\7Gke38ƤZW S9=CLĪNvI?=Hfnڴ_mwlɟk FU@DAfS6”Z jBKkٮ0scN|2BFH(;) 8,e"zOQ#ˎٺwؠfyӿ.aOcbQ }:]MA`V`Ero0qxlRpPI73h.ם#5tƌ)#5d*Ѷ#G';'sPllK1#/f'gs[j"UPoGviL?h(h@G#.{]xzo_?ٓ *7PxO􂣕cN.}nPѾt\q'ox5ho`Up"p]`2sx!j֬ 'Bkyi#+˝ogup?w7&Tp!G!#5J"s#5* #5=:ۺ'{Sl&>ɣw($?|ýy~@9=J)TQT!5ȡۯcpQe?P;9"oxn>.;Bh"nGiR/ C5hpZ l;0Y2SK&^BH*lՀg9 #5.1+=Qgk[=<#eçf1.k%gaay{.}WZG9HVmOL)Z'/38cIÜ!"i?/适ZJ&$b!DJ #+Pp>WkgGϷ_gGӗ#+#5_#/6Bҡ. VRܑI_1RHclOYޚ}ex5EwiD2j@s\ϛOUHTI(&Hjc *)Z6#+|:\I !ix9QD#5#5HHFJ#/@` QS*0P w^mcS+R\& l+-Uܫ?bHbI4K}%@瘧`L#+0)t_]c|9#+F}  #+]#/H"4IFRݓ@KH4b$iWN/}qe#5321 ȵXPLUA wR@[]j?~ޒ"5R/~apbfoYj\Nw)$PV7\%ԋ$HۂI IKg_L?ś8kFG c,1 s3.M~X=Cam Q꠯g/ͦ{NҺ!UWu~m% )'.X{dxCOW͇#m) 2D~Q?Lx6^TU$C~δl=*`tǴ;#/XfyV#mws/@z&܊{:t-`eS}B+BdHt:Ћmn1/5͵~8ؔE#/L`f&Mzp4޾Od׵7Q" Q0=/ !$/2.qC8:ܹ:SfYȫwOGď#pu?$7aFJ%6iBkK=#5X{YAU7/xn{7ǦN evBL7܂1(#5Y!Q 7m3bW8) oÍnd4bXB.j1D9D?$fߏQѤ-r +p#5XSKƽøk^4_SwȘ_ z:6G~3.}wɳB8=Bz*sYcjru;7zwsC$Er7NxFr[YB,H#5`̠=w a#\`7 *L %9 wew[wf(w0S z+RaxH ҃?é;&4>0 ۡ7muOP"NwC!%yr'\gQIJIh<܅kUgP TNC'':S'Ae";M%e41<Dt`.զ7t|՚HPYv>;72h< ޻=߅.NK#5#/5WJR.oŅ+̺YWBn%DB~q uE:>c`y=? usp :5.uW(=tDҵ=x?Ei}zdϯ!Onk[I)WǢam wy|T^m@sf>#Ϫxۆ0sZqu su%ryPQ#+#+ J]S{>} @ޗM(Fٹ`|<06 M#+*or84 +zH k6y^.Ivrs:sD%Fc~Dk#5Z="#+%խ5)U[>P,r=f|"$r)xWjQ(D̻ˎ\$J,0\J;q%N_Nn3-3[bIT8k}>#5a#+ҥ 週.J c<qDp{6 yG_NI[rÀ+D'rW3 Yzx#/ӪC#]#5#5c#5A0T` u(pd*)CNLT°xN:B+ͥ':t;A~K쬐UË&fvǔ8̄A|‰D[Є4!ٮi=։t#/xiu|FUM$T/wD 0f.HfݥL#5&J uK;0Uj#5;ujixXmLX;אj+Fd82$ 'E#5*Q7vͩ?Rgtc#$L9|ӱYsDF6?ф0+(~#+%o~Xe[VIH@)[s]8-mR`l) >޽:iY6#/}MݠnGs@ǩb!ovs4j#p}8)(c7iTeNc={4aɜz#wy^X8ϏpH\Aaϋ>t)뛉0#\bl[yTQH[ntL|YbSCO#/mckG7ǎ~`GzF3}(c~ې#/ Y!ٝ55{ c x:x >Uoo 9m7& xHlU\7NuIJ:#5+$5-lx!;92˴GO9AUQ%QC\tAjhb`g|8h-wAAGq[MX*CoB\/ůa>>O_? ʹX#b\7{I^8Rf'ߦOG>da޷,pK:}% nbK5nijJJ{8hp,̻ _;;VpeqVu]oYl& 8uY&u2d􍦂 M[^s1}TRM~gT~E{)#-d _E?/>_pPDg` հX0n{>GQgL)K k܌ꑵ+8٬]3?f_\ΓRg 0o5I{?Jp:+xH߼dU[u[$ cà P@Kz_:}lvunwz0=HOOc#j%Cٶ[~~9>p#+1H8(#1YdOov/nO7WGpW?'pog'_/Wzk^%'OЊ4_b { -<9?V$O {mٝkWU Ʉ 0mV\zxgz؍-L#5zFcW#+?Js$#/Mc,|/3Aң72T)Ȅ1 TD uAl<x>ͫd 0B/`b^2=!^ *>0h{NĩZJ@H!?[|OKǢ2 .l #/8n qFJ9Huw /[ϯc||QXJ'@.@H7#+#+⢽P|ٌﶴYf?$W?Kx:d'Ilu*{G^wol8 A/smb"LPƑ"oT`'Q/ ŴP#׼3e3j4EF;e5?,{0{==;,"i!5#+gC%L#;&RuGb>[I(I={q^NfJWg+LBlGΎm *]ăULe4֦ɠemvq_CR NUZJ_aZ.\{&:bdCe#/4_\)׬,\2V0FI=m]T8x!5FC<9ϥS\%˿C%̚lCB쨎fխdf\%$l!.qS9O|%C%yTwJ#/ A2@#+y^gm m$>Lf4F>W:q'-J|tTT(@PH2Wˀ1r ;w VLؽQy!ξ#/!"'%ԇȞ 3ϐ!j{fv}ɜ8T&Q@]x*%}z΢q ȈJ4a7#+ll@uv6`MvCiؙKIlav%9+&y*X!Ȅ@)x;l!GP(=&㞢<6(gD' &ٱ4C]߸ ԁG>ӦХזzf:S KJJs$ DH#V di I5[uυ @̚ݱAf.q>C P$2'Tc΍dz^:O*NP7 C(Qw+X; GsPŷbK6? Q_&;[aa ͵=Tđ!KB!Q-!f*v#+g3:N?aҢAI8q ]Q8gKI Abצq2 Iyh_]/_[rhlQnELYƂ#5Fٌp|s<~\~feYTܰ^4JjQ9p0Ht=8"S}'sN#5VI\죸o l#/d0mg{xҳ9q=矐ycCL'm--MGO|}|Li:W H6LBq#>Lb8N'$0I0oN!C@[)זdsd,chQ> '`CA*|-j:ٻaNط-T2EsN"#+|(4(*0\U4Di;Ag›b#5:hE&BcxWh܊u,Q#+qsedxr}S7) IH)2hzc'}SY:9fA˭bV/[#5I) ؒƒMs HTU3LQEuw82>$mᗶ˝I&1w9H~0 a@g]j]zSmcL'*͘$FepvScIDs~x՟҈91I5A'-N]L7ԩ~3j{*s#/HLJRM+TS7dZ AfɆ8)XùU|g][8 jyćF~-7#5s(Ge~]e,ط%6c`C#+#+h^Xa<s8gRz`KXY?7޺Vza&e3N2PbNn\7$lm5J{W,s^(ꁳ$X0]ɈyQUɫՊ",ANL#/هfx7#M2gOnb ;7("i;S`bł+K8Z<'PwvP$f:ju*H#/0#+7Xl7TnHi`ؑrrpmZ°KPl/t)h8Z&N96rDŽg#rbm9~Cءw1pi0pԼt|ga#,͡}~ CٶaNy9a-T#5:-gR$ҩSW:(0m#+VmT'ajHف\Vks8߹ќbrgj*}ά(ngnZi!6F ٻк:X"^$6lOlc>aU^Zz~c~y9 z >:{3xݝчm9 p>v BۮuЩ W~|6&qUvt>i{8@rw&.H\ܧ{3ya=vy ؠ@8{XoAiT //^+>h0hƓ;&q K:6wH#/tt)&">(PzN^%GA$a;CADzdy&Q[FoT=޽Cp9tx0ueT$`qmt<^Od݉>9_>|?!$2BI+B2E߭JPW:lh9 =9r:I`GyGY$+.(H\م&% ,!l-E<3H7*ٻ!pqT(?)4݅Rtg񟍔6{N|~(tCްp/[(p]Gye(?V?3Sb.oOW""@\#+D p yTF!#+?_ÿ_oF27QZ3-G9fM'E5{m(V֕p}P@#5R@7;7rW#5}9:S﷈x*0^),Hеۡqg"IPI$Q妇#+u.!VUQ =ʬ f"[fw 0~( '!-Ͱ.f-6&f}u풝6w" #+Am>!SԒS!- _by!P|/f8#+=d)[TI2۞P%qߗP"?>uiFR-mUYީ>s}1u^9 =11W9?pG W;G/_qDdT#5hxz({Ai#[[{}c{`jnXS l89DE jG߈~#+h4g$ P:WZA},ؠ к_(dA+wJT|ȺahW'_am^#/ 1+-72{Ͼ4ӞVxϼD_st?YοbJ8SYN}P?8>D@D={ .@N`(GwPϟ9c`|#ϡRS聓o=n?#5}0G0N?oƃ"oW"D{^>`tqCD s,ck}:~o:>Aw>"^>4ޫf HEEO d$>@}߯IC^pϖL~?1:Wge6f@]#5BH^H^𶚢(T"JƇ*ʨPkk+#+Q*#5O#/Jɵqy~L擽̟oaJϳV8u!&ɣZg$M>/ݟ-v.WqH &oR~`pnj)K(Y<*=}V\ޓCPR阘`yx!',>jβ^Ix#/ ͽE~|s f];Ƶ8b7Y;q2^kl#/RRamhAi#s7oұdg S%/sEHK`!#+Zr:z59VW?U_*f())v|uhuwk#+V@hRX|#+v(O=}ۜDg:-~8v)1χ־N:>yga$<5W=$qK33x;kw aC Na(y=nLΆM=]{aףqɥK}ө0E#&{M`,B+o,c#/tI2ݝ@ "#5J Uu#={V*#+HG7?|ހ=DD^R/3W#ׯ!5k-܂5A h%3j[v@<v9G!^pZo=/As#dd1JW.6s#/}n+0TЦ91YE@@Ώs#5`D33jx E#5?g-4hHW+ .Dv΃yUHߺSpBc"Ub CLkϬ,Ld !H |p9C=//X(+{.}e&}AQ^pե$T{%#/TQ myF!h{[`GY"sQy*xk%^V##/d;,|e\##/ʬ˭ r؏4sd#Ed@k9ƯbΊy_3ab"'bkUTirf?L9bp('KRI(R=Qz̺>OnWڲ^aƳ(gcHPhB~b_| 7v؁yv9VOI/͸)3N~rjM%Qe{y6؇]eCU<-#/E9^. lă*V2 ?uvRQ !7k^W\d@7'na$9wURq1 PM|ȱ~R-9`&9pyxx?w떨<˵;⾸| $&%[E}4؞ph];`D~&AIr(Vv״e<`z+l؝2K+U2W&]Wuu b_wnuXTsEZSN (I9LzFޛt;Pu;ː-{U6ˊ `qv䪅|֕ӞcWƸ1o#7>Wwދ=].%'n׊^Qf$KoKɇnd!f-^cx]#/*i_iY#5|~Y"1n,s &7"&(#+. #5d#5I%dtUZx|Wk]!̅D1SyOyh{k5CTdxQq'5[rhk50T*zE C":Ձ✚clzp7~oŀrNj16 _g|2>:q#/?\2`>8gT}|p\1/%zNISe /Epu{_mA#i؎q#{9ǪoI{7߳'O3Bzw\zYv$臄t}{:[UF#'7R14[੧w)ό:?5hbmٵ47>E8hrd *( }N]+v#5id5޼P'&ࠎ6*26>YM]$>X~5T}imϩi\~'nؿ*JQjWs~He>tD_^f9Ö>OV޳M#598ӦI889͏<2#5!J!DzSx Cx0?QdfR#/6#5Q,$UJ(B<b*3QT#/3'>=zO1"#/ H Riy$zq#5@Эڷwmörb)nwZuS8h5B)ۖscV9f-~Cț;ѷS4"J <(/VC8!`_<6u4c-4`\qh,AbVA`:>w6$Pg\B#5QenF((E1Ḽ8;>Jёi|CR~>: Bӂ Nk7(5T$K:6T$#/ }El$W9-Y bvF#59lA^mQ@*RHRAv+glf0$ p,IʪJ*S,d'MޜXe)CAQsp$8}#/=6[Q4/`Ӣ< ]l^Dhz aML$UshQaٙgsMgyĵ0gxpM$WAŰ$pؒ,,_m.?\mh&8DRߍDlAm3(䅘lTtN)4MfWXzZ#_x6;}`)M9 4H`?ه+=1׮=NU4ˆC\#5մp˅O5?i  TxfDFTݶMM@8#/d?y5Eʨ/v$Ov۱X|Ó󧿏u僺e^ GV%iow)&=ݎbB lXXCHU7%%|eFbt"g .#/۹#+g<;wE#5'G$ %+˽p4#5N"#WڇDJq$b#57ӈ{<(}r<z!n~??#+!J#ۿԼ?rrU<#+j@+JЧ!$#5iAhUSy|Ys&sax!`뾳Hp~&4rښMQt#W|@$ռ;#+7ʟĜ_/xCCáOI" =/i=5#/11EByPa 9 #s߱c\z~=3L8?>t `C(0FaM2+Ei}FYNXPv'=F#+4U?zB!+;AÇHy bać*뷏]9 ;% aSD?OPgC_ye݀>t`TH|Q'D|k#+0phfBiƲylIYIQXr):#/i4ĭʘ3FZ'Lߡ#/Zo!/wȝ"j6ru v6E51:ƚFޡ@CFYYhY#M03ZJhZO>~IGҊ#+#5_[0cmNn#5tx4@n0#+  ;=DD|K,=G@պ$YM#/1#/"]lLv)7-g[a:C#5~Ƴ >4v6Y17͇;~nɏ7c1]E9#/vwȌR. k#5mղ\!;4_N^lFo@ͮIqM#/x\?y7mN#+B1㽷ݿci>xs0܃Gߏ_p݌Ƣ^?.#/ZKV1xIG Be8""-`.28giݳ%_g)stMR0up>#/#50PJJze?x٧eY_yt1b+G8gܯJ K>o ih5mUj H|#+9{D,QW y@e)  @6 N U8Tkuأ̈́4:k% y8W u<{R6'?A?ΈH8wg>#+#+0޽稻18}#i(ɶQI#/f #I\ Eݞᤖ-WwMG'4z`j7{=zO^,=^aԑݒFVIrwl"`]xERi8*/HT/#+"t8N+묌$цOfOazir{7NYΧoㄚ7Gwr'S2lB;ߧO.g ͂NEIHrQ!dޮǎ'~ qU/yeϫR!2 HFf\2Hwrv&~\|a`d#52,#/R`JLx`"G3@; m(@><ķA1碠$sH15T ۡ#+/'ಆE{3yِvP%Mq`!Ja|<8+Pu~Pxʯ^jOd >k˻ [ߙ%Ddi6sz|S%7|C⒨,|8խ t=ð(]u2' #5'iS9m)n*0 ثL=y }G/=gWia$Z>c / c2&!qu.83yنlDzo)sN!0xM%(P 4;|Ҹ(i >sG%}aρ}G)ӤÅ#F I4acLp:ǀixх\ ##53!B(36_|݃H'ZvZbLSx8v|z~08Fk#/BE zX8\*#Ǔ^P#/mِIL?6o ɖZv| f#5#/xFwΡٷX$6g38|czgbSBRQYᆒRkGFAb8JD@yQ,Pr ӏ\%EKLjY˶YXXRF/4t|6~р?#5"׍SpBx~_׿95~]>쿷3p.M<_;:#5){y(uHXi8=k\`3tw0$3N@#5C5P(~. o JbԗjةSDCЃ``Sv0X{1y`ok #5)#5 RHògp;A!9xPi"ִ?x9nIh#+;&B'I6BT=VCq뉱c(aj{m ÕwJzF-Cah6P&8.KU%*Imi%Jjt˳l2I %oҪX4LJ3H_bl".#5 #YB˷]0?f-=+/381T,_ 2u=;N[8NͱAm#/`C5FVQM~~ q(#5Q` O?"}C5t7H62`SĕCejjP S6+b=$E?/Ü蟼uQ\)D4XE+'|#+~PVj`Xa$#?FK8fkJ#54)r^,蛟7#/ԭ ~Q&%^8#*3T\7q1#5y鸂9|~ ڻ*8z#5 6̔=CY j`2KX%|t\չr-6PjPT I7#/^ H7y4H:O[Z,7I:tB#/:CcZ!#+ܾ[9&a<6{ zR "M9}8בܝ>=ZU y{w.liuuf{yMG{9l3-o#}P߷Thx)<0#+@6~$Ob")e#+/`hh71|r;΢H.DD@I#5$q+o:vzػEҧ!p?ϊQV9Wb|0)䘩"e-Z*gRcY7sA(BI=}Stҵvv}NݠX&ٖ"b4CC-5#/a ;-:]5BLmf*VKka$ۋlXmڮeQS$EDc/[(T'\7>o;ī&AqIMv啀-92}W@/XnF0Ao^';#,pl1#/Rh4DA(޿|E,dā~*˻Z(n##~,_alDoVl6É,8kM0_,d ;c7_Ae'Š#+#?FAT#/qh>=J{&C`k|<" `HV61q hy ӀYWv6y&L Hhaasv?3w;7&qCGcձ俞MO#$U~Ï﮲b0e]}=#/~IJd#PQA:d_P C AyWAt>_ fU#5I5>#+fc=t 9(#/tMG;C #+?n>?2svI#/#+H&{~`{C>+_$?a$~=Qk0P$ ]cX J*8~ToT~/Њ#+ˈ;ie^6>Ǵ#/ɉ6}fo:^p;4ԜRT)thC Ӝ$#5uĩT kL~C(O;?do|M'%=h= LurμgT`D#/!{z'vRYcJ3ӡrkN^n#/zg45M#4-  s>&'O8:b#+}&鈯_sۜ{gdfFYCcFۃW7%t'w~jguɢ#5hy*g^p TybLGYjt~ ߋ ov2}~#g3VWC657ױ/&L)hD}bVtt1:'}a#+&< #y.#Jъ3OO OsYu $H8tQ Q37$0>6"_K>\[`r[NNj~&ϟǘN<~n &`C[&I?@o& 9Lx'U~8z %/L&^އݰyXlw4KmKvʽ?4COk;65w\!FA'|ՈG$C*z!CSp>PV##+$}#5ƥBU4BxLE%-if9l6":ɱkâ I횟mHlÇ4(E(Ew +E>c }ĩb `?sDjԹ`ؖ|OO=@>VIN~X|B{C~yΧ#X ##+!#/>ٽ'o:z}Rf?{qMNSV?~til}sX?v&'$sHT+,MeյMM%sR-Dr CWhs$xFIsps4 9*$.^>GQ!\.s60wgpY9}xr$C`_vvovlY!#>?MDFGv2w@{~p;o$ X&91|ރM$Sa.f#|tc|33p-u`8}((0q fN*K3(hP3{Q:z)B׺Ƣfo ,̂e*._.=~ ,OӷWdSo%nuı)`YCH704n+I1C.eKg#/9ĕ~ֳC$R!Ԃf5ųAT#/Bń?"C).+,@>c:pw~T$K0c/h &%DN!#+!YѡUP#+*! [/4#w檯<[0R#+sz@zC93MnQ#5J Fd!-ldߢ;Pt7 'ȆL!AP9&p8ppbHP z|nB~-OWRW?Çdd=&S0sL+ "eF?:c7ubC=z!H RPRJAHD0 ' ~}HV_8؇b3@pCPgpHD*plrgBOEv H`a|,:Lg 1 C=ϫ#/YvGY{PV(8AԱ#J-6p{qI cYgJ Gٙ &I1C!8t#5}Ӱ.'4;\=揉K#/_$#+z#+i_Gr3M`0si^4 3`#Ag#/9#56.NNɵr^}?lYl3#/EUrXqQLJ}?_45aLI)@P0yxcm?-QlC=^]1*#+VV%KϰrH}!AO4(o -,k)+ȩ0>dɖ%9f $rhsA7gFcAIb02x24Pi~>N !Ǩ\l:NŖp]fVp7]!O\DNHMМD_01O>8WNQujlߤۏKFLG4OlssbI9ԥ35Q?_M䆣N5Uvo'^-lx$~BC62B0'ߎB9\JI*;.Z9A;Yr̨Lm,kl)22 ڀ5x{^c]sd\vwO!^^ `sJ(Sa~ChP =(&ZHv,4Ձy#/cy%^sGfN"_x`s0orbDGd@x==)Z/G:=z.H(O2c$iTBك5\AمI0vh"WR#/%/烆Bs@۽55M> Ŗtb@pq4Xw?"SUMcMu5fA}06N/*Y1Wqe%ZBM4{#+w@3')F#/ndIF DIuYd ~<-]$(˦n3VL77pN5H#l<4 ;v8Uֿ_ο~Ru[l b^]/#+A(q1D{ZF%&XQ m,[yUjxO1:"Qȇ5khUU*$MVFO#fǜAB`9N60xYA-qYxq=yEB *=j~̾C^:C97$MA4Ͷ}Q30 n@2g#5@>(^[ ;3v Can#//ChY"S!](~$|S83Mb#/*=q߶1ޯ'1mE` PP`9|<ʼwON#/p?>sJy4d| #+o}fgpo>H~Xl`֝ɡ@#/#5FAEF\ {11784y#Vv(wO5l,?F:<۲$EE`y3w$(8#/pDPt7:1kׅs$,ɛ٤dDI%4Ar+zMH9)3 m|{& @nV#giLTUV|%_4|{E#ӭ389qI}{ќ}AT /`+YM/Bo@]sѾ+6fY5iY[w3şroE'[uU(mlm}Tq_n+|@aז.!6V Sz\A~M_G/#/,=#+>a&8n!jX @tW?Og M~/ƤD)&IkپC#+ǐ*OPO8zOM^˕5LY'Wܤ zq1{C:óÜ>y #+>K'֘s0`ia+-!\%Dt%ES08O=uN;mU4!#Or\<#+r2 [q?o#/BG&g0g#5KRxq1Hrči)`>M7p_KA!$ۃbeXiH,dPSi1N)Y1{~iXu4pTE67x5'64^;͎ZHѢ5Gz{v&`9';ֹ'6h*eւjn]#Ta lq̊/!g…C͏3`I$ba#+$; fd""#/AD[`) ,# tkA_"+_~Lώ n~qPa6~XpY~×~;p헙?IFNl⯉8랾X&unPT_3JLg@GN,5FP>sr[g}T 1#+wT#/0ΐ x˸0Oc#5!}H9TM-!MSĎ}}dj>H石ۈN_/B#/0 9ų2؉Y]#,z{9oՃ`x2A.?RZ?ZVP* rG.wUC_P &5Wy~ӳO,ϗTX5a$$Ϗ~o?uQ։@G;dv|+,\j@ y#5P#/8E3iT-?|k<ΖfuC2/ذ$1#+KxKgA[QD*tX2=*96ev6<%q}#/c2~AHq!sՃ#/>B`HF^j#/Їߙ@K@ 4s|YIŅ%]uY$>g(?#5*;?Sl$9Aj8g$#/ E}@y.RcX $rN7;#/Mz¬9>S(!WX=#5 l?Il.*^HR _PxQuP:#5/%M4*/#Rj1?fLzBfIk+lR*|MH9S 5G#+^ + b*C)y؜QCRSZ<\U9g˖ry_#/Opl@Fx"/Mwa^1C$&AdX3-; EGWOm$XMƽ7;gdRAP#5hG/r>@YF00kUw'R Q#+ir ͋p_x\_#/_^:0#/#50@AJGZ,PB/#/~TNX{q-͗d}!?O[u= "˞˞&'%mE 8?ڡmղ)VG6l~؆޲?hS\>~#/*@ i66 <DnI7&?e3FQc_W)6Kd,S<b7ކADi=9MK8e=bO`ηx^>RyK{0cp.gգ}G#/m_0nH/xB\d{&IX?0QF:JZ2:1gS h>e]{7r8AT,&Y'\;u?ί^=o3Ƒ#/⋢9w{HAށwtqsTaE7[iD`#+|C _pa;2}$-|rwSgc PL4/DˣI|CK#/>vɹJBgPȈl #+kN{8[#/4r{E8d&4i#+f M.DħA"5\#5ӯ+QI)a :#5k#5jm~#+kѬiU!ĮZ"#1$~!e8?~ѯOP\xCz\ƸJc|ug^Q+!;‰99<[dJ U^N.EC(lvA6.@W]N'QH$m#+x#+ m9?{G]ys#m o;":|{_]_3/3xplcġZg.eސqvtN_} c>Uhza4\2WDzO „.["M#5FZR1K^JE#5Ho];r8l[s氎G S"XH9^q4eudGP0_ z>b;<_SP.Ӹ뒳 #+ QŵD<\|QG|K:m4Q Qx0G?J1a!DȒ,+w#/Y@v(:qWpD#5wmD@ԅKpodZ[ߵ1c-Pdbq!y$A;^Ot&(r%TH<9g!2G*rZTQS=:f%s8$'J?6#/,<} th&'Y#/ڶr [ @<p{?F'ߊ?P=3;ߘf,?w'CC|`DI:1/Q~P?p~^+ӥ31#cZj~16zBjheG p{5>k[v[nY s7mq-- #+{Ag4Q<gh'O(QQ~ suLXHcMQT#/{|פOPz!skOw>A/5&/rT>k#/nv٬00Yv$o?4(m}>gaX.{#+,S:#/&4`rL=4ӆhĪipr}ΤcK0}Loo"m\ybz!zO$]/wk\HWuOM eOE'(x(<Nv|%=5/#/&_$aY?y0tX_dmcm?.~ΣkEZ@(v=|jNwyxbJ` @`C#5 *Z w%F_VlK#4S CO-qlDRANʚ H~O{G>O$U#5ݷaǑCpUUUoC M$^r66~Hhcl7v0ڇ$#/0k qXɍ#/яzmc ţ"*TE5#/gIx#5tCI>2\G[dDMK$<\%50Av}/;[{dkmL,gEftԴ A4AΜa,6,U~Χ"9En=@ypv]z[DvFMfGM% ixLšYM?8K:ϟd,KF>$Z"k->Kt::NG\)Z#5T.rDJ)њ%j`03;b4iH=2=d)>Z- 4PRQ1Ujl+ 0wt^okkx`*-%eyr,YnaBLtಗ 1I0b$ ]1ޫ4I_ ml#+&lF}f\=P"wiYk4,uZ:Q}xgLJ5"gg,Pzs5%Ln)ߺTm;%O7#/l A΃49 r@ў}ǁb[ced> "~`UK?$g`]5񭅖d`xf3/vnr@Q"d 925gʽk#<E#5|J=|x8ӏUi#/ #/:zS{6M֪;z_#5#۳Dha$銛W糟b( I ?ۍC4 _yy4Ӟ98&ra][AC>ܝg/.Ot]pMP)YIg\__b!m]`ЙnđjH] J7]7D5Ahm-LSikX&FoǦ6}$4r iEZ^ŶVpvG`F_D1#/#/v,9ydxq#/78|NADiE@5,rdq=QoG!a :&Mǔ !z`|#|worQ-FPP1 ab4 FQ'a:LяXL@Smzs79"#/΂Nbp<CKWOoMX.q#k6\e;)jGZ#s8uxE3"I ,ixXMF1f#/hNjjXZ^xC"ڐ 97@û$o#/!*#/VO5PS jw^5:10d̓3 #+kDͧAUV+rڽ6}{Iy#/ hQ!=QZڻv"2S,zbGjMqʑ:ƨ؃: 3^;9B3S1 #+qGXu=˂=y"RaV'co1 SN͞RK5#5Fsॢx+՚mמ\1&(GxɡtA#+J }Kި?+DD.Mft4nnȦv3)N%,AU~9/"(%~*ik1hȕ?q/꾝2Ac1%̘-ަ7ϗ]Eg)0vMQmp Ũ'ۦNj }H^4ny>~Z,<+37tv)[L;)/!ߊ/4#/o<;%T>"#/m<`"7j_Dtn%z#+joa~id"PR'J#5@$$hXL(#/ FvA?0i{wu9'ݮ1,Ȑ-}PYԺ&F%Q%d6Ll99utnz2y'7ur_OB+gPlH#/p&8~ĞQw,cWA@7" ^Q0Z);yq;U<#@yJj`sQ_u؏;/~?7󘜑H3:+60b-tם}tTz&o\,aI mf`vnb2$$xEnXh q#+/P L)=Ha:5:8AhӴnwX)ќ[#+TW|H`L0pF,B̅T`h/I{6?{JpĽ#/ |>w%N:| \08=e:'=O{#+QIT>2Jh@ fGb1` <:=7/9#ѭIpR b#+V,Y UU?7-#+#5mKhDU{`*p\ 8{GغWjWo*}ST=[KC4a#WD $%'"hŇ ܇O#/X#+#+3^?տÝ+r~l{ߝ$at¼'ttM)~;Mpsj4~4"$q&guί}\J{C>}~PoOЉ})& x'֦%e<lڊ&Lp8#/,^G$\ɠ#5#+GjP ~B }PJ{u#+szT (Aj@L^`CP$)Az6٤C#5O'hR d!^4 #+wcVع8bbf2vlBM;$[>+peBɟ0 8"hLBI> I@5(ԇDz\(g\'ï͂`XDF ~|Q̜)ZӈD$= ,(2ȁ >@!QЫNO?Yn 1)Hm5a%buMbժ?p$VLflŹ}("I//9BaLԎ -4L̐Q!v:oyT#nGΪ$!):k)d($i#+H&3 mt[tAzE 5oU@Q#5 0Hd#/4wJoTgan#+ThVZHnnyՐ#_Q}ܰȂ Dp55A#m49A=]TeB=|| !frڧ6~ma|:ND0PXfGI`p|kqgSLGaA. #Rтbr"8Mۓ Q@zTu1wqeMzU}(_FrN#+৲!L*~<_A<#/߃GFUI6\n|}57h瘃1%$n0{bJ~oud>zYBJ8Y1?v*C'5#/G$#5v::<0U3NҪϐm>gp:/5P U&';l`^>%pղoH;y@p'GR,Ys0m&?>w07\\k,:3!sU#5 AiD{+AR5YFK#mχOz>wzx4h0< ګ֟˿3>G]9#/舾#5 ?#5i( H Iy!#+P0#+B5QR$6c6nۦs݊Ĩ`j}Ƀט@.NE!U_{[ a!:׉cTNNs_9?0&<]`,Z69NAOcslM5WU M6?ƙX:TNvi֬<-Xb+ye e5&X6h1#5{ߖU#&$E#)Ka_;#/7  (BH(Z (i)#5 i(f#5@#5b)J %%(V%"!(X)H"XbVfF BJhZZ#5IB;~?dl{K\J1g!ǤIk2eJfRfD`@=X'  ӎ& }R`n4&`m?a>6Ӗ;DIi5! }@AwY-5#+a%*B4טq:!-tCz֑pB?H܂S721gcX tw!:'*~Q_o[$/5xp/q7ɢ؊Lqӂ܇hslΎL1ˮnR$5]iX'N n3e`(O2H9i9?9Ӯ#/7G1䭹#D2& -4#/)rTСX#/WV#5PХqؓ`J780rDKHzyt#/oyT/^e#+nm! y]WK Nw1"_w3ΠfH%(2#5Puw\r4 O]J;#5Sɤ#/ )$U#+|)Mh&B"#+)Z%#/"#ʎ@~qPP\151RJA%T DJ,B|#+9+×U^߄?Ֆ"X98pOӟ@ҁJSє .Zs$(d>w~OaݱL>T#/Q#53QCsNLHeDjLb+``p}nO@4DNe>#+cIT|𻚾fc#/(c5'`Uv֩&`̌s9ACOK-{@8l~;_Ql|wQ7`ٙ~]t.,Ks%Mj$~ݝ''*t09@Ro(F_ރZy yd$RxC]#\u=.~{OlJb!O=[G6x#/{| t%%vA?s̡"sLςBC[1<=Ns%P`s;l}k6ln^85>'Ns͇j>C$;=c /j@#/9PqO#5LD4K%^DMJ)j`Ek'ta|hgLg(vh![Kw-i{n37q"  cqDQ@ PP1P%+J)('%PC{IAvc*?>XC4ov[H0X^%(͓`IK&c`QQvp[ /ҿ?R:l} l#׮ȡDoj8ϩ#/6$+:F=CF Sd&bBJH|J]/lP-(_~Or`q@K(Q`ϧ8$ƒ7 *(ɁG@g.KA@YG(GB#5Dsb($J"(Y$tM~zvԛɃsY]*p`DiH9}N'ߩ};>`{B wl͝?2'Yr6^;&;y#/bT@ܦŪe}e4#/oјהS^׳=;džy`a fӭ,^\|!xi[i!GQE Qsr\39hm\uv;Dg6Ľߺ1V}J`FՌyYaUBL&J.REII&VߋM‡'w9,GG8kae~_{F-iQ$\|O#/l9NooZÞz؀nO7&X#yyC6Ys[&=;HipFYK {Wn54Ȩe{1sF)?jB5G\R:1=IkkpzCǚcN%f}YYq şNzۖ1ޜ#+~XہUBI0Oo> ϧ>WZ }6"=OބL`g}<>4"ђ) 0v/t7f#/MJH=]=ɾ4S>Z#5bF#a[P1cT#}HZ-̉` j~cPQ#굝l*"s]k$BǶ)iߠf'R6p^4$ITĎ!wFԼ"R)KdX^gi%u<]:~L'_OrjB$h~x"O>h}<;;3]SE,g,_9"Uh5fK-sb4o s#/w5I!"~%4Tp&H>??fH"\>;)!~ Ԕ0.,963D;P[<#5icM+LۼM̆,s*Ab\bYGtfm#/\cy2Qۄcw %#5Qh!Jbi`j)06/63m#/MA{:y$h8h#/8 BN._O xˇ۪5#{@8lm- $LL)"7O8vJt~U#+v;J6;F乏@n}H^ hFRke1ՕRQ/MQE־mUGD8Pg PHKC~*H^#+ 9}#+O6kp(>>5=pC}8p>NP K!'@^#5@AT'#5@֌bPh1#5 H8'4cpΉ Dxp.rBf^8/,HPD`Q\[xn[I6b_g#+"A/{=sفѳcbCD) L`ě۵rSl2$"5i0X3yHʛAZ`7LQ߆o&)KAM|\4$ÎS D~9KC؎qVMLLQ4àlqy1^8 .NPM L?A ;m+n7sUå#5U)@  I:D(8HKfZ:\**"r~(R|533QI,{HQM4 i߅k2#,vu"əmBEv<<>DKLCz|uC_{a|-#/a#+D'ƹu ,P2c}mť㕘$D$ꋶR\F#/~K~T#+HF)B~%Lʅ#/|R*v=݆u"H+Y4tjg7 lLBb:^f_sbw'(}ra#Cu%hK"#!ZfUr78f"2b1yj$)HM%{^eViCg:V(cCtfo2P`Dq#/#ȇBP&>#/1 `(A ABn 2A4(cbOMvFф02o4Fd>ݻDFDH[*3F)?erq40W%̠ 0z_7R#+=U;@yl@D-3 U(D|z?ݽ9A(U@&AcDO#/oWi#/&Y#qI.#/1j}'xై{{a> T,#/|04vPaSKo#/0g4NsAs;ױb }Eu 5dAp#/M )RBM:tq{z#K.{ct_,F 60RN̛Tb}ߧD뉠$;~T4s3N":۲\=JuVzIm?{h!)F kp!$7:A}!}?%v=WթI*~NJc퓐{RH@r#/PК*4HҙX"% ga76ىvO!:4U43܍`.$ z2(A #51b c$E 㪷tP{}#/9uYA@U'""("#5" i8JrSC~\萌`4y;)#5D s4PAC:9K^K#/'"%q3q !hLPbduD3#5'T!>`bxÎAWLID]"8Hvyb~#:7@)!6oա67Lé0M#+f޵m+ag$ L9f Y-G0?&I. XLUNԤÙႧyH 8lgdN`.gö!P! '#+&g"j3&j^hl=&t68ƹ R›"Aؗ'1SB#67H:xaZNhj@L2#/mA#5`[4-݁fD`1^iEyFu.u|sΪLqŖy/Bg))S}Olj/l9y$FԎ!#5^w }m&mrc;&BczOAB*0{/N/057ZlA3Q  ZG#70:27i~"!k-L-PDM{:7#+6[(3 (X3 h%;[Ą2_c&)@9p^_A#/ĺFu&$nK =f8 +,uH^1$aI6;#BHpQ,q!.P&X`$JaC=I²ho*c:#5 rcV`"vAӝ'ݾfXf aLM bȩf8scH|#Ov-$$€E``rL19I!],1Iv{~7v{rm)AWY3cUW0crEq9u7G"/ƻG|Y[aXa@jZF?E97=y+8R`LI6d#*?-5N+w(k˿"#lUml!3 Zo^ ٥J˛ MR;N[HkPB>}4ÝVqJ?9F J%^⻝e;mP,Cq4NJ #/kKKmd+NCRg?Nɳʨěksa cns'уE&-/>W CDԆ or]j-L5sMD VC)9 #5DKlpX=f(I 7|0=N#+ʥZ>ad2.#Pc S,6B#5LÄCp%=|/J<6hG 4VD>M5.iGi"Y@BB0>*^#=3çzqzxfTvoyTyFñНhUUI$a!N"/^5Ƚ!it$O^v Vt[hl8/sټ"Ad+O);+4}KgmcZ>sQ/>!l{Ud8,|E9,IR,?4bw܌c1\̽C†i>1Cb0[VEZko0NiR7+ysmq9q+rosY18֌#--OX0w&$h);#5 N_S(&Cd.4VUKN_7wL)#/UwrѶtk[0׹P4<çڌJgb0v]$:N;HpcUK(vpyń[==i7bA0U.8vKcZNMӿt']khBIMJs(qMDV #/.D63f$wFkFɣb#(>s6v4#5Voz5 ]@Erl_8;r\cQQKQk-hnv/b{#5E|duo1 l36AƇ#5cT&)[8siscpx&5_Hp5P]\a#3Ah22M蒦0<1A.#+%WXWlcM;#5#5 vy΃02dDQD06^SׇjF>ln@ڧERf6'7D]gaTv;kk60\&Ɋҗp\>:-&۫l^|#/☤2獣#GX*6ؒ/Z+:BebRQ^͖*\.sK]ǫcl.&9nm8ڈm=M)j#.S^ٮnm#5mTنo]sA&ϧEoqM4Fv|L4%` e:f/F0k}IAfٍHq#e|PŔ9KGC'8mxNDEMd/#/^I{-p;h|9t#/m~6RuW80rXui20uåӮhˎ(WFH&7j!Ii2nzqDr`r(g9+$Bma؞YaZz+-W~fX nsYLm#`#;]vRK<$#+V젧w78k$m'+~8ЎP߅y[E 8c{9 &tiv5sz~>3zw8i Z8Cd䣅`D6`M{~÷.Jmc8205pk85$ZM;#/>!m| mCK8wn2$CFP$6\Ħ~5væa |uGtql1˗m} 3L0HY8b1‡uy8)9m*hle`fAZU\0;񶸓ZܷBnˆIm,tCKGvwr(9-acAɮ)Y&(3$KeZFfƜFQfUX0vM|;'30:.m86CLj1b7!3 [`R2q#:Rr3;q ٛL]҆{Y2S!1}L0lND#ehVN2aa؄!N`zQf#/<ڝsp 7CvMV#z:rBDƖS!;2C:1qvB >Q&FJ!9cn,6کqF\>-"4 2BZEvNƇ璺^M\#"2 a#5U8;HLu6(+!n "6v;]s/mU.͘ӎ!q8Ϧiyy;ķ݁uTdvQvd52:Z|psYuv[r%cPDݾzdZ>Pʪ "ޭY*a('JYlT ӪHlѡbCHЎ>!dH 0·Od÷|N `{z_L;X5`1\#5m)"Y6{yT+.*fwf *[^;jLls ؚI0r5\I3c[9+vAq#S}g1Xhn̄k}xw޳\m۟c(v~Jvo 2i%i=V|-ύQʣO58 -K1܊Niu5g[Wtj#+8Gh>P 5s64=Q<靗+ !&>!@vcRPKVճHͤ>Dss9FNJ#m4pȶck z\ 6ܭCJޏ\'nw~4ON8@$dÝCZahJho^]X; ^輣ylodO-I'$82X '6_J";dy-&Ɂl8ɇ]] )#/Y8!#/0^ f^oalT 0 #/تم(=޹:(6U&e2DeKB9 h))E]*Ӌ%[MFm^i@Փ5)ŘHh{{5Xia፬d#/t&+C=`yK&EםRyc"3ȘmNӜQt/ e]gE>a`0MKh6ox^eXc^Z8u]jH.6QcQQVDX:0DZߘ}7#+7,&qꡩy#5jjh]Xi%h.v}2v1遇jۊA A>xα5@{6BM233:jkrzql`аj9ԶaTD8m*#ΚNqc+|Qq<͜ !@fg%1Uw9x'.{rgsc8C낂t8 MQ BFqckKM)vH=(oy-cb=qTCڶb#5S]+N` f$ ͑݌K#+D4@ES`LD!H;YT"f?؁S;~;#KGhas4 " 0V'\=۹S ?ץG;$#+b;`!&%#5#+e,\Ы~u#E#5|(SPs;OjئNJßPd%sc<#/Q<:gn;䴁DJ'dү ,h)KIBBp`4ҏ$#5V@ 8Dh6 sdNN([yjw0yevpbgX8O˾6hwA) gLڠÏťoj!K!n7x;Ih'IB3:{uDg3qWAeܔ$THGfqKVS9|= @Q ӳIzD÷&SeLVLcH|jiFzUizCFA'NqNmn,m۰ 'CdfcS]x0^r#<(M9\BBR5ٝu(WdQPeiF'r6GWC'6yD⪅Xջo~{rqBSJ(s5Ayɻ=MCa4_Ŷ#/%;kl]F{aCRJKPiLlwe:: &zlP^Jžr5eLCm!j,'hy}!6coG:zer|Onz̦=u2]5ʶ} BXeÒ&Odr`0#5EV|VԚuDm[j-54D],̆1l(/sm4@" R ӏ ?4'B$#+&Ƒu/X=9e+Mj>]qRO#bGB.2\Rh Np'))6IǥI#/jllUCB#+u!ܝOFHo!#QJɎϻ'h;p[oTfl@Q##dTC` ^$"f厠2DIJ" V)"Rh(h)jIIE4D0 #/d=Z=G)zBufg2"5&a'(!0bU`l~~#5iHG+{V",z>;6,!4W#5u[ۓTcILx9x~hrqQ0:vŲ\"j__HoLhIlnaquPH$H[-M[h.4

FR#5ij[HAUB&$?ϐ.#+m$Cg0r#5#2t`bې{V8Z ?'o]?n`Z8Uf+U5ĺ#5 OAF}Fn#/A C1w'~|z`0~J= 7ia#5#5#+)EI!:Ba)HXJXddl _yT_iLX]~-7zr;S0".g}YRvNuZgmgxw/FfxÌحfRi%a=!SܾSi79/IAҦGN3ӭmX..Z3y%T>rzCִmg{~3:I+f"Lj=D=CrJ~e}"S7Ju>&Nu|>g1u&&jUICvyK+mYX|@N8R52ߴ.O =N'vLU<:xލz'OFfy[NcD7diV1K.ܥowޚ)Ol;2FLr0?:ߞ0$t&o}3>r.Klc|1P91$Cu.sNڛD>⹍$3w 83[+O\eΖQ*c?76m33(hŸ-3ˢDH[Xw3 <4ig>c#5ӡv֢9qN,0/[GMM:Lz\q^\uX戣'5jg{-`ʹfn#5lPҮ̩#ط*.u孓#5w1 >q!.:Ht&c382r {#܆Gu^<74='a.gͽuK(t#}qD犃rʒJ.H*4%>Gp>df;1oMsQ ls$C6]wvA/MtA1M\ CFūka ⓶;wX8凖<5 ŪŮA/gE5s ӛKam6!yyu>Xy)#/JGBqt4IȊڇ40.Bf9Q7P8G@v 5ލ$t*TbPR(Poזr*:F4L2:2#+#+i!r#+&ԩ($n?[|1 Td|?N1Vt('t9 ?#ü=&~򂐵ئBe`##/Y:FKW**Ŀa#/QC8xA?>w̬IJv8I!cHDgɚd}צ5Ty> ?D#/v Ϭ.Z?š@o0E%#+hЪ`L2*3&Tax"*Kx{ 9;yA1g8Yĝqꦋ|P$gƅY*Dp6.(:;ĶѩY!sM=E@9$+Ggÿ;o}=ò'(x:Hɏо93aaVXN7sy#+8,jTKb '6Q0hcs92<#/QpsӇ;#w̨M"5l4(Y^K^k(-h(h@6i/u"GWiH-lILx%@6An҇#+idgCAJ ET ?9\ϸpsު BebZ*ɜ ! ^YR}^kA؀{suI#+XLwGIe$9M$X )>}j#Hbh4emyDU|MRl#/wX4x:1u'߅A8<98\Q̇E-"'L#/4#5FN#5ZB6δHt<0F5c+MPlBn@el]cZ,c d, 1Y＀ %((aA@`BR2##+p`no8ATPL =G+(`#/E[6ƚHXpaԚkK|.AB$tz((1'8s)!#+(k%d 2sǥ@;PFlR^O()]#s( i)S,@1["b;Ä1\HDHNlkQJZ{`cP&1)mE#/DC( 8 LPDaCI< 1"'#53(.H#5:¸/zhezzɴжntwy>}}K]A+Dz01#[wBAP@>DmHވ(3#+~3Gv* hCŽm^|dd OFuACIAE#$JJL1!$S<)IHGbh22G%$#c9ҚZFoJHGC( (# 1@$.|dLJ,P4 ,KCB@R?8(b VXaHh*$_%AMMUTD4 0A#SDTIAI0TREK3 QBDĵ$-$2TE0@I C- 1%CIĄQM#/RP#/!L4ADA C24AHDP$DKH2UPPTD45LQI1A I QHD+AJ2LDPʊC#+D%$D #+T a"8HXQ!)FePCH<`F҄D%Qā#/$Q#5$(KчObպg \}8_̌5,+% ZbO&7(n+8(]Obs]`'S‰$4# .yq4})K#+DNbJP'("#+Mb#5a:&:O&N‰3]ya\xb#/HOp0conӰY]:-%$/9 S?(lw@hm>37%J8 Dmo~嶎[YPy"t;즆aKxhКjx{J@."H0 Nru}Qq#/0#+L_!ꏜ˭WF4=與Sx 4#+7?sNb#d*2An#+ܶ`̕~E-#/&Bt~GĊb"6a288uqãMjA!8d]xZ̙`'#/~@P&!)B)R F}TP@Je).d }6vsL;uq@ZjoY]zpI@f_,}Gژ悜PD#+rjw}SLIsoOiDuOiI7#/I+:1 .X*buR/x@cR% OXKI,aAN5GJ jT.U"<GҊ:|񀫳6d)%VVaR#5zt!`<Kר\R؅7l" 4Y=;td^'wS#/^nG9$r8l LGp`jk|<h"A#5#/RǘOcrzo"g@hh(G#=" M;MVgkݚ<5HRhCg#5+Ri,u#+Pk8ɽq恔}BJ?t-#+}rCA0#5@8Ĉ;@k(8Ӏu(SfJQjSIcТØBhkU*.X 66;dHz)<`  #$GB>"Y);aS#%Qrxd!uH:GR9:TM`Y1 oTaQW-7 ՐFj&M][{`l`AzƲ8 `۱t1lLZY) pM !5P4!Iʂ{(Q?du!L"V=,5dC!=}{(I"HAA-@=D;!0##+(w(xt҆:chaYzcz>ߵ]` ԼԮ9UR *b tCZ pq'b1MDF2\0#5ŨkaoZv)Y&;hDh:[4P@Ox4XַhFө<򧓙p[e|Zk]EǿEMUQmly\z5ЉnzlIέ6:sI#/h<;utHM`{HWEV"Zm њ"<ŝq*@*b^k R`[Zhf٘&F `c-:I9 M?P{#YWeB=2W {b.79L*2\Q ӼsI2D #+!5<99$S@D+R!5?7& J#5@}xЅR{L, #5 2j%J>@#5iR\TI]Æi#5#+Z#/#+q0}c<C ETSk2EBSO6u#/v/}&K1cBPMp|ER w$N2- (ZR (avB*J2pgk z#+"cuPhF;#+ܣ°K|#5|4e5Iˏw.ԉRW|&J<9o״gwhtϸހҦc"IS9s_VoUKUM'Κe5*gp;He!ԕ#/UR,o?V$#EU^q/{~"6?Ֆ|ߞ?0A~sؿ3ke|dfU|5{5¡ͨ@~s鮈Tգ2H&*%^4&A*C12#/ I#F}>134L94wlCFc 'AR5{`hc#/nKO3݂IFPceO-5hE%E=(ie89bgdp:U{ȎM8uN$h38{e8pʙ fMN5Hü!}a7>~۱x8pg{v#+:D9{vQ<.@ZZkt4!G(j "rMkA’{jXe6Pa6af}b8<BZ#+ˈP 2dpr[&]qKKE' p4MI`pT12{a>#+11$CK4Hp#G6*imPE ![a %eqS[tc>D2T1)t营5 PDDD&#+H mͱb#5TO=Ah(" t#0WACe#5G}2OG#+BGsbb{y#+!"̀Q"|g9LI<Gp0C0׹C)'lj>6#5Pgo8aVԈ 4|1Q#@mu:)1VTU#+&M$&t1LT+E/Ȅ) |_E*kUJ^8MmCBFH }[Ø%=sȝy*g31R$c- ;bth3B`X@%]+QCДDH"% T%P"D%HEBLʔE:kT4B#+i#5B4#/!HIRP5K@,E(#Db:#+,B4PSD4AET@4-#+D4%H(ZB#+JJJi)F()b"B)) N#5D$"m#+$T/ Aa3w?2ud

#+9,9Ȣnv _A5UP2xDNAR9<9$Sl>~ &L|( h)&$ ")h#5; qI^7i}GMq #+7*?M;08I #+Ɠ|}E8bJ<dyuc;#+$BF!G<]R#+:Xwck4av#hTPJʨ[C:D:jHPɇԆ)rCί7GmOoj=ۮqps`E6xN1fAHT.Wfnm(/~hxnGw 첑#+>Hl=6vPdI#/#+O~4*&#/&6֌6@DW#+|^pB+H T#/  C$Ԣ(@c _D#+D R)CTrh9pgѦ{+Zi'Oc㚸JZ4[8Kх*Ru7N%1bQPxnG1>'WrȌWHI9<<tGa|>t8yp&zr#5x|~z%Jh#+>f#5CX@ajHze vBNVqb"715dtx D**$@Ҕ#5{$ t@)Q4#/:)#5tGQƚTs !lr/,8AT@s`bb"N!b@(CCX U4DqETe1 DDF˳$L$E&)iV9P>TJUH 4FJ#5GH&)@:EГH%*$ͦ)9b"R%/jh0A;('#5#5dHD `yk#+yBEi"m{Z;ʋ дD)Wtu#+ H<5tTh8VhFS7#+ JPW,-9:D:b*1H cEhlؔ!x(u.KQ0(F;7HmϒCٲ];)ĵ=+Bd!#59{p#/TX!sT=gaP) ~-A8HdXv~>'WwN+2yzqOLC J(a LvT|j @O,ExcCe.|L+#/^gME(\j"R)9Ð@#52AG ŏn"`yy)I'Ff]iOYb(_%fR#/ƇApr#5僇Y@"m 51U??5tƏ0ChI*%Le#Sbh! )(Z>٠bĐ#+I?؀.mM#/'5c&)Z("()BO>޿i'̝xQld#/a*uJ ,1< ?v_#+(ą۸#+67c;9x'@$O#!]X)}oM<(4wG7R3VQAy)/6vwƯOفL>4aQ}t#/wLGlFz4<&lmzb` , \ bvS37܀Pi;2㏘#5`aUT~rvz;ʔaE"Xx`PL1 M/z#+ֈ$DBa'#+Cz=U-4XЇYiد\du"#+g^Si6(#b#592bMe"\#+cJ$" '.i?h~TM@#/asO9A4BahÓ6GY=|8~%Ͳ\@á-TiOA3)DNLβeU!;P΁(jH#5i)Z %*JB"F"*jib"PbJi"()aaJfJFhJZ"#5(#5i)h%FJ(#5YB jBBOySC!!A~yU T  DMA "D2EIU$HIBQT"Q44", $),TRDBU5 #/PQS4L3%$KB!15T0@@ECU*2A0PHI(R MT,h^VYf5ċe<|Sp#/Ӑ&`]܀CP Ԡ)J~3 S#+Ah@1*Bv8P; x+&Z(#+@|6!:baɂ")L)0 H9hj$%?)1PSDA#/v JRLT4)!ER4!RSP2HC*$QlBW-3iRg XOWMJq M!X̞yCAc[1l95)֣`xu{OuC#/U"`AT??r#5D=+H vcC`^:#/5}%3@DAE4ACT%)J!@CIht! ӆ >e_ϊbfC v9x)58\;q'%ga5fMO8C}4d=ybOcCaS. #+tX5$ޞ9h] (3fSTu֮C9ؒɣTP[IH$̛@#Z+RVc`a9, %?ϐMP:D6#+ɰJ#+<`B#cfn}lD!hy #0ɔ8bؙ2ݖYe#&DqS )Oo?p' ڨx~H%jOѮ,8Ur9l?DO`)HB0UVai3c4XbP*^Kސ HLSD#5j$j(bR!#5*E(DFTQP 3MTUSU!Q)uzu}Ig#$gpA-4W쟹((%:KAm#5%&`((ݽ䧭i#<;45ۭEMDd]lI?^ǮGZ(RK$!@ƃf<XƌFqeDrzX¯Z#MK{Z˭SC%#5Qgzh.cG*W{n. 7tAӭ {J"~ߩ„S4Ѣq@/`:'#5L>F;ǘp彄R; SJD$,bSE-F27zAFm_tPl_Z:VFOzgE#).1L6BLʜRB8&Q `p8c`^v]agqpxqE8#5r6V0M#/"'#+ͮQDŭ:/Ak Nc%4!*\'\);ܠ|G^s#F,MG4<\&KG$f0+Ш]jF=iZhj:8!8'fAqؒDH5jGS3)RUcwp81&D\eMwmm;IEЊlBn 1Ct\Rhls ύZkM7r~Ao,É4)5ܦ+z(#/lֳHSWBhG=6>:ӳь;ER̍jAM4q ֓4ڈ~`v#5S3IrO<L*1E&Q U#5/7Ψ*j\Mt ;7@}6 C&3#+,l wvdXpb#+°Q*z%0"8B/ڝ ƉU O C#+ۮ u~+S{G]|C#+JQzXsۻ׃0O@vR0&tJ?Gg(uq{=%iWfyQ]_,ΐ< )@"U&"RCwD:֏P>RO@ІOsM~{{"r:1'#5L@H#bhBrSSːH-$/A׸SJ~?*$J{7,OYvAAIO\08>qO&ğձ3Sv%DˬĴ&1ȊV5"h"#+׼|(m$\?%!&9zY44h8J! 8NlC#/#@P( | B9d#5TCRd(J4įkfhXQ4BzM\>vC}/N萨go bE8BTm-C~;>}?pe7\|tp,'N&+]uvpfàj,a01`"Fwq^c.qᗅ( h-OFxgǶ&.gTGƿ{`/!%?K"OPeq'kBT,ET7M%iB.0EwcjKXQPO]t|97sOAF"U;av6Lİݿ̝vmw$`6Ǫ}e0u/AV$X֓R?u`ܖϊp%㽜%/ lqTܔm>0#5#5 S1)zEr]OA@!Ar"OniPqhJH\Aj#5m{ 20. DH}Ivx 6Ԥ;g!9"!3`"C_g!?d5"RRa#+ݜ78f3{^Z<T6ayZ9hH9tkZ.,E#!tetN]#/TbhNQ:"W2 v!T@BEM#52!! ہt#/43oq+z_q7>jy=Q m|wJO1Puhk]N>@9%N\e9!9*A(ӱ ʐ#+xȆ#/0ŒiS{f#:ԒD,:ˆ(7ՏMq#+<.#5ѲƘĹ!$ //#/Ut*;{a0 xi\A藜;*EO@l_b#/'dc̢hWǸ#+?/ا)+adₓuycr$=%+1}+65S f{` \.#H[fph*;>d|wvS-Uq#5#c*.AP[#+䎗CQRh5O>BFH)Ƿovag$tACsS{An">փ'yfnt|CPS>k;A6xxx  G4v ɩ'#3sqhLO*2Pr ɨ9e|eF-VQLr˄%^pK\7!ޙ 8Z0 G9ԼpqcXE))WV杖G;3IH\I)2a$!)aWPBb4U\ h';"ߍe1LPl,ȢEClJMwDc! 1bR#+Ly'Ж4^ Ӧ0딪390S*# RRIS(g=e74z((l1ihF`h#+y/.J>O$PA11VU=el3zbpS(#5@#5~||k*֗* 3ۛLj1?rS{ @"h=8!C#5*Pvb:>|CP^]"v|NÊBj$qQѧ=NeEҶ0_wR8;vA>(aYR&(va^Hp@z>:`<#/7/i ,%z}b ʇΩ3&~Y(Lmj?!tRx:;'X結  mچFd},)>D0#+#5#5 #5)2Zck1,:њș6pt6 smqHтqЄ3`@&h@ eևt#5p%iiEhM.u:F@_'ll3kѻll#/H{AkP7#++'"cfx $F~-Ym ""A+rȅ5y/pTmऎ\(h!t"%{ 16,h5GZ[džkb4{o9B9S/ 4-8G8GJYa*љ(#4SEmLiDRfsb'aeC"7RC`#5xg/š)2{3SQ9;<#/!IS;_owtC9r6`Hz i+1qOs0>OpP6:~ |'|s5L>q&G5?(f3Y q#/Żl44|/69K%"6LoL ,e#Dpqmbν:hy ={96#+EB#fqSP#+TL9{xhI9s$/(/JvTц.TC2 33?H5L8AtWS=%eI^nv,Ǝa#5L*<]22 ,`DSZC945,1n&ƍ7xQ P\W99N)TQ"@0e) #5Y FJ\}qf"B#+XPyÆ ><%h8 Hb@%ᓁ  'H((,` $e&"X 6Ls:9節EJU :uz<<1{YhLZJ֞pdïyF]Lv\#fODET+Q<~#/So桏o陋Λrʼnyz_2i76}fӶi["=c[X4^y#/j'#+H7/f1m5תh)Ē,;鐤P'=ipoX{s7#oI0ᒎ٨fhzۤy2޳-ٱܺm=I=BWW#5gc|8|jrB(d2,םUcim HA^rm=XFǓ{Q`k#5E.rNtiVvj*:랥LC5zd#/>$4ml׭!Nl+mbŃ[\J2%t҅MgpF+bU-8L6ӕɜB;OñړZH&# O0<h7^ 2SI.6B# =҄P"o hJ? #/t `)*vG>ohj6gEp%׆V\y8\iF#+"q<rsbJ#Qꥑo~?)(#5@PiQU@:A#+u4#+tM,ETQ-4#\K3k]7CTgI 5>\xcۜC:4L aNHP>ҽ ]ڪۅe^'adJH҅$h0b@I7,DffkP9!W2-% RhvTPKlB0$NJ!JBC#+@>0 X#+fʂ=ܝb(=DdU4Da0C*C#5<+DH`D`NS ,#+'O\[ra 9TUL!&HjT1BMÊꏧ*bhŜeĆPtP$:M C5(RA#JS4$UAdUT4DEQDxءDEnFA$ECLD4-#+RAKTT4%H4(K@LKQD%0E L @PHy11ԑ[}Q_yF0#5⭀2#/ Ed{k~޲CH&mc"cMM$HS@(Ђ$F{S5]bWմ_<|:NvODu#5 }Z5],%#+3#+\282(jBH&i@p>օRЀe)A)4h4Ģf5@iW*%(T%$l#+myHiJE"0HS" 5'iH&"#+*y#GJ h?O7^itH$x_cP ss7MHhq ( !t]ۇHA$!Pً@x,[0 &#:0 H32wI'Ooy+e)eCF%l|åymE#+^n/_>)Bѓl%"V 15d"&H-9iCf0 -C */v`Y0%Wd"S} g\KAceӤaakn].c!.AZ#/hea@.#/G^nCX.L#I~J 7B0D靮Wiˎ#/TGTMҨ%!di7LQQ5FJC+X:br6Sb¹4W3p! ϲ#+6p#TbP$QM U(DR2d^Be1"y'*vUi(@Qud(І5GzMxqi NI)T#5QC>'&\LMnm|83QV#5X{Xhi"8R#/ 臼2r@nf"!ʻ>qO }M#/9+b(5bUQe0d/9KBatݚPdN+4HUu>]#/:#Vi1hm286Lfl`B[$,,`q:@ȒA=XPBd")iJ*{loCZI3(()΂k}K9W(]q8BRiL@H4/>#$a䆴C;R_[F#+#5Pa$/c#<8Z(JvF@v5Th@LQ@Ø8U0.#3tD uUK+j ?>4+x"@9#/TiE_}pm,9ɴ J(+4C ^܀ ˠp@_:>m&pF:Ei{3Уځy|Rv.T02'W}lz0jR*T8j]#/#/j~ Z!5%8C!H{CRO7@߾bjIfwhB/ 0YJo669U#/ЊBaP1"ؑ!z>zGzzU#+Ѝ#+Cj*[<1iK;# 켑K(cyJmKpU| h`o#/R:~uy)ʄv籮T#J-Њ9E"I֬]H$tqO衊Ϭ*MP1vE*aX*'U#+@Ϗ (o*cא߮&Z:fM$58!'KR.8#/`#+ l™S<`G>ӁH')b!T7-ͶG hRf֫@e`)(ҡB@XcE<1x4Lr ls)F˃IZ1? E@LC@I-D \04pTTKAkbNU4rNr99&c;PH8E>Ox J0v '(B((x 8#+9CPC!)UCP:#tO/8&#+`Б#/LJ{Y6TPyjeN>iA*#/4*hdbt).EB쯴9r0.n;$'< jaʳ4>)GxQP+}B(ҧ:~|Rsp@͖pK5܈zߋ!E"Nֈf@#5FfQ(S|zkS b!*#/jTAhEAM(|@ P ٌ6{ q:PTd$@(A{'~(DEAP!J4"1#+A)H@TH4- ''9"d/t9f<+DRS܌Q#+Lda J̏ zh>A\F$ }[1pS8`N-+LDN쏢^IH<74O 4$@ЉO$#5[hnKISQ|! % R7 Lp*Iyl#猝s$ۻҞOaBzJEh" GkLLAJQ'|߬#+ B=&uA>r^k@@|&B"Jdd8#/8J =LH@>iNT6Љ4S"L1^HJļ]CYwhEd5+f,wL6P_P#KQ}C@: e\$ I#/);#+#+d&fX$I)RX%`P`" #+ RB]E,:u#+@L S1LZbR %LMC#+ fRdP");#]kMeOTQ/x4sj#/"=8b_*=;{#/PBƌ}Qs)1& EݡA4iP`q߃0I:GKF%#5}u\/ă}v#5H $di(Bo\4:!cPW#5]c!@V","pӮx#/VePhf#/ i}!3x鐏N]Cs!l6AJ+@#5rFMcnRb'lHO#5QtBW$BBQfh rSc MP1` c1n;@dNxPR SL>Xa$.e2&U'vOm#/]:٪DDLaǗ#5i4Z v.F})Dӑkv0$#+dqz&E&'@pW^790]wJG =HaR!E E#+byxj>#USHѠE6$e mT%#+%0L@4)9"SE0b18'/x\B8TMS)G@wJP)TWK݉=ەޜN.'(ޑ@٢`t;n/U<#O>0.T!#5G79*ِVLk&t?#m?+0TaMݖ[tKROQ' ΃S q4Gjr<^X.AE!NJIa2Ť?Z*5EQ!Oϊ)L#ׂ?# J!PhђQ)O =UMJD #/.PIt#xG E)Z-h)5}tʒ(#5A˞ud@Ԓ\w#+xď'_" Z40}iS82i#/aJp>}=}LH9@$i[o0$!OΓ(6<_siY}cXkI6{ Ȕ|s%씩oTc1rj3qQILmEaDfFiS!f0Z!Fr;* (Kit N ;.I:9ŁkTcv6NJ̑d qFddM' 9&z,0m Qɹ+`*c&F~hC}$ŤB Hy?tLb0}|Wpy1fXbv{,MD>Ժ#/DL糍qh*ۈ)H:L6rɘ_o?y+Ds4[j&)&.I33c#/ms` ^sC=G#xIZߔbUf*qam,P险y) W]E4 bUʉ;,"Y7ы[ sUژ^Ӎ-9}8Sl i6s)4~cɝ(S#+:v}4*%g>F_z4 I9ٵ fmؼ.C#}U:1F6I"y|!ݐ51Ա(6x(.)U&w|1QEeST2$g첥8A1 n4ܒ1It0kH*SZ3 FX+4#58m|pwc./#+'Fֲi#c@WBpK8{! QXY-=IK6Y#/5z\w@w3oSvȅ$?IBGSb a<"!a9u_[my< aHt#5φrD#+ er\Dİ0*ၘya>'Po!ID'6&HA^;׿I>LތBR:S[.ݗ$jU,#/>q/>KY߇$eJ"H}WI38 #>)YSw܌yّg/"u{W QvV#/R,%hgU +dkzg0adh4Ge8lh hb#5<`#/)Hv}3!S䭎I^omo\l=~#畲j'1U##t{ȹܳkiۮ)FR7&XfH怏@ʙ&:7]^4TfoSCPш Q}*I#5AJH"xq"o04Vt}LԹ"7Lm}Kl 3Z#+g0ijfZQS:Ncm2R@Lҷ\4OOCYN*7#>xfIT68vZI#u*_b˭[e-BQ3RO#+ F?-g/?;=#/otl>`_Bdp54o(m#+OrJD\?:p#+QP((Z#5@(`dDi3n (>q@G}^;+55䏆#5JNP^zɠG3ǙЖY_9h<#+!|#5Q({ tMox[dENcrH,RIO(J;dBz?x Ioݪz"*TBȫ9{^=FM7ǂ="#^}#/Jn}ۃ{Nي\)0Dcs˶8#+WJ&|>31TD(5'%&9@2x ͍2>׀o<9'ttF#/FC@^# +=:!\ӡq$2OqdvՎ@%bYT0u9RFGxTSyڌ}Xr~kC\' ;/ŖYqK'Ԅ_{|7#5#/3G ߃+hc19;]2"3`ЙMjky׼v L: #ٚ!+1.'D$npn^&:Ct.gvFLB?_‰gn 6 hi>_2B&߇Lh"n3r5qx =N̬tRuCkhlo l,!k~4L1A|O^k?O ѭB@ݏb)"(F""v1D<ℒD%AZg][ZSQ5UTJS0Q!U2MMLAD%4TS%LECSQ#/ITD-AQJ~ zNĦ B|vX#+$ HC+vm+[cZzZ[ X@|R}zTy+[?[; t8rF'f(ΜQz<Ŀ]h,=Fw!FᎺȃ^}ڸkJ M8U2}A&}Z.gAc"m!4HIL?JCܣC#+ ?]5rhI#=">{dL4kL1qHc-ezZF`q0 fF#/{p8˧=%Φ-k]>M̍/Fi" 60`IR!KYl1pE3oȜ;v[&lE|I<=RiaJ ƌ#/mC:HpŰi6KPeyc?K1Y6E>#5 PC -2KcD#/\|Ln#/nSA5SA|n.NA2Ibѩ -NAC5“Z<'$ffjhh쎨$)"*aZI&")>1nb6A]c158V(O`)v_7I(H)}*9 )@8ݎqXLt] Ql놵UcZ%pDgf2ɢ`+-$HMG mV{D c9 .#T;.)y{y":B/0<ЇmPVP@뜐""8\qFgJFι L[@(Jdѿ{=P>1yRI>l>75BA$)A'%BG&qB`2`#5"()Q@ECBi#5! "(6q5L_H% ;n{LDlD.pήFRrAW!#5uטuk!#+6iބ8EN]$$lmΉdQ1 婍hve8#<7AޑLG ':Aq#/9OV+6qEQ}0y(]4%0ZS'$h")'U"|<\3T\VL6>xrrWOw1BьD!{rSLJ@L6cCX9͔EbBÑDW. aCxU mO;jtEx$8lEb#KBiy°,WcLT2Ic'[Ri-7p{!FD֤mm$kLWycm=K5iي23 :'eHQ*$!/l{[YK=wJryi9#/W3.PtJ&.#57cm0w/tĎxDʂ#/d&`#(!kd#%4OWTva&,q2Y+%XpkL'b#+6L ^#/c>ZGLsCb8a&D,+)I" ȍb6ě@ZJGp6 Fa$54dc(XVDPf#+T(D0L6?$)m4HpBBG@#/Cu0*`(& (UY7(&'Š4jo.CKA}h({ A̝DZ8c d1 {'YCy)-[$MjcfO]\>?#/Lt\\Vd4t3DѺm jR2c0bHф,P {u{#/kzzΦdAvA#5-* D"EC1:pɐ{q_ l2#)"Q0/s 5;#/_ÿ )P!KA8M#+b#/fP O#5CE1R$Joq+'}"_I9mZl1s4weWDz ]fq6#+@$He(J)dB!D(@"Adb@\(=ylcxFG#5d$n9;&ժɪhJC Q~ e᠋%FѦO`=%Ma1 aۍcWp~0NJznr-˕#5.qu8>HvZ ! .Fe32Nwls](A55Qrlp>;թ;o\tDH2zh!QHv8I;Pw=xi*Sf7Cs:?ozsx]RaO 79A'P3C6`ь: Rϧ<=W-l_f֘IB"S9+QHàddrշrBF$HgkyMQa0M$CC#m*ߟ#5&ɥpN8y4^O(|ذ'b(bITy:fMjp_VQQ^E"_VhA/#5%z4gWTsu8اO #/$xC$P֪'_D>K݃M#5};pnFq&2][њ$#5͆jϝ=m~f *UTܵbд`0WmlO@qwϋ=SR@gG˥=U*_#5Z#hzJa{pt#+$ |k~p_} _8qFh[I! q3?mXC1Muh PL|*#5P2oxOYON@/ߓJ~c͹,~nMf "Pd@ʐOwXL>Z+sN:Z$%=RCeP'~nB#5?DŽnuF9xT54/VW2=,`uM.RF(P==;+(ZAJh)#5#/kAڿc<W#5ڢ/2m 6]@Gd!t8%t<Bh}NдҘҡ1HHtCZ3#5F"^( _ȀC|}fƐ!3gV<# تK}sAzyfqz8Nϫ>s##/ $>D˂x@t(b?ȋ#+>G#+GiNrVĹ83X@i0xO_Ӄ8[tXdPkZW БJ+RhJ>#5!S)IT! &w~%d@!-߮U?IUc#/:,e ))T iy}3D#+]"HOr5I#p#`" Z="VA!0C7)i Va P9i#5iP)#5-@ Bh"tGdu '^J$)=EjS jRCB z*!"Q%IBi"(Z#+!AZb&FF#+iQz=ࢀlԨiVC©*o!_CO8C>ݞ kMp#5kv0>,l8{~2 ȢAy#+Bɷ68ޣ`%) !XA=㿹7R4 P4<>(}G}g pxv$@B)#+( |+PM"iP?$&YBHbj&" *%) *"&(Z@" iHIF#5,kaC-rAێFrÍ#/r8Fy#NG%MUP:$ ݳ''w#5]pGg\ikZEӦ;SA&Ǽ S!GZ!HauiFʠ`;Ҹs47/6!=ፘ'F{2F6C")GCMxϑ a#5HEiH"H#+) j.a&M"ˬ#+WۇBLس`;pjd$=QB | Eb#+y'A:L;mhTf;:pq$?"SܣA SIl5RyU5/0b$0$ uO;Fyre3(<.cu>\r,g>.u$]&_fM4Qjq4@$g%TL:͞Y/{\샺Lf.}-#zbM5~9%Og#/vvtmۚ$_=K&Cs!no}Ti q2J^7bs6^/ǪE1]V/d.~[Iێޗw祥!/N;|j&^f3--׏[#/\#5\o Q9뻤 ҍLvա߱+/Ŧ]8m|pv;U#/J=r MJUÎ9$'iPe//i% B dUAv=*ER h2Yԋ6.Uy ̈Ps/}~ʙUg,6>“w9B[I/VGgӊN :|򤜻>)UUra!^o}-Ovbk/YNWxzv7TTk4#5Ld=iSnMe链k/C3e_8l>[)v*{Q-.ۖ:ӵx7tKI>`JB`peZoLr8#M6.>g1l7rto>äz-ɖ;m/Y{kWdg2=mH\#5KA}(5,zװ;p/5Ì$=|3.%`#+j4;qS["&6)|.no{uf,js㉷v50 7JM3&cfbB=C%I-K k%,l?Ox8#"$iʧL<+&:|V&U+1T)A$#5ڴ}{D6q`"q3}[tk (dP²I_{7֎'#5_9hM.cdYұwΛ}FQ[(T)\XჟMQr"ʗ.5ʲmp.yTEf0eRI j1D$y֖dTHЩ.uŤ,ml=Vg]? geKywQW_~-cy+޽xzv;T,*O_c۴lL9|Uxu5 -5: *T$)&+*m#/T6s8;]gn %s1UÌpA#{S݅䯹*LҡgE1ģEN&yQy5n/I=\NajW3+m:#{'ɇ]`=+|3E1=D7p7( :g7!e#BX暹ywƉ(I3 UPV 6܍,'o0:i|0f9l(2+"c_ 5h^9=H}9b91lsCK%ϊ=y?f:P$TKA,UMvluXL8}Kӻxn~=CY#/3Jdžk&?h_Cϟm Sl(Adz>cqdDaavPt#/<6є೶t3#/vq]BGwG`##+Ѡ *Ӵ;-YXϏdt)O=qfqUdqELזGrP"o#/#ګ ׊VwHh>_ GgZ\=h:\m41G#+(g@2`q㝘xn*O/>ȸ|~ *t4H 8._W0TD:w4U•bKxJІb:U=>#/)͐8䷩vrwTmsߝ7+bD.gمqB#+54cJ,I)ʁ3ܕ[QLf6e20f{4P#+b#5HP%UTQǻV&D;St́1*ueHi& xG_/XII`^Ng)8],v.5dEdzp{qQ-Q m+=MV<^?{9QU3fp>p|\0hL`]+&\(>5S>p~/5od5af.Px SXxq=$%bf#+7v1J/6A!2~f.˱Dog|+y&v8idUCcRI}s=Hʥ6PU 䊧TP4$QY@?T{DLT LSɊ k2Q.Nt+eB!~j#5> ITVZ0L#5_'Nkh3Q9%Sj-A@˄C|Y+i'=4}g`^2<AoQ8Ç22^vf[Y'#5ޛ7+9c+Pj `X21ٲoDkJRrdSHc7(G*(G %jt8 COǘxNJP>@dqҩB(#Y#/&6SP bỨ$ʨ  qK '0!3 O%$ӝسo9S8JFR(Q]f$yWŰcŏI.9mh^ 6xc(~zU1O4Gg#/"(?\#5i#5ZU?*R#5PAJ@B-hIxFֆEQ jɈQ@&LvQٹÕ7#58Sbl#5>8[N)I:ad15ٙ\m<3$IAB+m-FqGH׮W6"aƜI&Ox);&Qz6(0RyU%Ü31&1Xݣ4A#5F̏P1"E82HD1',D" at#/Cu×*"#5i 1jI#5 h+hl!4mq}.` (0}m+1j,r3h2(uTQ6=ss0D$pVƝ`#/j$Sn],&8rN: m#/ʥNC?7]-0i8RS(JcƭV6Ѷ+UwРi֨b$mE"$)GB#:8ڬl~x\õALt':o#DD͎n&;UZű;_g{O^#+bCUxpXϯ={hk-8r퇺9O6(.MKrᱶ("1cl9(^TU;sU.܂#/Q]M#22V)7j][H1ӋEAN0};[4ރع0'y>`ot|2' ?۶PRQWȜEra')]^R"pM(1p9!A$(C#+sk$F0}Galt!>f( s%*nY9rC^#5N^]@}waÌ'Ij^=Q%1Bt0pC{؆EADAUUzmИeW&]Psɶ#/oqi{bW/^R侴u#+ u{Jc59B(-&"paF<ŝf3 3Eń0xsgSI42TDT(cXBS`zGe,6̪z{7ێAbD#/5u1ZAKa=R|?= %). (l'bb36ŪqGf0,oƤ'aَj/,*DNl>kCYPL-9+DP#/S@8B:J9Ļdi9q7#56۲.%0[a#5i#5j.H&uD\LѦ,ES #/BS=tOB~|Vf#+(xK {8<y0gK*߱EX@ LSPEAQD $@B"3(ЀЕUU$SDT! 5*I,- T$T"$2#A!AM50QMQ1A-$4RIUM%ERJQKEE4HULE@C4C?4;s=;O7 =D!NH{ C~t&ޫqcdogwv'꾱V1|3XMpp0a6BAD>( 9$Ny^ùۄ9L(e^ x!P4Aze݃x|#/Vf@m29)co=5oYӢ#+ >'F%?-Jji.[#5(5W8[auE2Ie~P>V؟ oQ$-.4}`b"-{LzzȀ"\*BiwO~s y R\bLA~83l,UYÉB>IĐ$KN "w%#]8:HoFP(1UR^ Peq!xY Aj:Ѿbl)4Ms<';X".=D%EU+uh8%!lD]')Q@?#ͰVRm"\<})#"!H8#/$Ϸi*ZLDTxzGuStL0pT_Tݦ=O K4v#1*#<{#*僄X[p4K *gybk螃%##51X@'쫩Ս"$B6#5 ruV5Z}mBm;G>+7AF9}x2oh)b#"Ur `=` /0-CBpwᄎ*vcEeU"&C([#/q%-1Az'Wy6ޗ.8?@=i%|2bRxg;Z6K}sÎW!LHC&G0q2Q#+ն+*R ,7 tN9`">M7fvL %v/~ˁI}ݟ>wF#+0~RFX#TׇJcm=ģ2i+$ 3#5{8i4/ޫMc4߬43tN-f܎@"N[fxK'٪hoL3Ffm~ $L#/9olu8zf+9ƀrShelWvl6ýg,9/qi6l]aNٳNEĈvNîq#yk܆jWN.hVql]3Q;qrb"tRN,nIgv^.2}+Ë6oN g|Ru<܇#/ab3 b܁sqCbf`qn)wXx&:0Lc}5jL lSqg|y\#/"B hGK8NiiŒUn!Ƣ\enI0h~NdQԙ+;E'%mؠ1r`XaD50K]iaˬ츉hy#e=|6aIԳ ;̐bN=*C۲\SÔԅYjӔd馪#/:9qK3vl¢8$'w2#+`@{wRU{;,n=ѸIJ4j}Gd+Dn[c`sgu\-CLBmgKs [h*7osڙ(δ(5%"ā<0u2Heiȡ:&\w}seΒZ0.wj>pՆZ٤cHh#G]Ë us}MILjbU:KC4;wwL3eT]LG+9Zmqy9{~9yءC2#+4FrBS*|>)6Pi^755YDalՌ$hNT&Gk4_uurHe 2h^liiq-5Iy#+0i7-g[[,kCykI1C}VXCZdqRD#5sTlfo13&+GU$֊1̣m1i4m]m+CL,q%Nݔg#Ĝ;7',0V"RƮi#5t|.5F7;lb*6ݻ )d6ѵk0ХIR#51`9+pPol#5LbACܭ|a`Sldj N!cmS))`q5Lds?ڗqs L[@ }yM6E#2=ZSrKy9#/@2`Pn?6emsUSvEJfv[ m/>4Gܹ5:!B/=ľ bF%)X.΃Y "^#5P%HzǸ"t @NlF5@IR2kViջP\,=(łOS 檝ޱKF)UCKcig3z 2#+%ћ`!N:=PUaYp9S.-Dʹ{|QÅ8u&Fh΃)g`|$McƧ#+96k X)]bgS#5s) ##[L93Nc'߆dޜ}և;Ps]U.#+dZ,i#/9N͌#/tP3 lͦuJq%ɝOQbKYt5/&\aelMTzV1jHIIao,(+ixeӝmr<2C.VmǦ86YpF|z&:4^&32 !jxwjiwѓ}c Jz’$wى")>wcʸd$=,kD::n݊Bi㦸Gp$peX8R<#/>!^l!†@x]p#ncƣB3(1٘%MO=:\3/Ii-B4mE`#/wm~HՁok;ٜ#/n#+qx0Id.ChWu=[S30vy|S pXx #5Cx~zQ:/b&|p#+#/bmF]C,#t{be#5: tMdيP6~.LKe*(;⯻YOzتЪ 5Gɡ1s YeK,ǧԻsg'cf#/$Y{J CL!ܳ3 sͱ7.!2hesFe`5/Zrp{;O毝G{1%' Â){c4ĦȋupX㲎|'( %COS/kOuĽL9Iޙ7:Ψ/.CShT&8ͼnM*%55$Lji6XW[d{c5STd6SM ޝ"9.7:A`LZe I"v`H0TRIC-5\7,w{g#/kj@%yTF)*;Έƹ!s^s4 (,Cvv=JDP@il41D,F#5mq3,"xhI2700 ߇LvCyjv3]:4ڣ4໮ɕ40,2JAzo|j³.E#+Mrt34: zL'qzƨVkC4|=JY9Cf80f#5*@"V룪l1:A耑L#G(K3ǝ$vHФU]lQه`} 䍋Uf M7 2\g((ި]3qs uELz&2BR t1 /zWe;Ғ:x`6p`!}dtc :vq4i'hzi1A-(ۅdwj\+ɓ)mzK^6Ȧ!#/S$Ä~xPB՘gsEU*@1+ޡBT19d! >|C_w2ıJG6D@L#+D(~O}6$pff0#5hLM=p>`mtԡrA ?&u&1E)#/¢܋q0#+lSc&#+S{q3J"P CL%~8\wx@P>D%էb_bդW2u.86$CҪ-0 a ¶v(~%0TUs#"( @QT. (Z%w)iy|F^:lE.(5{Da>DP }ξ'MBBOeӔ/!$M#PpG8>%)!W OhfX&#/9q&B's wdGLNӌ6'͈(Ln{ݎƔ1#/\X.<=9+9ȻW@TEkVfs;8)fkmc< JsF77#5#+3DD11݈x`sSzEZh8Cp1S, lUqAq0%&!2N0#5}8Ce8|."8B'cfS-5S6g^"m@ Ɩ*b ]o5Z\#/!$ 9H,IŤN3y#mNALk#/)5-)V18#5;(-r0yp#5=sKbqǖ#/.Xƈ\,MwnX@MB'st0#u̙eƓ$%dݍ10Lː󜙆22d+81,"6xnsu1bzICtq2b^L$Зc4C.JSWq`;r ةRtεmCӢ-msR,(-#+62P"6‹WaC9p:JX1N=^d:Ul`:jl\`MjcAQw1Y |q Ӡ||D2:o|#/9˖#5)n !?@񙆳 M!K-PāKXHT|KB^%C$PDr@hv%N@4*R-H֑)QHER#5B_yQ#+]#+8#+Z#/jn̎1)\<@Qw&#/(׺D6#~h:dw9bKP2"@&!FW=gz\#+=z~a;D#+v!#5g7$%`!&$"Hf Jih"b((V8`4h #YR0`)4i"CۥM`7 &6 +|$$~oOeP!.{ 0|xvHJR-PΫy~CĶlD~/h|{|b.MήUԥ]7hx"NnZ1x6K,덉*;3ʼnbLr#/YNq1I۲7aGh0ٔaHfG|E>,~T2Vj4D;,p̆R 0)A2DV?ane6lU&O( Γ`[&Ă^$Q#5tAQ:^Ǘ$_oc=gjBf(x`eb'gC9N,8 k0ԅ !>JA7AK_Xyl_NI4V^Ԉ~dU@ݕ Ah6*N=Iyh#5)Hz2dOD1#/0UEZ#5! |FCZ\#5&P#+No|чw|S5{QuLb#5I#5^}wٝU\):y+, F`":\ `{\}%ti4!}ʩ#5|9xkugh(6⑏iI LdCTQJ4)&&O|.>#5#`T#5OPq;0o6KJy9XC=_DC F= U#+pps<*'r#+ʲЕHz0{AШp$#+i(%B(±1"*y#+#+\ LUbDZ14,h+ ( LD46]TCQFj@9/8\Ի#/!pxZSg&xRwb DQRGvUAƐ(YIDHP1 0L#+4~M/5羅 d:#/H~ºkӟޗ Zϟ2 QA&a֛G#+:^#+RLR!BP PB+!#E#+J ( C@ઝخ|Pv>#5"3"/_h#5J("#5J*ZB!`$*iHuϒs#5`$)("JYH&DJ#5!P5'2HѲ=DU(J$+J)A#t'Od XdTJm9"߀_.B砂#@d@@B%4!TUTt W+H4ɀCvr!t ĸdFŶZ̓J!#+ RCʑd#5Ih@r )^wZQP #+bA;{2D(DҬvF$5}B#+( ?Jn$*saM<^_Ji+&r;l<0 8"#5\1¾rXpne ^NlDsFadM(Pdļg.IcX-J\@vltJE#/#z>DK>T |΅& Q?ixO:O)Wrk(MQ5H_=~9 ObJ")BkXĆ90FXCA%IL16hu[MM.ZRJD@PBQIT4HFؕj JJ>#/P qt=nx#50 r#?E^'S#+_lDQHq,3'q* b4ȳ݊2f`Ev#/ pcLA2.;.aPZbfU ǙıBHu 37#P9tNpo9}Am&hT5s.W#` T! #B"t#5idPP@0TCDB4H!Lotw{n8KA-6k &tp;Z@-#5S_Q#5#5AF P*ϱ rd1hK5AnÄշ-h^qG 6(dlQЩ(#5M66-co`#U$  ă8h1SɡAP@CM  Q!K$;i'*` iHM9Js390ijb&VTD#/$J@AD$@d 3GpqB1쐧<<"$:\g@J; H&&UCl&u1&"M!w>0vxl#+N}ICxOO?.EER/ {ò` .ǃz?1I3Gm_(#5˿y8pϠ.C\m #`j`0yqLۊ8Uy#/'ec )77~f6E}i>\ȅ,UxUoV.ec wԎt7 R18PLI8E ~c?O)GHMCOٻ3KdJnU9Hz #5  QbR誠O5/#+(Sɿ{ J*9#5 'N'G'd*b,#/MƮFm%Fn\ˇ!Ilc43~'& 6[+Bi-B"CTTpS#5#5A7oSo#5"s}CD?~Ѿ>)#/%؈)j2~*F3HOy%[YC+T$efP8QyHtgpQ:PECC#//zr'vIYev]fh̙'q", Cp=ޛ{QQK}Tn}yCoV#/&#+`,)3ŗ*E#+#+.zP~oUO]"4xsV#5P8#/Nx'q#/i`CHkrAP9.FSs(lS1:8r;~KXڤw?;; ?/@h{(' IF3 Ot#/d*W#5Kձ`49k>lDGyӈS$! x~E:EN/͘BCiA?zωt&s I$WIA$ 6b?B )#+~eK'sq#5DKS#5r@'-&$D# %TԒT@ZuhACO|o:W5f8u~-8q/xSZΰ''4G>D9 'tt|`mW xHz4!j(17QYxM( 9>Q%J"&*{qD ]4A6;~SI <'m^y 3dzA8 E(AMÄHv҉Nㄿ<(z"n CK Avb^P4U÷1#/Y9gj2kzUB4n&#y D"pd{LJ[4RH 5tbd̈́C!D3,yQDǨxSMQq31i]͎tt`zm#W;>TɚՉ7N,PQIZv㲃 #`2D6s&_80sm>2oպ0(5"&BR6j2"ʪQgKL*̥m[Ǜd0!LQ!TKh\F=A0Ƹ1x) ML93rf6(=mkCt (r1#+݆QU=l)0ņ=2:CZM*u6wsZ 1ng8u&a*L&*(^_oX(b%U0j۬$>Lzh VPRT"0#odV`f>__H6_kKțmI| ݟ3E[&QфAG3FU?C:6SBj 3IϟgN<8R>9<^'ᙘ)i#5b#5(F*J#+OZjti!ǂ#5N)7sæ<>_IM_!/g! |xfwS#K/a[*@txﺹd))(j#5fNN("΢`b#+#+ZV!#5;LPQ8@B#/pqv09@ _WSmُhRC1Q@m|%lHȃ>'G@^U<_Zl v̤gmydϘ o5(Lfl(قAZ~~c3.L7D8-2ULߞaЩWh-!#'R]#56=H>Ϝ0ix,}C@lfT2D5T%BK+^)vSNI9;/jֽ]#CI i}aMRRTbKjx$rSqqeC8H[oyΐ l#/u#˘u18%B~0诱͐Ӿc%rn5(8I_HT;ZSI"5."KtÉ5Ӧg H]تЕCg{XͮoBBuWdiS05~aNbS -DYQu+P;xo(3]}\#/ iYgN2T2RrAWM-#+W$#/#5SWRP% DR"h؏^׃o'!`#/Hӵ`7<}bfA d - |[pm`6#5id8ҀH. k%rxdr9 G${&Ǐ >LâCϼAڂ#5ibG)@>^p9t}X>$z"{<AV=mLTU#/D4D#/DV4`dPDMl~\~cO.Hd@Xӈ?W+w#5~w-<_G۹t4I)O?݇lIwF‹#_sSbCzA:iGb b#+RQ <9t? J)Dy<_ӥڿ8]I~xwdp(c]Y{aePmڝC>@aR'G|ɋM÷?'$=M281挴#*ty4 }Zҿw<jued!lX2`3Յgo7|6³k˪sݯYpohһxUL\e|!6;hcY=h%c(1y L/ (3"fJUtFw(UTgcI<dn1* $V#*D{,ϞPTHPןd%o 18'_N@0=7/ nul if %ERRORLEVEL% EQU 0 SET TOKEN=tokens=3* ver | findstr /i "5\.1\." > nul if %ERRORLEVEL% EQU 0 SET TOKEN=tokens=3* ver | findstr /i "5\.2\." > nul if %ERRORLEVEL% EQU 0 SET TOKEN=tokens=3* -ver | findstr /i "6\.0\." > nul -if %ERRORLEVEL% EQU 0 SET TOKEN=tokens=2* -ver | findstr /i "6\.1\." > nul -if %ERRORLEVEL% EQU 0 SET TOKEN=tokens=2* rem Start calculating PYTHON and PYTHON_DIR set PYTHON= @@ -29,7 +25,7 @@ Setlocal EnableDelayedExpansion set PYTHON_DIR_OK=FALSE set REGPATH= -for %%i in (3.9 3.8 3.7 3.6 3.5 3.4 3.3 3.2 3.1 3.0 2.7 2.6 2.5) do ( +for %%i in (3.10 3.9 3.8 3.7 3.6 3.5 3.4 3.3 3.2 3.1 3.0 2.7 2.6 2.5) do ( for %%j in (HKCU HKLM) do ( for %%k in (SOFTWARE\Wow6432Node SOFTWARE) do ( for %%l in (Python\PythonCore IronPython) do ( @@ -88,7 +84,7 @@ rem @echo %PYTHON_DIR% if "%PYTHON%" == "" ( rem @echo No Python -set PYTHON=python +set PYTHON=py goto running ) diff --git a/wscript b/wscript index 4fae627f..956d98ad 100644 --- a/wscript +++ b/wscript @@ -20,12 +20,13 @@ class Subproject: ignore = False # if true will be ignored, set by user request mandatory = False - def __init__(self, name, dedicated=True, singlebin=False, mandatory = False, utility = False): + def __init__(self, name, dedicated=True, singlebin=False, mandatory = False, utility = False, fuzzer = False): self.name = name self.dedicated = dedicated self.singlebin = singlebin self.mandatory = mandatory self.utility = utility + self.fuzzer = fuzzer def is_enabled(self, ctx): if not self.mandatory: @@ -47,10 +48,14 @@ class Subproject: if self.utility and not ctx.env.ENABLE_UTILS: return False + if self.fuzzer and not ctx.env.ENABLE_FUZZER: + return False + return True SUBDIRS = [ Subproject('public', dedicated=False, mandatory = True), + Subproject('filesystem', dedicated=False, mandatory = True), Subproject('game_launch', singlebin=True), Subproject('ref_gl',), Subproject('ref_vk',), @@ -61,7 +66,8 @@ SUBDIRS = [ Subproject('stub/client'), Subproject('dllemu'), Subproject('engine', dedicated=False), - Subproject('utils/mdldec', utility=True) + Subproject('utils/mdldec', utility=True), + Subproject('utils/run-fuzzer', fuzzer=True) ] def subdirs(): @@ -99,6 +105,9 @@ def options(opt): grp.add_option('--enable-utils', action = 'store_true', dest = 'ENABLE_UTILS', default = False, help = 'enable building various development utilities [default: %default]') + grp.add_option('--enable-fuzzer', action = 'store_true', dest = 'ENABLE_FUZZER', default = False, + help = 'enable building libFuzzer runner [default: %default]' ) + opt.load('compiler_optimizations subproject') for i in SUBDIRS: @@ -147,6 +156,7 @@ def configure(conf): conf.options.NO_VGUI= True # skip vgui conf.options.NANOGL = True conf.options.GLWES = True + conf.options.GL4ES = True conf.options.GL = False elif conf.env.MAGX: conf.options.USE_SELECT = True @@ -186,7 +196,6 @@ def configure(conf): '-Werror=vla', '-Werror=tautological-compare', '-Werror=duplicated-cond', - '-Werror=duplicated-branches', # BEWARE: buggy '-Werror=bool-compare', '-Werror=bool-operation', '-Wcast-align', @@ -202,6 +211,7 @@ def configure(conf): # '-Werror=format=2', # '-Wdouble-promotion', # disable warning flood '-Wstrict-aliasing', + '-Wmisleading-indentation', ] c_compiler_optional_flags = [ @@ -248,6 +258,7 @@ def configure(conf): conf.define('STDINT_H', 'pstdint.h') conf.env.ENABLE_UTILS = conf.options.ENABLE_UTILS + conf.env.ENABLE_FUZZER = conf.options.ENABLE_FUZZER conf.env.DEDICATED = conf.options.DEDICATED conf.env.SINGLE_BINARY = conf.options.SINGLE_BINARY or conf.env.DEDICATED @@ -292,6 +303,34 @@ def configure(conf): else: conf.undefine('HAVE_TGMATH_H') + # set _FILE_OFFSET_BITS=64 for filesystems with 64-bit inodes + if conf.env.DEST_OS != 'win32' and conf.env.DEST_SIZEOF_VOID_P == 4: + # check was borrowed from libarchive source code + file_offset_bits_usable = conf.check_cc(fragment=''' +#define _FILE_OFFSET_BITS 64 +#include +#define KB ((off_t)1024) +#define MB ((off_t)1024 * KB) +#define GB ((off_t)1024 * MB) +#define TB ((off_t)1024 * GB) +int t2[(((64 * GB -1) % 671088649) == 268434537) + && (((TB - (64 * GB -1) + 255) % 1792151290) == 305159546)? 1: -1]; +int main(void) { return 0; }''', + msg='Checking if _FILE_OFFSET_BITS can be defined to 64', mandatory=False) + if file_offset_bits_usable: + conf.define('_FILE_OFFSET_BITS', 64) + else: conf.undefine('_FILE_OFFSET_BITS') + + if conf.env.DEST_OS != 'win32': + strcasestr_frag = '''#include +int main(int argc, char **argv) { strcasestr(argv[1], argv[2]); return 0; }''' + + if conf.check_cc(msg='Checking for strcasestr', mandatory=False, fragment=strcasestr_frag): + conf.define('HAVE_STRCASESTR', 1) + elif conf.check_cc(msg='... with _GNU_SOURCE?', mandatory=False, fragment=strcasestr_frag, defines='_GNU_SOURCE=1'): + conf.define('_GNU_SOURCE', 1) + conf.define('HAVE_STRCASESTR', 1) + # check if we can use alloca.h or malloc.h if conf.check_cc(header_name='alloca.h', mandatory=False): conf.define('ALLOCA_H', 'alloca.h') @@ -303,7 +342,7 @@ def configure(conf): conf.env.LIBDIR = conf.env.BINDIR = '${PREFIX}/lib/xash3d' conf.env.SHAREDIR = '${PREFIX}/share/xash3d' else: - if sys.platform != 'win32': + if sys.platform != 'win32' and not conf.env.DEST_OS == 'android': conf.env.PREFIX = '/' conf.env.SHAREDIR = conf.env.LIBDIR = conf.env.BINDIR = conf.env.PREFIX