diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index bbeaaa55..31c2157b 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -24,22 +24,23 @@ jobs: # targetos: linux # targetarch: aarch64 -# - os: ubuntu-18.04 +# - os: ubuntu-20.04 # targetos: android # targetarch: 32 -# - os: ubuntu-18.04 +# - os: ubuntu-20.04 # targetos: android # targetarch: 64 -# - os: ubuntu-18.04 +# - os: ubuntu-20.04 # targetos: motomagx # targetarch: armv6 -# FIXME suddenly started failing, disable temporarily as irrelevant for ref_vk effort -# - os: ubuntu-20.04 -# targetos: nswitch -# targetarch: arm64 - + - os: ubuntu-20.04 + targetos: nswitch + targetarch: arm64 + - os: ubuntu-20.04 + targetos: psvita + targetarch: armv7hf - os: windows-latest targetos: win32 targetarch: amd64 @@ -50,9 +51,7 @@ jobs: SDL_VERSION: 2.26.2 VULKAN_SDK_VERSION: 1.3.239 GH_CPU_ARCH: ${{ matrix.targetarch }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ANDROID_SDK_TOOLS_VER: 4333796 - UPLOADTOOL_ISPRERELEASE: true steps: - name: Checkout uses: actions/checkout@v3 @@ -62,8 +61,6 @@ jobs: run: bash scripts/gha/deps_${{ matrix.targetos }}.sh - name: Build engine run: bash scripts/gha/build_${{ matrix.targetos }}.sh - - name: Upload engine (prereleases) - run: bash scripts/continious_upload.sh artifacts/* - name: Upload engine (artifacts) uses: actions/upload-artifact@v3 with: @@ -95,3 +92,38 @@ jobs: # manifest-path: scripts/flatpak/${{ matrix.app }}.yml # - name: Upload engine (prereleases) # run: bash scripts/continious_upload.sh ${{ matrix.app }}.flatpak + release: + name: "Upload releases" + runs-on: ubuntu-latest + needs: [build, flatpak] + if: ${{ github.event_name == 'push' }} + steps: + - name: Remove old release + uses: FWGS/delete-tag-and-release@v0.2.1-dev + with: + tag_name: ${{ github.ref_name == 'master' && 'continuous' || format('continuous-{0}', github.ref_name) }} + delete_release: true + github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Fetch artifacts + uses: actions/download-artifact@v3.0.1 + with: + path: artifacts/ + - name: Repackage binaries and allow GitHub to process removed release for few seconds + run: | + cd artifacts/ + for i in artifact-* su.xash.Engine.*; do + mv "$i"/* . + rm -rf "$i" + done + ls -R . + cd ../ + sleep 20s + - name: Upload new release + uses: FWGS/action-gh-release@v0.1.15 + with: + name: Xash3D FWGS Continuous ${{ github.ref_name }} Build + tag_name: ${{ github.ref_name == 'master' && 'continuous' || format('continuous-{0}', github.ref_name) }} + prerelease: true + token: ${{ secrets.GITHUB_TOKEN }} + files: artifacts/* + draft: false diff --git a/3rdparty/extras/xash-extras b/3rdparty/extras/xash-extras index 9aba4527..853f633b 160000 --- a/3rdparty/extras/xash-extras +++ b/3rdparty/extras/xash-extras @@ -1 +1 @@ -Subproject commit 9aba4527435b1beda97ca8d8a5f1937cd0088c57 +Subproject commit 853f633be18892499a86e7f57fecf34640bd64f2 diff --git a/3rdparty/mainui b/3rdparty/mainui index 2f615a74..262128c0 160000 --- a/3rdparty/mainui +++ b/3rdparty/mainui @@ -1 +1 @@ -Subproject commit 2f615a74802e665014cddaf766e4edc2bac24a55 +Subproject commit 262128c0d1368177d85072d1642521f738942f9a diff --git a/3rdparty/opus/opus b/3rdparty/opus/opus index 997fdf54..8cf872a1 160000 --- a/3rdparty/opus/opus +++ b/3rdparty/opus/opus @@ -1 +1 @@ -Subproject commit 997fdf54e781ae1c04dee42018f35388a04fe483 +Subproject commit 8cf872a186b96085b1bb3a547afd598354ebeb87 diff --git a/3rdparty/vgui_support b/3rdparty/vgui_support index 63c134f1..e7b56e7b 160000 --- a/3rdparty/vgui_support +++ b/3rdparty/vgui_support @@ -1 +1 @@ -Subproject commit 63c134f188e7c0891927f5a4149f4444b43b0be8 +Subproject commit e7b56e7b26b240f811a8a192f6e2880f0d3898d3 diff --git a/Documentation/debugging-using-minidumps.md b/Documentation/debugging-using-minidumps.md new file mode 100644 index 00000000..0f0fad44 --- /dev/null +++ b/Documentation/debugging-using-minidumps.md @@ -0,0 +1,10 @@ +# Debugging your mod using minidump files (Windows only) +Minidump files is awesome instrument for debugging your mod after it's being released, or for catch specific crashes which are presented only in one specific configuration, but doesn't happens in other configurations or even on developer machine. It contains a lot of information useful for debugging, and therefore it size is not so small: around hundreds or even thousands of megabytes. But this is not a problem, since minidump files compresses very effectively using common algorithms. +There are short algorithm, explaining how to use this debugging instrument: + +1. User starts your mod with `-minidumps` startup parameter +2. Finally, crash happened on user's machine, and minidump file being written. This file has `.mdmp` extension and located in same folder, where mod folder located +3. User packs this minidump file to .zip/.7z archive, and somehow sends it to developer +4. Developer just opens minidump file in Visual Studio, then a virtual debugging session is opened and now reasons of crash is pretty easy to detect: you can see call stack, local function variables and global variables, information about exception and a lot of other information + +You can find more information about minidumps in this [awesome article](https://learn.microsoft.com/en-us/windows/win32/dxtecharts/crash-dump-analysis?source=recommendations#writing-a-minidump). diff --git a/Documentation/extensions/mp3-loops.md b/Documentation/extensions/mp3-loops.md new file mode 100644 index 00000000..59fd6efd --- /dev/null +++ b/Documentation/extensions/mp3-loops.md @@ -0,0 +1,22 @@ +## Looping MP3 extension + +It is now possible to loop MP3 file in Xash3D FWGS by adding a custom text tag with `LOOP_START` or `LOOPSTART` in description and time point (in raw samples) in value. + +### Example with foobar2000 +1. Open Foobar2000 +2. Add your .mp3 file to playlist +3. Right click to newly added file and select Properties +4. In Metadata tab, at the bottom of the table, select "+add new" +5. In newly added line replace `input field name` with `LOOP_START` (without any symbols). +6. Press Tab and enter loop time point in raw samples. For example, `0` will replay sound file from beginning to end indefinitely. + +### Possible alternatives +1. Classic WAV files looping. HQ WAV files can take too much disk space, and recommended software supporting cue points is paid, outdated and can't run on modern systems. (Although there is alternative that's proven to work with idTech-based engines called LoopAuditioneer.) +2. Vorbis looping through comment. Engine doesn't support Vorbis but this extension was highly inspired by this hack. + +### Known bugs and limitations +1. At this time using MP3 as SFX requires complete decoding. This can cause noticeable stutters, so keep MP3 file length in mind. +2. We deliberately only support modern ID3v2.3 and ID3v2.4 tags. Using ID3v1 is not possible. + + + diff --git a/Documentation/mod-porting-guide.md b/Documentation/mod-porting-guide.md index a2219c2c..a1c37bce 100644 --- a/Documentation/mod-porting-guide.md +++ b/Documentation/mod-porting-guide.md @@ -1,7 +1,40 @@ -# Server porting +# Self-made port +## Compatibility with RISC architectures +### Unaligned access +Unaligned access on **i386** causes only performance penalty, but on **RISC** it can cause unstable work. + +For HLSDK at least you need such patches in util.cpp: + - https://github.com/FWGS/halflife/commit/7bfefe86e35d67867ae7af830ac1fc38f2908360 + - https://github.com/FWGS/hlsdk-portable/commit/617d75545f2ecb9b2d46cc30728dc37c9eb6d35e + +### Signed chars +`char` type defined as **signed** for **x86** and as **unsigned** for **arm**. + +And sometimes such difference can break logic. + +As a solution you can use `signed char` type in code directly or use `-fsigned-char` option for gcc/clang compilers. + +For HLSDK at least you need such [fix](https://github.com/FWGS/hlsdk-portable/commit/1ca34fcb4381682bd517612b530db22a1354a795) in nodes.cpp/.h. + +## Compatibility with 64bit architectures +You need list of patches for Studio Model Render, MAKE_STRING macro and nodes: + - https://github.com/FWGS/hlsdk-portable/commit/d287ed446332e615ab5fb25ca81b99fa14d18a73 + - https://github.com/FWGS/hlsdk-portable/commit/3bce17e3a04f8af10a927a07ceb8ab0f09152ec4 + - https://github.com/FWGS/hlsdk-portable/commit/9ebfc981773ec4c7a89ffe52d9c249e1fbef9634 + - https://github.com/FWGS/hlsdk-portable/commit/00833188dab87ef5746286479ba5aeb9d83b4a0c + - https://github.com/FWGS/hlsdk-portable/commit/4661b5c1a5245b27a5532745c11e44b5540e4172 + - https://github.com/FWGS/hlsdk-portable/commit/2b61380146b1d58a8c465f0e312c061b12bda115 + - https://github.com/FWGS/hlsdk-portable/commit/8ef6cb2427ee16a763103bd3f315f38e2f01cfe2 + +## Mobility API +Xash3D FWGS has special extended interface in `mobility_int.h` which adds some new features like vibration on mobile platforms. + +## Porting server-side code Original valve's server code was compatible with linux and gcc 2.x. + Newer gcc versions have restriction which breaks build. -Now, to make it building with gcc 4.x, you need do following: + +Now, to make it building with gcc 4.x+ or clang, you need to do following: * Go to cbase.h and redefine macros as following ``` #define SetThink( a ) m_pfnThink = static_cast (&a) @@ -18,58 +51,60 @@ Now, to make it building with gcc 4.x, you need do following: * Replace all SetThink(NULL), SetTouch(NULL), setUse(NULL) and SetBlocked(NULL) by ResetThink(), ResetTouch(), ResetUse() and ResetBlocked() * Sometimes you may need to add #include if functions tolower or isspace are missing -# Client porting +## Porting client-side code -## VGUI library - -Valve's client uses vgui library which is available only on x86 systems and has big amount of mistakes in headers. The best and simplest way of porting is removing VGUI dependency at all. -Most singleplayer mods don't really use VGUI at all. It is used in multiplayer only to show score table and MOTD window - -## Porting - -### First strategy: full port - -#### Basic port (Stage 1) -* First, remove all files and headers which use vgui (contains "vgui" in file name). -* After that, try to build it and remove vgui includes. -* Remove all gViewPort usings. -* Remove all CVoiceManager usings (xash3d does not support voice) * Redefine all DLLEXPORT defines as empty field (Place it under _WIN32 macro if you want to keep windows compatibility). -* Remove hud_servers.cpp and Servers_Init/Servers_Shutdown from hud.cpp +* Remove hud_servers.cpp and Servers_Init/Servers_Shutdown from hud.cpp. * Fix CAPS filenames in includes (like STDIO.H, replace by stdio.h). -* Replace broken macros as DECLARE_MESSAGE, DECLARE_COMMAND by fixed examples from our hlsdk-xash3d port (cl_util.h) -* Add ctype.h where it is need (tolower, isspace functions) -* Add string.h where it is need (memcpy, strcpy, etc) -* Use in_defs.h from hlsdk_client -* Add scoreboard_stub.cpp and input_stub.cpp from hlsdk-xash3d to fix linking. -Now your client should be able to build and work correctly. Add input_xash3d.cpp from hlsdk-xash3d project to fix input. +* Replace broken macros as DECLARE_MESSAGE, DECLARE_COMMAND by fixed examples from our hlsdk-portable port (cl_util.h). +* Add ctype.h where is needed (tolower, isspace functions). +* Add string.h where is needed (memcpy, strcpy, etc). +* Use in_defs.h from hlsdk-portable. +* Add input_xash3d.cpp from hlsdk-portable project to fix input. -#### Multiplayer fix (Stage 2) -Look at hlsdk-xash3d project. +Now your client should be able to build and work correctly. -Main changes are: -* Add MOTD.cpp, scoreboard.cpp and input_xash3d.cpp -* Add missing functions to hud_redraw.cpp, hud.cpp and tri.cpp, fix class defination in hud.h -* Remove duplicate functions from hud.cpp and HOOK_MESSAGE's for it -* Remove +showscores/-showscores hooks from input.h -* Fix cl_util.h +# Porting mod to hlsdk-portable +Look at changes which was made. -### Second way: move mod to hlsdk-xash3d -Look at changes you made in client. +If there are not too much changes (for example, only some weapons was added), add these changes in hlsdk-portable. -If there are not much changes (for example, only some weapons was add), add these changes in hlsdk-xash3d. - -You may use diff with original HLSDK you used and apply it as patch to hlsdk-xash3d. +You can use diff with original HLSDK and apply it as patch to hlsdk-portable. +``` +NOTE: Many an old mods was made on HLSDK 2.0-2.1 and some rare mods on HLSDK 1.0. +So you need to have different versions of HLSDK to make diffs. +Plus different Spirit of Half-Life versions if mod was made on it. +Also, weapons in old HLSDK versions does not use client weapon prediction system and you may be need to port standart half-life weapons to server side. +``` Files must have same line endings (use dos2unix on all files). -I recommend to enable ignoring space changes in diff. -### Writing Makefiles +We recommend to enable ignoring space changes in diff. -Use Makefile from hlsdk-xash3d as Makefile example. +Move all new files to separate directories. -Get .c and .cpp file lists from Visual Studio project and fill SRCS and SRCS_C variables to Makefile. +# Possible replacements for non-portable external dependencies +1. If mod uses **fmod.dll** or **base.dll** to play mp3/ogg on client-side or to precache sounds on server-side, you can replace it with: + - [pfnPrimeMusicStream](https://github.com/FWGS/hlsdk-portable/blob/master/engine/cdll_int.h#L293=) engine callback; + - [miniaudio](https://github.com/mackron/miniaudio); + - [phonon](https://community.kde.org/Phonon). -Remove all files containing vgui in name from list, add missing include dirs. +2. If mod uses **OpenGL**, porting code to Xash3D render interface is recommended. + +3. If mod uses **cg.dll**, you can try to port code to [NVFX](https://github.com/tlorach/nvFX). + +4. If mod uses detours, comment code or try to find replacement somehow by yourself. + +# Additional recommendations +1. If mod uses STL, you can replace it with [MiniUTL](https://github.com/FWGS/MiniUTL). +2. Avoid to use dynamic casts to make small size binaries. +3. Avoid to use exceptions to make small size binaries. + +# Writing build scripts + +Use wscript/CMakeLists.txt files from hlsdk-portable as build scripts example. + +Get .c and .cpp file lists from Visual Studio project. + +Add missing include dirs. -Do same for Android.mk if you are building for android. diff --git a/Documentation/ports.md b/Documentation/ports.md index fa072aba..1fd29931 100644 --- a/Documentation/ports.md +++ b/Documentation/ports.md @@ -26,7 +26,8 @@ Table is sorted by status. | 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) +| PSVita | Supported | @fgsfdsfgs | +| Switch | Supported | @fgsfdsfgs | | 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 | diff --git a/Documentation/psvita.md b/Documentation/psvita.md new file mode 100644 index 00000000..a4b6394a --- /dev/null +++ b/Documentation/psvita.md @@ -0,0 +1,49 @@ +## PlayStation Vita port + +### Prerequisites +1. Make sure your PSVita is [set up to run homebrew applications](https://vita.hacks.guide/). +2. Install [kubridge](https://github.com/TheOfficialFloW/kubridge/releases/) by copying `kubridge.suprx` to your taiHEN plugins folder (usually `ux0:/tai`) and add it to your `config.txt`, for example: + ``` + *KERNEL + ux0:tai/kubridge.skprx + ``` +3. Install `libshacccg.suprx` by following [this guide](https://cimmerian.gitbook.io/vita-troubleshooting-guide/shader-compiler/extract-libshacccg.suprx). + +### Installation +1. If you have an old vitaXash3D install, remove it. +2. Get `xash3d-fwgs-psvita.7z` from the latest [automatic build](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous). +3. Install `xash.vpk` from the 7z archive onto your PSVita. +4. Copy the `data` directory from the 7z archive to the root of your PSVita's SD card. +5. Copy the valve folder and any other mod folders from your Half-Life install to `ux0:/data/xash3d/` (you can use other mountpoints instead of `ux0`). **Do not overwrite anything.** + +### Build instructions +1. Install [VitaSDK](https://vitasdk.org/). +2. Build and install [vitaGL](https://github.com/Rinnegatamante/vitaGL): + ``` + git clone https://github.com/Rinnegatamante/vitaGL.git + make -C vitaGL NO_TEX_COMBINER=1 HAVE_UNFLIPPED_FBOS=1 HAVE_PTHREAD=1 SINGLE_THREADED_GC=1 MATH_SPEEDHACK=1 DRAW_SPEEDHACK=1 HAVE_CUSTOM_HEAP=1 -j2 install + ``` +3. Build and install [vita-rtld](https://github.com/fgsfdsfgs/vita-rtld): + ``` + git clone https://github.com/fgsfdsfgs/vita-rtld.git && cd vita-rtld + mkdir build && cd build + cmake -DCMAKE_BUILD_TYPE=Release .. + make -j2 install + ``` +4. Build and install [this SDL2 fork](https://github.com/Northfear/SDL) with vitaGL integration: + ``` + git clone https://github.com/Northfear/SDL.git && cd SDL + mkdir build && cd build + cmake -DCMAKE_TOOLCHAIN_FILE=${VITASDK}/share/vita.toolchain.cmake -DCMAKE_BUILD_TYPE=Release -DVIDEO_VITA_VGL=ON .. + make -j2 install + ``` +5. Use `waf`: + ``` + ./waf configure -T release --psvita + ./waf build + ``` +6. Copy all the resulting `.so` files into a single folder: + ``` + ./waf install --destdir=xash3d + ``` +7. `xash.vpk` is located in `build/engine/`. diff --git a/common/defaults.h b/common/defaults.h index 746b0f4d..e8fbaec6 100644 --- a/common/defaults.h +++ b/common/defaults.h @@ -70,7 +70,7 @@ SETUP BACKENDS DEFINITIONS #define XASH_MESSAGEBOX MSGBOX_ANDROID #endif // XASH_MESSAGEBOX - #define XASH_USE_EVDEV + #define XASH_USE_EVDEV 1 #define XASH_DYNAMIC_DLADDR #elif XASH_LINUX // we are building for Linux without SDL2, can draw only to framebuffer yet @@ -86,7 +86,7 @@ SETUP BACKENDS DEFINITIONS #define XASH_SOUND SOUND_ALSA #endif // XASH_SOUND - #define XASH_USE_EVDEV + #define XASH_USE_EVDEV 1 #elif XASH_DOS4GW #ifndef XASH_VIDEO #define XASH_VIDEO VIDEO_DOS @@ -165,6 +165,12 @@ Default build-depended cvar and constant values #define DEFAULT_MODE_WIDTH 1280 #define DEFAULT_MODE_HEIGHT 720 #define DEFAULT_ALLOWCONSOLE 1 +#elif XASH_PSVITA + #define DEFAULT_TOUCH_ENABLE "0" + #define DEFAULT_M_IGNORE "1" + #define DEFAULT_MODE_WIDTH 960 + #define DEFAULT_MODE_HEIGHT 544 + #define DEFAULT_ALLOWCONSOLE 1 #elif XASH_MOBILE_PLATFORM #define DEFAULT_TOUCH_ENABLE "1" #define DEFAULT_M_IGNORE "1" @@ -185,6 +191,10 @@ Default build-depended cvar and constant values #define DEFAULT_M_IGNORE "0" #endif // DEFAULT_M_IGNORE +#ifndef DEFAULT_JOY_DEADZONE + #define DEFAULT_JOY_DEADZONE "4096" +#endif // DEFAULT_JOY_DEADZONE + #ifndef DEFAULT_DEV #define DEFAULT_DEV 0 #endif // DEFAULT_DEV diff --git a/common/port.h b/common/port.h index 63419cc6..76189847 100644 --- a/common/port.h +++ b/common/port.h @@ -42,6 +42,10 @@ GNU General Public License for more details. #if XASH_NSWITCH #define SOLDER_LIBDL_COMPAT #include + #elif XASH_PSVITA + #define VRTLD_LIBDL_COMPAT + #include + #define O_BINARY 0 #else #include #define HAVE_DUP diff --git a/common/xash3d_types.h b/common/xash3d_types.h index adca2246..3cd1cfec 100644 --- a/common/xash3d_types.h +++ b/common/xash3d_types.h @@ -50,6 +50,7 @@ typedef uint64_t longtime_t; #define MAX_SERVERINFO_STRING 512 // server handles too many settings. expand to 1024? #define MAX_LOCALINFO_STRING 32768 // localinfo used on server and not sended to the clients #define MAX_SYSPATH 1024 // system filepath +#define MAX_VA_STRING 1024 // string length returned by va() #define MAX_PRINT_MSG 8192 // how many symbols can handle single call of Con_Printf or Con_DPrintf #define MAX_TOKEN 2048 // parse token length #define MAX_MODS 512 // environment games that engine can keep visible diff --git a/engine/client/avi/avi.h b/engine/client/avi/avi.h index 6435a161..20fb0450 100644 --- a/engine/client/avi/avi.h +++ b/engine/client/avi/avi.h @@ -27,7 +27,6 @@ int AVI_GetAudioChunk( movie_state_t *Avi, char *audiodata, int offset, int leng void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audio, int quiet ); movie_state_t *AVI_LoadVideo( const char *filename, qboolean load_audio ); int AVI_TimeToSoundPosition( movie_state_t *Avi, int time ); -int AVI_GetVideoFrameCount( movie_state_t *Avi ); void AVI_CloseVideo( movie_state_t *Avi ); qboolean AVI_IsActive( movie_state_t *Avi ); void AVI_FreeVideo( movie_state_t *Avi ); diff --git a/engine/client/avi/avi_stub.c b/engine/client/avi/avi_stub.c index bb64b13f..0c6504d2 100644 --- a/engine/client/avi/avi_stub.c +++ b/engine/client/avi/avi_stub.c @@ -57,11 +57,6 @@ int AVI_TimeToSoundPosition( movie_state_t *Avi, int time ) return 0; } -int AVI_GetVideoFrameCount( movie_state_t *Avi ) -{ - return 0; -} - void AVI_CloseVideo( movie_state_t *Avi ) { ; diff --git a/engine/client/avi/avi_win.c b/engine/client/avi/avi_win.c index 534b31ce..128fe62f 100644 --- a/engine/client/avi/avi_win.c +++ b/engine/client/avi/avi_win.c @@ -1,5 +1,6 @@ /* avi_win.c - playing AVI files (based on original AVIKit code, Win32 version) +Copyright (c) 2003-2004, Ruari O'Sullivan Copyright (C) 2010 Uncle Mike This program is free software: you can redistribute it and/or modify @@ -63,7 +64,7 @@ static int (_stdcall *pAVIStreamTimeToSample)( PAVISTREAM pavi, LONG lTime ); static void* (_stdcall *pAVIStreamGetFrame)( PGETFRAME pg, LONG lPos ); static int (_stdcall *pAVIStreamGetFrameClose)( PGETFRAME pg ); static dword (_stdcall *pAVIStreamRelease)( PAVISTREAM pavi ); -static int (_stdcall *pAVIFileOpen)( PAVIFILE *ppfile, LPCSTR szFile, UINT uMode, LPCLSID lpHandler ); +static int (_stdcall *pAVIFileOpenW)( PAVIFILE *ppfile, LPCWSTR szFile, UINT uMode, LPCLSID lpHandler ); static int (_stdcall *pAVIFileGetStream)( PAVIFILE pfile, PAVISTREAM *ppavi, DWORD fccType, LONG lParam ); static int (_stdcall *pAVIStreamReadFormat)( PAVISTREAM pavi, LONG lPos,LPVOID lpFormat, LONG *lpcbFormat ); static int (_stdcall *pAVIStreamStart)( PAVISTREAM pavi ); @@ -76,7 +77,7 @@ static dllfunc_t avifile_funcs[] = { "AVIFileExit", (void **) &pAVIFileExit }, { "AVIFileGetStream", (void **) &pAVIFileGetStream }, { "AVIFileInit", (void **) &pAVIFileInit }, -{ "AVIFileOpenA", (void **) &pAVIFileOpen }, +{ "AVIFileOpenW", (void **) &pAVIFileOpenW }, { "AVIFileRelease", (void **) &pAVIFileRelease }, { "AVIStreamGetFrame", (void **) &pAVIStreamGetFrame }, { "AVIStreamGetFrameClose", (void **) &pAVIStreamGetFrameClose }, @@ -278,14 +279,6 @@ int AVI_GetVideoFrameNumber( movie_state_t *Avi, float time ) return (time * Avi->video_fps); } -int AVI_GetVideoFrameCount( movie_state_t *Avi ) -{ - if( !Avi->active ) - return 0; - - return Avi->video_frames; -} - int AVI_TimeToSoundPosition( movie_state_t *Avi, int time ) { if( !Avi->active || !Avi->audio_stream ) @@ -493,6 +486,7 @@ void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audi AVISTREAMINFO stream_info; int opened_streams = 0; LONG hr; + wchar_t pathBuffer[MAX_PATH]; // default state: non-working. Avi->active = false; @@ -501,8 +495,15 @@ void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audi // can't load Video For Windows :-( if( !avi_initialized ) return; + // convert to wide char + if( MultiByteToWideChar( CP_UTF8, 0, filename, -1, pathBuffer, ARRAYSIZE( pathBuffer )) <= 0 ) + { + Con_DPrintf( S_ERROR "filename buffer limit exceeded\n" ); + return; + } + // load the AVI - hr = pAVIFileOpen( &Avi->pfile, filename, OF_SHARE_DENY_WRITE, 0L ); + hr = pAVIFileOpenW( &Avi->pfile, pathBuffer, OF_SHARE_DENY_WRITE, 0L ); if( hr != 0 ) // error opening AVI: { @@ -657,7 +658,7 @@ movie_state_t *AVI_LoadVideo( const char *filename, qboolean load_audio ) // open cinematic Q_snprintf( path, sizeof( path ), "media/%s", filename ); - COM_DefaultExtension( path, ".avi" ); + COM_DefaultExtension( path, ".avi", sizeof( path )); fullpath = FS_GetDiskPath( path, false ); if( FS_FileExists( path, false ) && !fullpath ) diff --git a/engine/client/cl_cmds.c b/engine/client/cl_cmds.c index fcc3f3cc..5d7fdc6d 100644 --- a/engine/client/cl_cmds.c +++ b/engine/client/cl_cmds.c @@ -145,7 +145,7 @@ void CL_PlayCDTrack_f( void ) CL_ScreenshotGetName ================== */ -qboolean CL_ScreenshotGetName( int lastnum, char *filename ) +static qboolean CL_ScreenshotGetName( int lastnum, char *filename, size_t size ) { if( lastnum < 0 || lastnum > 9999 ) { @@ -153,9 +153,7 @@ qboolean CL_ScreenshotGetName( int lastnum, char *filename ) return false; } - Q_sprintf( filename, "scrshots/%s_shot%04d.png", clgame.mapname, lastnum ); - - return true; + return Q_snprintf( filename, size, "scrshots/%s_shot%04d.png", clgame.mapname, lastnum ) > 0; } /* @@ -163,7 +161,7 @@ qboolean CL_ScreenshotGetName( int lastnum, char *filename ) CL_SnapshotGetName ================== */ -qboolean CL_SnapshotGetName( int lastnum, char *filename ) +static qboolean CL_SnapshotGetName( int lastnum, char *filename, size_t size ) { if( lastnum < 0 || lastnum > 9999 ) { @@ -172,9 +170,7 @@ qboolean CL_SnapshotGetName( int lastnum, char *filename ) return false; } - Q_sprintf( filename, "../%s_%04d.png", clgame.mapname, lastnum ); - - return true; + return Q_snprintf( filename, size, "../%s_%04d.png", clgame.mapname, lastnum ) > 0; } /* @@ -207,7 +203,7 @@ void CL_ScreenShot_f( void ) // scan for a free filename for( i = 0; i < 9999; i++ ) { - if( !CL_ScreenshotGetName( i, checkname )) + if( !CL_ScreenshotGetName( i, checkname, sizeof( checkname ))) return; // no namespace if( !FS_FileExists( checkname, false )) @@ -247,7 +243,7 @@ void CL_SnapShot_f( void ) // scan for a free filename for( i = 0; i < 9999; i++ ) { - if( !CL_SnapshotGetName( i, checkname )) + if( !CL_SnapshotGetName( i, checkname, sizeof( checkname ))) return; // no namespace if( !FS_FileExists( checkname, false )) @@ -278,7 +274,7 @@ void CL_EnvShot_f( void ) return; } - Q_sprintf( cls.shotname, "gfx/env/%s", Cmd_Argv( 1 )); + Q_snprintf( cls.shotname, sizeof( cls.shotname ), "gfx/env/%s", Cmd_Argv( 1 )); cls.scrshot_action = scrshot_envshot; // build new frame for envshot cls.envshot_vieworg = NULL; // no custom view cls.envshot_viewsize = 0; @@ -299,7 +295,7 @@ void CL_SkyShot_f( void ) return; } - Q_sprintf( cls.shotname, "gfx/env/%s", Cmd_Argv( 1 )); + Q_snprintf( cls.shotname, sizeof( cls.shotname ),"gfx/env/%s", Cmd_Argv( 1 )); cls.scrshot_action = scrshot_skyshot; // build new frame for skyshot cls.envshot_vieworg = NULL; // no custom view cls.envshot_viewsize = 0; @@ -323,7 +319,8 @@ void CL_LevelShot_f( void ) // check for exist if( cls.demoplayback && ( cls.demonum != -1 )) { - Q_sprintf( cls.shotname, "levelshots/%s_%s.bmp", cls.demoname, refState.wideScreen ? "16x9" : "4x3" ); + Q_snprintf( cls.shotname, sizeof( cls.shotname ), + "levelshots/%s_%s.bmp", cls.demoname, refState.wideScreen ? "16x9" : "4x3" ); Q_snprintf( filename, sizeof( filename ), "%s.dem", cls.demoname ); // make sure what levelshot is newer than demo @@ -332,7 +329,8 @@ void CL_LevelShot_f( void ) } else { - Q_sprintf( cls.shotname, "levelshots/%s_%s.bmp", clgame.mapname, refState.wideScreen ? "16x9" : "4x3" ); + Q_snprintf( cls.shotname, sizeof( cls.shotname ), + "levelshots/%s_%s.bmp", clgame.mapname, refState.wideScreen ? "16x9" : "4x3" ); // make sure what levelshot is newer than bsp ft1 = FS_FileTime( cl.worldmodel->name, false ); @@ -360,7 +358,7 @@ void CL_SaveShot_f( void ) return; } - Q_sprintf( cls.shotname, DEFAULT_SAVE_DIRECTORY "%s.bmp", Cmd_Argv( 1 )); + Q_snprintf( cls.shotname, sizeof( cls.shotname ), DEFAULT_SAVE_DIRECTORY "%s.bmp", Cmd_Argv( 1 )); cls.scrshot_action = scrshot_savegame; // build new frame for saveshot } diff --git a/engine/client/cl_custom.c b/engine/client/cl_custom.c index b279b361..b028066e 100644 --- a/engine/client/cl_custom.c +++ b/engine/client/cl_custom.c @@ -78,7 +78,7 @@ qboolean CL_CheckFile( sizebuf_t *msg, resource_t *pResource ) } MSG_BeginClientCmd( msg, clc_stringcmd ); - MSG_WriteString( msg, va( "dlfile %s", filepath )); + MSG_WriteStringf( msg, "dlfile %s", filepath ); host.downloadcount++; return false; diff --git a/engine/client/cl_debug.c b/engine/client/cl_debug.c index 1467ebd2..ec686ffe 100644 --- a/engine/client/cl_debug.c +++ b/engine/client/cl_debug.c @@ -45,7 +45,7 @@ const char *CL_MsgInfo( int cmd ) { static string sz; - Q_strcpy( sz, "???" ); + Q_strncpy( sz, "???", sizeof( sz )); if( cmd >= 0 && cmd <= svc_lastmsg ) { diff --git a/engine/client/cl_demo.c b/engine/client/cl_demo.c index 94bdb469..7f08a004 100644 --- a/engine/client/cl_demo.c +++ b/engine/client/cl_demo.c @@ -1317,16 +1317,16 @@ void CL_CheckStartupDemos( void ) CL_DemoGetName ================== */ -static void CL_DemoGetName( int lastnum, char *filename ) +static void CL_DemoGetName( int lastnum, char *filename, size_t size ) { if( lastnum < 0 || lastnum > 9999 ) { // bound - Q_strcpy( filename, "demo9999" ); + Q_strncpy( filename, "demo9999.dem", size ); return; } - Q_sprintf( filename, "demo%04d", lastnum ); + Q_snprintf( filename, size, "demo%04d.dem", lastnum ); } /* @@ -1380,8 +1380,8 @@ void CL_Record_f( void ) // scan for a free filename for( n = 0; n < 10000; n++ ) { - CL_DemoGetName( n, demoname ); - if( !FS_FileExists( va( "%s.dem", demoname ), true )) + CL_DemoGetName( n, demoname, sizeof( demoname )); + if( !FS_FileExists( demoname, true )) break; } @@ -1394,7 +1394,7 @@ void CL_Record_f( void ) else Q_strncpy( demoname, name, sizeof( demoname )); // open the demo file - Q_sprintf( demopath, "%s.dem", demoname ); + Q_snprintf( demopath, sizeof( demopath ), "%s.dem", demoname ); // make sure that old demo is removed if( FS_FileExists( demopath, false )) diff --git a/engine/client/cl_efrag.c b/engine/client/cl_efrag.c index 366134a2..668c12da 100644 --- a/engine/client/cl_efrag.c +++ b/engine/client/cl_efrag.c @@ -33,46 +33,6 @@ static mnode_t *r_pefragtopnode; static vec3_t r_emins, r_emaxs; static cl_entity_t *r_addent; -/* -================ -R_RemoveEfrags - -Call when removing an object from the world or moving it to another position -================ -*/ -void R_RemoveEfrags( cl_entity_t *ent ) -{ - efrag_t *ef, *old, *walk, **prev; - - ef = ent->efrag; - - while( ef ) - { - prev = &ef->leaf->efrags; - while( 1 ) - { - walk = *prev; - if( !walk ) break; - - if( walk == ef ) - { - // remove this fragment - *prev = ef->leafnext; - break; - } - else prev = &walk->leafnext; - } - - old = ef; - ef = ef->entnext; - - // put it on the free list - old->entnext = clgame.free_efrags; - clgame.free_efrags = old; - } - ent->efrag = NULL; -} - /* =================== R_SplitEntityOnNode diff --git a/engine/client/cl_efx.c b/engine/client/cl_efx.c index 6a07dd49..c9dae218 100644 --- a/engine/client/cl_efx.c +++ b/engine/client/cl_efx.c @@ -140,26 +140,6 @@ void CL_FreeParticles( void ) cl_particles = NULL; } -/* -================ -CL_FreeParticle - -move particle to freelist -================ -*/ -void CL_FreeParticle( particle_t *p ) -{ - if( p->deathfunc ) - { - // call right the deathfunc before die - p->deathfunc( p ); - p->deathfunc = NULL; - } - - p->next = cl_free_particles; - cl_free_particles = p; -} - /* ================ CL_AllocParticleFast diff --git a/engine/client/cl_font.c b/engine/client/cl_font.c index b9fe68eb..a5ac2076 100644 --- a/engine/client/cl_font.c +++ b/engine/client/cl_font.c @@ -260,6 +260,18 @@ int CL_DrawString( float x, float y, const char *s, rgba_t color, cl_font_t *fon return draw_len; } +int CL_DrawStringf( cl_font_t *font, float x, float y, rgba_t color, int flags, const char *fmt, ... ) +{ + va_list va; + char buf[MAX_VA_STRING]; + + va_start( va, fmt ); + Q_vsnprintf( buf, sizeof( buf ), fmt, va ); + va_end( va ); + + return CL_DrawString( x, y, buf, color, font, flags ); +} + void CL_DrawCharacterLen( cl_font_t *font, int number, int *width, int *height ) { if( !font || !font->valid ) return; diff --git a/engine/client/cl_frame.c b/engine/client/cl_frame.c index 81976384..6715d0a6 100644 --- a/engine/client/cl_frame.c +++ b/engine/client/cl_frame.c @@ -241,7 +241,7 @@ CL_GetStudioEstimatedFrame ==================== */ -float CL_GetStudioEstimatedFrame( cl_entity_t *ent ) +static float CL_GetStudioEstimatedFrame( cl_entity_t *ent ) { studiohdr_t *pstudiohdr; mstudioseqdesc_t *pseqdesc; @@ -255,7 +255,7 @@ float CL_GetStudioEstimatedFrame( cl_entity_t *ent ) { sequence = bound( 0, ent->curstate.sequence, pstudiohdr->numseq - 1 ); pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence; - return ref.dllFuncs.R_StudioEstimateFrame( ent, pseqdesc ); + return ref.dllFuncs.R_StudioEstimateFrame( ent, pseqdesc, cl.time ); } } @@ -654,6 +654,24 @@ FRAME PARSING ========================================================================= */ +static qboolean CL_ParseEntityNumFromPacket( sizebuf_t *msg, int *newnum ) +{ + if( cls.legacymode ) + { + *newnum = MSG_ReadWord( msg ); + if( *newnum == 0 ) + return false; + } + else + { + *newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS ); + if( *newnum == LAST_EDICT ) + return false; + } + + return true; +} + /* ================= CL_FlushEntityPacket @@ -674,8 +692,8 @@ void CL_FlushEntityPacket( sizebuf_t *msg ) // read it all, but ignore it while( 1 ) { - newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS ); - if( newnum == LAST_EDICT ) break; // done + if( !CL_ParseEntityNumFromPacket( msg, &newnum )) + break; // done if( MSG_CheckOverflow( msg )) Host_Error( "CL_FlushEntityPacket: overflow\n" ); @@ -847,21 +865,12 @@ int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta ) while( 1 ) { - int lastedict; - if( cls.legacymode ) - { - newnum = MSG_ReadWord( msg ); - lastedict = 0; - } - else - { - newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS ); - lastedict = LAST_EDICT; - } + if( !CL_ParseEntityNumFromPacket( msg, &newnum )) + break; // done - if( newnum == lastedict ) break; // end of packet entities if( MSG_CheckOverflow( msg )) Host_Error( "CL_ParsePacketEntities: overflow\n" ); + player = CL_IsPlayerIndex( newnum ); while( oldnum < newnum ) @@ -970,9 +979,25 @@ all the visible entities should pass this filter */ qboolean CL_AddVisibleEntity( cl_entity_t *ent, int entityType ) { + qboolean draw_player = true; + if( !ent || !ent->model ) return false; + // don't add the player in firstperson mode + if( RP_LOCALCLIENT( ent )) + { + cl.local.apply_effects = true; + + if( !CL_IsThirdPerson( ) && ( ent->index == cl.viewentity )) + { + // we don't draw player in default renderer in firstperson mode + // but let the client.dll know about player entity anyway + // for use in custom renderers + draw_player = false; + } + } + // check for adding this entity if( !clgame.dllFuncs.pfnAddEntity( entityType, ent, ent->model->name )) { @@ -982,14 +1007,8 @@ qboolean CL_AddVisibleEntity( cl_entity_t *ent, int entityType ) return false; } - // don't add the player in firstperson mode - if( RP_LOCALCLIENT( ent )) - { - cl.local.apply_effects = true; - - if( !CL_IsThirdPerson( ) && ( ent->index == cl.viewentity )) - return false; - } + if( !draw_player ) + return false; if( entityType == ET_BEAM ) { diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index d95b672d..eae4d320 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -200,7 +200,7 @@ CL_InitCDAudio Initialize CD playlist ==================== */ -void CL_InitCDAudio( const char *filename ) +static void CL_InitCDAudio( const char *filename ) { byte *afile; char *pfile; @@ -221,8 +221,13 @@ void CL_InitCDAudio( const char *filename ) // format: trackname\n [num] while(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL ) { - if( !Q_stricmp( token, "blank" )) token[0] = '\0'; - Q_strncpy( clgame.cdtracks[c], token, sizeof( clgame.cdtracks[0] )); + if( !Q_stricmp( token, "blank" )) + clgame.cdtracks[c][0] = '\0'; + else + { + Q_snprintf( clgame.cdtracks[c], sizeof( clgame.cdtracks[c] ), + "media/%s", token ); + } if( ++c > MAX_CDTRACKS - 1 ) { @@ -590,7 +595,11 @@ static void CL_InitTitles( const char *filename ) // initialize text messages (game_text) for( i = 0; i < MAX_TEXTCHANNELS; i++ ) { - cl_textmessage[i].pName = _copystring( clgame.mempool, va( TEXT_MSGNAME, i ), __FILE__, __LINE__ ); + char name[MAX_VA_STRING]; + + Q_snprintf( name, sizeof( name ), TEXT_MSGNAME, i ); + + cl_textmessage[i].pName = _copystring( clgame.mempool, name, __FILE__, __LINE__ ); cl_textmessage[i].pMessage = cl_textbuffer[i]; } @@ -1060,23 +1069,18 @@ void CL_LinkUserMessage( char *pszName, const int svc_num, int iSize ) CL_ClearUserMessage( pszName, svc_num ); } -void CL_FreeEntity( cl_entity_t *pEdict ) -{ - Assert( pEdict != NULL ); - R_RemoveEfrags( pEdict ); - CL_KillDeadBeams( pEdict ); -} - void CL_ClearWorld( void ) { - cl_entity_t *worldmodel; + if( clgame.entities ) // check if we have entities, legacy protocol support kinda breaks this logic + { + cl_entity_t *worldmodel = clgame.entities; - worldmodel = clgame.entities; - worldmodel->curstate.modelindex = 1; // world model - worldmodel->curstate.solid = SOLID_BSP; - worldmodel->curstate.movetype = MOVETYPE_PUSH; - worldmodel->model = cl.worldmodel; - worldmodel->index = 0; + worldmodel->curstate.modelindex = 1; // world model + worldmodel->curstate.solid = SOLID_BSP; + worldmodel->curstate.movetype = MOVETYPE_PUSH; + worldmodel->model = cl.worldmodel; + worldmodel->index = 0; + } world.max_recursion = 0; @@ -1368,7 +1372,7 @@ pfnSPR_Frames */ int EXPORT pfnSPR_Frames( HSPRITE hPic ) { - int numFrames; + int numFrames = 0; ref.dllFuncs.R_GetSpriteParms( NULL, NULL, &numFrames, 0, CL_GetSpritePointer( hPic )); @@ -1383,7 +1387,7 @@ pfnSPR_Height */ static int GAME_EXPORT pfnSPR_Height( HSPRITE hPic, int frame ) { - int sprHeight; + int sprHeight = 0; ref.dllFuncs.R_GetSpriteParms( NULL, &sprHeight, NULL, frame, CL_GetSpritePointer( hPic )); @@ -1398,7 +1402,7 @@ pfnSPR_Width */ static int GAME_EXPORT pfnSPR_Width( HSPRITE hPic, int frame ) { - int sprWidth; + int sprWidth = 0; ref.dllFuncs.R_GetSpriteParms( &sprWidth, NULL, NULL, frame, CL_GetSpritePointer( hPic )); @@ -1413,7 +1417,13 @@ pfnSPR_Set */ static void GAME_EXPORT pfnSPR_Set( HSPRITE hPic, int r, int g, int b ) { - clgame.ds.pSprite = CL_GetSpritePointer( hPic ); + const model_t *sprite = CL_GetSpritePointer( hPic ); + + // a1ba: do not alter the state if invalid HSPRITE was passed + if( !sprite ) + return; + + clgame.ds.pSprite = sprite; clgame.ds.spriteColor[0] = bound( 0, r, 255 ); clgame.ds.spriteColor[1] = bound( 0, g, 255 ); clgame.ds.spriteColor[2] = bound( 0, b, 255 ); @@ -1717,14 +1727,12 @@ pfnServerCmd */ static int GAME_EXPORT pfnServerCmd( const char *szCmdString ) { - string buf; - if( !COM_CheckString( szCmdString )) return 0; // just like the client typed "cmd xxxxx" at the console - Q_snprintf( buf, sizeof( buf ) - 1, "cmd %s\n", szCmdString ); - Cbuf_AddText( buf ); + MSG_BeginClientCmd( &cls.netchan.message, clc_stringcmd ); + MSG_WriteString( &cls.netchan.message, szCmdString ); return 1; } @@ -1852,7 +1860,11 @@ client_textmessage_t *CL_TextMessageGet( const char *pName ) // first check internal messages for( i = 0; i < MAX_TEXTCHANNELS; i++ ) { - if( !Q_strcmp( pName, va( TEXT_MSGNAME, i ))) + char name[MAX_VA_STRING]; + + Q_snprintf( name, sizeof( name ), TEXT_MSGNAME, i ); + + if( !Q_strcmp( pName, name )) return cl_textmessage + i; } @@ -2112,58 +2124,52 @@ pfnCalcShake */ void GAME_EXPORT pfnCalcShake( void ) { - int i; - float fraction, freq; - float localAmp; + screen_shake_t *const shake = &clgame.shake; + float frametime, fraction, freq; + int i; - if( clgame.shake.time == 0 ) - return; - - if(( cl.time > clgame.shake.time ) || clgame.shake.amplitude <= 0 || clgame.shake.frequency <= 0 ) + if( cl.time > shake->time || shake->amplitude <= 0 || shake->frequency <= 0 || shake->duration <= 0 ) { - memset( &clgame.shake, 0, sizeof( clgame.shake )); + // reset shake + if( shake->time != 0 ) + { + shake->time = 0; + shake->applied_angle = 0; + VectorClear( shake->applied_offset ); + } + return; } - if( cl.time > clgame.shake.next_shake ) - { - // higher frequency means we recalc the extents more often and perturb the display again - clgame.shake.next_shake = cl.time + ( 1.0f / clgame.shake.frequency ); + frametime = cl_clientframetime(); - // compute random shake extents (the shake will settle down from this) + if( cl.time > shake->next_shake ) + { + // get next shake time based on frequency over duration + shake->next_shake = (float)cl.time + shake->frequency / shake->duration; + + // randomize each shake for( i = 0; i < 3; i++ ) - clgame.shake.offset[i] = COM_RandomFloat( -clgame.shake.amplitude, clgame.shake.amplitude ); - clgame.shake.angle = COM_RandomFloat( -clgame.shake.amplitude * 0.25f, clgame.shake.amplitude * 0.25f ); + shake->offset[i] = COM_RandomFloat( -shake->amplitude, shake->amplitude ); + shake->angle = COM_RandomFloat( -shake->amplitude * 0.25f, shake->amplitude * 0.25f ); } - // ramp down amplitude over duration (fraction goes from 1 to 0 linearly with slope 1/duration) - fraction = ( clgame.shake.time - cl.time ) / clgame.shake.duration; + // get initial fraction and frequency values over the duration + fraction = ((float)cl.time - shake->time ) / shake->duration; + freq = fraction != 0.0f ? ( shake->frequency / fraction ) * shake->frequency : 0.0f; - // ramp up frequency over duration - if( fraction ) - { - freq = ( clgame.shake.frequency / fraction ); - } - else - { - freq = 0; - } + // quickly approach zero but apply time over sine wave + fraction *= fraction * sin( cl.time * freq ); - // square fraction to approach zero more quickly - fraction *= fraction; + // apply shake offset + for( i = 0; i < 3; i++ ) + shake->applied_offset[i] = shake->offset[i] * fraction; - // Sine wave that slowly settles to zero - fraction = fraction * sin( cl.time * freq ); + // apply roll angle + shake->applied_angle = shake->angle * fraction; - // add to view origin - VectorScale( clgame.shake.offset, fraction, clgame.shake.applied_offset ); - - // add to roll - clgame.shake.applied_angle = clgame.shake.angle * fraction; - - // drop amplitude a bit, less for higher frequency shakes - localAmp = clgame.shake.amplitude * ( host.frametime / ( clgame.shake.duration * clgame.shake.frequency )); - clgame.shake.amplitude -= localAmp; + // decrease amplitude, but slower on longer shakes or higher frequency + shake->amplitude -= shake->amplitude * ( frametime / ( shake->frequency * shake->duration )); } /* @@ -2174,8 +2180,11 @@ pfnApplyShake */ void GAME_EXPORT pfnApplyShake( float *origin, float *angles, float factor ) { - if( origin ) VectorMA( origin, factor, clgame.shake.applied_offset, origin ); - if( angles ) angles[ROLL] += clgame.shake.applied_angle * factor; + if( origin ) + VectorMA( origin, factor, clgame.shake.applied_offset, origin ); + + if( angles ) + angles[ROLL] += clgame.shake.applied_angle * factor; } /* @@ -2560,7 +2569,7 @@ const char *pfnGetGameDirectory( void ) { static char szGetGameDir[MAX_SYSPATH]; - Q_strcpy( szGetGameDir, GI->gamefolder ); + Q_strncpy( szGetGameDir, GI->gamefolder, sizeof( szGetGameDir )); return szGetGameDir; } @@ -3359,8 +3368,7 @@ void GAME_EXPORT NetAPI_SendRequest( int context, int request, int flags, double nr->resp.remote_address.port = MSG_BigShort( PORT_MASTER ); // grab the list from the master server - Q_strcpy( &fullquery[22], GI->gamefolder ); - NET_SendPacket( NS_CLIENT, Q_strlen( GI->gamefolder ) + 23, fullquery, nr->resp.remote_address ); + NET_SendPacket( NS_CLIENT, len, fullquery, nr->resp.remote_address ); clgame.request_type = NET_REQUEST_CLIENT; clgame.master_request = nr; // holds the master request unitl the master acking } diff --git a/engine/client/cl_gameui.c b/engine/client/cl_gameui.c index 4ac95d9b..af8d4e6c 100644 --- a/engine/client/cl_gameui.c +++ b/engine/client/cl_gameui.c @@ -310,7 +310,7 @@ static void GAME_EXPORT UI_DrawLogo( const char *filename, float x, float y, flo // run cinematic if not Q_snprintf( path, sizeof( path ), "media/%s", filename ); - COM_DefaultExtension( path, ".avi" ); + COM_DefaultExtension( path, ".avi", sizeof( path )); fullpath = FS_GetDiskPath( path, false ); if( FS_FileExists( path, false ) && !fullpath ) @@ -839,7 +839,7 @@ send client connect */ static void GAME_EXPORT pfnClientJoin( const netadr_t adr ) { - Cbuf_AddText( va( "connect %s\n", NET_AdrToString( adr ))); + Cbuf_AddTextf( "connect %s\n", NET_AdrToString( adr )); } /* diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index 2aad47e1..9a8111c1 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -23,6 +23,7 @@ GNU General Public License for more details. #include "library.h" #include "vid_common.h" #include "pm_local.h" +#include "sequence.h" #define MAX_TOTAL_CMDS 32 #define MAX_CMD_BUFFER 8000 @@ -149,16 +150,6 @@ qboolean CL_DisableVisibility( void ) return cls.envshot_disable_vis; } -qboolean CL_IsBackgroundDemo( void ) -{ - return ( cls.demoplayback && cls.demonum != -1 ); -} - -qboolean CL_IsBackgroundMap( void ) -{ - return ( cl.background && !cls.demoplayback ); -} - char *CL_Userinfo( void ) { return cls.userinfo; @@ -239,6 +230,9 @@ void CL_SignonReply( void ) if( cl.proxy_redirect && !cls.spectator ) CL_Disconnect(); cl.proxy_redirect = false; + + if( cls.demoplayback ) + Sequence_OnLevelLoad( clgame.mapname ); break; } } @@ -1029,9 +1023,9 @@ void CL_SendConnectPacket( void ) Cvar_SetCheatState(); Cvar_FullSet( "sv_cheats", "0", FCVAR_READ_ONLY | FCVAR_SERVER ); - Info_SetValueForKey( protinfo, "d", va( "%d", input_devices ), sizeof( protinfo ) ); + Info_SetValueForKeyf( protinfo, "d", sizeof( protinfo ), "%d", input_devices ); Info_SetValueForKey( protinfo, "v", XASH_VERSION, sizeof( protinfo ) ); - Info_SetValueForKey( protinfo, "b", va( "%d", Q_buildnum() ), sizeof( protinfo ) ); + Info_SetValueForKeyf( protinfo, "b", sizeof( protinfo ), "%d", Q_buildnum( )); Info_SetValueForKey( protinfo, "o", Q_buildos(), sizeof( protinfo ) ); Info_SetValueForKey( protinfo, "a", Q_buildarch(), sizeof( protinfo ) ); } @@ -1065,7 +1059,7 @@ void CL_SendConnectPacket( void ) Info_SetValueForKey( protinfo, "uuid", key, sizeof( protinfo )); Info_SetValueForKey( protinfo, "qport", qport, sizeof( protinfo )); - Info_SetValueForKey( protinfo, "ext", va("%d", extensions), sizeof( protinfo )); + Info_SetValueForKeyf( protinfo, "ext", sizeof( protinfo ), "%d", extensions); Netchan_OutOfBandPrint( NS_CLIENT, adr, "connect %i %i \"%s\" \"%s\"\n", PROTOCOL_VERSION, cls.challenge, protinfo, cls.userinfo ); Con_Printf( "Trying to connect by modern protocol\n" ); @@ -1084,7 +1078,7 @@ Resend a connect message if the last one has timed out void CL_CheckForResend( void ) { netadr_t adr; - int res; + net_gai_state_t res; qboolean bandwidthTest; if( cls.internetservers_wait ) @@ -1117,13 +1111,13 @@ void CL_CheckForResend( void ) res = NET_StringToAdrNB( cls.servername, &adr ); - if( !res ) + if( res == NET_EAI_NONAME ) { CL_Disconnect(); return; } - if( res == 2 ) + if( res == NET_EAI_AGAIN ) { cls.connect_time = MAX_HEARTBEAT; return; @@ -1300,15 +1294,15 @@ void CL_Rcon_f( void ) NET_Config( true, false ); // allow remote - Q_strcat( message, "rcon " ); - Q_strcat( message, rcon_password.string ); - Q_strcat( message, " " ); + Q_strncat( message, "rcon ", sizeof( message )); + Q_strncat( message, rcon_password.string, sizeof( message )); + Q_strncat( message, " ", sizeof( message ) ); for( i = 1; i < Cmd_Argc(); i++ ) { Cmd_Escape( command, Cmd_Argv( i ), sizeof( command )); - Q_strcat( message, command ); - Q_strcat( message, " " ); + Q_strncat( message, command, sizeof( message )); + Q_strncat( message, " ", sizeof( message )); } if( cls.state >= ca_connected ) @@ -2086,7 +2080,7 @@ void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) if( NET_CompareAdr( from, cls.legacyserver )) { - Cbuf_AddText( va( "connect %s legacy\n", NET_AdrToString( from ))); + Cbuf_AddTextf( "connect %s legacy\n", NET_AdrToString( from )); memset( &cls.legacyserver, 0, sizeof( cls.legacyserver )); } } @@ -2589,7 +2583,7 @@ void CL_ServerCommand( qboolean reliable, const char *fmt, ... ) return; va_start( argptr, fmt ); - Q_vsprintf( string, fmt, argptr ); + Q_vsnprintf( string, sizeof( string ), fmt, argptr ); va_end( argptr ); if( reliable ) @@ -2862,6 +2856,7 @@ void CL_InitLocal( void ) Cvar_RegisterVariable( &cl_test_bandwidth ); Voice_RegisterCvars(); + VGui_RegisterCvars(); // register our variables cl_crosshair = Cvar_Get( "crosshair", "1", FCVAR_ARCHIVE, "show weapon chrosshair" ); @@ -3123,6 +3118,7 @@ void CL_Init( void ) VID_Init(); // init video S_Init(); // init sound Voice_Init( VOICE_DEFAULT_CODEC, 3 ); // init voice + Sequence_Init(); // unreliable buffer. unsed for unreliable commands and voice stream MSG_Init( &cls.datagram, "cls.datagram", cls.datagram_buf, sizeof( cls.datagram_buf )); diff --git a/engine/client/cl_netgraph.c b/engine/client/cl_netgraph.c index 484adc1b..ab693fd8 100644 --- a/engine/client/cl_netgraph.c +++ b/engine/client/cl_netgraph.c @@ -393,10 +393,10 @@ static void NetGraph_DrawTextFields( int x, int y, int w, wrect_t rect, int coun { y -= net_graphheight->value; - CL_DrawString( x, y, va( "%.1f fps" , 1.0f / framerate ), colors, font, FONT_DRAW_NORENDERMODE ); + CL_DrawStringf( font, x, y, colors, FONT_DRAW_NORENDERMODE, "%.1f fps" , 1.0f / framerate); if( avg > 1.0f ) - CL_DrawString( x + 75, y, va( "%i ms" , (int)avg ), colors, font, FONT_DRAW_NORENDERMODE ); + CL_DrawStringf( font, x + 75, y, colors, FONT_DRAW_NORENDERMODE, "%i ms" , (int)avg ); y += 15; @@ -404,10 +404,12 @@ static void NetGraph_DrawTextFields( int x, int y, int w, wrect_t rect, int coun if( !out ) out = lastout; else lastout = out; - CL_DrawString( x, y, va( "in : %i %.2f kb/s", netstat_graph[j].msgbytes, cls.netchan.flow[FLOW_INCOMING].avgkbytespersec ), colors, font, FONT_DRAW_NORENDERMODE ); + CL_DrawStringf( font, x, y, colors, FONT_DRAW_NORENDERMODE, + "in : %i %.2f kb/s", netstat_graph[j].msgbytes, cls.netchan.flow[FLOW_INCOMING].avgkbytespersec ); y += 15; - CL_DrawString( x, y, va( "out: %i %.2f kb/s", out, cls.netchan.flow[FLOW_OUTGOING].avgkbytespersec ), colors, font, FONT_DRAW_NORENDERMODE ); + CL_DrawStringf( font, x, y, colors, FONT_DRAW_NORENDERMODE, + "out: %i %.2f kb/s", out, cls.netchan.flow[FLOW_OUTGOING].avgkbytespersec ); y += 15; if( graphtype > 2 ) @@ -415,14 +417,14 @@ static void NetGraph_DrawTextFields( int x, int y, int w, wrect_t rect, int coun int loss = (int)(( packet_loss + PACKETLOSS_AVG_FRAC ) - 0.01f ); int choke = (int)(( packet_choke + PACKETCHOKE_AVG_FRAC ) - 0.01f ); - CL_DrawString( x, y, va( "loss: %i choke: %i", loss, choke ), colors, font, FONT_DRAW_NORENDERMODE ); + CL_DrawStringf( font, x, y, colors, FONT_DRAW_NORENDERMODE, "loss: %i choke: %i", loss, choke ); } } if( graphtype < 3 ) - CL_DrawString( ptx, pty, va( "%i/s", (int)cl_cmdrate->value ), colors, font, FONT_DRAW_NORENDERMODE ); + CL_DrawStringf( font, ptx, pty, colors, FONT_DRAW_NORENDERMODE, "%i/s", (int)cl_cmdrate->value ); - CL_DrawString( ptx, last_y, va( "%i/s" , (int)cl_updaterate->value ), colors, font, FONT_DRAW_NORENDERMODE ); + CL_DrawStringf( font, ptx, last_y, colors, FONT_DRAW_NORENDERMODE, "%i/s" , (int)cl_updaterate->value ); } /* diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index ba69c4a0..7471d80c 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -499,7 +499,7 @@ void CL_BatchResourceRequest( qboolean initialize ) if( !FBitSet( p->ucFlags, RES_REQUESTED )) { MSG_BeginClientCmd( &msg, clc_stringcmd ); - MSG_WriteString( &msg, va( "dlfile !MD5%s", MD5_Print( p->rgucMD5_hash ) ) ); + MSG_WriteStringf( &msg, "dlfile !MD5%s", MD5_Print( p->rgucMD5_hash ));; SetBits( p->ucFlags, RES_REQUESTED ); } break; @@ -587,7 +587,7 @@ int CL_EstimateNeededResources( void ) return nTotalSize; } -void CL_StartResourceDownloading( const char *pszMessage, qboolean bCustom ) +static void CL_StartResourceDownloading( const char *pszMessage, qboolean bCustom ) { resourceinfo_t ri; @@ -603,6 +603,8 @@ void CL_StartResourceDownloading( const char *pszMessage, qboolean bCustom ) } else { + HTTP_ResetProcessState(); + cls.state = ca_validate; cls.dl.custom = false; } @@ -849,6 +851,8 @@ void CL_ParseServerData( sizebuf_t *msg, qboolean legacy ) qboolean background; int i; + HPAK_CheckSize( CUSTOM_RES_PATH ); + Con_Reportf( "%s packet received.\n", legacy ? "Legacy serverdata" : "Serverdata" ); cls.timestart = Sys_DoubleTime(); @@ -1236,11 +1240,9 @@ CL_ParseSetAngle set the view angle to this absolute value ================ */ -void CL_ParseSetAngle( sizebuf_t *msg ) +static void CL_ParseSetAngle( sizebuf_t *msg ) { - cl.viewangles[0] = MSG_ReadBitAngle( msg, 16 ); - cl.viewangles[1] = MSG_ReadBitAngle( msg, 16 ); - cl.viewangles[2] = MSG_ReadBitAngle( msg, 16 ); + MSG_ReadVec3Angles( msg, cl.viewangles ); } /* @@ -1541,6 +1543,42 @@ void CL_SendConsistencyInfo( sizebuf_t *msg ) MSG_WriteOneBit( msg, 0 ); } +/* +================== +CL_StartDark +================== +*/ +static void CL_StartDark( void ) +{ + if( Cvar_VariableValue( "v_dark" )) + { + screenfade_t *sf = &clgame.fade; + float fadetime = 5.0f; + client_textmessage_t *title; + + title = CL_TextMessageGet( "GAMETITLE" ); + if( Host_IsQuakeCompatible( )) + fadetime = 1.0f; + + if( title ) + { + // get settings from titles.txt + sf->fadeEnd = title->holdtime + title->fadeout; + sf->fadeReset = title->fadeout; + } + else sf->fadeEnd = sf->fadeReset = fadetime; + + sf->fadeFlags = FFADE_IN; + sf->fader = sf->fadeg = sf->fadeb = 0; + sf->fadealpha = 255; + sf->fadeSpeed = (float)sf->fadealpha / sf->fadeReset; + sf->fadeReset += cl.time; + sf->fadeEnd += sf->fadeReset; + + Cvar_SetValue( "v_dark", 0.0f ); + } +} + /* ================== CL_RegisterResources @@ -1590,6 +1628,9 @@ void CL_RegisterResources( sizebuf_t *msg ) // tell rendering system we have a new set of models. ref.dllFuncs.R_NewMap (); + // check if this map must start from dark screen + CL_StartDark (); + CL_SetupOverviewParams(); // release unused SpriteTextures @@ -1607,7 +1648,7 @@ void CL_RegisterResources( sizebuf_t *msg ) // done with all resources, issue prespawn command. // Include server count in case server disconnects and changes level during d/l MSG_BeginClientCmd( msg, clc_stringcmd ); - MSG_WriteString( msg, va( "spawn %i", cl.servercount )); + MSG_WriteStringf( msg, "spawn %i", cl.servercount ); } } else @@ -1852,10 +1893,17 @@ Set screen shake */ 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 + Q_max( clgame.shake.duration, 0.01f ); + float amplitude = (float)(word)MSG_ReadShort( msg ) * ( 1.0f / (float)( 1 << 12 )); + float duration = (float)(word)MSG_ReadShort( msg ) * ( 1.0f / (float)( 1 << 12 )); + float frequency = (float)(word)MSG_ReadShort( msg ) * ( 1.0f / (float)( 1 << 8 )); + + // don't overwrite larger existing shake + if( amplitude > clgame.shake.amplitude ) + clgame.shake.amplitude = amplitude; + + clgame.shake.duration = duration; + clgame.shake.time = cl.time + clgame.shake.duration; + clgame.shake.frequency = frequency; clgame.shake.next_shake = 0.0f; // apply immediately } @@ -1998,10 +2046,10 @@ void CL_ParseExec( sizebuf_t *msg ) { Cbuf_AddText( "exec mapdefault.cfg\n" ); - COM_FileBase( clgame.mapname, mapname ); + COM_FileBase( clgame.mapname, mapname, sizeof( mapname )); if ( COM_CheckString( mapname ) ) - Cbuf_AddText( va( "exec %s.cfg\n", mapname ) ); + Cbuf_AddTextf( "exec %s.cfg\n", mapname ); } } @@ -2241,6 +2289,7 @@ void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message ) else cls.state = ca_connecting; cl.background = old_background; cls.connect_time = MAX_HEARTBEAT; + cls.connect_retry = 0; } break; case svc_setview: @@ -2665,15 +2714,14 @@ CL_ParseResourceList void CL_LegacyParseResourceList( sizebuf_t *msg ) { int i = 0; - static struct { int rescount; int restype[MAX_LEGACY_RESOURCES]; char resnames[MAX_LEGACY_RESOURCES][MAX_QPATH]; } reslist; - memset( &reslist, 0, sizeof( reslist )); + memset( &reslist, 0, sizeof( reslist )); reslist.rescount = MSG_ReadWord( msg ) - 1; if( reslist.rescount > MAX_LEGACY_RESOURCES ) @@ -2690,14 +2738,21 @@ void CL_LegacyParseResourceList( sizebuf_t *msg ) return; } + HTTP_ResetProcessState(); + host.downloadcount = 0; for( i = 0; i < reslist.rescount; i++ ) { + char soundpath[MAX_VA_STRING]; const char *path; if( reslist.restype[i] == t_sound ) - path = va( DEFAULT_SOUNDPATH "%s", reslist.resnames[i] ); + { + Q_snprintf( soundpath, sizeof( soundpath ), DEFAULT_SOUNDPATH "%s", reslist.resnames[i] ); + + path = soundpath; + } else path = reslist.resnames[i]; if( FS_FileExists( path, false )) @@ -2816,6 +2871,7 @@ void CL_ParseLegacyServerMessage( sizebuf_t *msg, qboolean normal_message ) else cls.state = ca_connecting; cl.background = old_background; cls.connect_time = MAX_HEARTBEAT; + cls.connect_retry = 0; } break; case svc_setview: @@ -2881,7 +2937,7 @@ void CL_ParseLegacyServerMessage( sizebuf_t *msg, qboolean normal_message ) cl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart; break; case svc_spawnstatic: - CL_ParseStaticEntity( msg ); + CL_LegacyParseStaticEntity( msg ); break; case svc_event_reliable: CL_ParseReliableEvent( msg ); @@ -3078,7 +3134,7 @@ void CL_LegacyPrecache_f( void ) // done with all resources, issue prespawn command. // Include server count in case server disconnects and changes level during d/l MSG_BeginClientCmd( &cls.netchan.message, clc_stringcmd ); - MSG_WriteString( &cls.netchan.message, va( "begin %i", spawncount )); + MSG_WriteStringf( &cls.netchan.message, "begin %i", spawncount ); cls.signon = SIGNONS; } diff --git a/engine/client/cl_remap.c b/engine/client/cl_remap.c index 3a9c74c8..07beb67e 100644 --- a/engine/client/cl_remap.c +++ b/engine/client/cl_remap.c @@ -157,7 +157,7 @@ void CL_UpdateStudioTexture( cl_entity_t *entity, mstudiotexture_t *ptexture, in // build name of original texture Q_strncpy( mdlname, entity->model->name, sizeof( mdlname )); - COM_FileBase( ptexture->name, name ); + COM_FileBase( ptexture->name, name, sizeof( name )); COM_StripExtension( mdlname ); Q_snprintf( texname, sizeof( texname ), "#%s/%s.mdl", mdlname, name ); diff --git a/engine/client/cl_securedstub.c b/engine/client/cl_securedstub.c index fd7746b1..f45ba024 100644 --- a/engine/client/cl_securedstub.c +++ b/engine/client/cl_securedstub.c @@ -405,12 +405,15 @@ static cldll_func_dst_t cldllFuncDst = 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; + cldll_func_src_t cldllFuncSrc = + { + (void *)&modFuncs, + NULL, + (void *)&cldllFuncDst + }; // trying to fill interface now F( &cldllFuncSrc ); diff --git a/engine/client/cl_tent.c b/engine/client/cl_tent.c index 8b230df7..7521abe6 100644 --- a/engine/client/cl_tent.c +++ b/engine/client/cl_tent.c @@ -1245,37 +1245,28 @@ apply params for exploding sprite */ void GAME_EXPORT R_Sprite_Explode( TEMPENTITY *pTemp, float scale, int flags ) { - if( !pTemp ) return; + qboolean noadditive, drawalpha, rotate; - if( FBitSet( flags, TE_EXPLFLAG_NOADDITIVE )) - { - // solid sprite - pTemp->entity.curstate.rendermode = kRenderNormal; - pTemp->entity.curstate.renderamt = 255; - } - else if( FBitSet( flags, TE_EXPLFLAG_DRAWALPHA )) - { - // alpha sprite (came from hl2) - pTemp->entity.curstate.rendermode = kRenderTransAlpha; - pTemp->entity.curstate.renderamt = 180; - } - else - { - // additive sprite - pTemp->entity.curstate.rendermode = kRenderTransAdd; - pTemp->entity.curstate.renderamt = 180; - } + if( !pTemp ) + return; - if( FBitSet( flags, TE_EXPLFLAG_ROTATE )) - { - // came from hl2 - pTemp->entity.angles[2] = COM_RandomLong( 0, 360 ); - } + noadditive = FBitSet( flags, TE_EXPLFLAG_NOADDITIVE ); + drawalpha = FBitSet( flags, TE_EXPLFLAG_DRAWALPHA ); + rotate = FBitSet( flags, TE_EXPLFLAG_ROTATE ); - pTemp->entity.curstate.renderfx = kRenderFxNone; - pTemp->entity.baseline.origin[2] = 8; - pTemp->entity.origin[2] += 10; pTemp->entity.curstate.scale = scale; + pTemp->entity.baseline.origin[2] = 8.0f; + pTemp->entity.origin[2] = pTemp->entity.origin[2] + 10.0f; + if( rotate ) + pTemp->entity.angles[2] = COM_RandomFloat( 0.0, 360.0f ); + + pTemp->entity.curstate.rendermode = noadditive ? kRenderNormal : + drawalpha ? kRenderTransAlpha : kRenderTransAdd; + pTemp->entity.curstate.renderamt = noadditive ? 0xff : 0xb4; + pTemp->entity.curstate.renderfx = 0; + pTemp->entity.curstate.rendercolor.r = 0; + pTemp->entity.curstate.rendercolor.g = 0; + pTemp->entity.curstate.rendercolor.b = 0; } /* @@ -2932,7 +2923,9 @@ void CL_PlayerDecal( int playernum, int customIndex, int entityIndex, float *pos if( !pCust->nUserData1 ) { int sprayTextureIndex; - const char *decalname = va( "player%dlogo%d", playernum, customIndex ); + char decalname[MAX_VA_STRING]; + + Q_snprintf( decalname, sizeof( decalname ), "player%dlogo%d", playernum, customIndex ); sprayTextureIndex = ref.dllFuncs.GL_FindTexture( decalname ); if( sprayTextureIndex != 0 ) { diff --git a/engine/client/cl_view.c b/engine/client/cl_view.c index cac27075..fca79f63 100644 --- a/engine/client/cl_view.c +++ b/engine/client/cl_view.c @@ -229,6 +229,53 @@ void V_RefApplyOverview( ref_viewpass_t *rvp ) ref.dllFuncs.GL_OrthoBounds( mins, maxs ); } +/* +==================== +V_CalcFov +==================== +*/ +static float V_CalcFov( float *fov_x, float width, float height ) +{ + float x, half_fov_y; + + if( *fov_x < 1.0f || *fov_x > 179.0f ) + *fov_x = 90.0f; // default value + + x = width / tan( DEG2RAD( *fov_x ) * 0.5f ); + half_fov_y = atan( height / x ); + + return RAD2DEG( half_fov_y ) * 2; +} + +/* +==================== +V_AdjustFov +==================== +*/ +static void V_AdjustFov( float *fov_x, float *fov_y, float width, float height, qboolean lock_x ) +{ + float x, y; + + if( width * 3 == 4 * height || width * 4 == height * 5 ) + { + // 4:3 or 5:4 ratio + return; + } + + if( lock_x ) + { + *fov_y = 2 * atan((width * 3) / (height * 4) * tan( *fov_y * M_PI_F / 360.0f * 0.5f )) * 360 / M_PI_F; + return; + } + + y = V_CalcFov( fov_x, 640, 480 ); + x = *fov_x; + + *fov_x = V_CalcFov( &y, height, width ); + if( *fov_x < x ) *fov_x = x; + else *fov_y = y; +} + /* ============= V_GetRefParams diff --git a/engine/client/client.h b/engine/client/client.h index d4e40441..a354bfa4 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -817,6 +817,7 @@ int CL_DrawCharacter( float x, float y, int number, rgba_t color, cl_font_t *fon int CL_DrawString( float x, float y, const char *s, rgba_t color, cl_font_t *font, int flags ); void CL_DrawCharacterLen( cl_font_t *font, int number, int *width, int *height ); void CL_DrawStringLen( cl_font_t *font, const char *s, int *width, int *height, int flags ); +int CL_DrawStringf( cl_font_t *font, float x, float y, rgba_t color, int flags, const char *fmt, ... ) _format( 6 ); // @@ -834,7 +835,6 @@ void CL_FreeEdicts( void ); void CL_ClearWorld( void ); void CL_DrawCenterPrint( void ); void CL_ClearSpriteTextures( void ); -void CL_FreeEntity( cl_entity_t *pEdict ); void CL_CenterPrint( const char *text, float y ); void CL_TextMessageParse( byte *pMemFile, int fileSize ); client_textmessage_t *CL_TextMessageGet( const char *pName ); @@ -891,7 +891,6 @@ void CL_ParseLegacyServerMessage( sizebuf_t *msg, qboolean normal_message ); void CL_LegacyPrecache_f( void ); void CL_ParseTempEntity( sizebuf_t *msg ); -void CL_StartResourceDownloading( const char *pszMessage, qboolean bCustom ); qboolean CL_DispatchUserMessage( const char *pszName, int iSize, void *pbuf ); qboolean CL_RequestMissingResources( void ); void CL_RegisterResources ( sizebuf_t *msg ); @@ -1002,7 +1001,6 @@ const ref_overview_t *GL_GetOverviewParms( void ); // void R_StoreEfrags( efrag_t **ppefrag, int framecount ); void R_AddEfrags( cl_entity_t *ent ); -void R_RemoveEfrags( cl_entity_t *ent ); // // cl_tent.c // @@ -1157,7 +1155,6 @@ void CL_PlayVideo_f( void ); // keys.c // int Key_IsDown( int keynum ); -const char *Key_IsBind( int keynum ); void Key_Event( int key, int down ); void Key_Init( void ); void Key_WriteBindings( file_t *f ); diff --git a/engine/client/console.c b/engine/client/console.c index 07894d17..99dc67eb 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -213,37 +213,6 @@ void Con_ClearTyping( void ) Cmd_AutoCompleteClear(); } -/* -============ -Con_StringLength - -skipped color prefixes -============ -*/ -int Con_StringLength( const char *string ) -{ - int len; - const char *p; - - if( !string ) return 0; - - len = 0; - p = string; - - while( *p ) - { - if( IsColorString( p )) - { - p += 2; - continue; - } - len++; - p++; - } - - return len; -} - /* ================ Con_MessageMode_f @@ -1596,13 +1565,13 @@ void Key_Console( int key ) } // console scrolling - if( key == K_PGUP ) + if( key == K_PGUP || key == K_DPAD_UP ) { Con_PageUp( 1 ); return; } - if( key == K_PGDN ) + if( key == K_PGDN || key == K_DPAD_DOWN ) { Con_PageDown( 1 ); return; @@ -2098,7 +2067,6 @@ void Con_DrawVersion( void ) Con_DrawStringLen( curbuild, &stringLen, &charH ); start = refState.width - stringLen * 1.05f; - stringLen = Con_StringLength( curbuild ); height -= charH * 1.05f; Con_DrawString( start, height, curbuild, color ); diff --git a/engine/client/in_joy.c b/engine/client/in_joy.c index 8c407fd7..1a3db099 100644 --- a/engine/client/in_joy.c +++ b/engine/client/in_joy.c @@ -393,10 +393,10 @@ void Joy_Init( void ) joy_forward_key_threshold = Cvar_Get( "joy_forward_key_threshold", "24576", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "forward axis key event emit threshold. Value from 0 to 32767"); // by default, we rely on deadzone detection come from system, but some glitchy devices report false deadzones - joy_side_deadzone = Cvar_Get( "joy_side_deadzone", "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "side axis deadzone. Value from 0 to 32767" ); - joy_forward_deadzone = Cvar_Get( "joy_forward_deadzone", "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "forward axis deadzone. Value from 0 to 32767"); - joy_pitch_deadzone = Cvar_Get( "joy_pitch_deadzone", "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "pitch axis deadzone. Value from 0 to 32767"); - joy_yaw_deadzone = Cvar_Get( "joy_yaw_deadzone", "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "yaw axis deadzone. Value from 0 to 32767" ); + joy_side_deadzone = Cvar_Get( "joy_side_deadzone", DEFAULT_JOY_DEADZONE, FCVAR_ARCHIVE | FCVAR_FILTERABLE, "side axis deadzone. Value from 0 to 32767" ); + joy_forward_deadzone = Cvar_Get( "joy_forward_deadzone", DEFAULT_JOY_DEADZONE, FCVAR_ARCHIVE | FCVAR_FILTERABLE, "forward axis deadzone. Value from 0 to 32767"); + joy_pitch_deadzone = Cvar_Get( "joy_pitch_deadzone", DEFAULT_JOY_DEADZONE, FCVAR_ARCHIVE | FCVAR_FILTERABLE, "pitch axis deadzone. Value from 0 to 32767"); + joy_yaw_deadzone = Cvar_Get( "joy_yaw_deadzone", DEFAULT_JOY_DEADZONE, FCVAR_ARCHIVE | FCVAR_FILTERABLE, "yaw axis deadzone. Value from 0 to 32767" ); joy_axis_binding = Cvar_Get( "joy_axis_binding", "sfpyrl", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "axis hardware id to engine inner axis binding, " "s - side, f - forward, y - yaw, p - pitch, r - left trigger, l - right trigger" ); diff --git a/engine/client/in_touch.c b/engine/client/in_touch.c index a35a1a67..490b49a1 100644 --- a/engine/client/in_touch.c +++ b/engine/client/in_touch.c @@ -87,7 +87,7 @@ typedef struct touchbuttonlist_s touch_button_t *last; } touchbuttonlist_t; -struct touch_s +static struct touch_s { qboolean initialized; qboolean config_loaded; @@ -340,7 +340,7 @@ static void Touch_ExportConfig_f( void ) if( Q_strstr( name, "touch_presets/" )) { - COM_FileBase( name, profilebase ); + COM_FileBase( name, profilebase, sizeof( profilebase )); Q_snprintf( profilename, sizeof( profilebase ), "touch_profiles/%s (copy).cfg", profilebase ); } else Q_strncpy( profilename, name, sizeof( profilename )); @@ -737,7 +737,7 @@ static void Touch_ReloadConfig_f( void ) touch.edit = touch.selection = NULL; touch.resize_finger = touch.move_finger = touch.look_finger = touch.wheel_finger = -1; - Cbuf_AddText( va("exec %s\n", touch_config_file->string ) ); + Cbuf_AddTextf( "exec %s\n", touch_config_file->string ); } static touch_button_t *Touch_AddButton( touchbuttonlist_t *list, @@ -1111,7 +1111,7 @@ static void Touch_InitConfig( void ) //pfnGetScreenInfo( NULL ); //HACK: update hud screen parameters like iHeight if( FS_FileExists( touch_config_file->string, true ) ) { - Cbuf_AddText( va( "exec \"%s\"\n", touch_config_file->string ) ); + Cbuf_AddTextf( "exec \"%s\"\n", touch_config_file->string ); Cbuf_Execute(); } else @@ -1498,9 +1498,9 @@ static void Touch_EditMove( touchEventType type, int fingerID, float x, float y, touch.hidebutton->flags &= ~TOUCH_FL_HIDE; if( FBitSet( button->flags, TOUCH_FL_HIDE )) - Q_strcpy( touch.hidebutton->texturefile, "touch_default/edit_show" ); + Q_strncpy( touch.hidebutton->texturefile, "touch_default/edit_show", sizeof( touch.hidebutton->texturefile )); else - Q_strcpy( touch.hidebutton->texturefile, "touch_default/edit_hide" ); + Q_strncpy( touch.hidebutton->texturefile, "touch_default/edit_hide", sizeof( touch.hidebutton->texturefile )); } } if( type == event_motion ) // shutdown button move diff --git a/engine/client/input.c b/engine/client/input.c index 24e71349..6452dfd2 100644 --- a/engine/client/input.c +++ b/engine/client/input.c @@ -176,14 +176,14 @@ void IN_ToggleClientMouse( int newstate, int oldstate ) // since SetCursorType controls cursor visibility // execute it first, and then check mouse grab state - if( newstate == key_menu || newstate == key_console || newstate == key_message ) + if( newstate == key_menu || newstate == key_console ) { Platform_SetCursorType( dc_arrow ); #if XASH_ANDROID Android_ShowMouse( true ); #endif -#ifdef XASH_USE_EVDEV +#if XASH_USE_EVDEV Evdev_SetGrab( false ); #endif } @@ -194,7 +194,7 @@ void IN_ToggleClientMouse( int newstate, int oldstate ) #if XASH_ANDROID Android_ShowMouse( false ); #endif -#ifdef XASH_USE_EVDEV +#if XASH_USE_EVDEV Evdev_SetGrab( true ); #endif } @@ -214,7 +214,7 @@ void IN_CheckMouseState( qboolean active ) static qboolean s_bRawInput, s_bMouseGrab; #if XASH_WIN32 - qboolean useRawInput = CVAR_TO_BOOL( m_rawinput ) && clgame.client_dll_uses_sdl || clgame.dllFuncs.pfnLookEvent; + qboolean useRawInput = ( CVAR_TO_BOOL( m_rawinput ) && clgame.client_dll_uses_sdl ) || clgame.dllFuncs.pfnLookEvent != NULL; #else qboolean useRawInput = true; // always use SDL code #endif @@ -399,7 +399,7 @@ void IN_Shutdown( void ) { IN_DeactivateMouse( ); -#ifdef XASH_USE_EVDEV +#if XASH_USE_EVDEV Evdev_Shutdown(); #endif @@ -426,7 +426,7 @@ void IN_Init( void ) Touch_Init(); -#ifdef XASH_USE_EVDEV +#if XASH_USE_EVDEV Evdev_Init(); #endif } @@ -532,7 +532,7 @@ static void IN_CollectInput( float *forward, float *side, float *pitch, float *y *pitch += y * m_pitch->value; *yaw -= x * m_yaw->value; -#ifdef XASH_USE_EVDEV +#if XASH_USE_EVDEV IN_EvdevMove( yaw, pitch ); #endif } @@ -590,7 +590,7 @@ void IN_EngineAppendMove( float frametime, void *cmd1, qboolean active ) void IN_Commands( void ) { -#ifdef XASH_USE_EVDEV +#if XASH_USE_EVDEV IN_EvdevFrame(); #endif diff --git a/engine/client/keys.c b/engine/client/keys.c index 292d5a29..16d0f839 100644 --- a/engine/client/keys.c +++ b/engine/client/keys.c @@ -159,18 +159,6 @@ int GAME_EXPORT Key_IsDown( int keynum ) return keys[keynum].down; } -/* -=================== -Key_GetBind -=================== -*/ -const char *Key_IsBind( int keynum ) -{ - if( keynum == -1 || !keys[keynum].binding ) - return NULL; - return keys[keynum].binding; -} - /* =================== Key_StringToKeynum @@ -446,8 +434,8 @@ void Key_Bind_f( void ) for( i = 2; i < c; i++ ) { - Q_strcat( cmd, Cmd_Argv( i )); - if( i != ( c - 1 )) Q_strcat( cmd, " " ); + Q_strncat( cmd, Cmd_Argv( i ), sizeof( cmd )); + if( i != ( c - 1 )) Q_strncat( cmd, " ", sizeof( cmd )); } Key_SetBinding( b, cmd ); @@ -553,8 +541,8 @@ void Key_AddKeyCommands( int key, const char *kb, qboolean down ) if( button[0] == '+' ) { // button commands add keynum as a parm - if( down ) Q_sprintf( cmd, "%s %i\n", button, key ); - else Q_sprintf( cmd, "-%s %i\n", button + 1, key ); + if( down ) Q_snprintf( cmd, sizeof( cmd ), "%s %i\n", button, key ); + else Q_snprintf( cmd, sizeof( cmd ), "-%s %i\n", button + 1, key ); Cbuf_AddText( cmd ); } else if( down ) @@ -842,7 +830,7 @@ void GAME_EXPORT Key_SetKeyDest( int key_dest ) cls.key_dest = key_menu; break; case key_console: -#if !XASH_NSWITCH // if we don't disable this, pops up the keyboard during load +#if !XASH_NSWITCH && !XASH_PSVITA // if we don't disable this, pops up the keyboard during load Key_EnableTextInput( true, false ); #endif cls.key_dest = key_console; diff --git a/engine/client/ref_common.c b/engine/client/ref_common.c index f30bb9cf..80733e42 100644 --- a/engine/client/ref_common.c +++ b/engine/client/ref_common.c @@ -75,16 +75,6 @@ static void pfnStudioEvent( const mstudioevent_t *event, const cl_entity_t *e ) clgame.dllFuncs.pfnStudioEvent( event, e ); } -static efrag_t* pfnGetEfragsFreeList( void ) -{ - return clgame.free_efrags; -} - -static void pfnSetEfragsFreeList( efrag_t *list ) -{ - clgame.free_efrags = list; -} - static model_t *pfnGetDefaultSprite( enum ref_defaultsprite_e spr ) { switch( spr ) @@ -225,7 +215,7 @@ static qboolean R_DoResetGamma( void ) static qboolean R_Init_Video_( const int type ) { host.apply_opengl_config = true; - Cbuf_AddText( va( "exec %s.cfg", ref.dllFuncs.R_GetConfigName())); + Cbuf_AddTextf( "exec %s.cfg", ref.dllFuncs.R_GetConfigName()); Cbuf_Execute(); host.apply_opengl_config = false; @@ -527,7 +517,7 @@ static void R_GetRendererName( char *dest, size_t size, const char *opt ) else { // full path - Q_strcpy( dest, opt ); + Q_strncpy( dest, opt, size ); } } @@ -571,66 +561,68 @@ static void SetWidthAndHeightFromCommandLine( void ) static void SetFullscreenModeFromCommandLine( void ) { #if !XASH_MOBILE_PLATFORM - if ( Sys_CheckParm("-fullscreen") ) + if( Sys_CheckParm( "-fullscreen" )) { Cvar_Set( "fullscreen", "1" ); } - else if ( Sys_CheckParm( "-windowed" ) ) + else if( Sys_CheckParm( "-windowed" )) { Cvar_Set( "fullscreen", "0" ); } #endif } -void R_CollectRendererNames( void ) +static void R_CollectRendererNames( void ) { - const char *renderers[] = DEFAULT_RENDERERS; - int i, cur; - - cur = 0; - for( i = 0; i < DEFAULT_RENDERERS_LEN; i++ ) + // ordering is important! + static const char *shortNames[] = { - string temp; - void *dll, *pfn; +#if XASH_REF_GL_ENABLED + "gl", +#endif +#if XASH_REF_NANOGL_ENABLED + "gles1", +#endif +#if XASH_REF_GLWES_ENABLED + "gles2", +#endif +#if XASH_REF_GL4ES_ENABLED + "gl4es", +#endif +#if XASH_REF_SOFT_ENABLED + "soft", +#endif +#if XASH_REF_VULKAN_ENABLED + "vk" +#endif + }; - R_GetRendererName( temp, sizeof( temp ), renderers[i] ); + // ordering is important here too! + static const char *readableNames[ARRAYSIZE( shortNames )] = + { +#if XASH_REF_GL_ENABLED + "OpenGL", +#endif +#if XASH_REF_NANOGL_ENABLED + "GLES1 (NanoGL)", +#endif +#if XASH_REF_GLWES_ENABLED + "GLES2 (gl-wes-v2)", +#endif +#if XASH_REF_GL4ES_ENABLED + "GL4ES", +#endif +#if XASH_REF_SOFT_ENABLED + "Software", +#endif +#if XASH_REF_VULKAN_ENABLED + "Vulkan" +#endif + }; - dll = COM_LoadLibrary( temp, false, true ); - if( !dll ) - { - Con_Reportf( "R_CollectRendererNames: can't load library %s: %s\n", temp, COM_GetLibraryError() ); - continue; - } - - pfn = COM_GetProcAddress( dll, GET_REF_API ); - if( !pfn ) - { - Con_Reportf( "R_CollectRendererNames: can't find API entry point in %s\n", temp ); - COM_FreeLibrary( dll ); - continue; - } - - Q_strncpy( ref.shortNames[cur], renderers[i], sizeof( ref.shortNames[cur] )); - - pfn = COM_GetProcAddress( dll, GET_REF_HUMANREADABLE_NAME ); - if( !pfn ) // just in case - { - Con_Reportf( "R_CollectRendererNames: can't find GetHumanReadableName export in %s\n", temp ); - Q_strncpy( ref.readableNames[cur], renderers[i], sizeof( ref.readableNames[cur] )); - } - else - { - REF_HUMANREADABLE_NAME GetHumanReadableName = (REF_HUMANREADABLE_NAME)pfn; - - GetHumanReadableName( ref.readableNames[cur], sizeof( ref.readableNames[cur] )); - } - - Con_Printf( "Found renderer %s: %s\n", ref.shortNames[cur], ref.readableNames[cur] ); - - cur++; - COM_FreeLibrary( dll ); - } - ref.numRenderers = cur; + ref.numRenderers = ARRAYSIZE( shortNames ); + ref.shortNames = shortNames; + ref.readableNames = readableNames; } const ref_device_t *R_GetRenderDevice( unsigned int idx ) @@ -746,30 +738,28 @@ qboolean R_Init( void ) // 1. Command line `-ref` argument. // 2. `ref_dll` cvar. // 3. Detected renderers in `DEFAULT_RENDERERS` order. - requested[0] = '\0'; - if( !Sys_GetParmFromCmdLine( "-ref", requested ) && COM_CheckString( r_refdll->string ) ) - // r_refdll is set to empty by default, so we can change hardcoded defaults just in case - Q_strncpy( requested, r_refdll->string, sizeof( requested ) ); + requested[0] = 0; - if ( requested[0] ) + if( !success && Sys_GetParmFromCmdLine( "-ref", requested )) success = R_LoadRenderer( requested ); + if( !success && COM_CheckString( r_refdll->string )) + { + Q_strncpy( requested, r_refdll->string, sizeof( requested )); + success = R_LoadRenderer( requested ); + } + if( !success ) { int i; - // cycle through renderers that we collected in CollectRendererNames - for( i = 0; i < ref.numRenderers; i++ ) + for( i = 0; i < ref.numRenderers && !success; i++ ) { // skip renderer that was requested but failed to load - if( !Q_strcmp( requested, ref.shortNames[i] ) ) + if( !Q_strcmp( requested, ref.shortNames[i] )) continue; success = R_LoadRenderer( ref.shortNames[i] ); - - // yay, found working one - if( success ) - break; } } diff --git a/engine/client/ref_common.h b/engine/client/ref_common.h index 8058d42f..1ba0c23e 100644 --- a/engine/client/ref_common.h +++ b/engine/client/ref_common.h @@ -27,9 +27,10 @@ struct ref_state_s HINSTANCE hInstance; ref_interface_t dllFuncs; + // depends on build configuration int numRenderers; - string shortNames[DEFAULT_RENDERERS_LEN]; - string readableNames[DEFAULT_RENDERERS_LEN]; + const char **shortNames; + const char **readableNames; }; extern struct ref_state_s ref; diff --git a/engine/client/s_main.c b/engine/client/s_main.c index 3ba4ea13..718014b8 100644 --- a/engine/client/s_main.c +++ b/engine/client/s_main.c @@ -159,7 +159,7 @@ void S_UpdateSoundFade( void ) } // spline it. - f = SimpleSpline( f ); + f = -( cos( M_PI * f ) - 1 ) / 2; f = bound( 0.0f, f, 1.0f ); soundfade.percent = soundfade.initial_percent * f; @@ -1292,36 +1292,6 @@ void S_StreamAviSamples( void *Avi, int entnum, float fvol, float attn, float sy } } -/* -=================== -S_GetRawSamplesLength -=================== -*/ -uint S_GetRawSamplesLength( int entnum ) -{ - rawchan_t *ch; - - if( !( ch = S_FindRawChannel( entnum, false ))) - return 0; - - return ch->s_rawend <= paintedtime ? 0 : (float)(ch->s_rawend - paintedtime) * DMA_MSEC_PER_SAMPLE; -} - -/* -=================== -S_ClearRawChannel -=================== -*/ -void S_ClearRawChannel( int entnum ) -{ - rawchan_t *ch; - - if( !( ch = S_FindRawChannel( entnum, false ))) - return; - - ch->s_rawend = 0; -} - /* =================== S_FreeIdleRawChannels @@ -1842,8 +1812,12 @@ void S_Music_f( void ) for( i = 0; i < 2; i++ ) { - const char *intro_path = va( "media/%s.%s", intro, ext[i] ); - const char *main_path = va( "media/%s.%s", main, ext[i] ); + char intro_path[MAX_VA_STRING]; + char main_path[MAX_VA_STRING]; + char track_path[MAX_VA_STRING]; + + Q_snprintf( intro_path, sizeof( intro_path ), "media/%s.%s", intro, ext[i] ); + Q_snprintf( main_path, sizeof( main_path ), "media/%s.%s", main, ext[i] ); if( FS_FileExists( intro_path, false ) && FS_FileExists( main_path, false )) { @@ -1851,7 +1825,10 @@ void S_Music_f( void ) S_StartBackgroundTrack( intro, main, 0, false ); break; } - else if( FS_FileExists( va( "media/%s.%s", track, ext[i] ), false )) + + Q_snprintf( track_path, sizeof( track_path ), "media/%s.%s", track, ext[i] ); + + if( FS_FileExists( track_path, false )) { // single non-looped theme S_StartBackgroundTrack( track, NULL, 0, false ); diff --git a/engine/client/s_mix.c b/engine/client/s_mix.c index f58b5063..11a5232b 100644 --- a/engine/client/s_mix.c +++ b/engine/client/s_mix.c @@ -117,13 +117,6 @@ _inline void MIX_ActivatePaintbuffer( int ipaintbuffer ) paintbuffers[ipaintbuffer].factive = true; } -// don't mix into this paintbuffer -_inline void MIX_DeactivatePaintbuffer( int ipaintbuffer ) -{ - Assert( ipaintbuffer < CPAINTBUFFERS ); - paintbuffers[ipaintbuffer].factive = false; -} - _inline void MIX_SetCurrentPaintbuffer( int ipaintbuffer ) { Assert( ipaintbuffer < CPAINTBUFFERS ); @@ -169,12 +162,6 @@ _inline void MIX_ResetPaintbufferFilterCounters( void ) paintbuffers[i].ifilter = FILTERTYPE_NONE; } -_inline void MIX_ResetPaintbufferFilterCounter( int ipaintbuffer ) -{ - Assert( ipaintbuffer < CPAINTBUFFERS ); - paintbuffers[ipaintbuffer].ifilter = 0; -} - // return pointer to front paintbuffer pbuf, given index _inline portable_samplepair_t *MIX_GetPFrontFromIPaint( int ipaintbuffer ) { diff --git a/engine/client/s_stream.c b/engine/client/s_stream.c index 10ed064a..1c19e7b9 100644 --- a/engine/client/s_stream.c +++ b/engine/client/s_stream.c @@ -95,7 +95,7 @@ void S_StartBackgroundTrack( const char *introTrack, const char *mainTrack, int else Q_strncpy( s_bgTrack.loopName, mainTrack, sizeof( s_bgTrack.loopName )); // open stream - s_bgTrack.stream = FS_OpenStream( va( "media/%s", introTrack )); + s_bgTrack.stream = FS_OpenStream( introTrack ); Q_strncpy( s_bgTrack.current, introTrack, sizeof( s_bgTrack.current )); memset( &musicfade, 0, sizeof( musicfade )); // clear any soundfade s_bgTrack.source = cls.key_dest; @@ -242,7 +242,7 @@ void S_StreamBackgroundTrack( void ) if( s_bgTrack.loopName[0] ) { FS_FreeStream( s_bgTrack.stream ); - s_bgTrack.stream = FS_OpenStream( va( "media/%s", s_bgTrack.loopName )); + s_bgTrack.stream = FS_OpenStream( s_bgTrack.loopName ); Q_strncpy( s_bgTrack.current, s_bgTrack.loopName, sizeof( s_bgTrack.current )); if( !s_bgTrack.stream ) return; diff --git a/engine/client/s_vox.c b/engine/client/s_vox.c index 73818943..d96a4b3a 100644 --- a/engine/client/s_vox.c +++ b/engine/client/s_vox.c @@ -142,7 +142,7 @@ static const char *VOX_GetDirectory( char *szpath, const char *psz, int nsize ) if( !p ) { - Q_strcpy( szpath, "vox/" ); + Q_strncpy( szpath, "vox/", nsize ); return psz; } diff --git a/engine/client/sound.h b/engine/client/sound.h index d55ee533..48c7d5ad 100644 --- a/engine/client/sound.h +++ b/engine/client/sound.h @@ -276,8 +276,6 @@ void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word chan void S_RawSamples( uint samples, uint rate, word width, word channels, const byte *data, int entnum ); void S_StopSound( int entnum, int channel, const char *soundname ); void S_UpdateFrame( struct ref_viewpass_s *rvp ); -uint S_GetRawSamplesLength( int entnum ); -void S_ClearRawChannel( int entnum ); void S_StopAllSounds( qboolean ambient ); void S_FreeSounds( void ); diff --git a/engine/client/titles.c b/engine/client/titles.c index 4442ef76..bf5e283d 100644 --- a/engine/client/titles.c +++ b/engine/client/titles.c @@ -218,6 +218,7 @@ void CL_TextMessageParse( byte *pMemFile, int fileSize ) client_textmessage_t textMessages[MAX_MESSAGES]; int i, nameHeapSize, textHeapSize, messageSize, nameOffset; int messageCount, lastNamePos; + size_t textHeapSizeRemaining; lastNamePos = 0; lineNumber = 0; @@ -252,7 +253,7 @@ void CL_TextMessageParse( byte *pMemFile, int fileSize ) Con_Reportf( "TextMessage: unexpected '}' found, line %d\n", lineNumber ); return; } - Q_strcpy( currentName, trim ); + Q_strncpy( currentName, trim, sizeof( currentName )); break; case MSGFILE_TEXT: if( IsEndOfText( trim )) @@ -266,7 +267,7 @@ void CL_TextMessageParse( byte *pMemFile, int fileSize ) return; } - Q_strcpy( nameHeap + lastNamePos, currentName ); + Q_strncpy( nameHeap + lastNamePos, currentName, sizeof( nameHeap ) - lastNamePos ); // terminate text in-place in the memory file // (it's temporary memory that will be deleted) @@ -329,15 +330,20 @@ void CL_TextMessageParse( byte *pMemFile, int fileSize ) // copy text & fixup pointers + textHeapSizeRemaining = textHeapSize; pCurrentText = pNameHeap + nameHeapSize; for( i = 0; i < messageCount; i++ ) { + size_t currentTextSize = Q_strlen( clgame.titles[i].pMessage ) + 1; + clgame.titles[i].pName = pNameHeap; // adjust name pointer (parallel buffer) - Q_strcpy( pCurrentText, clgame.titles[i].pMessage ); // copy text over + Q_strncpy( pCurrentText, clgame.titles[i].pMessage, textHeapSizeRemaining ); // copy text over clgame.titles[i].pMessage = pCurrentText; + pNameHeap += Q_strlen( pNameHeap ) + 1; - pCurrentText += Q_strlen( pCurrentText ) + 1; + pCurrentText += currentTextSize; + textHeapSizeRemaining -= currentTextSize; } if(( pCurrentText - (char *)clgame.titles ) != ( textHeapSize + nameHeapSize + messageSize )) diff --git a/engine/common/base_cmd.c b/engine/common/base_cmd.c index efdf718e..78b5b33f 100644 --- a/engine/common/base_cmd.c +++ b/engine/common/base_cmd.c @@ -17,10 +17,20 @@ GNU General Public License for more details. #include "base_cmd.h" #include "cdll_int.h" -// TODO: use another hash function, as COM_HashKey depends on string length #define HASH_SIZE 128 // 128 * 4 * 4 == 2048 bytes + +typedef struct base_command_hashmap_s +{ + base_command_t *basecmd; // base command: cvar, alias or command + const char *name; // key for searching + base_command_type_e type; // type for faster searching + struct base_command_hashmap_s *next; +} base_command_hashmap_t; + static base_command_hashmap_t *hashed_cmds[HASH_SIZE]; +#define BaseCmd_HashKey( x ) COM_HashKey( name, HASH_SIZE ) + /* ============ BaseCmd_FindInBucket @@ -28,7 +38,7 @@ BaseCmd_FindInBucket Find base command in bucket ============ */ -base_command_hashmap_t *BaseCmd_FindInBucket( base_command_hashmap_t *bucket, base_command_type_e type, const char *name ) +static base_command_hashmap_t *BaseCmd_FindInBucket( base_command_hashmap_t *bucket, base_command_type_e type, const char *name ) { base_command_hashmap_t *i = bucket; for( ; i && ( i->type != type || Q_stricmp( name, i->name ) ); // filter out @@ -44,9 +54,9 @@ BaseCmd_GetBucket Get bucket which contain basecmd by given name ============ */ -base_command_hashmap_t *BaseCmd_GetBucket( const char *name ) +static base_command_hashmap_t *BaseCmd_GetBucket( const char *name ) { - return hashed_cmds[ COM_HashKey( name, HASH_SIZE ) ]; + return hashed_cmds[ BaseCmd_HashKey( name ) ]; } /* @@ -73,7 +83,7 @@ BaseCmd_Find Find every type of base command and write into arguments ============ */ -void BaseCmd_FindAll(const char *name, base_command_t **cmd, base_command_t **alias, base_command_t **cvar) +void BaseCmd_FindAll( const char *name, base_command_t **cmd, base_command_t **alias, base_command_t **cvar ) { base_command_hashmap_t *base = BaseCmd_GetBucket( name ); base_command_hashmap_t *i = base; @@ -101,7 +111,6 @@ void BaseCmd_FindAll(const char *name, base_command_t **cmd, base_command_t **al } } } - } /* @@ -113,41 +122,23 @@ Add new typed base command to hashmap */ void BaseCmd_Insert( base_command_type_e type, base_command_t *basecmd, const char *name ) { - uint hash = COM_HashKey( name, HASH_SIZE ); - base_command_hashmap_t *elem; + base_command_hashmap_t *elem, *cur, *find; + uint hash = BaseCmd_HashKey( name ); elem = Z_Malloc( sizeof( base_command_hashmap_t ) ); elem->basecmd = basecmd; elem->type = type; elem->name = name; - elem->next = hashed_cmds[hash]; - hashed_cmds[hash] = elem; -} -/* -============ -BaseCmd_Replace + // link the variable in alphanumerical order + for( cur = NULL, find = hashed_cmds[hash]; + find && Q_strcmp( find->name, elem->name ) < 0; + cur = find, find = find->next ); -Used in case, when basecmd has been registered, but gamedll wants to register it's own -============ -*/ -qboolean BaseCmd_Replace( base_command_type_e type, base_command_t *basecmd, const char *name ) -{ - base_command_hashmap_t *i = BaseCmd_GetBucket( name ); + if( cur ) cur->next = elem; + else hashed_cmds[hash] = elem; - for( ; i && ( i->type != type || Q_stricmp( name, i->name ) ) ; // filter out - i = i->next ); - - if( !i ) - { - Con_Reportf( S_ERROR "BaseCmd_Replace: couldn't find %s\n", name); - return false; - } - - i->basecmd = basecmd; - i->name = name; // may be freed after - - return true; + elem->next = find; } /* @@ -159,7 +150,7 @@ Remove base command from hashmap */ void BaseCmd_Remove( base_command_type_e type, const char *name ) { - uint hash = COM_HashKey( name, HASH_SIZE ); + uint hash = BaseCmd_HashKey( name ); base_command_hashmap_t *i, *prev; for( prev = NULL, i = hashed_cmds[hash]; i && @@ -221,23 +212,27 @@ void BaseCmd_Stats_f( void ) if( len > maxsize ) maxsize = len; + } - Con_Printf( "Base command stats:\n"); - Con_Printf( "Bucket minimal length: %d\n", minsize ); - Con_Printf( "Bucket maximum length: %d\n", maxsize ); - Con_Printf( "Empty buckets: %d\n", empty ); + Con_Printf( "min length: %d, max length: %d, empty: %d\n", minsize, maxsize, empty ); } +typedef struct +{ + qboolean valid; + int lookups; +} basecmd_test_stats_t; + 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; + basecmd_test_stats_t *stats = ptr; - if( !v ) + stats->lookups++; + if( !BaseCmd_Find( HM_CVAR, key )) { Con_Printf( "Cvar %s is missing in basecmd\n", key ); - *invalid = true; + stats->valid = false; } } @@ -250,38 +245,51 @@ testing order matches cbuf execute */ void BaseCmd_Test_f( void ) { - void *cmd; - cmdalias_t *a; - qboolean invalid = false; + basecmd_test_stats_t stats; + double start, end, dt; + int i; - // Cmd_LookupCmds don't allows to check alias, so just iterate - for( a = Cmd_AliasGetList(); a; a = a->next ) + stats.valid = true; + stats.lookups = 0; + + start = Sys_DoubleTime() * 1000; + + for( i = 0; i < 1000; i++ ) { - base_command_t *v = BaseCmd_Find( HM_CMDALIAS, a->name ); + cmdalias_t *a; + void *cmd; - if( !v ) + // Cmd_LookupCmds don't allows to check alias, so just iterate + for( a = Cmd_AliasGetList(); a; a = a->next, stats.lookups++ ) { - Con_Printf( "Alias %s is missing in basecmd\n", a->name ); - invalid = true; + if( !BaseCmd_Find( HM_CMDALIAS, a->name )) + { + Con_Printf( "Alias %s is missing in basecmd\n", a->name ); + stats.valid = false; + } } + + for( cmd = Cmd_GetFirstFunctionHandle(); cmd; + cmd = Cmd_GetNextFunctionHandle( cmd ), stats.lookups++ ) + { + if( !BaseCmd_Find( HM_CMD, Cmd_GetName( cmd ))) + { + Con_Printf( "Command %s is missing in basecmd\n", Cmd_GetName( cmd )); + stats.valid = false; + } + } + + Cvar_LookupVars( 0, NULL, &stats.valid, (setpair_t)BaseCmd_CheckCvars ); } - for( cmd = Cmd_GetFirstFunctionHandle(); cmd; - cmd = Cmd_GetNextFunctionHandle( cmd ) ) - { - base_command_t *v = BaseCmd_Find( HM_CMD, Cmd_GetName( cmd ) ); + end = Sys_DoubleTime() * 1000; - if( !v ) - { - Con_Printf( "Command %s is missing in basecmd\n", Cmd_GetName( cmd ) ); - invalid = true; - } - } + dt = end - start; - Cvar_LookupVars( 0, NULL, &invalid, (setpair_t)BaseCmd_CheckCvars ); - - if( !invalid ) - { + if( !stats.valid ) Con_Printf( "BaseCmd is valid\n" ); - } + + Con_Printf( "Test took %.3f ms, %d lookups, %.3f us/lookup\n", dt, stats.lookups, dt / stats.lookups * 1000 ); + + BaseCmd_Stats_f(); } diff --git a/engine/common/base_cmd.h b/engine/common/base_cmd.h index f507c9e1..671dee42 100644 --- a/engine/common/base_cmd.h +++ b/engine/common/base_cmd.h @@ -17,8 +17,6 @@ GNU General Public License for more details. #ifndef BASE_CMD_H #define BASE_CMD_H -// TODO: Find cases when command hashmap works incorrect -// and maybe disable it #define XASH_HASHED_VARS #ifdef XASH_HASHED_VARS @@ -33,23 +31,13 @@ typedef enum base_command_type typedef void base_command_t; -typedef struct base_command_hashmap_s -{ - base_command_t *basecmd; // base command: cvar, alias or command - const char *name; // key for searching - base_command_type_e type; // type for faster searching - struct base_command_hashmap_s *next; -} base_command_hashmap_t; void BaseCmd_Init( void ); -base_command_hashmap_t *BaseCmd_GetBucket( const char *name ); -base_command_hashmap_t *BaseCmd_FindInBucket( base_command_hashmap_t *bucket, base_command_type_e type, const char *name ); base_command_t *BaseCmd_Find( base_command_type_e type, const char *name ); void BaseCmd_FindAll( const char *name, base_command_t **cmd, base_command_t **alias, base_command_t **cvar ); void BaseCmd_Insert ( base_command_type_e type, base_command_t *basecmd, const char *name ); -qboolean BaseCmd_Replace( base_command_type_e type, base_command_t *basecmd, const char *name ); // only if same name void BaseCmd_Remove ( base_command_type_e type, const char *name ); void BaseCmd_Stats_f( void ); // to be registered later void BaseCmd_Test_f( void ); // to be registered later diff --git a/engine/common/cmd.c b/engine/common/cmd.c index 2a7c1a8c..e8d514c7 100644 --- a/engine/common/cmd.c +++ b/engine/common/cmd.c @@ -120,6 +120,18 @@ void Cbuf_AddText( const char *text ) Cbuf_AddTextToBuffer( &cmd_text, text ); } +void Cbuf_AddTextf( const char *fmt, ... ) +{ + va_list va; + char buf[MAX_VA_STRING]; + + va_start( va, fmt ); + Q_vsnprintf( buf, sizeof( buf ), fmt, va ); + va_end( va ); + + Cbuf_AddText( buf ); +} + /* ============ Cbuf_AddFilteredText @@ -1150,13 +1162,13 @@ void Cmd_ForwardToServer( void ) str[0] = 0; if( Q_stricmp( Cmd_Argv( 0 ), "cmd" )) { - Q_strcat( str, Cmd_Argv( 0 )); - Q_strcat( str, " " ); + Q_strncat( str, Cmd_Argv( 0 ), sizeof( str )); + Q_strncat( str, " ", sizeof( str )); } if( Cmd_Argc() > 1 ) - Q_strcat( str, Cmd_Args( )); - else Q_strcat( str, "\n" ); + Q_strncat( str, Cmd_Args( ), sizeof( str )); + else Q_strncat( str, "\n", sizeof( str )); MSG_WriteString( &cls.netchan.message, str ); } @@ -1254,21 +1266,21 @@ static void Cmd_Apropos_f( void ) cmdalias_t *alias; const char *partial; int count = 0; - qboolean ispattern; + char buf[MAX_VA_STRING]; - if( Cmd_Argc() > 1 ) - { - partial = Cmd_Args(); - } - else + if( Cmd_Argc() < 2 ) { Msg( "apropos what?\n" ); return; } - ispattern = partial && Q_strpbrk( partial, "*?" ); - if( !ispattern ) - partial = va( "*%s*", partial ); + partial = Cmd_Args(); + + if( !Q_strpbrk( partial, "*?" )) + { + Q_snprintf( buf, sizeof( buf ), "*%s*", partial ); + partial = buf; + } for( var = (convar_t*)Cvar_GetList(); var; var = var->next ) { diff --git a/engine/common/com_strings.h b/engine/common/com_strings.h index 8eeee53d..d3dc7dca 100644 --- a/engine/common/com_strings.h +++ b/engine/common/com_strings.h @@ -68,8 +68,4 @@ GNU General Public License for more details. #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" } -#define DEFAULT_RENDERERS_LEN 6 - #endif//COM_STRINGS_H diff --git a/engine/common/common.c b/engine/common/common.c index 684718dd..391b4e50 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -151,6 +151,30 @@ int GAME_EXPORT COM_RandomLong( int lLow, int lHigh ) return lLow + (n % x); } +/* +============ +va + +does a varargs printf into a temp buffer, +so I don't need to have varargs versions +of all text functions. +============ +*/ +char *va( const char *format, ... ) +{ + va_list argptr; + static char string[16][MAX_VA_STRING], *s; + static int stringindex = 0; + + s = string[stringindex]; + stringindex = (stringindex + 1) & 15; + va_start( argptr, format ); + Q_vsnprintf( s, sizeof( string[0] ), format, argptr ); + va_end( argptr ); + + return s; +} + /* =============================================================================== @@ -540,7 +564,7 @@ int GAME_EXPORT COM_ExpandFilename( const char *fileName, char *nameOutBuffer, i // models\barney.mdl - D:\Xash3D\bshift\models\barney.mdl if(( path = FS_GetDiskPath( fileName, false )) != NULL ) { - Q_sprintf( result, "%s/%s", host.rootdir, path ); + Q_snprintf( result, sizeof( result ), "%s/%s", host.rootdir, path ); // check for enough room if( Q_strlen( result ) > nameOutBufferSize ) @@ -876,9 +900,7 @@ cvar_t *pfnCvar_RegisterClientVariable( const char *szName, const char *szValue, if( !Q_stricmp( szName, "motdfile" )) flags |= FCVAR_PRIVILEGED; - if( FBitSet( flags, FCVAR_GLCONFIG )) - return (cvar_t *)Cvar_Get( szName, szValue, flags, va( CVAR_GLCONFIG_DESCRIPTION, szName )); - return (cvar_t *)Cvar_Get( szName, szValue, flags|FCVAR_CLIENTDLL, Cvar_BuildAutoDescription( flags|FCVAR_CLIENTDLL )); + return (cvar_t *)Cvar_Get( szName, szValue, flags|FCVAR_CLIENTDLL, Cvar_BuildAutoDescription( szName, flags|FCVAR_CLIENTDLL )); } /* @@ -889,9 +911,7 @@ pfnCvar_RegisterVariable */ cvar_t *pfnCvar_RegisterGameUIVariable( const char *szName, const char *szValue, int flags ) { - if( FBitSet( flags, FCVAR_GLCONFIG )) - return (cvar_t *)Cvar_Get( szName, szValue, flags, va( CVAR_GLCONFIG_DESCRIPTION, szName )); - return (cvar_t *)Cvar_Get( szName, szValue, flags|FCVAR_GAMEUIDLL, Cvar_BuildAutoDescription( flags|FCVAR_GAMEUIDLL )); + return (cvar_t *)Cvar_Get( szName, szValue, flags|FCVAR_GAMEUIDLL, Cvar_BuildAutoDescription( szName, flags|FCVAR_GAMEUIDLL )); } /* @@ -986,7 +1006,7 @@ pfnGetGameDir void GAME_EXPORT pfnGetGameDir( char *szGetGameDir ) { if( !szGetGameDir ) return; - Q_strcpy( szGetGameDir, GI->gamefolder ); + Q_strncpy( szGetGameDir, GI->gamefolder, sizeof( GI->gamefolder )); } qboolean COM_IsSafeFileToDownload( const char *filename ) @@ -1033,15 +1053,32 @@ qboolean COM_IsSafeFileToDownload( const char *filename ) return true; } +const char *COM_GetResourceTypeName( resourcetype_t restype ) +{ + switch( restype ) + { + case t_decal: return "decal"; + case t_eventscript: return "eventscript"; + case t_generic: return "generic"; + case t_model: return "model"; + case t_skin: return "skin"; + case t_sound: return "sound"; + case t_world: return "world"; + default: return "unknown"; + } +} + char *_copystring( poolhandle_t mempool, const char *s, const char *filename, int fileline ) { + size_t size; char *b; if( !s ) return NULL; if( !mempool ) mempool = host.mempool; - b = _Mem_Alloc( mempool, Q_strlen( s ) + 1, false, filename, fileline ); - Q_strcpy( b, s ); + size = Q_strlen( s ) + 1; + b = _Mem_Alloc( mempool, size, false, filename, fileline ); + Q_strncpy( b, s, size ); return b; } @@ -1066,7 +1103,6 @@ void *GAME_EXPORT pfnSequenceGet( const char *fileName, const char *entryName ) { Msg( "Sequence_Get: file %s, entry %s\n", fileName, entryName ); - return Sequence_Get( fileName, entryName ); } diff --git a/engine/common/common.h b/engine/common/common.h index 3d747d3c..0d6f7e94 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -425,6 +425,7 @@ void FS_Shutdown( void ); void Cbuf_Init( void ); void Cbuf_Clear( void ); void Cbuf_AddText( const char *text ); +void Cbuf_AddTextf( const char *text, ... ) _format( 1 ); void Cbuf_AddFilteredText( const char *text ); void Cbuf_InsertText( const char *text ); void Cbuf_ExecStuffCmds( void ); @@ -565,6 +566,7 @@ int FS_GetStreamPos( stream_t *stream ); void FS_FreeStream( stream_t *stream ); qboolean Sound_Process( wavdata_t **wav, int rate, int width, uint flags ); uint Sound_GetApproxWavePlayLen( const char *filepath ); +qboolean Sound_SupportedFileFormat( const char *fileext ); // // host.c @@ -636,6 +638,7 @@ void COM_HexConvert( const char *pszInput, int nInputLength, byte *pOutput ); int COM_SaveFile( const char *filename, const void *data, int len ); byte* COM_LoadFileForMe( const char *filename, int *pLength ); qboolean COM_IsSafeFileToDownload( const char *filename ); +const char *COM_GetResourceTypeName( resourcetype_t restype ); cvar_t *pfnCVarGetPointer( const char *szVarName ); int pfnDrawConsoleString( int x, int y, char *string ); void pfnDrawSetTextColor( float r, float g, float b ); @@ -777,8 +780,6 @@ int SV_GetMaxClients( void ); qboolean CL_IsRecordDemo( void ); qboolean CL_IsTimeDemo( void ); qboolean CL_IsPlaybackDemo( void ); -qboolean CL_IsBackgroundDemo( void ); -qboolean CL_IsBackgroundMap( void ); qboolean SV_Initialized( void ); qboolean CL_LoadProgs( const char *name ); void CL_ProcessFile( qboolean successfully_received, const char *filename ); @@ -818,6 +819,7 @@ const char *Info_ValueForKey( const char *s, const char *key ); void Info_RemovePrefixedKeys( char *start, char prefix ); qboolean Info_RemoveKey( char *s, const char *key ); qboolean Info_SetValueForKey( char *s, const char *key, const char *value, int maxsize ); +qboolean Info_SetValueForKeyf( char *s, const char *key, int maxsize, const char *format, ... ) _format( 4 ); qboolean Info_SetValueForStarKey( char *s, const char *key, const char *value, int maxsize ); qboolean Info_IsValid( const char *s ); void Info_WriteVars( file_t *f ); @@ -843,6 +845,7 @@ 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 ); +char *va( const char *format, ... ) _format( 1 ); // soundlib shared exports qboolean S_Init( void ); @@ -855,7 +858,6 @@ void S_StopAllSounds( qboolean ambient ); // gamma routines void BuildGammaTable( float gamma, float brightness ); byte LightToTexGamma( byte b ); -byte TextureToGamma( byte b ); // // identification.c @@ -871,6 +873,10 @@ void NET_InitMasters( void ); void NET_SaveMasters( void ); qboolean NET_SendToMasters( netsrc_t sock, size_t len, const void *data ); qboolean NET_IsMasterAdr( netadr_t adr ); +void NET_MasterHeartbeat( void ); +void NET_MasterClear( void ); +void NET_MasterShutdown( void ); +qboolean NET_GetMaster( netadr_t from, uint *challenge, double *last_heartbeat ); #ifdef REF_DLL #error "common.h in ref_dll" diff --git a/engine/common/con_utils.c b/engine/common/con_utils.c index 3aed173f..126a289d 100644 --- a/engine/common/con_utils.c +++ b/engine/common/con_utils.c @@ -101,8 +101,7 @@ int Cmd_ListMaps( search_t *t, char *lastmapname, size_t len ) if( hdrext->id == IDEXTRAHEADER ) version = hdrext->version; Q_strncpy( entfilename, t->filenames[i], sizeof( entfilename )); - COM_StripExtension( entfilename ); - COM_DefaultExtension( entfilename, ".ent" ); + COM_ReplaceExtension( entfilename, ".ent", sizeof( entfilename )); ents = (char *)FS_LoadFile( entfilename, NULL, true ); if( !ents && lumplen >= 10 ) @@ -146,7 +145,7 @@ int Cmd_ListMaps( search_t *t, char *lastmapname, size_t len ) } if( f ) FS_Close(f); - COM_FileBase( t->filenames[i], mapname ); + COM_FileBase( t->filenames[i], mapname, sizeof( mapname )); switch( ver ) { @@ -194,7 +193,7 @@ qboolean Cmd_GetMapList( const char *s, char *completedname, int length ) t = FS_Search( va( "maps/%s*.bsp", s ), true, con_gamemaps->value ); if( !t ) return false; - COM_FileBase( t->filenames[0], matchbuf ); + COM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf )); if( completedname && length ) Q_strncpy( completedname, matchbuf, length ); if( t->numfilenames == 1 ) return true; @@ -231,7 +230,7 @@ qboolean Cmd_GetDemoList( const char *s, char *completedname, int length ) t = FS_Search( va( "%s*.dem", s ), true, true ); if( !t ) return false; - COM_FileBase( t->filenames[0], matchbuf ); + COM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf )); if( completedname && length ) Q_strncpy( completedname, matchbuf, length ); if( t->numfilenames == 1 ) return true; @@ -241,7 +240,7 @@ qboolean Cmd_GetDemoList( const char *s, char *completedname, int length ) if( Q_stricmp( COM_FileExtension( t->filenames[i] ), "dem" )) continue; - COM_FileBase( t->filenames[i], matchbuf ); + COM_FileBase( t->filenames[i], matchbuf, sizeof( matchbuf )); Con_Printf( "%16s\n", matchbuf ); numdems++; } @@ -277,7 +276,7 @@ qboolean Cmd_GetMovieList( const char *s, char *completedname, int length ) t = FS_Search( va( "media/%s*.avi", s ), true, false ); if( !t ) return false; - COM_FileBase( t->filenames[0], matchbuf ); + COM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf )); if( completedname && length ) Q_strncpy( completedname, matchbuf, length ); if( t->numfilenames == 1 ) return true; @@ -287,7 +286,7 @@ qboolean Cmd_GetMovieList( const char *s, char *completedname, int length ) if( Q_stricmp( COM_FileExtension( t->filenames[i] ), "avi" )) continue; - COM_FileBase( t->filenames[i], matchbuf ); + COM_FileBase( t->filenames[i], matchbuf, sizeof( matchbuf )); Con_Printf( "%16s\n", matchbuf ); nummovies++; } @@ -324,7 +323,7 @@ qboolean Cmd_GetMusicList( const char *s, char *completedname, int length ) t = FS_Search( va( "media/%s*.*", s ), true, false ); if( !t ) return false; - COM_FileBase( t->filenames[0], matchbuf ); + COM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf )); if( completedname && length ) Q_strncpy( completedname, matchbuf, length ); if( t->numfilenames == 1 ) return true; @@ -336,7 +335,7 @@ qboolean Cmd_GetMusicList( const char *s, char *completedname, int length ) if( Q_stricmp( ext, "wav" ) && Q_stricmp( ext, "mp3" )) continue; - COM_FileBase( t->filenames[i], matchbuf ); + COM_FileBase( t->filenames[i], matchbuf, sizeof( matchbuf )); Con_Printf( "%16s\n", matchbuf ); numtracks++; } @@ -372,7 +371,7 @@ qboolean Cmd_GetSavesList( const char *s, char *completedname, int length ) t = FS_Search( va( DEFAULT_SAVE_DIRECTORY "%s*.sav", s ), true, true ); // lookup only in gamedir if( !t ) return false; - COM_FileBase( t->filenames[0], matchbuf ); + COM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf )); if( completedname && length ) Q_strncpy( completedname, matchbuf, length ); if( t->numfilenames == 1 ) return true; @@ -382,7 +381,7 @@ qboolean Cmd_GetSavesList( const char *s, char *completedname, int length ) if( Q_stricmp( COM_FileExtension( t->filenames[i] ), "sav" )) continue; - COM_FileBase( t->filenames[i], matchbuf ); + COM_FileBase( t->filenames[i], matchbuf, sizeof( matchbuf )); Con_Printf( "%16s\n", matchbuf ); numsaves++; } @@ -419,7 +418,7 @@ qboolean Cmd_GetConfigList( const char *s, char *completedname, int length ) t = FS_Search( va( "%s*.cfg", s ), true, false ); if( !t ) return false; - COM_FileBase( t->filenames[0], matchbuf ); + COM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf )); if( completedname && length ) Q_strncpy( completedname, matchbuf, length ); if( t->numfilenames == 1 ) return true; @@ -429,7 +428,7 @@ qboolean Cmd_GetConfigList( const char *s, char *completedname, int length ) if( Q_stricmp( COM_FileExtension( t->filenames[i] ), "cfg" )) continue; - COM_FileBase( t->filenames[i], matchbuf ); + COM_FileBase( t->filenames[i], matchbuf, sizeof( matchbuf )); Con_Printf( "%16s\n", matchbuf ); numconfigs++; } @@ -519,7 +518,7 @@ qboolean Cmd_GetItemsList( const char *s, char *completedname, int length ) t = FS_Search( va( "%s/%s*.txt", clgame.itemspath, s ), true, false ); if( !t ) return false; - COM_FileBase( t->filenames[0], matchbuf ); + COM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf )); if( completedname && length ) Q_strncpy( completedname, matchbuf, length ); if( t->numfilenames == 1 ) return true; @@ -529,7 +528,7 @@ qboolean Cmd_GetItemsList( const char *s, char *completedname, int length ) if( Q_stricmp( COM_FileExtension( t->filenames[i] ), "txt" )) continue; - COM_FileBase( t->filenames[i], matchbuf ); + COM_FileBase( t->filenames[i], matchbuf, sizeof( matchbuf )); Con_Printf( "%16s\n", matchbuf ); numitems++; } @@ -571,7 +570,7 @@ qboolean Cmd_GetKeysList( const char *s, char *completedname, int length ) const char *keyname = Key_KeynumToString( i ); if(( *s == '*' ) || !Q_strnicmp( keyname, s, len)) - Q_strcpy( keys[numkeys++], keyname ); + Q_strncpy( keys[numkeys++], keyname, sizeof( keys[0] )); } if( !numkeys ) return false; @@ -711,7 +710,7 @@ qboolean Cmd_GetCustomList( const char *s, char *completedname, int length ) t = FS_Search( va( "%s*.hpk", s ), true, false ); if( !t ) return false; - COM_FileBase( t->filenames[0], matchbuf ); + COM_FileBase( t->filenames[0], matchbuf, sizeof( matchbuf )); if( completedname && length ) Q_strncpy( completedname, matchbuf, length ); if( t->numfilenames == 1 ) return true; @@ -721,7 +720,7 @@ qboolean Cmd_GetCustomList( const char *s, char *completedname, int length ) if( Q_stricmp( COM_FileExtension( t->filenames[i] ), "hpk" )) continue; - COM_FileBase( t->filenames[i], matchbuf ); + COM_FileBase( t->filenames[i], matchbuf, sizeof( matchbuf )); Con_Printf( "%16s\n", matchbuf ); numitems++; } @@ -765,7 +764,7 @@ qboolean Cmd_GetGamesList( const char *s, char *completedname, int length ) for( i = 0, numgamedirs = 0; i < FI->numgames; i++ ) { if(( *s == '*' ) || !Q_strnicmp( FI->games[i]->gamefolder, s, len)) - Q_strcpy( gamedirs[numgamedirs++], FI->games[i]->gamefolder ); + Q_strncpy( gamedirs[numgamedirs++], FI->games[i]->gamefolder, sizeof( gamedirs[0] )); } if( !numgamedirs ) return false; @@ -826,7 +825,7 @@ qboolean Cmd_GetCDList( const char *s, char *completedname, int length ) for( i = 0, numcdcommands = 0; i < 8; i++ ) { if(( *s == '*' ) || !Q_strnicmp( cd_command[i], s, len)) - Q_strcpy( cdcommands[numcdcommands++], cd_command[i] ); + Q_strncpy( cdcommands[numcdcommands++], cd_command[i], sizeof( cdcommands[0] )); } if( !numcdcommands ) return false; @@ -861,6 +860,7 @@ qboolean Cmd_CheckMapsList_R( qboolean fRefresh, qboolean onlyingamedir ) byte buf[MAX_SYSPATH]; string mpfilter; char *buffer; + size_t buffersize; string result; int i, size; search_t *t; @@ -883,7 +883,8 @@ qboolean Cmd_CheckMapsList_R( qboolean fRefresh, qboolean onlyingamedir ) return false; } - buffer = Mem_Calloc( host.mempool, t->numfilenames * 2 * sizeof( result )); + buffersize = t->numfilenames * 2 * sizeof( result ); + buffer = Mem_Calloc( host.mempool, buffersize ); use_filter = COM_CheckStringEmpty( GI->mp_filter ) ? true : false; for( i = 0; i < t->numfilenames; i++ ) @@ -899,7 +900,7 @@ qboolean Cmd_CheckMapsList_R( qboolean fRefresh, qboolean onlyingamedir ) continue; f = FS_Open( t->filenames[i], "rb", onlyingamedir ); - COM_FileBase( t->filenames[i], mapname ); + COM_FileBase( t->filenames[i], mapname, sizeof( mapname )); if( f ) { @@ -923,8 +924,7 @@ qboolean Cmd_CheckMapsList_R( qboolean fRefresh, qboolean onlyingamedir ) lumplen = entities.filelen; Q_strncpy( entfilename, t->filenames[i], sizeof( entfilename )); - COM_StripExtension( entfilename ); - COM_DefaultExtension( entfilename, ".ent" ); + COM_ReplaceExtension( entfilename, ".ent", sizeof( entfilename )); ents = (char *)FS_LoadFile( entfilename, NULL, true ); if( !ents && lumplen >= 10 ) @@ -969,8 +969,8 @@ qboolean Cmd_CheckMapsList_R( qboolean fRefresh, qboolean onlyingamedir ) if( num_spawnpoints ) { // format: mapname "maptitle"\n - Q_sprintf( result, "%s \"%s\"\n", mapname, message ); - Q_strcat( buffer, result ); // add new string + Q_snprintf( result, sizeof( result ), "%s \"%s\"\n", mapname, message ); + Q_strncat( buffer, result, buffersize ); // add new string } } } @@ -1029,6 +1029,7 @@ autocomplete_list_t cmd_list[] = { "play", 1, Cmd_GetSoundList }, { "map", 1, Cmd_GetMapList }, { "cd", 1, Cmd_GetCDList }, +{ "mp3", 1, Cmd_GetCDList }, { NULL }, // termiantor }; @@ -1041,10 +1042,14 @@ compare first argument with string */ static qboolean Cmd_CheckName( const char *name ) { - if( !Q_stricmp( Cmd_Argv( 0 ), name )) + const char *p = Cmd_Argv( 0 ); + + if( !Q_stricmp( p, name )) return true; - if( !Q_stricmp( Cmd_Argv( 0 ), va( "\\%s", name ))) + + if( p[0] == '\\' && !Q_stricmp( &p[1], name )) return true; + return false; } diff --git a/engine/common/crashhandler.c b/engine/common/crashhandler.c index d47e5489..cc94c5a3 100644 --- a/engine/common/crashhandler.c +++ b/engine/common/crashhandler.c @@ -34,6 +34,7 @@ Crash handler, called from system #include #include #include +#include #ifndef XASH_SDL typedef ULONG_PTR DWORD_PTR, *PDWORD_PTR; @@ -189,6 +190,64 @@ static void Sys_StackTrace( PEXCEPTION_POINTERS pInfo ) SymCleanup( process ); } + +static void Sys_GetProcessName( char *processName, size_t bufferSize ) +{ + char fullpath[MAX_PATH]; + + GetModuleBaseName( GetCurrentProcess(), NULL, fullpath, sizeof( fullpath ) - 1 ); + COM_FileBase( fullpath, processName, bufferSize ); +} + +static void Sys_GetMinidumpFileName( const char *processName, char *mdmpFileName, size_t bufferSize ) +{ + time_t currentUtcTime = time( NULL ); + struct tm *currentLocalTime = localtime( ¤tUtcTime ); + + Q_snprintf( mdmpFileName, bufferSize, "%s_%s_crash_%d%.2d%.2d_%.2d%.2d%.2d.mdmp", + processName, + Q_buildcommit(), + currentLocalTime->tm_year + 1900, + currentLocalTime->tm_mon + 1, + currentLocalTime->tm_mday, + currentLocalTime->tm_hour, + currentLocalTime->tm_min, + currentLocalTime->tm_sec); +} + +static qboolean Sys_WriteMinidump(PEXCEPTION_POINTERS exceptionInfo, MINIDUMP_TYPE minidumpType) +{ + HRESULT errorCode; + string processName; + string mdmpFileName; + MINIDUMP_EXCEPTION_INFORMATION minidumpInfo; + + Sys_GetProcessName( processName, sizeof( processName )); + Sys_GetMinidumpFileName( processName, mdmpFileName, sizeof( mdmpFileName )); + + SetLastError( NOERROR ); + HANDLE fileHandle = CreateFile( + mdmpFileName, GENERIC_WRITE, FILE_SHARE_WRITE, + NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + errorCode = HRESULT_FROM_WIN32( GetLastError( )); + if( !SUCCEEDED( errorCode )) { + CloseHandle( fileHandle ); + return false; + } + + minidumpInfo.ThreadId = GetCurrentThreadId(); + minidumpInfo.ExceptionPointers = exceptionInfo; + minidumpInfo.ClientPointers = FALSE; + + qboolean status = MiniDumpWriteDump( + GetCurrentProcess(), GetCurrentProcessId(), fileHandle, + minidumpType, &minidumpInfo, NULL, NULL); + + CloseHandle( fileHandle ); + return status; +} + #endif /* DBGHELP */ LPTOP_LEVEL_EXCEPTION_FILTER oldFilter; @@ -210,6 +269,26 @@ static long _stdcall Sys_Crash( PEXCEPTION_POINTERS pInfo ) CL_Crashed(); // tell client about crash else host.status = HOST_CRASHED; +#if DBGHELP + if( Sys_CheckParm( "-minidumps" )) + { + int minidumpFlags = ( + MiniDumpWithDataSegs | + MiniDumpWithCodeSegs | + MiniDumpWithHandleData | + MiniDumpWithFullMemory | + MiniDumpWithFullMemoryInfo | + MiniDumpWithIndirectlyReferencedMemory | + MiniDumpWithThreadInfo | + MiniDumpWithModuleHeaders); + + if( !Sys_WriteMinidump( pInfo, (MINIDUMP_TYPE)minidumpFlags )) { + // fallback method, create minidump with minimal info in it + Sys_WriteMinidump( pInfo, MiniDumpWithDataSegs ); + } + } +#endif + if( host_developer.value <= 0 ) { // no reason to call debugger in release build - just exit diff --git a/engine/common/cvar.c b/engine/common/cvar.c index 397a5808..657a1dc7 100644 --- a/engine/common/cvar.c +++ b/engine/common/cvar.c @@ -39,6 +39,10 @@ static cvar_filter_quirks_t cvar_filter_quirks[] = "ricochet", "r_drawviewmodel", }, + { + "dod", + "cl_dodmusic" // Day of Defeat Beta 1.3 cvar + }, }; static cvar_filter_quirks_t *cvar_active_filter_quirks = NULL; @@ -95,10 +99,16 @@ Cvar_BuildAutoDescription build cvar auto description that based on the setup flags ============ */ -const char *Cvar_BuildAutoDescription( int flags ) +const char *Cvar_BuildAutoDescription( const char *szName, int flags ) { static char desc[256]; + if( FBitSet( flags, FCVAR_GLCONFIG )) + { + Q_snprintf( desc, sizeof( desc ), CVAR_GLCONFIG_DESCRIPTION, szName ); + return desc; + } + desc[0] = '\0'; if( FBitSet( flags, FCVAR_EXTDLL )) @@ -469,6 +479,23 @@ convar_t *Cvar_Get( const char *name, const char *value, int flags, const char * return var; } +/* +============ +Cvar_Getf +============ +*/ +convar_t *Cvar_Getf( const char *var_name, int flags, const char *description, const char *format, ... ) +{ + char value[MAX_VA_STRING]; + va_list args; + + va_start( args, format ); + Q_vsnprintf( value, sizeof( value ), format, args ); + va_end( args ); + + return Cvar_Get( var_name, value, flags, description ); +} + /* ============ Cvar_RegisterVariable @@ -1063,7 +1090,7 @@ void Cvar_Toggle_f( void ) v = !Cvar_VariableInteger( Cmd_Argv( 1 )); - Cvar_Set( Cmd_Argv( 1 ), va( "%i", v )); + Cvar_Set( Cmd_Argv( 1 ), v ? "1" : "0" ); } /* @@ -1092,8 +1119,8 @@ void Cvar_Set_f( void ) len = Q_strlen( Cmd_Argv(i) + 1 ); if( l + len >= MAX_CMD_TOKENS - 2 ) break; - Q_strcat( combined, Cmd_Argv( i )); - if( i != c-1 ) Q_strcat( combined, " " ); + Q_strncat( combined, Cmd_Argv( i ), sizeof( combined )); + if( i != c-1 ) Q_strncat( combined, " ", sizeof( combined )); l += len; } @@ -1145,7 +1172,6 @@ void Cvar_List_f( void ) { convar_t *var; const char *match = NULL; - char *value; int count = 0; size_t matchlen = 0; @@ -1157,6 +1183,8 @@ void Cvar_List_f( void ) for( var = cvar_vars; var; var = var->next ) { + char value[MAX_VA_STRING]; + if( var->name[0] == '@' ) continue; // never shows system cvars @@ -1164,12 +1192,12 @@ void Cvar_List_f( void ) continue; if( Q_colorstr( var->string )) - value = va( "\"%s\"", var->string ); - else value = va( "\"^2%s^7\"", var->string ); + Q_snprintf( value, sizeof( value ), "\"%s\"", var->string ); + else Q_snprintf( value, sizeof( value ), "\"^2%s^7\"", var->string ); if( FBitSet( var->flags, FCVAR_EXTENDED|FCVAR_ALLOCATED )) Con_Printf( " %-*s %s ^3%s^7\n", 32, var->name, value, var->desc ); - else Con_Printf( " %-*s %s ^3%s^7\n", 32, var->name, value, Cvar_BuildAutoDescription( var->flags )); + else Con_Printf( " %-*s %s ^3%s^7\n", 32, var->name, value, Cvar_BuildAutoDescription( var->name, var->flags )); count++; } diff --git a/engine/common/cvar.h b/engine/common/cvar.h index b234e919..aea979d3 100644 --- a/engine/common/cvar.h +++ b/engine/common/cvar.h @@ -63,12 +63,13 @@ cvar_t *Cvar_GetList( void ); convar_t *Cvar_FindVarExt( const char *var_name, int ignore_group ); void Cvar_RegisterVariable( convar_t *var ); convar_t *Cvar_Get( const char *var_name, const char *value, int flags, const char *description ); +convar_t *Cvar_Getf( const char *var_name, int flags, const char *description, const char *format, ... ) _format( 4 ); void Cvar_LookupVars( int checkbit, void *buffer, void *ptr, setpair_t callback ); void Cvar_FullSet( const char *var_name, const char *value, int flags ); void Cvar_DirectSet( convar_t *var, const char *value ); void Cvar_Set( const char *var_name, const char *value ); void Cvar_SetValue( const char *var_name, float value ); -const char *Cvar_BuildAutoDescription( int flags ); +const char *Cvar_BuildAutoDescription( const char *szName, int flags ); float Cvar_VariableValue( const char *var_name ); int Cvar_VariableInteger( const char *var_name ); const char *Cvar_VariableString( const char *var_name ); diff --git a/engine/common/dedicated.c b/engine/common/dedicated.c index a7d4ba8b..39ea7c4a 100644 --- a/engine/common/dedicated.c +++ b/engine/common/dedicated.c @@ -70,16 +70,6 @@ qboolean CL_DisableVisibility( void ) return false; } -qboolean CL_IsBackgroundDemo( void ) -{ - return false; -} - -qboolean CL_IsBackgroundMap( void ) -{ - return false; -} - void CL_Init( void ) { diff --git a/engine/common/filesystem_engine.c b/engine/common/filesystem_engine.c index 6d3ff03e..668a44a4 100644 --- a/engine/common/filesystem_engine.c +++ b/engine/common/filesystem_engine.c @@ -1,6 +1,9 @@ /* filesystem.c - game filesystem based on DP fs +Copyright (C) 2003-2006 Mathieu Olivier +Copyright (C) 2000-2007 DarkPlaces contributors Copyright (C) 2007 Uncle Mike +Copyright (C) 2015-2023 Xash3D FWGS contributors 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 @@ -101,10 +104,7 @@ 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" ); diff --git a/engine/common/gamma.c b/engine/common/gamma.c index 87fd772c..aac4a6c4 100644 --- a/engine/common/gamma.c +++ b/engine/common/gamma.c @@ -20,7 +20,6 @@ GNU General Public License for more details. //----------------------------------------------------------------------------- // Gamma conversion support //----------------------------------------------------------------------------- -static byte texgammatable[256]; // palette is sent through this to convert to screen gamma static byte lightgammatable[256]; static int lineargammatable[1024]; static int screengammatable[1024]; @@ -60,13 +59,6 @@ void BuildGammaTable( float lightgamma, float brightness ) lightgammatable[i] = bound( 0, inf, 255 ); } - for( i = 0; i < 256; i++ ) - { - f = 255.0f * pow(( float )i / 255.0f, TEXGAMMA ); - inf = (int)(f + 0.5f); - texgammatable[i] = bound( 0, inf, 255 ); - } - for( i = 0; i < 1024; i++ ) { // convert from screen gamma space to linear space @@ -84,11 +76,3 @@ byte LightToTexGamma( byte b ) else return lightgammatable[b]; } - -byte TextureToGamma( byte b ) -{ - if( FBitSet( host.features, ENGINE_LINEAR_GAMMA_SPACE )) - return b; - else - return texgammatable[b]; -} diff --git a/engine/common/host.c b/engine/common/host.c index c3297c96..ddf488d5 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -114,6 +114,7 @@ void Sys_PrintUsage( void ) #if XASH_WIN32 O("-noavi ","disable AVI support") O("-nointro ","disable intro video") + O("-minidumps ","enable writing minidumps when game crashed") #endif // XASH_WIN32 #if XASH_DOS @@ -345,10 +346,10 @@ void Host_ChangeGame_f( void ) } else { - const char *arg1 = va( "%s", Cmd_Argv( 1 )); - const char *arg2 = va( "change game to '%s'", FI->games[i]->title ); + char finalmsg[MAX_VA_STRING]; - Host_NewInstance( arg1, arg2 ); + Q_snprintf( finalmsg, sizeof( finalmsg ), "change game to '%s'", FI->games[i]->title ); + Host_NewInstance( Cmd_Argv( 1 ), finalmsg ); } } @@ -383,9 +384,11 @@ void Host_Exec_f( void ) "pyro.cfg", "spy.cfg", "engineer.cfg", "civilian.cfg" }; int i; + char temp[MAX_VA_STRING]; qboolean allow = false; - unprivilegedWhitelist[0] = va( "%s.cfg", clgame.mapname ); + Q_snprintf( temp, sizeof( temp ), "%s.cfg", clgame.mapname ); + unprivilegedWhitelist[0] = temp; for( i = 0; i < ARRAYSIZE( unprivilegedWhitelist ); i++ ) { @@ -412,7 +415,7 @@ void Host_Exec_f( void ) } Q_strncpy( cfgpath, arg, sizeof( cfgpath )); - COM_DefaultExtension( cfgpath, ".cfg" ); // append as default + COM_DefaultExtension( cfgpath, ".cfg", sizeof( cfgpath )); // append as default f = FS_LoadFile( cfgpath, &len, false ); if( !f ) @@ -506,7 +509,7 @@ qboolean Host_RegisterDecal( const char *name, int *count ) if( !COM_CheckString( name )) return 0; - COM_FileBase( name, shortname ); + COM_FileBase( name, shortname, sizeof( shortname )); for( i = 1; i < MAX_DECALS && host.draw_decals[i][0]; i++ ) { @@ -730,7 +733,7 @@ void GAME_EXPORT Host_Error( const char *error, ... ) va_list argptr; va_start( argptr, error ); - Q_vsprintf( hosterror1, error, argptr ); + Q_vsnprintf( hosterror1, sizeof( hosterror1 ), error, argptr ); va_end( argptr ); CL_WriteMessageHistory (); // before Q_error call @@ -766,7 +769,7 @@ void GAME_EXPORT Host_Error( const char *error, ... ) recursive = true; Q_strncpy( hosterror2, hosterror1, MAX_SYSPATH ); host.errorframe = host.framecount; // to avoid multply calls per frame - Q_sprintf( host.finalmsg, "Server crashed: %s", hosterror1 ); + Q_snprintf( host.finalmsg, sizeof( host.finalmsg ), "Server crashed: %s", hosterror1 ); // clearing cmd buffer to prevent execute any commands COM_InitHostState(); @@ -826,7 +829,7 @@ void Host_Userconfigd_f( void ) for( i = 0; i < t->numfilenames; i++ ) { - Cbuf_AddText( va("exec %s\n", t->filenames[i] ) ); + Cbuf_AddTextf( "exec %s\n", t->filenames[i] ); } Mem_Free( t ); @@ -1015,6 +1018,12 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha #if TARGET_OS_IOS const char *IOS_GetDocsDir(); Q_strncpy( host.rootdir, IOS_GetDocsDir(), sizeof(host.rootdir) ); +#elif XASH_PSVITA + if ( !PSVita_GetBasePath( host.rootdir, sizeof( host.rootdir ) ) ) + { + Sys_Error( "couldn't find xash3d data directory" ); + host.rootdir[0] = 0; + } #elif (XASH_SDL == 2) && !XASH_NSWITCH // GetBasePath not impl'd in switch-sdl2 char *szBasePath; @@ -1166,10 +1175,10 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa 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 " XASH_VERSION " %s %s %s", Q_buildnum(), 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)" ); + build = Cvar_Getf( "buildnum", FCVAR_READ_ONLY, "returns a current build number", "%i", Q_buildnum_compat()); + ver = Cvar_Getf( "ver", FCVAR_READ_ONLY, "shows an engine version", "%i/%s (hw build %i)", PROTOCOL_VERSION, XASH_COMPAT_VERSION, Q_buildnum_compat()); + Cvar_Getf( "host_ver", FCVAR_READ_ONLY, "detailed info about this build", "%i " XASH_VERSION " %s %s %s", Q_buildnum(), Q_buildos(), Q_buildarch(), Q_buildcommit()); + Cvar_Getf( "host_lowmemorymode", FCVAR_READ_ONLY, "indicates if engine compiled for low RAM consumption (0 - normal, 1 - low engine limits, 2 - low protocol limits)", "%i", XASH_LOW_MEMORY ); Mod_Init(); NET_Init(); @@ -1204,6 +1213,8 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa } else Cmd_AddRestrictedCommand( "minimize", Host_Minimize_f, "minimize main window to tray" ); + HPAK_CheckIntegrity( CUSTOM_RES_PATH ); + host.errorframe = 0; // post initializations @@ -1214,7 +1225,7 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa Wcon_ShowConsole( false ); // hide console #endif // execute startup config and cmdline - Cbuf_AddText( va( "exec %s.rc\n", SI.rcName )); + Cbuf_AddTextf( "exec %s.rc\n", SI.rcName ); Cbuf_Execute(); if( !host.config_executed ) { @@ -1248,7 +1259,7 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa // execute server.cfg after commandline // so we have a chance to set servercfgfile - Cbuf_AddText( va( "exec %s\n", Cvar_VariableString( "servercfgfile" ))); + Cbuf_AddTextf( "exec %s\n", Cvar_VariableString( "servercfgfile" )); Cbuf_Execute(); } diff --git a/engine/common/hpak.c b/engine/common/hpak.c index a949fe5b..f7d882d2 100644 --- a/engine/common/hpak.c +++ b/engine/common/hpak.c @@ -29,10 +29,10 @@ typedef struct hash_pack_queue_s struct hash_pack_queue_s *next; } hash_pack_queue_t; -convar_t *hpk_maxsize; -hash_pack_queue_t *gp_hpak_queue = NULL; -hpak_header_t hash_pack_header; -hpak_info_t hash_pack_info; +static CVAR_DEFINE_AUTO( hpk_maxsize, "4", FCVAR_ARCHIVE, "set limit by size for all HPK-files ( 0 - unlimited )" ); +static hash_pack_queue_t *gp_hpak_queue = NULL; +static hpak_header_t hash_pack_header; +static hpak_info_t hash_pack_info; const char *HPAK_TypeFromIndex( int type ) { @@ -101,7 +101,6 @@ void HPAK_CreatePak( const char *filename, resource_t *pResource, byte *pData, f byte md5[16]; file_t *fout; MD5Context_t ctx; - dresource_t dresource; if( !COM_CheckString( filename )) return; @@ -110,7 +109,7 @@ void HPAK_CreatePak( const char *filename, resource_t *pResource, byte *pData, f return; Q_strncpy( pakname, filename, sizeof( pakname )); - COM_ReplaceExtension( pakname, ".hpk" ); + COM_ReplaceExtension( pakname, ".hpk", sizeof( pakname )); Con_Printf( "creating HPAK %s.\n", pakname ); @@ -258,7 +257,7 @@ void HPAK_AddLump( qboolean bUseQueue, const char *name, resource_t *pResource, } Q_strncpy( srcname, name, sizeof( srcname )); - COM_ReplaceExtension( srcname, ".hpk" ); + COM_ReplaceExtension( srcname, ".hpk", sizeof( srcname )); file_src = FS_Open( srcname, "rb", true ); @@ -270,7 +269,7 @@ void HPAK_AddLump( qboolean bUseQueue, const char *name, resource_t *pResource, } Q_strncpy( dstname, srcname, sizeof( dstname )); - COM_ReplaceExtension( dstname, ".hp2" ); + COM_ReplaceExtension( dstname, ".hp2", sizeof( dstname )); file_dst = FS_Open( dstname, "wb", true ); @@ -377,7 +376,7 @@ void HPAK_AddLump( qboolean bUseQueue, const char *name, resource_t *pResource, FS_Rename( dstname, srcname ); } -static qboolean HPAK_Validate( const char *filename, qboolean quiet ) +static qboolean HPAK_Validate( const char *filename, qboolean quiet, qboolean delete ) { file_t *f; hpak_lump_t *dataDir; @@ -396,7 +395,7 @@ static qboolean HPAK_Validate( const char *filename, qboolean quiet ) return true; Q_strncpy( pakname, filename, sizeof( pakname )); - COM_ReplaceExtension( pakname, ".hpk" ); + COM_ReplaceExtension( pakname, ".hpk", sizeof( pakname )); f = FS_Open( pakname, "rb", true ); if( !f ) @@ -412,6 +411,7 @@ static qboolean HPAK_Validate( const char *filename, qboolean quiet ) { Con_DPrintf( S_ERROR "HPAK_ValidatePak: %s does not have a valid HPAK header.\n", pakname ); FS_Close( f ); + if( delete ) FS_Delete( pakname ); return false; } @@ -422,6 +422,7 @@ static qboolean HPAK_Validate( const char *filename, qboolean quiet ) { Con_DPrintf( S_ERROR "HPAK_ValidatePak: %s has too many lumps %u.\n", pakname, num_lumps ); FS_Close( f ); + if( delete ) FS_Delete( pakname ); return false; } @@ -439,7 +440,8 @@ static qboolean HPAK_Validate( const char *filename, qboolean quiet ) // odd max size Con_DPrintf( S_ERROR "HPAK_ValidatePak: lump %i has invalid size %s\n", i, Q_pretifymem( dataDir[i].disksize, 2 )); Mem_Free( dataDir ); - FS_Close(f); + FS_Close( f ); + if( delete ) FS_Delete( pakname ); return false; } @@ -454,8 +456,11 @@ static qboolean HPAK_Validate( const char *filename, qboolean quiet ) pRes = &dataDir[i].resource; - Con_Printf( "%i: %s %s %s: ", i, HPAK_TypeFromIndex( pRes->type ), - Q_pretifymem( pRes->nDownloadSize, 2 ), pRes->szFileName ); + if( !quiet ) + { + Con_Printf( "%i: %s %s %s: ", i, HPAK_TypeFromIndex( pRes->type ), + Q_pretifymem( pRes->nDownloadSize, 2 ), pRes->szFileName ); + } if( memcmp( md5, pRes->rgucMD5_hash, 0x10 )) { @@ -465,6 +470,7 @@ static qboolean HPAK_Validate( const char *filename, qboolean quiet ) Mem_Free( dataPak ); Mem_Free( dataDir ); FS_Close( f ); + if( delete ) FS_Delete( pakname ); return false; } else Con_DPrintf( S_ERROR "failed\n" ); @@ -483,11 +489,6 @@ static qboolean HPAK_Validate( const char *filename, qboolean quiet ) return true; } -void HPAK_ValidatePak( const char *filename ) -{ - HPAK_Validate( filename, true ); -} - void HPAK_CheckIntegrity( const char *filename ) { string pakname; @@ -496,9 +497,9 @@ void HPAK_CheckIntegrity( const char *filename ) return; Q_strncpy( pakname, filename, sizeof( pakname )); - COM_ReplaceExtension( pakname, ".hpk" ); + COM_ReplaceExtension( pakname, ".hpk", sizeof( pakname )); - HPAK_ValidatePak( pakname ); + HPAK_Validate( pakname, true, true ); } void HPAK_CheckSize( const char *filename ) @@ -506,19 +507,19 @@ void HPAK_CheckSize( const char *filename ) string pakname; int maxsize; - maxsize = hpk_maxsize->value; + maxsize = hpk_maxsize.value; if( maxsize <= 0 ) return; if( !COM_CheckString( filename ) ) return; Q_strncpy( pakname, filename, sizeof( pakname )); - COM_ReplaceExtension( pakname, ".hpk" ); + COM_ReplaceExtension( pakname, ".hpk", sizeof( pakname )); if( FS_FileSize( pakname, false ) > ( maxsize * 1048576 )) { - Con_Printf( "Server: Size of %s > %f MB, deleting.\n", filename, hpk_maxsize->value ); - Log_Printf( "Server: Size of %s > %f MB, deleting.\n", filename, hpk_maxsize->value ); + Con_Printf( "Server: Size of %s > %f MB, deleting.\n", filename, hpk_maxsize.value ); + Log_Printf( "Server: Size of %s > %f MB, deleting.\n", filename, hpk_maxsize.value ); FS_Delete( filename ); } } @@ -546,7 +547,7 @@ qboolean HPAK_ResourceForHash( const char *filename, byte *hash, resource_t *pRe } Q_strncpy( pakname, filename, sizeof( pakname )); - COM_ReplaceExtension( pakname, ".hpk" ); + COM_ReplaceExtension( pakname, ".hpk", sizeof( pakname )); f = FS_Open( pakname, "rb", true ); if( !f ) return false; @@ -594,7 +595,7 @@ static qboolean HPAK_ResourceForIndex( const char *filename, int index, resource return false; Q_strncpy( pakname, filename, sizeof( pakname )); - COM_ReplaceExtension( pakname, ".hpk" ); + COM_ReplaceExtension( pakname, ".hpk", sizeof( pakname )); f = FS_Open( pakname, "rb", true ); if( !f ) @@ -680,7 +681,7 @@ qboolean HPAK_GetDataPointer( const char *filename, resource_t *pResource, byte } Q_strncpy( pakname, filename, sizeof( pakname )); - COM_ReplaceExtension( pakname, ".hpk" ); + COM_ReplaceExtension( pakname, ".hpk", sizeof( pakname )); f = FS_Open( pakname, "rb", true ); if( !f ) return false; @@ -763,7 +764,7 @@ void HPAK_RemoveLump( const char *name, resource_t *pResource ) HPAK_FlushHostQueue(); Q_strncpy( read_path, name, sizeof( read_path )); - COM_ReplaceExtension( read_path, ".hpk" ); + COM_ReplaceExtension( read_path, ".hpk", sizeof( read_path )); file_src = FS_Open( read_path, "rb", true ); if( !file_src ) @@ -773,7 +774,7 @@ void HPAK_RemoveLump( const char *name, resource_t *pResource ) } Q_strncpy( save_path, read_path, sizeof( save_path )); - COM_ReplaceExtension( save_path, ".hp2" ); + COM_ReplaceExtension( save_path, ".hp2", sizeof( save_path )); file_dst = FS_Open( save_path, "wb", true ); if( !file_dst ) @@ -892,7 +893,7 @@ void HPAK_List_f( void ) HPAK_FlushHostQueue(); Q_strncpy( pakname, Cmd_Argv( 1 ), sizeof( pakname )); - COM_ReplaceExtension( pakname, ".hpk" ); + COM_ReplaceExtension( pakname, ".hpk", sizeof( pakname )); Con_Printf( "Contents for %s.\n", pakname ); f = FS_Open( pakname, "rb", true ); @@ -937,7 +938,7 @@ void HPAK_List_f( void ) for( nCurrent = 0; nCurrent < directory.count; nCurrent++ ) { entry = &directory.entries[nCurrent]; - COM_FileBase( entry->resource.szFileName, lumpname ); + COM_FileBase( entry->resource.szFileName, lumpname, sizeof( lumpname )); type = HPAK_TypeFromIndex( entry->resource.type ); size = Q_memprint( entry->resource.nDownloadSize ); @@ -983,7 +984,7 @@ void HPAK_Extract_f( void ) HPAK_FlushHostQueue(); Q_strncpy( pakname, Cmd_Argv( 1 ), sizeof( pakname )); - COM_ReplaceExtension( pakname, ".hpk" ); + COM_ReplaceExtension( pakname, ".hpk", sizeof( pakname )); Con_Printf( "Contents for %s.\n", pakname ); f = FS_Open( pakname, "rb", true ); @@ -1032,7 +1033,7 @@ void HPAK_Extract_f( void ) if( nIndex != -1 && nIndex != nCurrent ) continue; - COM_FileBase( entry->resource.szFileName, lumpname ); + COM_FileBase( entry->resource.szFileName, lumpname, sizeof( lumpname ) ); type = HPAK_TypeFromIndex( entry->resource.type ); size = Q_memprint( entry->resource.nDownloadSize ); @@ -1090,7 +1091,7 @@ void HPAK_Validate_f( void ) return; } - HPAK_Validate( Cmd_Argv( 1 ), false ); + HPAK_Validate( Cmd_Argv( 1 ), false, false ); } void HPAK_Init( void ) @@ -1099,7 +1100,7 @@ void HPAK_Init( void ) Cmd_AddRestrictedCommand( "hpkremove", HPAK_Remove_f, "remove specified file from HPK-file" ); Cmd_AddRestrictedCommand( "hpkval", HPAK_Validate_f, "validate specified HPK-file" ); Cmd_AddRestrictedCommand( "hpkextract", HPAK_Extract_f, "extract all lumps from specified HPK-file" ); - hpk_maxsize = Cvar_Get( "hpk_maxsize", "0", FCVAR_ARCHIVE, "set limit by size for all HPK-files ( 0 - unlimited )" ); + Cvar_RegisterVariable( &hpk_maxsize ); gp_hpak_queue = NULL; } diff --git a/engine/common/identification.c b/engine/common/identification.c index cc5e50ba..a7d6ff7f 100644 --- a/engine/common/identification.c +++ b/engine/common/identification.c @@ -35,7 +35,7 @@ static bloomfilter_t id; #define bf64_mask ((1U<<6)-1) -bloomfilter_t BloomFilter_Process( const char *buffer, int size ) +static bloomfilter_t BloomFilter_Process( const char *buffer, int size ) { dword crc32; bloomfilter_t value = 0; @@ -55,12 +55,12 @@ bloomfilter_t BloomFilter_Process( const char *buffer, int size ) return value; } -bloomfilter_t BloomFilter_ProcessStr( const char *buffer ) +static bloomfilter_t BloomFilter_ProcessStr( const char *buffer ) { return BloomFilter_Process( buffer, Q_strlen( buffer ) ); } -uint BloomFilter_Weight( bloomfilter_t value ) +static uint BloomFilter_Weight( bloomfilter_t value ) { int weight = 0; @@ -77,7 +77,7 @@ uint BloomFilter_Weight( bloomfilter_t value ) return weight; } -qboolean BloomFilter_ContainsString( bloomfilter_t filter, const char *str ) +static qboolean BloomFilter_ContainsString( bloomfilter_t filter, const char *str ) { bloomfilter_t value = BloomFilter_ProcessStr( str ); @@ -94,9 +94,9 @@ IDENTIFICATION #define MAXBITS_GEN 30 #define MAXBITS_CHECK MAXBITS_GEN + 6 -qboolean ID_ProcessFile( bloomfilter_t *value, const char *path ); +static qboolean ID_ProcessFile( bloomfilter_t *value, const char *path ); -void ID_BloomFilter_f( void ) +static void ID_BloomFilter_f( void ) { bloomfilter_t value = 0; int i; @@ -111,7 +111,7 @@ void ID_BloomFilter_f( void ) // Msg( "%s: %d\n", Cmd_Argv( i ), BloomFilter_ContainsString( value, Cmd_Argv( i ) ) ); } -qboolean ID_VerifyHEX( const char *hex ) +static qboolean ID_VerifyHEX( const char *hex ) { uint chars = 0; char prev = 0; @@ -153,7 +153,7 @@ qboolean ID_VerifyHEX( const char *hex ) return false; } -void ID_VerifyHEX_f( void ) +static void ID_VerifyHEX_f( void ) { if( ID_VerifyHEX( Cmd_Argv( 1 ) ) ) Msg( "Good\n" ); @@ -162,7 +162,7 @@ void ID_VerifyHEX_f( void ) } #if XASH_LINUX -qboolean ID_ProcessCPUInfo( bloomfilter_t *value ) +static qboolean ID_ProcessCPUInfo( bloomfilter_t *value ) { int cpuinfofd = open( "/proc/cpuinfo", O_RDONLY ); char buffer[1024], *pbuf, *pbuf2; @@ -198,7 +198,7 @@ qboolean ID_ProcessCPUInfo( bloomfilter_t *value ) return true; } -qboolean ID_ValidateNetDevice( const char *dev ) +static qboolean ID_ValidateNetDevice( const char *dev ) { const char *prefix = "/sys/class/net"; byte *pfile; @@ -226,7 +226,7 @@ qboolean ID_ValidateNetDevice( const char *dev ) return true; } -int ID_ProcessNetDevices( bloomfilter_t *value ) +static int ID_ProcessNetDevices( bloomfilter_t *value ) { const char *prefix = "/sys/class/net"; DIR *dir; @@ -250,7 +250,7 @@ int ID_ProcessNetDevices( bloomfilter_t *value ) return count; } -int ID_CheckNetDevices( bloomfilter_t value ) +static int ID_CheckNetDevices( bloomfilter_t value ) { const char *prefix = "/sys/class/net"; @@ -278,7 +278,7 @@ int ID_CheckNetDevices( bloomfilter_t value ) return count; } -void ID_TestCPUInfo_f( void ) +static void ID_TestCPUInfo_f( void ) { bloomfilter_t value = 0; @@ -290,7 +290,7 @@ void ID_TestCPUInfo_f( void ) #endif -qboolean ID_ProcessFile( bloomfilter_t *value, const char *path ) +static qboolean ID_ProcessFile( bloomfilter_t *value, const char *path ) { int fd = open( path, O_RDONLY ); char buffer[256]; @@ -317,7 +317,7 @@ qboolean ID_ProcessFile( bloomfilter_t *value, const char *path ) } #if !XASH_WIN32 -int ID_ProcessFiles( bloomfilter_t *value, const char *prefix, const char *postfix ) +static int ID_ProcessFiles( bloomfilter_t *value, const char *prefix, const char *postfix ) { DIR *dir; struct dirent *entry; @@ -337,7 +337,7 @@ int ID_ProcessFiles( bloomfilter_t *value, const char *prefix, const char *postf return count; } -int ID_CheckFiles( bloomfilter_t value, const char *prefix, const char *postfix ) +static int ID_CheckFiles( bloomfilter_t value, const char *prefix, const char *postfix ) { DIR *dir; struct dirent *entry; @@ -360,7 +360,7 @@ int ID_CheckFiles( bloomfilter_t value, const char *prefix, const char *postfix return count; } #else -int ID_GetKeyData( HKEY hRootKey, char *subKey, char *value, LPBYTE data, DWORD cbData ) +static int ID_GetKeyData( HKEY hRootKey, char *subKey, char *value, LPBYTE data, DWORD cbData ) { HKEY hKey; @@ -376,7 +376,7 @@ int ID_GetKeyData( HKEY hRootKey, char *subKey, char *value, LPBYTE data, DWORD RegCloseKey( hKey ); return 1; } -int ID_SetKeyData( HKEY hRootKey, char *subKey, DWORD dwType, char *value, LPBYTE data, DWORD cbData) +static int ID_SetKeyData( HKEY hRootKey, char *subKey, DWORD dwType, char *value, LPBYTE data, DWORD cbData) { HKEY hKey; if( RegCreateKey( hRootKey, subKey, &hKey ) != ERROR_SUCCESS ) @@ -394,7 +394,7 @@ int ID_SetKeyData( HKEY hRootKey, char *subKey, DWORD dwType, char *value, LPBYT #define BUFSIZE 4096 -int ID_RunWMIC(char *buffer, const char *cmdline) +static int ID_RunWMIC(char *buffer, const char *cmdline) { HANDLE g_IN_Rd = NULL; HANDLE g_IN_Wr = NULL; @@ -438,7 +438,7 @@ int ID_RunWMIC(char *buffer, const char *cmdline) return bSuccess; } -int ID_ProcessWMIC( bloomfilter_t *value, const char *cmdline ) +static int ID_ProcessWMIC( bloomfilter_t *value, const char *cmdline ) { char buffer[BUFSIZE], token[BUFSIZE], *pbuf; int count = 0; @@ -458,7 +458,7 @@ int ID_ProcessWMIC( bloomfilter_t *value, const char *cmdline ) return count; } -int ID_CheckWMIC( bloomfilter_t value, const char *cmdline ) +static int ID_CheckWMIC( bloomfilter_t value, const char *cmdline ) { char buffer[BUFSIZE], token[BUFSIZE], *pbuf; int count = 0; @@ -486,7 +486,7 @@ int ID_CheckWMIC( bloomfilter_t value, const char *cmdline ) char *IOS_GetUDID( void ); #endif -bloomfilter_t ID_GenerateRawId( void ) +static bloomfilter_t ID_GenerateRawId( void ) { bloomfilter_t value = 0; int count = 0; @@ -519,7 +519,7 @@ bloomfilter_t ID_GenerateRawId( void ) return value; } -uint ID_CheckRawId( bloomfilter_t filter ) +static uint ID_CheckRawId( bloomfilter_t filter ) { bloomfilter_t value = 0; int count = 0; @@ -563,7 +563,7 @@ uint ID_CheckRawId( bloomfilter_t filter ) #define SYSTEM_XOR_MASK 0x10331c2dce4c91db #define GAME_XOR_MASK 0x7ffc48fbac1711f1 -void ID_Check( void ) +static void ID_Check( void ) { uint weight = BloomFilter_Weight( id ); uint mincount = weight >> 2; @@ -677,7 +677,7 @@ void ID_Init( void ) MD5Final( (byte*)md5, &hash ); for( i = 0; i < 16; i++ ) - Q_sprintf( &id_md5[i*2], "%02hhx", md5[i] ); + Q_snprintf( &id_md5[i*2], sizeof( id_md5 ) - i * 2, "%02hhx", md5[i] ); #if XASH_ANDROID && !XASH_DEDICATED Android_SaveID( va("%016llX", id^SYSTEM_XOR_MASK ) ); diff --git a/engine/common/imagelib/img_main.c b/engine/common/imagelib/img_main.c index 230d5520..850d9774 100644 --- a/engine/common/imagelib/img_main.c +++ b/engine/common/imagelib/img_main.c @@ -321,10 +321,9 @@ rgbdata_t *FS_LoadImage( const char *filename, const byte *buffer, size_t size ) { for( i = 0; i < 6; i++ ) { - if( Image_ProbeLoad( extfmt, loadname, cmap->type[i].suf, cmap->type[i].hint ) && - FS_AddSideToPack( cmap->type[i].flags )) // process flags to flip some sides + if( Image_ProbeLoad( extfmt, loadname, cmap->type[i].suf, cmap->type[i].hint )) { - break; + FS_AddSideToPack( cmap->type[i].flags ); } if( image.num_sides != i + 1 ) // check side @@ -339,7 +338,7 @@ rgbdata_t *FS_LoadImage( const char *filename, const byte *buffer, size_t size ) } } - // make sure what all sides is loaded + // make sure that all sides is loaded if( image.num_sides != 6 ) { // unexpected errors ? @@ -423,7 +422,8 @@ qboolean FS_SaveImage( const char *filename, rgbdata_t *pix ) { for( i = 0; i < 6; i++ ) { - Q_sprintf( path, format->formatstring, savename, box[i].suf, format->ext ); + Q_snprintf( path, sizeof( path ), + format->formatstring, savename, box[i].suf, format->ext ); if( !format->savefunc( path, pix )) break; // there were errors pix->buffer += pix->size; // move pointer } @@ -445,7 +445,8 @@ qboolean FS_SaveImage( const char *filename, rgbdata_t *pix ) { if( !Q_stricmp( ext, format->ext )) { - Q_sprintf( path, format->formatstring, savename, "", format->ext ); + Q_snprintf( path, sizeof( path ), + format->formatstring, savename, "", format->ext ); if( format->savefunc( path, pix )) { // clear any force flags @@ -577,10 +578,13 @@ void Test_RunImagelib( void ) for( i = 0; i < sizeof(extensions) / sizeof(extensions[0]); i++ ) { - const char *name = va( "test_gen.%s", extensions[i] ); + qboolean ret; + char name[MAX_VA_STRING]; + + Q_snprintf( name, sizeof( name ), "test_gen.%s", extensions[i] ); // test saving - qboolean ret = FS_SaveImage( name, &rgb ); + ret = FS_SaveImage( name, &rgb ); Con_Printf( "Checking if we can save images in '%s' format...\n", extensions[i] ); ASSERT(ret == true); diff --git a/engine/common/infostring.c b/engine/common/infostring.c index 220cc9e8..fc9d56be 100644 --- a/engine/common/infostring.c +++ b/engine/common/infostring.c @@ -277,7 +277,9 @@ qboolean GAME_EXPORT Info_RemoveKey( char *s, const char *key ) if( !Q_strncmp( key, pkey, cmpsize )) { - Q_strcpy( start, s ); // remove this part + size_t size = Q_strlen( s ) + 1; + + memmove( start, s, size ); // remove this part return true; } @@ -496,3 +498,16 @@ qboolean Info_SetValueForKey( char *s, const char *key, const char *value, int m return Info_SetValueForStarKey( s, key, value, maxsize ); } + +qboolean Info_SetValueForKeyf( char *s, const char *key, int maxsize, const char *format, ... ) +{ + char value[MAX_VA_STRING]; + va_list args; + + va_start( args, format ); + Q_vsnprintf( value, sizeof( value ), format, args ); + va_end( args ); + + return Info_SetValueForKey( s, key, value, maxsize ); +} + diff --git a/engine/common/launcher.c b/engine/common/launcher.c index 598c1c17..4c2f9d66 100644 --- a/engine/common/launcher.c +++ b/engine/common/launcher.c @@ -91,9 +91,13 @@ _inline int Sys_Start( void ) #if !XASH_WIN32 int main( int argc, char **argv ) { +#if XASH_PSVITA + // inject -dev -console into args if required + szArgc = PSVita_GetArgv( argc, argv, &szArgv ); +#else szArgc = argc; szArgv = argv; - +#endif // XASH_PSVITA return Sys_Start(); } #else diff --git a/engine/common/masterlist.c b/engine/common/masterlist.c index bb2edcc8..0900918b 100644 --- a/engine/common/masterlist.c +++ b/engine/common/masterlist.c @@ -14,22 +14,49 @@ GNU General Public License for more details. */ #include "common.h" #include "netchan.h" +#include "server.h" typedef struct master_s { struct master_s *next; - qboolean sent; + qboolean sent; // TODO: get rid of this internal state qboolean save; string address; netadr_t adr; // temporary, rewritten after each send + + uint heartbeat_challenge; + double last_heartbeat; } master_t; -struct masterlist_s +static struct masterlist_s { master_t *list; qboolean modified; } ml; +static CVAR_DEFINE_AUTO( sv_verbose_heartbeats, "0", 0, "print every heartbeat to console" ); + +#define HEARTBEAT_SECONDS ((sv_nat.value > 0.0f) ? 60.0f : 300.0f) // 1 or 5 minutes + +/* +======================== +NET_GetMasterHostByName +======================== +*/ +static net_gai_state_t NET_GetMasterHostByName( master_t *m ) +{ + net_gai_state_t res = NET_StringToAdrNB( m->address, &m->adr ); + + if( res == NET_EAI_OK ) + return res; + + m->adr.type = NA_UNUSED; + if( res == NET_EAI_NONAME ) + Con_Reportf( "Can't resolve adr: %s\n", m->address ); + + return res; +} + /* ======================== NET_SendToMasters @@ -45,48 +72,167 @@ qboolean NET_SendToMasters( netsrc_t sock, size_t len, const void *data ) for( list = ml.list; list; list = list->next ) { - int res; - if( list->sent ) continue; - res = NET_StringToAdrNB( list->address, &list->adr ); - - if( !res ) - { - Con_Reportf( "Can't resolve adr: %s\n", list->address ); - list->sent = true; - list->adr.type = NA_UNUSED; - continue; - } - - if( res == 2 ) + switch( NET_GetMasterHostByName( list )) { + case NET_EAI_AGAIN: list->sent = false; - list->adr.type = NA_UNUSED; wait = true; - continue; + break; + case NET_EAI_NONAME: + list->sent = true; + break; + case NET_EAI_OK: + list->sent = true; + NET_SendPacket( sock, len, data, list->adr ); + break; } - - list->sent = true; - - NET_SendPacket( sock, len, data, list->adr ); } if( !wait ) { - list = ml.list; - - while( list ) - { + // reset sent state + for( list = ml.list; list; list = list->next ) list->sent = false; - list = list->next; - } } return wait; } +/* +======================== +NET_AnnounceToMaster + +======================== +*/ +static void NET_AnnounceToMaster( master_t *m ) +{ + sizebuf_t msg; + char buf[16]; + + m->heartbeat_challenge = COM_RandomLong( 0, INT_MAX ); + + MSG_Init( &msg, "Master Join", buf, sizeof( buf )); + MSG_WriteBytes( &msg, "q\xFF", 2 ); + MSG_WriteDword( &msg, m->heartbeat_challenge ); + + NET_SendPacket( NS_SERVER, MSG_GetNumBytesWritten( &msg ), MSG_GetBuf( &msg ), m->adr ); + + if( sv_verbose_heartbeats.value ) + { + Con_Printf( S_NOTE "sent heartbeat to %s (%s, 0x%x)\n", + m->address, NET_AdrToString( m->adr ), m->heartbeat_challenge ); + } +} + +/* +======================== +NET_AnnounceToMaster + +======================== +*/ +void NET_MasterClear( void ) +{ + master_t *m; + + for( m = ml.list; m; m = m->next ) + m->last_heartbeat = MAX_HEARTBEAT; +} + +/* +======================== +NET_MasterHeartbeat + +======================== +*/ +void NET_MasterHeartbeat( void ) +{ + master_t *m; + + if(( !public_server.value && !sv_nat.value ) || svs.maxclients == 1 ) + return; // only public servers send heartbeats + + for( m = ml.list; m; m = m->next ) + { + if( host.realtime - m->last_heartbeat < HEARTBEAT_SECONDS ) + continue; + + switch( NET_GetMasterHostByName( m )) + { + case NET_EAI_AGAIN: + m->last_heartbeat = MAX_HEARTBEAT; // retry on next frame + if( sv_verbose_heartbeats.value ) + Con_Printf( S_NOTE "delay heartbeat to next frame until %s resolves\n", m->address ); + + break; + case NET_EAI_NONAME: + m->last_heartbeat = host.realtime; // try to resolve again on next heartbeat + break; + case NET_EAI_OK: + m->last_heartbeat = host.realtime; + NET_AnnounceToMaster( m ); + break; + } + } +} + +/* +================= +NET_MasterShutdown + +Informs all masters that this server is going down +(ignored by master servers in current implementation) +================= +*/ +void NET_MasterShutdown( void ) +{ + NET_Config( true, false ); // allow remote + while( NET_SendToMasters( NS_SERVER, 2, "\x62\x0A" )); +} + + +/* +======================== +NET_GetMasterFromAdr + +======================== +*/ +static master_t *NET_GetMasterFromAdr( netadr_t adr ) +{ + master_t *master; + + for( master = ml.list; master; master = master->next ) + { + if( NET_CompareAdr( adr, master->adr )) + return master; + } + + return NULL; +} + + +/* +======================== +NET_GetMaster +======================== +*/ +qboolean NET_GetMaster( netadr_t from, uint *challenge, double *last_heartbeat ) +{ + master_t *m; + + m = NET_GetMasterFromAdr( from ); + + if( m ) + { + *challenge = m->heartbeat_challenge; + *last_heartbeat = m->last_heartbeat; + } + + return m != NULL; +} + /* ======================== NET_IsMasterAdr @@ -95,15 +241,7 @@ NET_IsMasterAdr */ qboolean NET_IsMasterAdr( netadr_t adr ) { - master_t *master; - - for( master = ml.list; master; master = master->next ) - { - if( NET_CompareAdr( adr, master->adr )) - return true; - } - - return false; + return NET_GetMasterFromAdr( adr ) != NULL; } /* @@ -277,6 +415,8 @@ void NET_InitMasters( void ) Cmd_AddRestrictedCommand( "clearmasters", NET_ClearMasters_f, "clear masterserver list" ); Cmd_AddCommand( "listmasters", NET_ListMasters_f, "list masterservers" ); + Cvar_RegisterVariable( &sv_verbose_heartbeats ); + // keep main master always there NET_AddMaster( MASTERSERVER_ADR, false ); NET_LoadMasters( ); diff --git a/engine/common/mod_bmodel.c b/engine/common/mod_bmodel.c index 8d2c0be3..8c63462c 100644 --- a/engine/common/mod_bmodel.c +++ b/engine/common/mod_bmodel.c @@ -24,6 +24,9 @@ GNU General Public License for more details. #include "client.h" #include "server.h" // LUMP_ error codes #include "ref_common.h" + +#define MIPTEX_CUSTOM_PALETTE_SIZE_BYTES ( sizeof( int16_t ) + 768 ) + typedef struct wadlist_s { char wadnames[MAX_MAP_WADS][32]; @@ -207,6 +210,134 @@ static mlumpinfo_t extlumps[EXTRA_LUMPS] = { LUMP_SHADOWMAP, 0, MAX_MAP_LIGHTING / 3, sizeof( byte ), -1, "shadowmap", USE_EXTRAHEADER, (const void **)&srcmodel.shadowdata, &srcmodel.shadowdatasize }, }; +/* +=============================================================================== + + Static helper functions + +=============================================================================== +*/ + +static mip_t *Mod_GetMipTexForTexture( dbspmodel_t *bmod, int i ) +{ + if( i < 0 || i >= bmod->textures->nummiptex ) + return NULL; + + if( bmod->textures->dataofs[i] == -1 ) + return NULL; + + return (mip_t *)((byte *)bmod->textures + bmod->textures->dataofs[i] ); +} + +// Returns index of WAD that texture was found in, or -1 if not found. +static int Mod_FindTextureInWadList( wadlist_t *list, const char *name, char *dst, size_t size ) +{ + int i; + + if( !list || !COM_CheckString( name )) + return -1; + + // check wads in reverse order + for( i = list->count - 1; i >= 0; i-- ) + { + char path[MAX_VA_STRING]; + + Q_snprintf( path, sizeof( path ), "%s.wad/%s.mip", list->wadnames[i], name ); + + if( FS_FileExists( path, false )) + { + if( dst && size > 0 ) + Q_strncpy( dst, path, size ); + + return i; + } + } + + return -1; +} + +static fs_offset_t Mod_CalculateMipTexSize( mip_t *mt, qboolean palette ) +{ + if( !mt ) + return 0; + + return sizeof( *mt ) + (( mt->width * mt->height * 85 ) >> 6 ) + + ( palette ? MIPTEX_CUSTOM_PALETTE_SIZE_BYTES : 0 ); +} + +static qboolean Mod_CalcMipTexUsesCustomPalette( dbspmodel_t *bmod, int textureIndex ) +{ + int nextTextureIndex = 0; + mip_t *mipTex; + fs_offset_t size, remainingBytes; + + mipTex = Mod_GetMipTexForTexture( bmod, textureIndex ); + + if( !mipTex || mipTex->offsets[0] <= 0 ) + return false; + + // Calculate the size assuming we are not using a custom palette. + size = Mod_CalculateMipTexSize( mipTex, false ); + + // Compute next data offset to determine allocated miptex space + for( nextTextureIndex = textureIndex + 1; nextTextureIndex < loadmodel->numtextures; nextTextureIndex++ ) + { + int nextOffset = bmod->textures->dataofs[nextTextureIndex]; + + if( nextOffset != -1 ) + { + remainingBytes = nextOffset - ( bmod->textures->dataofs[textureIndex] + size ); + return remainingBytes >= MIPTEX_CUSTOM_PALETTE_SIZE_BYTES; + } + } + + // There was no other miptex after this one. + // See if there is enough space between the end and our offset. + remainingBytes = bmod->texdatasize - ( bmod->textures->dataofs[textureIndex] + size ); + return remainingBytes >= MIPTEX_CUSTOM_PALETTE_SIZE_BYTES; +} + +static qboolean Mod_NameImpliesTextureIsAnimated( texture_t *tex ) +{ + if( !tex ) + return false; + + // Not an animated texture name + if( tex->name[0] != '-' && tex->name[0] != '+' ) + return false; + + // Name implies texture is animated - check second character is valid. + if( !( tex->name[1] >= '0' && tex->name[1] <= '9' ) && + !( tex->name[1] >= 'a' && tex->name[1] <= 'j' )) + { + Con_Printf( S_ERROR "Mod_NameImpliesTextureIsAnimated: animating texture \"%s\" has invalid name\n", tex->name ); + return false; + } + + return true; +} + +static void Mod_CreateDefaultTexture( texture_t **texture ) +{ + texture_t *tex; + + // Pointer must be valid, and value pointed to must be null. + if( !texture || *texture != NULL ) + return; + + *texture = tex = Mem_Calloc( loadmodel->mempool, sizeof( *tex )); + Q_strncpy( tex->name, REF_DEFAULT_TEXTURE, sizeof( tex->name )); + +#if !XASH_DEDICATED + if( !Host_IsDedicated( )) + { + tex->gl_texturenum = R_GetBuiltinTexture( REF_DEFAULT_TEXTURE ); + tex->width = 16; + tex->height = 16; + } +#endif // XASH_DEDICATED +} + /* =============================================================================== @@ -309,7 +440,7 @@ static void Mod_LoadLump( const byte *in, mlumpinfo_t *info, mlumpstat_t *stat, if( l->filelen % real_entrysize ) { if( !FBitSet( flags, LUMP_SILENT )) - Con_DPrintf( S_ERROR "Mod_Load%s: Lump size %d was not a multiple of %lu bytes\n", msg2, l->filelen, real_entrysize ); + Con_DPrintf( S_ERROR "Mod_Load%s: Lump size %d was not a multiple of %zu bytes\n", msg2, l->filelen, real_entrysize ); loadstat.numerrors++; return; } @@ -745,24 +876,6 @@ qboolean Mod_HeadnodeVisible( mnode_t *node, const byte *visbits, int *lastleaf return false; } -/* -================== -Mod_AmbientLevels - -grab the ambient sound levels for current point -================== -*/ -void Mod_AmbientLevels( const vec3_t p, byte *pvolumes ) -{ - mleaf_t *leaf; - - if( !worldmodel || !p || !pvolumes ) - return; - - leaf = Mod_PointInLeaf( p, worldmodel->nodes ); - *(int *)pvolumes = *(int *)leaf->ambient_sound_level; -} - /* ================= Mod_FindModelOrigin @@ -778,7 +891,7 @@ static void Mod_FindModelOrigin( const char *entities, const char *modelname, ve qboolean model_found; qboolean origin_found; - if( !entities || !modelname || !*modelname ) + if( !entities || !COM_CheckString( modelname )) return; if( !origin || !VectorIsNull( origin )) @@ -1370,7 +1483,7 @@ static qboolean Mod_LoadColoredLighting( dbspmodel_t *bmod ) fs_offset_t litdatasize; byte *in; - COM_FileBase( loadmodel->name, modelname ); + COM_FileBase( loadmodel->name, modelname, sizeof( modelname )); Q_snprintf( path, sizeof( path ), "maps/%s.lit", modelname ); // make sure what deluxemap is actual @@ -1395,7 +1508,7 @@ static qboolean Mod_LoadColoredLighting( dbspmodel_t *bmod ) if( litdatasize != ( bmod->lightdatasize * 3 )) { - Con_Printf( S_ERROR "%s has mismatched size (%li should be %lu)\n", path, litdatasize, bmod->lightdatasize * 3 ); + Con_Printf( S_ERROR "%s has mismatched size (%llu should be %zu)\n", path, litdatasize, bmod->lightdatasize * 3 ); Mem_Free( in ); return false; } @@ -1425,7 +1538,7 @@ static void Mod_LoadDeluxemap( dbspmodel_t *bmod ) if( !FBitSet( host.features, ENGINE_LOAD_DELUXEDATA )) return; - COM_FileBase( loadmodel->name, modelname ); + COM_FileBase( loadmodel->name, modelname, sizeof( modelname )); Q_snprintf( path, sizeof( path ), "maps/%s.dlit", modelname ); // make sure what deluxemap is actual @@ -1450,7 +1563,7 @@ static void Mod_LoadDeluxemap( dbspmodel_t *bmod ) if( deluxdatasize != bmod->lightdatasize ) { - Con_Reportf( S_ERROR "%s has mismatched size (%li should be %lu)\n", path, deluxdatasize, bmod->lightdatasize ); + Con_Reportf( S_ERROR "%s has mismatched size (%llu should be %zu)\n", path, deluxdatasize, bmod->lightdatasize ); Mem_Free( in ); return; } @@ -1517,7 +1630,10 @@ static void Mod_SetupSubmodels( dbspmodel_t *bmod ) if( i != 0 ) { - Mod_FindModelOrigin( ents, va( "*%i", i ), bm->origin ); + char temp[MAX_VA_STRING]; + + Q_snprintf( temp, sizeof( temp ), "*%i", i ); + Mod_FindModelOrigin( ents, temp, bm->origin ); // mark models that have origin brushes if( !VectorIsNull( bm->origin )) @@ -1648,7 +1764,7 @@ static void Mod_LoadEntities( dbspmodel_t *bmod ) // world is check for entfile too Q_strncpy( entfilename, loadmodel->name, sizeof( entfilename )); - COM_ReplaceExtension( entfilename, ".ent" ); + COM_ReplaceExtension( entfilename, ".ent", sizeof( entfilename )); // make sure what entity patch is never than bsp ft1 = FS_FileTime( loadmodel->name, false ); @@ -1711,13 +1827,13 @@ static void Mod_LoadEntities( dbspmodel_t *bmod ) wadstring[MAX_TOKEN - 2] = 0; if( !Q_strchr( wadstring, ';' )) - Q_strcat( wadstring, ";" ); + Q_strncat( wadstring, ";", sizeof( wadstring )); // parse wad pathes for( pszWadFile = strtok( wadstring, ";" ); pszWadFile != NULL; pszWadFile = strtok( NULL, ";" )) { COM_FixSlashes( pszWadFile ); - COM_FileBase( pszWadFile, token ); + COM_FileBase( pszWadFile, token, sizeof( token )); // make sure what wad is really exist if( FS_FileExists( va( "%s.wad", token ), false )) @@ -1896,6 +2012,289 @@ static void Mod_LoadMarkSurfaces( dbspmodel_t *bmod ) } } +static void Mod_LoadTextureData( dbspmodel_t *bmod, int textureIndex ) +{ +#if !XASH_DEDICATED + texture_t *texture = NULL; + mip_t *mipTex = NULL; + qboolean usesCustomPalette = false; + uint32_t txFlags = 0; + + // Don't load texture data on dedicated server, as there is no renderer. + // FIXME: for ENGINE_IMPROVED_LINETRACE we need to load textures on server too + // but there is no facility for this yet + if( Host_IsDedicated( )) + return; + + texture = loadmodel->textures[textureIndex]; + mipTex = Mod_GetMipTexForTexture( bmod, textureIndex ); + + if( FBitSet( host.features, ENGINE_IMPROVED_LINETRACE ) && mipTex->name[0] == '{' ) + SetBits( txFlags, TF_KEEP_SOURCE ); // Paranoia2 texture alpha-tracing + + usesCustomPalette = Mod_CalcMipTexUsesCustomPalette( bmod, textureIndex ); + + // check for multi-layered sky texture (quake1 specific) + if( bmod->isworld && Q_strncmp( mipTex->name, "sky", 3 ) == 0 && ( mipTex->width / mipTex->height ) == 2 ) + { + ref.dllFuncs.R_InitSkyClouds( mipTex, texture, usesCustomPalette ); // load quake sky + + if( R_GetBuiltinTexture( REF_SOLIDSKY_TEXTURE ) && R_GetBuiltinTexture( REF_ALPHASKY_TEXTURE )) + SetBits( world.flags, FWORLD_SKYSPHERE ); + + // No texture to load in this case, so just exit. + return; + } + + // Texture loading order: + // 1. From WAD + // 2. Internal from map + + // Try WAD texture (force while r_wadtextures is 1) + if(( r_wadtextures->value && bmod->wadlist.count > 0 ) || mipTex->offsets[0] <= 0 ) + { + char texpath[MAX_VA_STRING]; + int wadIndex = Mod_FindTextureInWadList( &bmod->wadlist, mipTex->name, texpath, sizeof( texpath )); + + if( wadIndex >= 0 ) + { + texture->gl_texturenum = ref.dllFuncs.GL_LoadTexture( texpath, NULL, 0, txFlags ); + bmod->wadlist.wadusage[wadIndex]++; + } + } + + // WAD failed, so use internal texture (if present) + if( mipTex->offsets[0] > 0 && texture->gl_texturenum == 0 ) + { + char texName[64]; + const size_t size = Mod_CalculateMipTexSize( mipTex, usesCustomPalette ); + + Q_snprintf( texName, sizeof( texName ), "#%s:%s.mip", loadstat.name, mipTex->name ); + texture->gl_texturenum = ref.dllFuncs.GL_LoadTexture( texName, (byte *)mipTex, size, txFlags ); + } + + // If texture is completely missed: + if( texture->gl_texturenum == 0 ) + { + Con_DPrintf( S_ERROR "Unable to find %s.mip\n", mipTex->name ); + texture->gl_texturenum = R_GetBuiltinTexture( REF_DEFAULT_TEXTURE ); + } + + // Check for luma texture + if( FBitSet( REF_GET_PARM( PARM_TEX_FLAGS, texture->gl_texturenum ), TF_HAS_LUMA )) + { + char texName[64]; + Q_snprintf( texName, sizeof( texName ), "#%s:%s_luma.mip", loadstat.name, mipTex->name ); + + if( mipTex->offsets[0] > 0 ) + { + const size_t size = Mod_CalculateMipTexSize( mipTex, usesCustomPalette ); + texture->fb_texturenum = ref.dllFuncs.GL_LoadTexture( texName, (byte *)mipTex, size, TF_MAKELUMA ); + } + else + { + char texpath[MAX_VA_STRING]; + int wadIndex; + fs_offset_t srcSize = 0; + byte* src = NULL; + + // NOTE: We can't load the _luma texture from the WAD as normal because it + // doesn't exist there. The original texture is already loaded, but cannot be modified. + // Instead, load the original texture again and convert it to luma. + + wadIndex = Mod_FindTextureInWadList( &bmod->wadlist, texture->name, texpath, sizeof( texpath )); + + if( wadIndex >= 0 ) + { + src = FS_LoadFile( texpath, &srcSize, false ); + bmod->wadlist.wadusage[wadIndex]++; + } + + // OK, loading it from wad or hi-res version + texture->fb_texturenum = ref.dllFuncs.GL_LoadTexture( texName, src, srcSize, TF_MAKELUMA ); + + if( src ) + Mem_Free( src ); + } + } +#endif // !XASH_DEDICATED +} + +static void Mod_LoadTexture( dbspmodel_t *bmod, int textureIndex ) +{ + texture_t *texture; + mip_t *mipTex; + + if( textureIndex < 0 || textureIndex >= loadmodel->numtextures ) + return; + + mipTex = Mod_GetMipTexForTexture( bmod, textureIndex ); + + if( !mipTex ) + { + // No data for this texture. + // Create default texture (some mods require this). + Mod_CreateDefaultTexture( &loadmodel->textures[textureIndex] ); + return; + } + + if( mipTex->name[0] == '\0' ) + Q_snprintf( mipTex->name, sizeof( mipTex->name ), "miptex_%i", textureIndex ); + + texture = (texture_t *)Mem_Calloc( loadmodel->mempool, sizeof( *texture )); + loadmodel->textures[textureIndex] = texture; + + // Ensure texture name is lowercase. + Q_strnlwr( mipTex->name, texture->name, sizeof( texture->name )); + + texture->width = mipTex->width; + texture->height = mipTex->height; + + Mod_LoadTextureData( bmod, textureIndex ); +} + +static void Mod_LoadAllTextures( dbspmodel_t *bmod ) +{ + int i; + + for( i = 0; i < loadmodel->numtextures; i++ ) + Mod_LoadTexture( bmod, i ); +} + +static void Mod_SequenceAnimatedTexture( int baseTextureIndex ) +{ + texture_t *anims[10]; + texture_t *altanims[10]; + texture_t *baseTexture; + int max = 0; + int altmax = 0; + int candidateIndex; + + if( baseTextureIndex < 0 || baseTextureIndex >= loadmodel->numtextures ) + return; + + baseTexture = loadmodel->textures[baseTextureIndex]; + + if( !Mod_NameImpliesTextureIsAnimated( baseTexture )) + return; + + // Already sequenced + if( baseTexture->anim_next ) + return; + + // find the number of frames in the animation + memset( anims, 0, sizeof( anims )); + memset( altanims, 0, sizeof( altanims )); + + if( baseTexture->name[1] >= '0' && baseTexture->name[1] <= '9' ) + { + // This texture is a standard animation frame. + int frameIndex = (int)baseTexture->name[1] - (int)'0'; + + anims[frameIndex] = baseTexture; + max = frameIndex + 1; + } + else + { + // This texture is an alternate animation frame. + int frameIndex = (int)baseTexture->name[1] - (int)'a'; + + altanims[frameIndex] = baseTexture; + altmax = frameIndex + 1; + } + + // Now search the rest of the textures to find all other frames. + for( candidateIndex = baseTextureIndex + 1; candidateIndex < loadmodel->numtextures; candidateIndex++ ) + { + texture_t *altTexture = loadmodel->textures[candidateIndex]; + + if( !Mod_NameImpliesTextureIsAnimated( altTexture )) + continue; + + // This texture is animated, but is it part of the same group as + // the original texture we encountered? Check that the rest of + // the name matches the original (both will be valid for at least + // string index 2). + if( Q_strcmp( altTexture->name + 2, baseTexture->name + 2 ) != 0 ) + continue; + + if( altTexture->name[1] >= '0' && altTexture->name[1] <= '9' ) + { + // This texture is a standard frame. + int frameIndex = (int)altTexture->name[1] - (int)'0'; + anims[frameIndex] = altTexture; + + if( frameIndex >= max ) + max = frameIndex + 1; + } + else + { + // This texture is an alternate frame. + int frameIndex = (int)altTexture->name[1] - (int)'a'; + altanims[frameIndex] = altTexture; + + if( frameIndex >= altmax ) + altmax = frameIndex + 1; + } + } + + // Link all standard animated frames together. + for( candidateIndex = 0; candidateIndex < max; candidateIndex++ ) + { + texture_t *tex = anims[candidateIndex]; + + if( !tex ) + { + Con_Printf( S_ERROR "Mod_SequenceAnimatedTexture: missing frame %i of animated texture \"%s\"\n", + candidateIndex, + baseTexture->name ); + + baseTexture->anim_total = 0; + break; + } + + tex->anim_total = max * ANIM_CYCLE; + tex->anim_min = candidateIndex * ANIM_CYCLE; + tex->anim_max = ( candidateIndex + 1 ) * ANIM_CYCLE; + tex->anim_next = anims[( candidateIndex + 1 ) % max]; + + if( altmax > 0 ) + tex->alternate_anims = altanims[0]; + } + + // Link all alternate animated frames together. + for( candidateIndex = 0; candidateIndex < altmax; candidateIndex++ ) + { + texture_t *tex = altanims[candidateIndex]; + + if( !tex ) + { + Con_Printf( S_ERROR "Mod_SequenceAnimatedTexture: missing alternate frame %i of animated texture \"%s\"\n", + candidateIndex, + baseTexture->name ); + + baseTexture->anim_total = 0; + break; + } + + tex->anim_total = altmax * ANIM_CYCLE; + tex->anim_min = candidateIndex * ANIM_CYCLE; + tex->anim_max = ( candidateIndex + 1 ) * ANIM_CYCLE; + tex->anim_next = altanims[( candidateIndex + 1 ) % altmax]; + + if( max > 0 ) + tex->alternate_anims = anims[0]; + } +} + +static void Mod_SequenceAllAnimatedTextures( void ) +{ + int i; + + for( i = 0; i < loadmodel->numtextures; i++ ) + Mod_SequenceAnimatedTexture( i ); +} + /* ================= Mod_LoadTextures @@ -1903,300 +2302,31 @@ Mod_LoadTextures */ static void Mod_LoadTextures( dbspmodel_t *bmod ) { - dmiptexlump_t *in; - texture_t *tx, *tx2; - texture_t *anims[10]; - texture_t *altanims[10]; - int num, max, altmax; - qboolean custom_palette; - char texname[64]; - mip_t *mt; - int i, j; + dmiptexlump_t *lump; - if( bmod->isworld ) - { #if !XASH_DEDICATED - // release old sky layers first - if( !Host_IsDedicated() ) - { - ref.dllFuncs.GL_FreeTexture( R_GetBuiltinTexture( REF_ALPHASKY_TEXTURE )); - ref.dllFuncs.GL_FreeTexture( R_GetBuiltinTexture( REF_SOLIDSKY_TEXTURE )); - } -#endif + // release old sky layers first + if( !Host_IsDedicated() && bmod->isworld ) + { + ref.dllFuncs.GL_FreeTexture( R_GetBuiltinTexture( REF_ALPHASKY_TEXTURE )); + ref.dllFuncs.GL_FreeTexture( R_GetBuiltinTexture( REF_SOLIDSKY_TEXTURE )); } +#endif - if( !bmod->texdatasize ) + lump = bmod->textures; + + if( bmod->texdatasize < 1 || !lump || lump->nummiptex < 1 ) { // no textures loadmodel->textures = NULL; return; } - in = bmod->textures; - loadmodel->textures = (texture_t **)Mem_Calloc( loadmodel->mempool, in->nummiptex * sizeof( texture_t* )); - loadmodel->numtextures = in->nummiptex; + loadmodel->textures = (texture_t **)Mem_Calloc( loadmodel->mempool, lump->nummiptex * sizeof( texture_t * )); + loadmodel->numtextures = lump->nummiptex; - for( i = 0; i < loadmodel->numtextures; i++ ) - { - int txFlags = 0; - - if( in->dataofs[i] == -1 ) - { - // create default texture (some mods requires this) - tx = Mem_Calloc( loadmodel->mempool, sizeof( *tx )); - loadmodel->textures[i] = tx; - - Q_strncpy( tx->name, "*default", sizeof( tx->name )); -#if !XASH_DEDICATED - if( !Host_IsDedicated() ) - { - tx->gl_texturenum = R_GetBuiltinTexture( REF_DEFAULT_TEXTURE ); - tx->width = tx->height = 16; - } -#endif - continue; // missed - } - - mt = (mip_t *)((byte *)in + in->dataofs[i] ); - - if( !mt->name[0] ) - Q_snprintf( mt->name, sizeof( mt->name ), "miptex_%i", i ); - tx = Mem_Calloc( loadmodel->mempool, sizeof( *tx )); - loadmodel->textures[i] = tx; - - // convert to lowercase - Q_strncpy( tx->name, mt->name, sizeof( tx->name )); - Q_strnlwr( tx->name, tx->name, sizeof( tx->name )); - custom_palette = false; - - tx->width = mt->width; - tx->height = mt->height; - - if( FBitSet( host.features, ENGINE_IMPROVED_LINETRACE ) && mt->name[0] == '{' ) - SetBits( txFlags, TF_KEEP_SOURCE ); // Paranoia2 texture alpha-tracing - - if( mt->offsets[0] > 0 ) - { - int size = (int)sizeof( mip_t ) + ((mt->width * mt->height * 85)>>6); - int next_dataofs = 0, remaining; - - // compute next dataofset to determine allocated miptex space - for( j = i + 1; j < loadmodel->numtextures; j++ ) - { - next_dataofs = in->dataofs[j]; - if( next_dataofs != -1 ) break; - } - - if( j == loadmodel->numtextures ) - next_dataofs = bmod->texdatasize; - - // NOTE: imagelib detect miptex version by size - // 770 additional bytes is indicated custom palette - remaining = next_dataofs - (in->dataofs[i] + size); - if( remaining >= 770 ) custom_palette = true; - } - -#if !XASH_DEDICATED - if( !Host_IsDedicated() ) - { - // check for multi-layered sky texture (quake1 specific) - if( bmod->isworld && !Q_strncmp( mt->name, "sky", 3 ) && (( mt->width / mt->height ) == 2 ) ) - { - ref.dllFuncs.R_InitSkyClouds( mt, tx, custom_palette ); // load quake sky - - if( R_GetBuiltinTexture( REF_SOLIDSKY_TEXTURE ) && - R_GetBuiltinTexture( REF_ALPHASKY_TEXTURE ) ) - SetBits( world.flags, FWORLD_SKYSPHERE ); - continue; - } - - // texture loading order: - // 1. from wad - // 2. internal from map - - // trying wad texture (force while r_wadtextures is 1) - if(( r_wadtextures->value && bmod->wadlist.count > 0 ) || ( mt->offsets[0] <= 0 )) - { - Q_snprintf( texname, sizeof( texname ), "%s.mip", mt->name ); - - // check wads in reverse order - for( j = bmod->wadlist.count - 1; j >= 0; j-- ) - { - char *texpath = va( "%s.wad/%s", bmod->wadlist.wadnames[j], texname ); - - if( FS_FileExists( texpath, false )) - { - tx->gl_texturenum = ref.dllFuncs.GL_LoadTexture( texpath, NULL, 0, txFlags ); - bmod->wadlist.wadusage[j]++; // this wad are really used - break; - } - } - } - - // wad failed, so use internal texture (if present) - if( mt->offsets[0] > 0 && !tx->gl_texturenum ) - { - // NOTE: imagelib detect miptex version by size - // 770 additional bytes is indicated custom palette - int size = (int)sizeof( mip_t ) + ((mt->width * mt->height * 85)>>6); - - if( custom_palette ) size += sizeof( short ) + 768; - Q_snprintf( texname, sizeof( texname ), "#%s:%s.mip", loadstat.name, mt->name ); - tx->gl_texturenum = ref.dllFuncs.GL_LoadTexture( texname, (byte *)mt, size, txFlags ); - } - - // if texture is completely missed - if( !tx->gl_texturenum ) - { - Con_DPrintf( S_ERROR "unable to find %s.mip\n", mt->name ); - tx->gl_texturenum = R_GetBuiltinTexture( REF_DEFAULT_TEXTURE ); - } - - // check for luma texture - if( FBitSet( REF_GET_PARM( PARM_TEX_FLAGS, tx->gl_texturenum ), TF_HAS_LUMA )) - { - Q_snprintf( texname, sizeof( texname ), "#%s:%s_luma.mip", loadstat.name, mt->name ); - - if( mt->offsets[0] > 0 ) - { - // NOTE: imagelib detect miptex version by size - // 770 additional bytes is indicated custom palette - int size = (int)sizeof( mip_t ) + ((mt->width * mt->height * 85)>>6); - - if( custom_palette ) size += sizeof( short ) + 768; - tx->fb_texturenum = ref.dllFuncs.GL_LoadTexture( texname, (byte *)mt, size, TF_MAKELUMA ); - } - else - { - fs_offset_t srcSize = 0; - byte *src = NULL; - - // NOTE: we can't loading it from wad as normal because _luma texture doesn't exist - // and not be loaded. But original texture is already loaded and can't be modified - // So load original texture manually and convert it to luma - - // check wads in reverse order - for( j = bmod->wadlist.count - 1; j >= 0; j-- ) - { - char *texpath = va( "%s.wad/%s.mip", bmod->wadlist.wadnames[j], tx->name ); - - if( FS_FileExists( texpath, false )) - { - src = FS_LoadFile( texpath, &srcSize, false ); - bmod->wadlist.wadusage[j]++; // this wad are really used - break; - } - } - - // okay, loading it from wad or hi-res version - tx->fb_texturenum = ref.dllFuncs.GL_LoadTexture( texname, src, srcSize, TF_MAKELUMA ); - if( src ) Mem_Free( src ); - } - } - } -#endif - } - - // sequence the animations and detail textures - for( i = 0; i < loadmodel->numtextures; i++ ) - { - tx = loadmodel->textures[i]; - - if( !tx || ( tx->name[0] != '-' && tx->name[0] != '+' )) - continue; - - if( tx->anim_next ) - continue; // already sequenced - - // find the number of frames in the animation - memset( anims, 0, sizeof( anims )); - memset( altanims, 0, sizeof( altanims )); - - max = tx->name[1]; - altmax = 0; - - if( max >= '0' && max <= '9' ) - { - max -= '0'; - altmax = 0; - anims[max] = tx; - max++; - } - else if( max >= 'a' && max <= 'j' ) - { - altmax = max - 'a'; - max = 0; - altanims[altmax] = tx; - altmax++; - } - else Con_Printf( S_ERROR "Mod_LoadTextures: bad animating texture %s\n", tx->name ); - - for( j = i + 1; j < loadmodel->numtextures; j++ ) - { - tx2 = loadmodel->textures[j]; - - if( !tx2 || ( tx2->name[0] != '-' && tx2->name[0] != '+' )) - continue; - - if( Q_strcmp( tx2->name + 2, tx->name + 2 )) - continue; - - num = tx2->name[1]; - - if( num >= '0' && num <= '9' ) - { - num -= '0'; - anims[num] = tx2; - if( num + 1 > max ) - max = num + 1; - } - else if( num >= 'a' && num <= 'j' ) - { - num = num - 'a'; - altanims[num] = tx2; - if( num + 1 > altmax ) - altmax = num + 1; - } - else Con_Printf( S_ERROR "Mod_LoadTextures: bad animating texture %s\n", tx->name ); - } - - // link them all together - for( j = 0; j < max; j++ ) - { - tx2 = anims[j]; - - if( !tx2 ) - { - Con_Printf( S_ERROR "Mod_LoadTextures: missing frame %i of %s\n", j, tx->name ); - tx->anim_total = 0; - break; - } - - tx2->anim_total = max * ANIM_CYCLE; - tx2->anim_min = j * ANIM_CYCLE; - tx2->anim_max = (j + 1) * ANIM_CYCLE; - tx2->anim_next = anims[(j + 1) % max]; - if( altmax ) tx2->alternate_anims = altanims[0]; - } - - for( j = 0; j < altmax; j++ ) - { - tx2 = altanims[j]; - - if( !tx2 ) - { - Con_Printf( S_ERROR "Mod_LoadTextures: missing frame %i of %s\n", j, tx->name ); - tx->anim_total = 0; - break; - } - - tx2->anim_total = altmax * ANIM_CYCLE; - tx2->anim_min = j * ANIM_CYCLE; - tx2->anim_max = (j+1) * ANIM_CYCLE; - tx2->anim_next = altanims[(j + 1) % altmax]; - if( max ) tx2->alternate_anims = anims[0]; - } - } + Mod_LoadAllTextures( bmod ); + Mod_SequenceAllAnimatedTextures(); } /* @@ -2299,7 +2429,7 @@ static void Mod_LoadSurfaces( dbspmodel_t *bmod ) if(( in->firstedge + in->numedges ) > loadmodel->numsurfedges ) { - Con_Reportf( S_ERROR "bad surface %i from %lu\n", i, bmod->numsurfaces ); + Con_Reportf( S_ERROR "bad surface %i from %zu\n", i, bmod->numsurfaces ); continue; } @@ -2319,7 +2449,7 @@ static void Mod_LoadSurfaces( dbspmodel_t *bmod ) if( !Q_strncmp( tex->name, "sky", 3 )) SetBits( out->flags, SURF_DRAWSKY ); - if(( tex->name[0] == '*' && Q_stricmp( tex->name, "*default" )) || tex->name[0] == '!' ) + if(( tex->name[0] == '*' && Q_stricmp( tex->name, REF_DEFAULT_TEXTURE )) || tex->name[0] == '!' ) SetBits( out->flags, SURF_DRAWTURB ); if( !Host_IsQuakeCompatible( )) @@ -2634,7 +2764,7 @@ static void Mod_LoadLightVecs( dbspmodel_t *bmod ) if( bmod->deluxdatasize != bmod->lightdatasize ) { if( bmod->deluxdatasize > 0 ) - Con_Printf( S_ERROR "Mod_LoadLightVecs: has mismatched size (%lu should be %i)\n", bmod->deluxdatasize, bmod->lightdatasize ); + Con_Printf( S_ERROR "Mod_LoadLightVecs: has mismatched size (%zu should be %zu)\n", bmod->deluxdatasize, bmod->lightdatasize ); else Mod_LoadDeluxemap( bmod ); // old method return; } @@ -2653,7 +2783,7 @@ static void Mod_LoadShadowmap( dbspmodel_t *bmod ) if( bmod->shadowdatasize != ( bmod->lightdatasize / 3 )) { if( bmod->shadowdatasize > 0 ) - Con_Printf( S_ERROR "Mod_LoadShadowmap: has mismatched size (%i should be %lu)\n", bmod->shadowdatasize, bmod->lightdatasize / 3 ); + Con_Printf( S_ERROR "Mod_LoadShadowmap: has mismatched size (%zu should be %zu)\n", bmod->shadowdatasize, bmod->lightdatasize / 3 ); return; } @@ -3019,9 +3149,13 @@ Mod_LoadBrushModel */ void Mod_LoadBrushModel( model_t *mod, const void *buffer, qboolean *loaded ) { + char poolname[MAX_VA_STRING]; + + Q_snprintf( poolname, sizeof( poolname ), "^2%s^7", loadmodel->name ); + if( loaded ) *loaded = false; - loadmodel->mempool = Mem_AllocPool( va( "^2%s^7", loadmodel->name )); + loadmodel->mempool = Mem_AllocPool( poolname ); loadmodel->type = mod_brush; // loading all the lumps into heap diff --git a/engine/common/mod_local.h b/engine/common/mod_local.h index 3dcf50dc..f02d5a0d 100644 --- a/engine/common/mod_local.h +++ b/engine/common/mod_local.h @@ -156,7 +156,6 @@ int Mod_CheckLump( const char *filename, const int lump, int *lumpsize ); int Mod_ReadLump( const char *filename, const int lump, void **lumpdata, int *lumpsize ); int Mod_SaveLump( const char *filename, const int lump, void *lumpdata, int lumpsize ); mleaf_t *Mod_PointInLeaf( const vec3_t p, mnode_t *node ); -void Mod_AmbientLevels( const vec3_t p, byte *pvolumes ); int Mod_SampleSizeForFace( msurface_t *surf ); byte *Mod_GetPVSForPoint( const vec3_t p ); void Mod_UnloadBrushModel( model_t *mod ); diff --git a/engine/common/mod_sprite.c b/engine/common/mod_sprite.c index 5cc31827..f76774ae 100644 --- a/engine/common/mod_sprite.c +++ b/engine/common/mod_sprite.c @@ -35,6 +35,7 @@ void Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, ui dsprite_hl_t *pinhl; dsprite_t *pin; msprite_t *psprite; + char poolname[MAX_VA_STRING]; int i, size; if( loaded ) *loaded = false; @@ -54,7 +55,8 @@ void Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, ui return; } - mod->mempool = Mem_AllocPool( va( "^2%s^7", mod->name )); + Q_snprintf( poolname, sizeof( poolname ), "^2%s^7", mod->name ); + mod->mempool = Mem_AllocPool( poolname ); if( i == SPRITE_VERSION_Q1 || i == SPRITE_VERSION_32 ) { diff --git a/engine/common/mod_studio.c b/engine/common/mod_studio.c index 504ff415..4886fd33 100644 --- a/engine/common/mod_studio.c +++ b/engine/common/mod_studio.c @@ -429,7 +429,7 @@ void *R_StudioGetAnim( studiohdr_t *m_pStudioHeader, model_t *m_pSubModel, mstud { string filepath, modelname, modelpath; - COM_FileBase( m_pSubModel->name, modelname ); + COM_FileBase( m_pSubModel->name, modelname, sizeof( modelname )); COM_ExtractFilePath( m_pSubModel->name, modelpath ); // NOTE: here we build real sub-animation filename because stupid user may rename model without recompile @@ -848,10 +848,13 @@ Mod_LoadStudioModel */ void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded ) { + char poolname[MAX_VA_STRING]; studiohdr_t *phdr; + Q_snprintf( poolname, sizeof( poolname ), "^2%s^7", loadmodel->name ); + if( loaded ) *loaded = false; - loadmodel->mempool = Mem_AllocPool( va( "^2%s^7", loadmodel->name )); + loadmodel->mempool = Mem_AllocPool( poolname ); loadmodel->type = mod_studio; phdr = R_StudioLoadHeader( mod, buffer ); diff --git a/engine/common/net_buffer.c b/engine/common/net_buffer.c index 567047f2..1b4cc5f0 100644 --- a/engine/common/net_buffer.c +++ b/engine/common/net_buffer.c @@ -359,17 +359,6 @@ void MSG_WriteVec3Angles( sizebuf_t *sb, const float *fa ) MSG_WriteBitAngle( sb, fa[2], 16 ); } -void MSG_WriteBitFloat( sizebuf_t *sb, float val ) -{ - int intVal; - - Assert( sizeof( int ) == sizeof( float )); - Assert( sizeof( float ) == 4 ); - - intVal = *((int *)&val ); - MSG_WriteUBitLong( sb, intVal, 32 ); -} - void MSG_WriteCmdExt( sizebuf_t *sb, int cmd, netsrc_t type, const char *name ) { #ifdef DEBUG_NET_MESSAGES_SEND @@ -452,6 +441,18 @@ qboolean MSG_WriteString( sizebuf_t *sb, const char *pStr ) return !sb->bOverflow; } +qboolean MSG_WriteStringf( sizebuf_t *sb, const char *format, ... ) +{ + va_list va; + char buf[MAX_VA_STRING]; + + va_start( va, format ); + Q_vsnprintf( buf, sizeof( buf ), format, va ); + va_end( va ); + + return MSG_WriteString( sb, buf ); +} + int MSG_ReadOneBit( sizebuf_t *sb ) { if( !MSG_Overflow( sb, 1 )) @@ -511,32 +512,6 @@ uint MSG_ReadUBitLong( sizebuf_t *sb, int numbits ) return ret; } -float MSG_ReadBitFloat( sizebuf_t *sb ) -{ - int val; - int bit, byte; - - Assert( sizeof( float ) == sizeof( int )); - Assert( sizeof( float ) == 4 ); - - if( MSG_Overflow( sb, 32 )) - return 0.0f; - - bit = sb->iCurBit & 0x7; - byte = sb->iCurBit >> 3; - - val = sb->pData[byte] >> bit; - val |= ((int)sb->pData[byte + 1]) << ( 8 - bit ); - val |= ((int)sb->pData[byte + 2]) << ( 16 - bit ); - val |= ((int)sb->pData[byte + 3]) << ( 24 - bit ); - - if( bit != 0 ) - val |= ((int)sb->pData[byte + 4]) << ( 32 - bit ); - sb->iCurBit += 32; - - return *((float *)&val); -} - qboolean MSG_ReadBits( sizebuf_t *sb, void *pOutData, int nBits ) { byte *pOut = (byte *)pOutData; diff --git a/engine/common/net_buffer.h b/engine/common/net_buffer.h index 62ded8dc..b532daed 100644 --- a/engine/common/net_buffer.h +++ b/engine/common/net_buffer.h @@ -84,7 +84,6 @@ void MSG_WriteSBitLong( sizebuf_t *sb, int data, int numbits ); void MSG_WriteBitLong( sizebuf_t *sb, uint data, int numbits, qboolean bSigned ); qboolean MSG_WriteBits( sizebuf_t *sb, const void *pData, int nBits ); void MSG_WriteBitAngle( sizebuf_t *sb, float fAngle, int numbits ); -void MSG_WriteBitFloat( sizebuf_t *sb, float val ); // Byte-write functions #define MSG_BeginServerCmd( sb, cmd ) MSG_WriteCmdExt( sb, cmd, NS_SERVER, NULL ) @@ -102,6 +101,7 @@ void MSG_WriteVec3Coord( sizebuf_t *sb, const float *fa ); void MSG_WriteVec3Angles( sizebuf_t *sb, const float *fa ); qboolean MSG_WriteBytes( sizebuf_t *sb, const void *pBuf, int nBytes ); // same as MSG_WriteData qboolean MSG_WriteString( sizebuf_t *sb, const char *pStr ); // returns false if it overflows the buffer. +qboolean MSG_WriteStringf( sizebuf_t *sb, const char *format, ... ) _format( 2 ); // helper functions _inline int MSG_GetNumBytesWritten( sizebuf_t *sb ) { return BitByte( sb->iCurBit ); } @@ -116,7 +116,6 @@ _inline byte *MSG_GetBuf( sizebuf_t *sb ) { return sb->pData; } // just an alias // Bit-read functions int MSG_ReadOneBit( sizebuf_t *sb ); -float MSG_ReadBitFloat( sizebuf_t *sb ); qboolean MSG_ReadBits( sizebuf_t *sb, void *pOutData, int nBits ); float MSG_ReadBitAngle( sizebuf_t *sb, int numbits ); int MSG_ReadSBitLong( sizebuf_t *sb, int numbits ); diff --git a/engine/common/net_chan.c b/engine/common/net_chan.c index 934cf007..aec27f75 100644 --- a/engine/common/net_chan.c +++ b/engine/common/net_chan.c @@ -251,7 +251,7 @@ void Netchan_Init( void ) net_showpackets = Cvar_Get ("net_showpackets", "0", 0, "show network packets" ); net_chokeloopback = Cvar_Get( "net_chokeloop", "0", 0, "apply bandwidth choke to loopback packets" ); net_showdrop = Cvar_Get( "net_showdrop", "0", 0, "show packets that are dropped" ); - net_qport = Cvar_Get( "net_qport", va( "%i", port ), FCVAR_READ_ONLY, "current quake netport" ); + net_qport = Cvar_Getf( "net_qport", FCVAR_READ_ONLY, "current quake netport", "%i", port ); net_mempool = Mem_AllocPool( "Network Pool" ); @@ -273,8 +273,8 @@ void Netchan_ReportFlow( netchan_t *chan ) Assert( chan != NULL ); - Q_strcpy( incoming, Q_pretifymem((float)chan->flow[FLOW_INCOMING].totalbytes, 3 )); - Q_strcpy( outgoing, Q_pretifymem((float)chan->flow[FLOW_OUTGOING].totalbytes, 3 )); + Q_strncpy( incoming, Q_pretifymem((float)chan->flow[FLOW_INCOMING].totalbytes, 3 ), sizeof( incoming )); + Q_strncpy( outgoing, Q_pretifymem((float)chan->flow[FLOW_OUTGOING].totalbytes, 3 ), sizeof( outgoing )); Con_DPrintf( "Signon network traffic: %s from server, %s to server\n", incoming, outgoing ); } @@ -973,7 +973,7 @@ int Netchan_CreateFileFragments( netchan_t *chan, const char *filename ) else chunksize = FRAGMENT_MAX_SIZE; // fallback Q_strncpy( compressedfilename, filename, sizeof( compressedfilename )); - COM_ReplaceExtension( compressedfilename, ".ztmp" ); + COM_ReplaceExtension( compressedfilename, ".ztmp", sizeof( compressedfilename )); compressedFileTime = FS_FileTime( compressedfilename, false ); fileTime = FS_FileTime( filename, false ); @@ -1559,7 +1559,7 @@ void Netchan_TransmitBits( netchan_t *chan, int length, byte *data ) char compressedfilename[MAX_OSPATH]; Q_strncpy( compressedfilename, pbuf->filename, sizeof( compressedfilename )); - COM_ReplaceExtension( compressedfilename, ".ztmp" ); + COM_ReplaceExtension( compressedfilename, ".ztmp", sizeof( compressedfilename )); file = FS_Open( compressedfilename, "rb", false ); } else file = FS_Open( pbuf->filename, "rb", false ); @@ -1711,21 +1711,6 @@ void Netchan_TransmitBits( netchan_t *chan, int length, byte *data ) } } -/* -=============== -Netchan_Transmit - -tries to send an unreliable message to a connection, and handles the -transmition / retransmition of the reliable messages. - -A 0 length will still generate a packet and deal with the reliable messages. -================ -*/ -void Netchan_Transmit( netchan_t *chan, int lengthInBytes, byte *data ) -{ - Netchan_TransmitBits( chan, lengthInBytes << 3, data ); -} - /* ================= Netchan_Process diff --git a/engine/common/net_encode.c b/engine/common/net_encode.c index dc1c0f6c..6dd52019 100644 --- a/engine/common/net_encode.c +++ b/engine/common/net_encode.c @@ -28,6 +28,26 @@ GNU General Public License for more details. #define DELTA_PATH "delta.lst" +#define DT_BYTE BIT( 0 ) // A byte +#define DT_SHORT BIT( 1 ) // 2 byte field +#define DT_FLOAT BIT( 2 ) // A floating point field +#define DT_INTEGER BIT( 3 ) // 4 byte integer +#define DT_ANGLE BIT( 4 ) // A floating point angle ( will get masked correctly ) +#define DT_TIMEWINDOW_8 BIT( 5 ) // A floating point timestamp, relative to sv.time +#define DT_TIMEWINDOW_BIG BIT( 6 ) // and re-encoded on the client relative to the client's clock +#define DT_STRING BIT( 7 ) // A null terminated string, sent as 8 byte chars +#define DT_SIGNED BIT( 8 ) // sign modificator + +#define NUM_FIELDS( x ) ((sizeof( x ) / sizeof( x[0] )) - 1) + +// helper macroses +#define ENTS_DEF( x ) #x, offsetof( entity_state_t, x ), sizeof( ((entity_state_t *)0)->x ) +#define UCMD_DEF( x ) #x, offsetof( usercmd_t, x ), sizeof( ((usercmd_t *)0)->x ) +#define EVNT_DEF( x ) #x, offsetof( event_args_t, x ), sizeof( ((event_args_t *)0)->x ) +#define PHYS_DEF( x ) #x, offsetof( movevars_t, x ), sizeof( ((movevars_t *)0)->x ) +#define CLDT_DEF( x ) #x, offsetof( clientdata_t, x ), sizeof( ((clientdata_t *)0)->x ) +#define WPDT_DEF( x ) #x, offsetof( weapon_data_t, x ), sizeof( ((weapon_data_t *)0)->x ) + static qboolean delta_init = false; // list of all the struct names @@ -308,7 +328,7 @@ static delta_info_t dt_info[] = { NULL }, }; -delta_info_t *Delta_FindStruct( const char *name ) +static delta_info_t *Delta_FindStruct( const char *name ) { int i; @@ -340,7 +360,7 @@ delta_info_t *Delta_FindStructByIndex( int index ) return &dt_info[index]; } -delta_info_t *Delta_FindStructByEncoder( const char *encoderName ) +static delta_info_t *Delta_FindStructByEncoder( const char *encoderName ) { int i; @@ -356,7 +376,7 @@ delta_info_t *Delta_FindStructByEncoder( const char *encoderName ) return NULL; } -delta_info_t *Delta_FindStructByDelta( const delta_t *pFields ) +static delta_info_t *Delta_FindStructByDelta( const delta_t *pFields ) { int i; @@ -371,7 +391,7 @@ delta_info_t *Delta_FindStructByDelta( const delta_t *pFields ) return NULL; } -void Delta_CustomEncode( delta_info_t *dt, const void *from, const void *to ) +static void Delta_CustomEncode( delta_info_t *dt, const void *from, const void *to ) { int i; @@ -387,7 +407,7 @@ void Delta_CustomEncode( delta_info_t *dt, const void *from, const void *to ) } } -delta_field_t *Delta_FindFieldInfo( const delta_field_t *pInfo, const char *fieldName ) +static delta_field_t *Delta_FindFieldInfo( const delta_field_t *pInfo, const char *fieldName ) { if( !fieldName || !*fieldName ) return NULL; @@ -400,7 +420,7 @@ delta_field_t *Delta_FindFieldInfo( const delta_field_t *pInfo, const char *fiel return NULL; } -int Delta_IndexForFieldInfo( const delta_field_t *pInfo, const char *fieldName ) +static int Delta_IndexForFieldInfo( const delta_field_t *pInfo, const char *fieldName ) { int i; @@ -415,7 +435,7 @@ int Delta_IndexForFieldInfo( const delta_field_t *pInfo, const char *fieldName ) return -1; } -qboolean Delta_AddField( const char *pStructName, const char *pName, int flags, int bits, float mul, float post_mul ) +static qboolean Delta_AddField( const char *pStructName, const char *pName, int flags, int bits, float mul, float post_mul ) { delta_info_t *dt; delta_field_t *pFieldInfo; @@ -494,14 +514,14 @@ void Delta_WriteTableField( sizebuf_t *msg, int tableIndex, const delta_t *pFiel MSG_WriteUBitLong( msg, pField->bits - 1, 5 ); // max received value is 32 (32 bit) // multipliers is null-compressed - if( !Q_equal(pField->multiplier, 1.0f) ) + if( !Q_equal( pField->multiplier, 1.0f )) { MSG_WriteOneBit( msg, 1 ); MSG_WriteFloat( msg, pField->multiplier ); } else MSG_WriteOneBit( msg, 0 ); - if( !Q_equal(pField->post_multiplier, 1.0f) ) + if( !Q_equal( pField->post_multiplier, 1.0f )) { MSG_WriteOneBit( msg, 1 ); MSG_WriteFloat( msg, pField->post_multiplier ); @@ -555,7 +575,7 @@ void Delta_ParseTableField( sizebuf_t *msg ) Delta_AddField( dt->pName, pName, flags, bits, mul, post_mul ); } -qboolean Delta_ParseField( char **delta_script, const delta_field_t *pInfo, delta_t *pField, qboolean bPost ) +static qboolean Delta_ParseField( char **delta_script, const delta_field_t *pInfo, delta_t *pField, qboolean bPost ) { string token; delta_field_t *pFieldInfo; @@ -633,7 +653,7 @@ qboolean Delta_ParseField( char **delta_script, const delta_field_t *pInfo, delt // read delta-bits if(( *delta_script = COM_ParseFile( *delta_script, token, sizeof( token ))) == NULL ) { - Con_DPrintf( S_ERROR "Delta_ReadField: %s field bits argument is missing\n", pField->name ); + Con_DPrintf( S_ERROR "Delta_ParseField: %s field bits argument is missing\n", pField->name ); return false; } @@ -642,14 +662,14 @@ qboolean Delta_ParseField( char **delta_script, const delta_field_t *pInfo, delt *delta_script = COM_ParseFile( *delta_script, token, sizeof( token )); if( Q_strcmp( token, "," )) { - Con_DPrintf( S_ERROR "Delta_ReadField: expected ',', found '%s' instead\n", token ); + Con_DPrintf( S_ERROR "Delta_ParseField: expected ',', found '%s' instead\n", token ); return false; } // read delta-multiplier if(( *delta_script = COM_ParseFile( *delta_script, token, sizeof( token ))) == NULL ) { - Con_DPrintf( S_ERROR "Delta_ReadField: %s missing 'multiplier' argument\n", pField->name ); + Con_DPrintf( S_ERROR "Delta_ParseField: %s missing 'multiplier' argument\n", pField->name ); return false; } @@ -660,14 +680,14 @@ qboolean Delta_ParseField( char **delta_script, const delta_field_t *pInfo, delt *delta_script = COM_ParseFile( *delta_script, token, sizeof( token )); if( Q_strcmp( token, "," )) { - Con_DPrintf( S_ERROR "Delta_ReadField: expected ',', found '%s' instead\n", token ); + Con_DPrintf( S_ERROR "Delta_ParseField: expected ',', found '%s' instead\n", token ); return false; } // read delta-postmultiplier if(( *delta_script = COM_ParseFile( *delta_script, token, sizeof( token ))) == NULL ) { - Con_DPrintf( S_ERROR "Delta_ReadField: %s missing 'post_multiply' argument\n", pField->name ); + Con_DPrintf( S_ERROR "Delta_ParseField: %s missing 'post_multiply' argument\n", pField->name ); return false; } @@ -695,7 +715,7 @@ qboolean Delta_ParseField( char **delta_script, const delta_field_t *pInfo, delt return true; } -void Delta_ParseTable( char **delta_script, delta_info_t *dt, const char *encodeDll, const char *encodeFunc ) +static void Delta_ParseTable( char **delta_script, delta_info_t *dt, const char *encodeDll, const char *encodeFunc ) { string token; delta_t *pField; @@ -749,7 +769,7 @@ void Delta_ParseTable( char **delta_script, delta_info_t *dt, const char *encode dt->bInitialized = true; // table is ok } -void Delta_InitFields( void ) +static void Delta_InitFields( void ) { byte *afile; char *pfile; @@ -773,7 +793,7 @@ void Delta_InitFields( void ) pfile = COM_ParseFile( pfile, encodeDll, sizeof( encodeDll )); if( !Q_stricmp( encodeDll, "none" )) - Q_strcpy( encodeFunc, "null" ); + Q_strncpy( encodeFunc, "null", sizeof( encodeFunc )); else pfile = COM_ParseFile( pfile, encodeFunc, sizeof( encodeFunc )); // jump to '{' @@ -894,7 +914,7 @@ Delta_ClampIntegerField prevent data to out of range ===================== */ -int Delta_ClampIntegerField( delta_t *pField, int iValue, qboolean bSigned, int numbits ) +static int Delta_ClampIntegerField( delta_t *pField, int iValue, qboolean bSigned, int numbits ) { #ifdef _DEBUG if( numbits < 32 && abs( iValue ) >= (uint)BIT( numbits )) @@ -902,7 +922,7 @@ int Delta_ClampIntegerField( delta_t *pField, int iValue, qboolean bSigned, int #endif if( numbits < 32 ) { - int signbits = bSigned ? (numbits - 1) : numbits; + int signbits = bSigned ? ( numbits - 1 ) : numbits; int maxnum = BIT( signbits ) - 1; int minnum = bSigned ? ( -maxnum - 1 ) : 0; iValue = bound( minnum, iValue, maxnum ); @@ -919,7 +939,7 @@ compare fields by offsets assume from and to is valid ===================== */ -qboolean Delta_CompareField( delta_t *pField, void *from, void *to, double timebase ) +static qboolean Delta_CompareField( delta_t *pField, void *from, void *to, double timebase ) { qboolean bSigned = ( pField->flags & DT_SIGNED ) ? true : false; float val_a, val_b; @@ -1009,8 +1029,8 @@ qboolean Delta_CompareField( delta_t *pField, void *from, void *to, double timeb val_b = Q_rint((*(float *)((byte *)to + pField->offset )) * 100.0 ); val_a -= Q_rint(timebase * 100.0); val_b -= Q_rint(timebase * 100.0); - fromF = *((int *)&val_a); - toF = *((int *)&val_b); + fromF = FloatAsInt( val_a ); + toF = FloatAsInt( val_b ); } else if( pField->flags & DT_TIMEWINDOW_BIG ) { @@ -1030,8 +1050,8 @@ qboolean Delta_CompareField( delta_t *pField, void *from, void *to, double timeb val_b = timebase - val_b; } - fromF = *((int *)&val_a); - toF = *((int *)&val_b); + fromF = FloatAsInt( val_a ); + toF = FloatAsInt( val_b ); } else if( pField->flags & DT_STRING ) { @@ -1111,7 +1131,7 @@ write fields by offsets assume from and to is valid ===================== */ -qboolean Delta_WriteField( sizebuf_t *msg, delta_t *pField, void *from, void *to, double timebase ) +static 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; @@ -1259,7 +1279,7 @@ read fields by offsets assume 'from' and 'to' is valid ===================== */ -qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, void *to, double timebase ) +static qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, void *to, double timebase ) { qboolean bSigned = ( pField->flags & DT_SIGNED ) ? true : false; float flValue, flAngle, flTime; diff --git a/engine/common/net_encode.h b/engine/common/net_encode.h index 85d533c9..0b594797 100644 --- a/engine/common/net_encode.h +++ b/engine/common/net_encode.h @@ -18,26 +18,6 @@ GNU General Public License for more details. #include "eiface.h" -#define DT_BYTE BIT( 0 ) // A byte -#define DT_SHORT BIT( 1 ) // 2 byte field -#define DT_FLOAT BIT( 2 ) // A floating point field -#define DT_INTEGER BIT( 3 ) // 4 byte integer -#define DT_ANGLE BIT( 4 ) // A floating point angle ( will get masked correctly ) -#define DT_TIMEWINDOW_8 BIT( 5 ) // A floating point timestamp, relative to sv.time -#define DT_TIMEWINDOW_BIG BIT( 6 ) // and re-encoded on the client relative to the client's clock -#define DT_STRING BIT( 7 ) // A null terminated string, sent as 8 byte chars -#define DT_SIGNED BIT( 8 ) // sign modificator - -#define NUM_FIELDS( x ) ((sizeof( x ) / sizeof( x[0] )) - 1) - -// helper macroses -#define ENTS_DEF( x ) #x, offsetof( entity_state_t, x ), sizeof( ((entity_state_t *)0)->x ) -#define UCMD_DEF( x ) #x, offsetof( usercmd_t, x ), sizeof( ((usercmd_t *)0)->x ) -#define EVNT_DEF( x ) #x, offsetof( event_args_t, x ), sizeof( ((event_args_t *)0)->x ) -#define PHYS_DEF( x ) #x, offsetof( movevars_t, x ), sizeof( ((movevars_t *)0)->x ) -#define CLDT_DEF( x ) #x, offsetof( clientdata_t, x ), sizeof( ((clientdata_t *)0)->x ) -#define WPDT_DEF( x ) #x, offsetof( weapon_data_t, x ), sizeof( ((weapon_data_t *)0)->x ) - enum { CUSTOM_NONE = 0, @@ -97,7 +77,6 @@ typedef struct void Delta_Init( void ); void Delta_InitClient( void ); void Delta_Shutdown( void ); -void Delta_InitFields( void ); int Delta_NumTables( void ); delta_info_t *Delta_FindStructByIndex( int index ); void Delta_AddEncoder( char *name, pfnDeltaEncode encodeFunc ); diff --git a/engine/common/net_ws.c b/engine/common/net_ws.c index bc676d30..779b1052 100644 --- a/engine/common/net_ws.c +++ b/engine/common/net_ws.c @@ -25,6 +25,10 @@ GNU General Public License for more details. #else #include "platform/posix/net.h" #endif +#if XASH_PSVITA +#include "platform/psvita/net_psvita.h" +static const struct in6_addr in6addr_any; +#endif #define NET_USE_FRAGMENTS @@ -394,7 +398,7 @@ void *NET_ThreadStart( void *unused ) #define mutex_lock EnterCriticalSection #define mutex_unlock LeaveCriticalSection #define detach_thread( x ) CloseHandle(x) -#define create_thread( pfn ) nsthread.thread = CreateThread( NULL, 0, pfn, NULL, 0, NULL ) +#define create_thread( pfn ) ( nsthread.thread = CreateThread( NULL, 0, pfn, NULL, 0, NULL )) #define mutex_t CRITICAL_SECTION #define thread_t HANDLE DWORD WINAPI NET_ThreadStart( LPVOID unused ) @@ -473,7 +477,7 @@ idnewt:28000 192.246.40.70:28000 ============= */ -static int NET_StringToSockaddr( const char *s, struct sockaddr_storage *sadr, qboolean nonblocking, int family ) +static net_gai_state_t NET_StringToSockaddr( const char *s, struct sockaddr_storage *sadr, qboolean nonblocking, int family ) { int ret = 0, port; char *colon; @@ -482,7 +486,7 @@ static int NET_StringToSockaddr( const char *s, struct sockaddr_storage *sadr, q struct sockaddr_storage temp; if( !net.initialized ) - return false; + return NET_EAI_NONAME; memset( sadr, 0, sizeof( *sadr )); @@ -493,7 +497,7 @@ static int NET_StringToSockaddr( const char *s, struct sockaddr_storage *sadr, q ((struct sockaddr_in6 *)sadr)->sin6_port = htons((short)port); memcpy(((struct sockaddr_in6 *)sadr)->sin6_addr.s6_addr, ip6, sizeof( struct in6_addr )); - return true; + return NET_EAI_OK; } Q_strncpy( copy, s, sizeof( copy )); @@ -526,14 +530,14 @@ static int NET_StringToSockaddr( const char *s, struct sockaddr_storage *sadr, q if( nsthread.busy ) { mutex_unlock( &nsthread.mutexres ); - return 2; + return NET_EAI_AGAIN; } if( !Q_strcmp( copy, nsthread.hostname )) { ret = nsthread.result; - nsthread.hostname[0] = 0; + nsthread.hostname[0] = '\0'; nsthread.family = AF_UNSPEC; temp = nsthread.addr; memset( &nsthread.addr, 0, sizeof( nsthread.addr )); @@ -550,11 +554,11 @@ static int NET_StringToSockaddr( const char *s, struct sockaddr_storage *sadr, q if( create_thread( NET_ThreadStart )) { asyncfailed = false; - return 2; + return NET_EAI_AGAIN; } else // failed to create thread { - Con_Reportf( S_ERROR "NET_StringToSockaddr: failed to create thread!\n"); + Con_Reportf( S_ERROR "NET_StringToSockaddr: failed to create thread!\n"); nsthread.busy = false; } } @@ -573,7 +577,8 @@ static int NET_StringToSockaddr( const char *s, struct sockaddr_storage *sadr, q if( family == AF_INET6 ) sadr->ss_family = AF_INET6; else sadr->ss_family = AF_INET; - return 0; + + return NET_EAI_NONAME; } sadr->ss_family = temp.ss_family; @@ -591,7 +596,7 @@ static int NET_StringToSockaddr( const char *s, struct sockaddr_storage *sadr, q } } - return 1; + return NET_EAI_OK; } /* @@ -727,7 +732,8 @@ const char *NET_AdrToString( const netadr_t a ) return s; } - Q_sprintf( s, "%i.%i.%i.%i:%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3], ntohs( a.port )); + Q_snprintf( s, sizeof( s ), + "%i.%i.%i.%i:%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3], ntohs( a.port )); return s; } @@ -753,7 +759,8 @@ const char *NET_BaseAdrToString( const netadr_t a ) return s; } - Q_sprintf( s, "%i.%i.%i.%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3] ); + Q_snprintf( s, sizeof( s ), + "%i.%i.%i.%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3] ); return s; } @@ -1037,10 +1044,9 @@ qboolean NET_StringToAdrEx( const char *string, netadr_t *adr, int family ) return true; } - if( !NET_StringToSockaddr( string, &s, false, family )) + if( NET_StringToSockaddr( string, &s, false, family ) != NET_EAI_OK ) return false; NET_SockadrToNetadr( &s, adr ); - return true; } @@ -1050,13 +1056,13 @@ qboolean NET_StringToAdr( const char *string, netadr_t *adr ) return NET_StringToAdrEx( string, adr, AF_UNSPEC ); } -int NET_StringToAdrNB( const char *string, netadr_t *adr ) +net_gai_state_t NET_StringToAdrNB( const char *string, netadr_t *adr ) { struct sockaddr_storage s; - int res; + net_gai_state_t res; memset( adr, 0, sizeof( netadr_t )); - if( !Q_stricmp( string, "localhost" ) || !Q_stricmp( string, "loopback" )) + if( !Q_stricmp( string, "localhost" ) || !Q_stricmp( string, "loopback" )) { adr->type = NA_LOOPBACK; return true; @@ -1064,12 +1070,10 @@ int NET_StringToAdrNB( const char *string, netadr_t *adr ) res = NET_StringToSockaddr( string, &s, true, AF_UNSPEC ); - if( res == 0 || res == 2 ) - return res; + if( res == NET_EAI_OK ) + NET_SockadrToNetadr( &s, adr ); - NET_SockadrToNetadr( &s, adr ); - - return true; + return res; } /* @@ -1679,68 +1683,6 @@ void NET_SendPacket( netsrc_t sock, size_t length, const void *data, netadr_t to NET_SendPacketEx( sock, length, data, to, 0 ); } -/* -==================== -NET_BufferToBufferCompress - -generic fast compression -==================== -*/ -qboolean NET_BufferToBufferCompress( byte *dest, uint *destLen, byte *source, uint sourceLen ) -{ - uint uCompressedLen = 0; - byte *pbOut = NULL; - - memcpy( dest, source, sourceLen ); - pbOut = LZSS_Compress( source, sourceLen, &uCompressedLen ); - - if( pbOut && uCompressedLen > 0 && uCompressedLen <= *destLen ) - { - memcpy( dest, pbOut, uCompressedLen ); - *destLen = uCompressedLen; - free( pbOut ); - return true; - } - else - { - if( pbOut ) free( pbOut ); - memcpy( dest, source, sourceLen ); - *destLen = sourceLen; - return false; - } -} - -/* -==================== -NET_BufferToBufferDecompress - -generic fast decompression -==================== -*/ -qboolean NET_BufferToBufferDecompress( byte *dest, uint *destLen, byte *source, uint sourceLen ) -{ - if( LZSS_IsCompressed( source )) - { - uint uDecompressedLen = LZSS_GetActualSize( source ); - - if( uDecompressedLen <= *destLen ) - { - *destLen = LZSS_Decompress( source, dest ); - } - else - { - return false; - } - } - else - { - memcpy( dest, source, sourceLen ); - *destLen = sourceLen; - } - - return true; -} - /* ==================== NET_IPSocket @@ -2040,11 +1982,11 @@ void NET_Config( qboolean multiplayer, qboolean changeport ) nov6 = net.allow_ip6 && NET_IsSocketError( net.ip6_sockets[NS_SERVER] ); if( nov4 && nov6 ) - Host_Error( "Couldn't allocate IPv4 and IPv6 server ports." ); + Host_Error( "Couldn't allocate IPv4 and IPv6 server ports.\n" ); else if( nov4 && !nov6 ) - Con_Printf( S_ERROR "Couldn't allocate IPv4 server port" ); + Con_Printf( S_ERROR "Couldn't allocate IPv4 server port\n" ); else if( !nov4 && nov6 ) - Con_Printf( S_ERROR "Couldn't allocate IPv6 server_port" ); + Con_Printf( S_ERROR "Couldn't allocate IPv6 server_port\n" ); } // get our local address, if possible @@ -2162,9 +2104,9 @@ void NET_Init( void ) net_address = Cvar_Get( "net_address", "0", FCVAR_PRIVILEGED|FCVAR_READ_ONLY, "contain local address of current client" ); net_ipname = Cvar_Get( "ip", "localhost", FCVAR_PRIVILEGED, "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_hostport = Cvar_Getf( "hostport", FCVAR_READ_ONLY, "network default host port", "%i", PORT_SERVER ); net_ipclientport = Cvar_Get( "ip_clientport", "0", FCVAR_READ_ONLY, "network ip client port" ); - net_clientport = Cvar_Get( "clientport", va( "%i", PORT_CLIENT ), FCVAR_READ_ONLY, "network default client port" ); + net_clientport = Cvar_Getf( "clientport", FCVAR_READ_ONLY, "network default client port", "%i", PORT_CLIENT ); net_fakelag = Cvar_Get( "fakelag", "0", FCVAR_PRIVILEGED, "lag all incoming network data (including loopback) by xxx ms." ); net_fakeloss = Cvar_Get( "fakeloss", "0", FCVAR_PRIVILEGED, "act like we dropped the packet this % of the time." ); @@ -2574,7 +2516,6 @@ void HTTP_Run( void ) for( curfile = http.first_file; curfile; curfile = curfile->next ) { - int res; struct sockaddr_storage addr; if( curfile->state == HTTP_FREE ) @@ -2635,18 +2576,23 @@ void HTTP_Run( void ) if( curfile->state < HTTP_NS_RESOLVED ) { + net_gai_state_t res; + char hostport[MAX_VA_STRING]; + if( fResolving ) continue; - res = NET_StringToSockaddr( va( "%s:%d", curfile->server->host, curfile->server->port ), &addr, true, AF_INET ); + Q_snprintf( hostport, sizeof( hostport ), "%s:%d", curfile->server->host, curfile->server->port ); - if( res == 2 ) + res = NET_StringToSockaddr( hostport, &addr, true, AF_INET ); + + if( res == NET_EAI_AGAIN ) { fResolving = true; continue; } - if( !res ) + if( res == NET_EAI_NONAME ) { Con_Printf( S_ERROR "failed to resolve server address for %s!\n", curfile->server->host ); HTTP_FreeFile( curfile, true ); // Cannot connect @@ -2657,7 +2603,7 @@ void HTTP_Run( void ) if( curfile->state < HTTP_CONNECTED ) // Connection not enstabilished { - res = connect( curfile->socket, (struct sockaddr*)&addr, NET_SockAddrLen( &addr ) ); + int res = connect( curfile->socket, (struct sockaddr*)&addr, NET_SockAddrLen( &addr ) ); if( res ) { @@ -2676,11 +2622,23 @@ void HTTP_Run( void ) if( curfile->state < HTTP_REQUEST ) // Request not formatted { + string useragent; + + if( !COM_CheckStringEmpty( http_useragent->string ) || !Q_strcmp( http_useragent->string, "xash3d" )) + { + Q_snprintf( useragent, sizeof( useragent ), "%s/%s (%s-%s; build %d; %s)", + XASH_ENGINE_NAME, XASH_VERSION, Q_buildos( ), Q_buildarch( ), Q_buildnum( ), Q_buildcommit( )); + } + else + { + Q_strncpy( useragent, http_useragent->string, sizeof( useragent )); + } + curfile->query_length = Q_snprintf( curfile->buf, sizeof( curfile->buf ), "GET %s%s HTTP/1.0\r\n" "Host: %s\r\n" "User-Agent: %s\r\n\r\n", curfile->server->path, - curfile->path, curfile->server->host, http_useragent->string ); + curfile->path, curfile->server->host, useragent ); curfile->header_size = 0; curfile->bytes_sent = 0; curfile->state = HTTP_REQUEST; @@ -2692,8 +2650,7 @@ void HTTP_Run( void ) while( curfile->bytes_sent < curfile->query_length ) { - res = send( curfile->socket, curfile->buf + curfile->bytes_sent, curfile->query_length - curfile->bytes_sent, 0 ); - + int res = send( curfile->socket, curfile->buf + curfile->bytes_sent, curfile->query_length - curfile->bytes_sent, 0 ); if( res < 0 ) { @@ -3023,16 +2980,16 @@ void HTTP_Init( void ) http.first_file = http.last_file = NULL; - Cmd_AddRestrictedCommand("http_download", &HTTP_Download_f, "add file to download queue"); - Cmd_AddRestrictedCommand("http_skip", &HTTP_Skip_f, "skip current download server"); - Cmd_AddRestrictedCommand("http_cancel", &HTTP_Cancel_f, "cancel current download"); - Cmd_AddRestrictedCommand("http_clear", &HTTP_Clear_f, "cancel all downloads"); - Cmd_AddCommand("http_list", &HTTP_List_f, "list all queued downloads"); - Cmd_AddCommand("http_addcustomserver", &HTTP_AddCustomServer_f, "add custom fastdl server"); - http_useragent = Cvar_Get( "http_useragent", "xash3d", FCVAR_ARCHIVE, "User-Agent string" ); - http_autoremove = Cvar_Get( "http_autoremove", "1", FCVAR_ARCHIVE, "remove broken files" ); - http_timeout = Cvar_Get( "http_timeout", "45", FCVAR_ARCHIVE, "timeout for http downloader" ); - http_maxconnections = Cvar_Get( "http_maxconnections", "4", FCVAR_ARCHIVE, "maximum http connection number" ); + Cmd_AddRestrictedCommand( "http_download", HTTP_Download_f, "add file to download queue" ); + Cmd_AddRestrictedCommand( "http_skip", HTTP_Skip_f, "skip current download server" ); + Cmd_AddRestrictedCommand( "http_cancel", HTTP_Cancel_f, "cancel current download" ); + Cmd_AddRestrictedCommand( "http_clear", HTTP_Clear_f, "cancel all downloads" ); + Cmd_AddRestrictedCommand( "http_list", HTTP_List_f, "list all queued downloads" ); + Cmd_AddCommand( "http_addcustomserver", HTTP_AddCustomServer_f, "add custom fastdl server"); + http_useragent = Cvar_Get( "http_useragent", "", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "User-Agent string" ); + http_autoremove = Cvar_Get( "http_autoremove", "1", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "remove broken files" ); + http_timeout = Cvar_Get( "http_timeout", "45", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "timeout for http downloader" ); + http_maxconnections = Cvar_Get( "http_maxconnections", "4", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "maximum http connection number" ); // Read servers from fastdl.txt line = serverfile = (char *)FS_LoadFile( "fastdl.txt", 0, false ); diff --git a/engine/common/net_ws.h b/engine/common/net_ws.h index 72780902..effd597a 100644 --- a/engine/common/net_ws.h +++ b/engine/common/net_ws.h @@ -23,6 +23,13 @@ typedef enum NS_COUNT } netsrc_t; +typedef enum +{ + NET_EAI_NONAME = 0, + NET_EAI_OK = 1, + NET_EAI_AGAIN = 2 +} net_gai_state_t; + // Max length of unreliable message #define MAX_DATAGRAM 16384 @@ -59,14 +66,12 @@ qboolean NET_IsReservedAdr( netadr_t a ); qboolean NET_CompareClassBAdr( const netadr_t a, const netadr_t b ); qboolean NET_StringToAdr( const char *string, netadr_t *adr ); qboolean NET_StringToFilterAdr( const char *s, netadr_t *adr, uint *prefixlen ); -int NET_StringToAdrNB( const char *string, netadr_t *adr ); +net_gai_state_t NET_StringToAdrNB( const char *string, netadr_t *adr ); int NET_CompareAdrSort( const void *_a, const void *_b ); qboolean NET_CompareAdr( const netadr_t a, const netadr_t b ); qboolean NET_CompareBaseAdr( const netadr_t a, const netadr_t b ); qboolean NET_CompareAdrByMask( const netadr_t a, const netadr_t b, uint prefixlen ); qboolean NET_GetPacket( netsrc_t sock, netadr_t *from, byte *data, size_t *length ); -qboolean NET_BufferToBufferCompress( byte *dest, uint *destLen, byte *source, uint sourceLen ); -qboolean NET_BufferToBufferDecompress( byte *dest, uint *destLen, byte *source, uint sourceLen ); void NET_SendPacket( netsrc_t sock, size_t length, const void *data, netadr_t to ); void NET_SendPacketEx( netsrc_t sock, size_t length, const void *data, netadr_t to, size_t splitsize ); void NET_ClearLagData( qboolean bClient, qboolean bServer ); @@ -82,6 +87,7 @@ void HTTP_AddCustomServer( const char *url ); void HTTP_AddDownload( const char *path, int size, qboolean process ); void HTTP_ClearCustomServers( void ); void HTTP_Shutdown( void ); +void HTTP_ResetProcessState( void ); void HTTP_Init( void ); void HTTP_Run( void ); diff --git a/engine/common/netchan.h b/engine/common/netchan.h index 3acc28bb..6a283803 100644 --- a/engine/common/netchan.h +++ b/engine/common/netchan.h @@ -294,7 +294,6 @@ qboolean Netchan_CopyNormalFragments( netchan_t *chan, sizebuf_t *msg, size_t *l qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg ); void Netchan_CreateFragments( netchan_t *chan, sizebuf_t *msg ); int Netchan_CreateFileFragments( netchan_t *chan, const char *filename ); -void Netchan_Transmit( netchan_t *chan, int lengthInBytes, byte *data ); void Netchan_TransmitBits( netchan_t *chan, int lengthInBits, byte *data ); void Netchan_OutOfBand( int net_socket, netadr_t adr, int length, byte *data ); void Netchan_OutOfBandPrint( int net_socket, netadr_t adr, const char *format, ... ) _format( 3 ); diff --git a/engine/common/protocol.h b/engine/common/protocol.h index 7d525677..868ebbea 100644 --- a/engine/common/protocol.h +++ b/engine/common/protocol.h @@ -128,7 +128,7 @@ GNU General Public License for more details. #define MAX_USER_MESSAGES 197 // another 58 messages reserved for engine routines #define MAX_DLIGHTS 32 // dynamic lights (rendered per one frame) #define MAX_ELIGHTS 64 // entity only point lights -#define MAX_LIGHTSTYLES 64 // original quake limit +#define MAX_LIGHTSTYLES 256 // a1ba: increased from 64 to 256, protocol limit #define MAX_RENDER_DECALS 4096 // max rendering decals per a level // sound proto diff --git a/engine/common/sequence.c b/engine/common/sequence.c index ca9b72e1..187ced7b 100644 --- a/engine/common/sequence.c +++ b/engine/common/sequence.c @@ -62,7 +62,7 @@ Sequence_GetCommandEnumForName ============= */ -sequenceCommandEnum_e Sequence_GetCommandEnumForName( const char *commandName, sequenceCommandType_e type ) +static sequenceCommandEnum_e Sequence_GetCommandEnumForName( const char *commandName, sequenceCommandType_e type ) { int i; @@ -82,7 +82,7 @@ Sequence_ResetDefaults ============= */ -void Sequence_ResetDefaults( sequenceCommandLine_s *destination, sequenceCommandLine_s *source ) +static void Sequence_ResetDefaults( sequenceCommandLine_s *destination, sequenceCommandLine_s *source ) { if( !source ) { @@ -131,7 +131,7 @@ Sequence_WriteDefaults ============= */ -void Sequence_WriteDefaults( sequenceCommandLine_s *source, sequenceCommandLine_s *destination ) +static void Sequence_WriteDefaults( sequenceCommandLine_s *source, sequenceCommandLine_s *destination ) { if( !destination ) Con_Reportf( S_ERROR "Attempt to bake defaults into a non-existant command." ); @@ -210,7 +210,7 @@ Sequence_BakeDefaults ============= */ -void Sequence_BakeDefaults( sequenceCommandLine_s *destination, sequenceCommandLine_s *source ) +static void Sequence_BakeDefaults( sequenceCommandLine_s *destination, sequenceCommandLine_s *source ) { char *saveName, *saveMessage; @@ -243,7 +243,7 @@ Sequence_SkipWhitespace ============= */ -qboolean Sequence_SkipWhitespace( void ) +static qboolean Sequence_SkipWhitespace( void ) { qboolean newLine = false; @@ -267,7 +267,7 @@ Sequence_IsNameValueChar ============= */ -qboolean Sequence_IsNameValueChar( char ch ) +static qboolean Sequence_IsNameValueChar( char ch ) { if( isalnum( ch ) ) return true; @@ -291,7 +291,7 @@ Sequence_IsSymbol ============= */ -qboolean Sequence_IsSymbol( char ch ) +static qboolean Sequence_IsSymbol( char ch ) { switch( ch ) { @@ -316,7 +316,7 @@ Sequence_GetNameValueString ============= */ -size_t Sequence_GetNameValueString( char *token, size_t len ) +static size_t Sequence_GetNameValueString( char *token, size_t len ) { char *p; @@ -346,7 +346,7 @@ Sequence_GetSymbol ============= */ -char Sequence_GetSymbol( void ) +static char Sequence_GetSymbol( void ) { char ch; @@ -366,7 +366,7 @@ Sequence_ValidateNameValueString ============= */ -void Sequence_ValidateNameValueString( char *token ) +static void Sequence_ValidateNameValueString( char *token ) { char *scan; @@ -383,7 +383,7 @@ Sequence_GetToken ============= */ -size_t Sequence_GetToken( char *token, size_t size ) +static size_t Sequence_GetToken( char *token, size_t size ) { Sequence_SkipWhitespace( ); @@ -408,7 +408,7 @@ Sequence_GetLine ============= */ -size_t Sequence_GetLine( char *line, int lineMaxLen ) +static size_t Sequence_GetLine( char *line, int lineMaxLen ) { int lineLen; char *read; @@ -439,7 +439,7 @@ Sequence_StripComments ============= */ -void Sequence_StripComments( char *buffer, int *pBufSize ) +static void Sequence_StripComments( char *buffer, int *pBufSize ) { char *eof = buffer + *pBufSize; char *read = buffer; @@ -506,7 +506,7 @@ Sequence_ReadInt ============= */ -int Sequence_ReadInt( void ) +static int Sequence_ReadInt( void ) { char str[MAX_STRING]; @@ -522,7 +522,7 @@ Sequence_ReadFloat ============= */ -float Sequence_ReadFloat( void ) +static float Sequence_ReadFloat( void ) { char str[MAX_STRING]; @@ -538,7 +538,7 @@ Sequence_ReadFloat ============= */ -void Sequence_ReadString( char **dest, char *string, size_t len ) +static void Sequence_ReadString( char **dest, char *string, size_t len ) { Sequence_SkipWhitespace( ); Sequence_GetNameValueString( string, len ); @@ -552,7 +552,7 @@ Sequence_ReadQuotedString ============= */ -void Sequence_ReadQuotedString( char **dest, char *str, size_t len ) +static void Sequence_ReadQuotedString( char **dest, char *str, size_t len ) { char *write, ch; @@ -585,7 +585,7 @@ Sequence_ConfirmCarriageReturnOrSymbol ============= */ -qboolean Sequence_ConfirmCarriageReturnOrSymbol( char symbol ) +static qboolean Sequence_ConfirmCarriageReturnOrSymbol( char symbol ) { if( Sequence_SkipWhitespace( ) ) return true; @@ -599,7 +599,7 @@ Sequence_IsCommandAModifier ============= */ -qboolean Sequence_IsCommandAModifier( sequenceCommandEnum_e commandEnum ) +static qboolean Sequence_IsCommandAModifier( sequenceCommandEnum_e commandEnum ) { int i; @@ -619,7 +619,7 @@ Sequence_ReadCommandData ============= */ -void Sequence_ReadCommandData( sequenceCommandEnum_e commandEnum, sequenceCommandLine_s *defaults ) +static void Sequence_ReadCommandData( sequenceCommandEnum_e commandEnum, sequenceCommandLine_s *defaults ) { char temp[1024]; @@ -722,7 +722,7 @@ Sequence_ParseModifier ============= */ -char Sequence_ParseModifier( sequenceCommandLine_s *defaults ) +static char Sequence_ParseModifier( sequenceCommandLine_s *defaults ) { char modifierName[MAX_STRING]; char delimiter; @@ -756,7 +756,7 @@ Sequence_AddCommandLineToEntry ============= */ -void Sequence_AddCommandLineToEntry( sequenceCommandLine_s *commandLine, sequenceEntry_s *entry ) +static void Sequence_AddCommandLineToEntry( sequenceCommandLine_s *commandLine, sequenceEntry_s *entry ) { sequenceCommandLine_s *scan; @@ -776,7 +776,7 @@ Sequence_ParseModifierLine ============= */ -char Sequence_ParseModifierLine( sequenceEntry_s *entry, sequenceCommandType_e modifierType ) +static char Sequence_ParseModifierLine( sequenceEntry_s *entry, sequenceCommandType_e modifierType ) { sequenceCommandLine_s *newCommandLine; char delimiter = ','; @@ -808,7 +808,7 @@ Sequence_ParseCommand ============= */ -char Sequence_ParseCommand( sequenceCommandLine_s *newCommandLine ) +static char Sequence_ParseCommand( sequenceCommandLine_s *newCommandLine ) { char commandName[MAX_STRING], ch; sequenceCommandEnum_e commandEnum; @@ -847,7 +847,7 @@ Sequence_ParseCommandLine ============= */ -char Sequence_ParseCommandLine( sequenceEntry_s *entry ) +static char Sequence_ParseCommandLine( sequenceEntry_s *entry ) { char symbol; sequenceCommandLine_s *newCommandLine; @@ -874,7 +874,7 @@ Sequence_ParseMacro ============= */ -char Sequence_ParseMacro( sequenceEntry_s *entry ) +static char Sequence_ParseMacro( sequenceEntry_s *entry ) { char symbol; sequenceCommandLine_s *newCommandLine; @@ -902,7 +902,7 @@ Sequence_ParseLine ============= */ -char Sequence_ParseLine( char start, sequenceEntry_s *entry ) +static char Sequence_ParseLine( char start, sequenceEntry_s *entry ) { char end = '\0'; @@ -933,7 +933,7 @@ Sequence_CalcEntryDuration ============= */ -float Sequence_CalcEntryDuration( sequenceEntry_s *entry ) +static float Sequence_CalcEntryDuration( sequenceEntry_s *entry ) { float duration; sequenceCommandLine_s *cmd; @@ -952,7 +952,7 @@ Sequence_DoesEntryContainInfiniteLoop ============= */ -qboolean Sequence_DoesEntryContainInfiniteLoop( sequenceEntry_s *entry ) +static qboolean Sequence_DoesEntryContainInfiniteLoop( sequenceEntry_s *entry ) { sequenceCommandLine_s *cmd; @@ -971,7 +971,7 @@ Sequence_IsEntrySafe ============= */ -qboolean Sequence_IsEntrySafe( sequenceEntry_s *entry ) +static qboolean Sequence_IsEntrySafe( sequenceEntry_s *entry ) { float duration; sequenceCommandLine_s *cmd; @@ -998,7 +998,7 @@ Sequence_CreateDefaultsCommand ============= */ -void Sequence_CreateDefaultsCommand( sequenceEntry_s *entry ) +static void Sequence_CreateDefaultsCommand( sequenceEntry_s *entry ) { sequenceCommandLine_s *cmd; @@ -1026,7 +1026,7 @@ Sequence_ParseEntry ============= */ -char Sequence_ParseEntry( void ) +static char Sequence_ParseEntry( void ) { char symbol; char token[MAX_STRING]; @@ -1068,7 +1068,7 @@ Sequence_FindSentenceGroup ============= */ -sentenceGroupEntry_s *Sequence_FindSentenceGroup( const char *groupName ) +static sentenceGroupEntry_s *Sequence_FindSentenceGroup( const char *groupName ) { sentenceGroupEntry_s *groupEntry; @@ -1152,7 +1152,7 @@ Sequence_AddSentenceGroup ============= */ -sentenceGroupEntry_s *Sequence_AddSentenceGroup( char *groupName ) +static sentenceGroupEntry_s *Sequence_AddSentenceGroup( char *groupName ) { sentenceGroupEntry_s *entry, *last; @@ -1181,7 +1181,7 @@ Sequence_AddSentenceToGroup ============= */ -void Sequence_AddSentenceToGroup( char *groupName, char *data ) +static void Sequence_AddSentenceToGroup( char *groupName, char *data ) { sentenceEntry_s *entry, *last; sentenceGroupEntry_s *group; @@ -1223,7 +1223,7 @@ Sequence_ParseSentenceLine ============= */ -qboolean Sequence_ParseSentenceLine( void ) +static qboolean Sequence_ParseSentenceLine( void ) { char data[1024]; char fullgroup[64]; @@ -1265,7 +1265,7 @@ Sequence_ParseSentenceBlock ============== */ -char Sequence_ParseSentenceBlock( void ) +static char Sequence_ParseSentenceBlock( void ) { qboolean end = false; char ch = Sequence_GetSymbol( ); @@ -1286,7 +1286,7 @@ Sequence_ParseGlobalDataBlock ============== */ -char Sequence_ParseGlobalDataBlock( void ) +static char Sequence_ParseGlobalDataBlock( void ) { char token[MAX_STRING]; @@ -1304,7 +1304,7 @@ Sequence_GetEntryForName ============== */ -sequenceEntry_s *Sequence_GetEntryForName( const char *entryName ) +static sequenceEntry_s *Sequence_GetEntryForName( const char *entryName ) { sequenceEntry_s *scan; @@ -1323,7 +1323,7 @@ Sequence_CopyCommand ============== */ -sequenceCommandLine_s *Sequence_CopyCommand( sequenceCommandLine_s *commandOrig ) +static sequenceCommandLine_s *Sequence_CopyCommand( sequenceCommandLine_s *commandOrig ) { sequenceCommandLine_s *commandCopy; @@ -1354,7 +1354,7 @@ Sequence_CopyCommandList ============== */ -sequenceCommandLine_s *Sequence_CopyCommandList( sequenceCommandLine_s *list ) +static sequenceCommandLine_s *Sequence_CopyCommandList( sequenceCommandLine_s *list ) { sequenceCommandLine_s *scan, *copy, *new, *prev; @@ -1389,7 +1389,7 @@ Sequence_ExpandGosubsForEntry ============== */ -qboolean Sequence_ExpandGosubsForEntry( sequenceEntry_s *entry ) +static qboolean Sequence_ExpandGosubsForEntry( sequenceEntry_s *entry ) { sequenceCommandLine_s *cmd, *copyList, *scan; sequenceEntry_s *gosubEntry; @@ -1437,7 +1437,7 @@ Sequence_ExpandAllGosubs ============== */ -void Sequence_ExpandAllGosubs( void ) +static void Sequence_ExpandAllGosubs( void ) { sequenceEntry_s *scan; qboolean isComplete = true; @@ -1457,7 +1457,7 @@ Sequence_FlattenEntry ============== */ -void Sequence_FlattenEntry( sequenceEntry_s *entry ) +static void Sequence_FlattenEntry( sequenceEntry_s *entry ) { sequenceCommandLine_s *cmd, *last = NULL; @@ -1491,7 +1491,7 @@ Sequence_FlattenAllEntries ============== */ -void Sequence_FlattenAllEntries( void ) +static void Sequence_FlattenAllEntries( void ) { sequenceEntry_s *entry; @@ -1551,12 +1551,12 @@ Sequence_ParseFile ============== */ -void Sequence_ParseFile( const char *fileName, qboolean isGlobal ) +static void Sequence_ParseFile( const char *fileName, qboolean isGlobal ) { byte *buffer; fs_offset_t bufSize = 0; - Q_strcpy( g_sequenceParseFileName, fileName ); + Q_strncpy( g_sequenceParseFileName, fileName, sizeof( g_sequenceParseFileName )); g_sequenceParseFileIsGlobal = isGlobal; buffer = FS_LoadFile( va("sequences/%s.seq", fileName ), &bufSize, true ); @@ -1608,7 +1608,7 @@ Sequence_FreeCommand ============== */ -void Sequence_FreeCommand( sequenceCommandLine_s *kill ) +static void Sequence_FreeCommand( sequenceCommandLine_s *kill ) { Z_Free( kill->fireTargetNames ); Z_Free( kill->speakerName ); @@ -1625,7 +1625,7 @@ Sequence_FreeEntry ============== */ -void Sequence_FreeEntry( sequenceEntry_s *kill ) +static void Sequence_FreeEntry( sequenceEntry_s *kill ) { sequenceCommandLine_s *dead; @@ -1647,7 +1647,7 @@ Sequence_FreeSentence ============== */ -void Sequence_FreeSentence( sentenceEntry_s *sentenceEntry ) +static void Sequence_FreeSentence( sentenceEntry_s *sentenceEntry ) { Z_Free( sentenceEntry->data ); Z_Free( sentenceEntry ); @@ -1659,7 +1659,7 @@ Sequence_FreeSentenceGroup ============== */ -void Sequence_FreeSentenceGroup( sentenceGroupEntry_s *groupEntry ) +static void Sequence_FreeSentenceGroup( sentenceGroupEntry_s *groupEntry ) { Z_Free( groupEntry->groupName ); Z_Free( groupEntry ); @@ -1671,7 +1671,7 @@ Sequence_FreeSentenceGroupEntries ============== */ -void Sequence_FreeSentenceGroupEntries( sentenceGroupEntry_s *groupEntry, qboolean purgeGlobals ) +static void Sequence_FreeSentenceGroupEntries( sentenceGroupEntry_s *groupEntry, qboolean purgeGlobals ) { sentenceEntry_s *sentenceEntry; sentenceEntry_s *deadSentence; @@ -1711,7 +1711,7 @@ Sequence_PurgeEntries ============== */ -void Sequence_PurgeEntries( qboolean purgeGlobals ) +static void Sequence_PurgeEntries( qboolean purgeGlobals ) { sequenceEntry_s *scan; sequenceEntry_s *dead; diff --git a/engine/common/soundlib/snd_main.c b/engine/common/soundlib/snd_main.c index bd32b8d4..7c9e3a6e 100644 --- a/engine/common/soundlib/snd_main.c +++ b/engine/common/soundlib/snd_main.c @@ -90,7 +90,9 @@ wavdata_t *FS_LoadSound( const char *filename, const byte *buffer, size_t size ) { if( anyformat || !Q_stricmp( ext, format->ext )) { - Q_sprintf( path, format->formatstring, loadname, "", format->ext ); + Q_snprintf( path, sizeof( path ), + format->formatstring, loadname, "", format->ext ); + f = FS_LoadFile( path, &filesize, false ); if( f && filesize > 0 ) { @@ -175,7 +177,9 @@ stream_t *FS_OpenStream( const char *filename ) { if( anyformat || !Q_stricmp( ext, format->ext )) { - Q_sprintf( path, format->formatstring, loadname, "", format->ext ); + Q_snprintf( path, sizeof( path ), + format->formatstring, loadname, "", format->ext ); + if(( stream = format->openfunc( path )) != NULL ) { stream->format = format; @@ -280,7 +284,6 @@ void FS_FreeStream( stream_t *stream ) } #if XASH_ENGINE_TESTS - #define IMPLEMENT_SOUNDLIB_FUZZ_TARGET( export, target ) \ int EXPORT export( const uint8_t *Data, size_t Size ) \ { \ diff --git a/engine/common/soundlib/snd_mp3.c b/engine/common/soundlib/snd_mp3.c index 16e142f6..15adce8b 100644 --- a/engine/common/soundlib/snd_mp3.c +++ b/engine/common/soundlib/snd_mp3.c @@ -16,6 +16,184 @@ GNU General Public License for more details. #include "soundlib.h" #include "libmpg/libmpg.h" +#pragma pack( push, 1 ) +typedef struct did3v2_header_s +{ + char ident[3]; // must be "ID3" + uint8_t major_ver; // must be 4 + uint8_t minor_ver; // must be 0 + uint8_t flags; + uint32_t length; // size of extended header, padding and frames +} did3v2_header_t; +STATIC_ASSERT( sizeof( did3v2_header_t ) == 10, + "invalid did3v2_header_t size" ); + +typedef struct did3v2_extended_header_s +{ + uint32_t length; + uint8_t flags_length; + uint8_t flags[1]; +} did3v2_extended_header_t; +STATIC_ASSERT( sizeof( did3v2_extended_header_t ) == 6, + "invalid did3v2_extended_header_t size" ); + +typedef struct did3v2_frame_s +{ + char frame_id[4]; + uint32_t length; + uint8_t flags[2]; +} did3v2_frame_t; +STATIC_ASSERT( sizeof( did3v2_frame_t ) == 10, + "invalid did3v2_frame_t size" ); +#pragma pack( pop ) + +typedef enum did3v2_header_flags_e +{ + ID3V2_HEADER_UNSYHCHRONIZATION = BIT( 7U ), + ID3V2_HEADER_EXTENDED_HEADER = BIT( 6U ), + ID3V2_HEADER_EXPERIMENTAL = BIT( 5U ), + ID3V2_HEADER_FOOTER_PRESENT = BIT( 4U ), +} did3v2_header_flags_t; + +#define CHECK_IDENT( ident, b0, b1, b2 ) ((( ident )[0]) == ( b0 ) && (( ident )[1]) == ( b1 ) && (( ident )[2]) == ( b2 )) +#define CHECK_FRAME_ID( ident, b0, b1, b2, b3 ) ( CHECK_IDENT( ident, b0, b1, b2 ) && (( ident )[3]) == ( b3 )) + +static uint32_t Sound_ParseSynchInteger( uint32_t v ) +{ + uint32_t res = 0; + + // read as big endian + res |= (( v >> 24 ) & 0x7f ) << 0; + res |= (( v >> 16 ) & 0x7f ) << 7; + res |= (( v >> 8 ) & 0x7f ) << 14; + res |= (( v >> 0 ) & 0x7f ) << 21; + + return res; +} + +static void Sound_HandleCustomID3Comment( const char *key, const char *value ) +{ + if( !Q_strcmp( key, "LOOP_START" ) || !Q_strcmp( key, "LOOPSTART" )) + sound.loopstart = Q_atoi( value ); + // unknown comment is not an error +} + +static qboolean Sound_ParseID3Frame( const did3v2_frame_t *frame, const byte *buffer, size_t frame_length ) +{ + if( CHECK_FRAME_ID( frame->frame_id, 'T', 'X', 'X', 'X' )) + { + string key, value; + int32_t key_len, value_len; + + if( buffer[0] == 0x00 || buffer[1] == 0x03 ) + { + key_len = Q_strncpy( key, &buffer[1], sizeof( key )); + value_len = frame_length - (1 + key_len + 1); + if( value_len <= 0 || value_len >= sizeof( value )) + { + Con_Printf( S_ERROR "Sound_ParseID3Frame: invalid TXXX description, possibly broken file.\n" ); + return false; + } + + memcpy( value, &buffer[1 + key_len + 1], value_len ); + value[value_len + 1] = 0; + + Sound_HandleCustomID3Comment( key, value ); + } + else + { + if( buffer[0] == 0x01 || buffer[0] == 0x02 ) // UTF-16 with BOM + Con_Printf( S_ERROR "Sound_ParseID3Frame: UTF-16 encoding is unsupported. Use UTF-8 or ISO-8859!\n" ); + else + Con_Printf( S_ERROR "Sound_ParseID3Frame: unknown TXXX tag encoding %d, possibly broken file.\n", buffer[0] ); + return false; + } + } + + return true; +} + +static qboolean Sound_ParseID3Tag( const byte *buffer, fs_offset_t filesize ) +{ + const did3v2_header_t *header = (const did3v2_header_t *)buffer; + const byte *buffer_begin = buffer; + uint32_t tag_length; + + if( filesize < sizeof( *header )) + return false; + + buffer += sizeof( *header ); + + // support only id3v2 + if( !CHECK_IDENT( header->ident, 'I', 'D', '3' )) + { + // old id3v1 header found + if( CHECK_IDENT( header->ident, 'T', 'A', 'G' )) + Con_Printf( S_ERROR "Sound_ParseID3Tag: ID3v1 is not supported! Convert to ID3v2.4!\n", header->major_ver ); + + return true; // missing tag header is not an error + } + + // support only latest id3 v2.4 + if( header->major_ver != 4 || header->minor_ver == 0xff ) + { + Con_Printf( S_ERROR "Sound_ParseID3Tag: invalid ID3v2 tag version 2.%d.%d. Convert to ID3v2.4!\n", header->major_ver, header->minor_ver ); + return false; + } + + tag_length = Sound_ParseSynchInteger( header->length ); + if( tag_length > filesize - sizeof( *header )) + { + Con_Printf( S_ERROR "Sound_ParseID3Tag: invalid tag length %u, possibly broken file.\n", tag_length ); + return false; + } + + // just skip extended header + if( FBitSet( header->flags, ID3V2_HEADER_EXTENDED_HEADER )) + { + const did3v2_extended_header_t *ext_header = (const did3v2_extended_header_t *)buffer; + uint32_t ext_length = Sound_ParseSynchInteger( ext_header->length ); + + if( ext_length > tag_length ) + { + Con_Printf( S_ERROR "Sound_ParseID3Tag: invalid extended header length %u, possibly broken file.\n", ext_length ); + return false; + } + + buffer += ext_length; + } + + while( buffer - buffer_begin < tag_length ) + { + const did3v2_frame_t *frame = (const did3v2_frame_t *)buffer; + uint32_t frame_length = Sound_ParseSynchInteger( frame->length ); + + if( frame_length > tag_length ) + { + Con_Printf( S_ERROR "Sound_ParseID3Tag: invalid frame length %u, possibly broken file.\n", frame_length ); + return false; + } + + buffer += sizeof( *frame ); + + // parse can fail, but it's ok to continue + Sound_ParseID3Frame( frame, buffer, frame_length ); + + buffer += frame_length; + } + + return true; +} + +#if XASH_ENGINE_TESTS +int EXPORT Fuzz_Sound_ParseID3Tag( const uint8_t *Data, size_t Size ) +{ + memset( &sound, 0, sizeof( sound )); + Sound_ParseID3Tag( Data, Size ); + return 0; +} +#endif + /* ================================================================= @@ -59,6 +237,12 @@ qboolean Sound_LoadMPG( const char *name, const byte *buffer, fs_offset_t filesi padsize = sound.size % FRAME_SIZE; pos += FRAME_SIZE; // evaluate pos + if( !Sound_ParseID3Tag( buffer, filesize )) + { + Con_DPrintf( S_WARN "Sound_LoadMPG: (%s) failed to extract LOOP_START tag\n", name ); + sound.loopstart = -1; + } + if( !sound.size ) { // bad mpeg file ? diff --git a/engine/common/soundlib/snd_utils.c b/engine/common/soundlib/snd_utils.c index 98626066..0319bdaf 100644 --- a/engine/common/soundlib/snd_utils.c +++ b/engine/common/soundlib/snd_utils.c @@ -290,3 +290,17 @@ qboolean Sound_Process( wavdata_t **wav, int rate, int width, uint flags ) return false; } + +qboolean Sound_SupportedFileFormat( const char *fileext ) +{ + const loadwavfmt_t *format; + if( COM_CheckStringEmpty( fileext )) + { + for( format = sound.loadformats; format && format->formatstring; format++ ) + { + if( !Q_stricmp( format->ext, fileext )) + return true; + } + } + return false; +} diff --git a/engine/common/sys_con.c b/engine/common/sys_con.c index 53cb27d3..8b4a3670 100644 --- a/engine/common/sys_con.c +++ b/engine/common/sys_con.c @@ -270,6 +270,14 @@ static void Sys_PrintStdout( const char *logtime, const char *msg ) fprintf( stderr, "%s %s", logtime, buf ); #endif // XASH_NSWITCH && NSWITCH_DEBUG +#if XASH_PSVITA + // spew to stderr only in developer mode + if( host_developer.value ) + { + fprintf( stderr, "%s %s", logtime, buf ); + } +#endif + #elif !XASH_WIN32 // Wcon does the job Sys_PrintLogfile( STDOUT_FILENO, logtime, msg, XASH_COLORIZE_CONSOLE ); Sys_FlushStdout(); diff --git a/engine/common/system.c b/engine/common/system.c index d7d08165..08ed09fe 100644 --- a/engine/common/system.c +++ b/engine/common/system.c @@ -40,6 +40,10 @@ GNU General Public License for more details. #include #endif +#if XASH_PSVITA +#include +#endif + #include "menu_int.h" // _UPDATE_PAGE macro #include "library.h" @@ -129,6 +133,11 @@ const char *Sys_GetCurrentUser( void ) if( GetUserName( s_userName, &size )) return s_userName; +#elif XASH_PSVITA + static string username; + sceAppUtilSystemParamGetString( SCE_SYSTEM_PARAM_ID_USERNAME, username, sizeof( username ) - 1 ); + if( COM_CheckStringEmpty( username )) + return username; #elif XASH_POSIX && !XASH_ANDROID && !XASH_NSWITCH uid_t uid = geteuid(); struct passwd *pw = getpwuid( uid ); @@ -613,6 +622,12 @@ qboolean Sys_NewInstance( const char *gamedir ) newargs[i++] = strdup( "-changegame" ); newargs[i] = NULL; +#if XASH_PSVITA + // under normal circumstances it's always going to be the same path + exe = strdup( "app0:/eboot.bin" ); + Host_Shutdown( ); + sceAppMgrLoadExec( exe, newargs, NULL ); +#else exelen = wai_getExecutablePath( NULL, 0, NULL ); exe = malloc( exelen + 1 ); wai_getExecutablePath( exe, exelen, NULL ); @@ -621,6 +636,7 @@ qboolean Sys_NewInstance( const char *gamedir ) Host_Shutdown(); execv( exe, newargs ); +#endif // if execv returned, it's probably an error printf( "execv failed: %s", strerror( errno )); diff --git a/engine/common/whereami.c b/engine/common/whereami.c index 0a0fb570..7581ac51 100644 --- a/engine/common/whereami.c +++ b/engine/common/whereami.c @@ -169,7 +169,7 @@ int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) return length; } -#elif defined(__linux__) || defined(__CYGWIN__) || defined(__sun) || defined(WAI_USE_PROC_SELF_EXE) +#elif defined(__linux__) || defined(__CYGWIN__) || defined(__sun) || defined(__serenity__) || defined(WAI_USE_PROC_SELF_EXE) #include #include @@ -795,7 +795,7 @@ int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) return length; } -#elif defined(__sgi) || defined(__SWITCH__) +#elif defined(__sgi) || defined(__SWITCH__) || defined(__vita__) /* * These functions are stubbed for now to get the code compiling. diff --git a/engine/common/zone.c b/engine/common/zone.c index 98af81eb..0e082858 100644 --- a/engine/common/zone.c +++ b/engine/common/zone.c @@ -1,6 +1,9 @@ /* zone.c - zone memory allocation from DarkPlaces +Copyright (C) 1996-1997 Id Software, Inc. +Copyright (C) 2000-2007 DarkPlaces contributors Copyright (C) 2007 Uncle Mike +Copyright (C) 2015-2023 Xash3D FWGS contributors 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 diff --git a/engine/platform/android/vid_android.c b/engine/platform/android/vid_android.c index 4f38997a..2365c04f 100644 --- a/engine/platform/android/vid_android.c +++ b/engine/platform/android/vid_android.c @@ -240,7 +240,7 @@ int Android_GetSelectedPixelFormat( void ) qboolean R_Init_Video( const int type ) { - string safe; + char buf[MAX_VA_STRING]; qboolean retval; switch( Android_GetSelectedPixelFormat() ) @@ -258,14 +258,8 @@ qboolean R_Init_Video( const int type ) if( FS_FileExists( GI->iconpath, true ) ) { - if( host.rodir[0] ) - { - Android_SetIcon( va( "%s/%s/%s", host.rodir, GI->gamefolder, GI->iconpath ) ); - } - else - { - Android_SetIcon( va( "%s/%s/%s", host.rootdir, GI->gamefolder, GI->iconpath ) ); - } + Q_snprintf( buf, sizeof( buf ), "%s/%s/%s", COM_CheckStringEmpty( host.rodir ) ? host.rodir : host.rootdir, GI->gamefolder, GI->iconpath ); + Android_SetIcon( buf ); } Android_SetTitle( GI->title ); @@ -280,8 +274,8 @@ qboolean R_Init_Video( const int type ) case REF_GL: Sys_LoadLibrary( &egl_info ); - if( !glw_state.safe && Sys_GetParmFromCmdLine( "-safegl", safe ) ) - glw_state.safe = bound( SAFE_NO, Q_atoi( safe ), SAFE_DONTCARE ); + if( !glw_state.safe && Sys_GetParmFromCmdLine( "-safegl", buf ) ) + glw_state.safe = bound( SAFE_NO, Q_atoi( buf ), SAFE_DONTCARE ); break; default: diff --git a/engine/platform/emscripten/lib_em.c b/engine/platform/emscripten/lib_em.c index b239056f..aeb67542 100644 --- a/engine/platform/emscripten/lib_em.c +++ b/engine/platform/emscripten/lib_em.c @@ -24,14 +24,15 @@ void *EMSCRIPTEN_LoadLibrary( const char *dllname ) void *pHandle = NULL; #ifdef EMSCRIPTEN_LIB_FS - char path[MAX_SYSPATH]; + char path[MAX_SYSPATH], buf[MAX_VA_STRING]; string prefix; Q_strcpy(prefix, getenv( "LIBRARY_PREFIX" ) ); Q_snprintf( path, MAX_SYSPATH, "%s%s%s", prefix, dllname, getenv( "LIBRARY_SUFFIX" ) ); pHandle = dlopen( path, RTLD_LAZY ); if( !pHandle ) { - COM_PushLibraryError( va("Loading %s:\n", path ) ); + Q_snprintf( buf, sizeof( buf ), "Loading %s:\n", path ); + COM_PushLibraryError( buf ); COM_PushLibraryError( dlerror() ); } return pHandle; diff --git a/engine/platform/linux/in_evdev.c b/engine/platform/linux/in_evdev.c index 46075f38..bb8c7a13 100644 --- a/engine/platform/linux/in_evdev.c +++ b/engine/platform/linux/in_evdev.c @@ -13,7 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "platform/platform.h" -#ifdef XASH_USE_EVDEV +#if XASH_USE_EVDEV #include "common.h" diff --git a/engine/platform/platform.h b/engine/platform/platform.h index d7d5e546..948930a8 100644 --- a/engine/platform/platform.h +++ b/engine/platform/platform.h @@ -57,6 +57,14 @@ void NSwitch_Init( void ); void NSwitch_Shutdown( void ); #endif +#if XASH_PSVITA +void PSVita_Init( void ); +void PSVita_Shutdown( void ); +qboolean PSVita_GetBasePath( char *buf, const size_t buflen ); +int PSVita_GetArgv( int in_argc, char **in_argv, char ***out_argv ); +void PSVita_InputUpdate( void ); +#endif + /* ============================================================================== @@ -141,7 +149,7 @@ qboolean SW_CreateBuffer( int width, int height, uint *stride, uint *bpp, uint * // // in_evdev.c // -#ifdef XASH_USE_EVDEV +#if XASH_USE_EVDEV void Evdev_SetGrab( qboolean grab ); void Evdev_Shutdown( void ); void Evdev_Init( void ); diff --git a/engine/platform/posix/lib_posix.c b/engine/platform/posix/lib_posix.c index 315fc059..62c40346 100644 --- a/engine/platform/posix/lib_posix.c +++ b/engine/platform/posix/lib_posix.c @@ -82,6 +82,7 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d { dll_user_t *hInst = NULL; void *pHandle = NULL; + char buf[MAX_VA_STRING]; COM_ResetLibraryError(); @@ -110,7 +111,8 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d if( pHandle ) return pHandle; - COM_PushLibraryError( va( "Failed to find library %s", dllname )); + Q_snprintf( buf, sizeof( buf ), "Failed to find library %s", dllname ); + COM_PushLibraryError( buf ); COM_PushLibraryError( dlerror() ); return NULL; } @@ -118,7 +120,8 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d if( hInst->custom_loader ) { - COM_PushLibraryError( va( "Custom library loader is not available. Extract library %s and fix gameinfo.txt!", hInst->fullPath )); + Q_snprintf( buf, sizeof( buf ), "Custom library loader is not available. Extract library %s and fix gameinfo.txt!", hInst->fullPath ); + COM_PushLibraryError( buf ); Mem_Free( hInst ); return NULL; } @@ -128,14 +131,16 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d { if( hInst->encrypted ) { - COM_PushLibraryError( va( "Library %s is encrypted. Cannot load", hInst->shortPath ) ); + Q_snprintf( buf, sizeof( buf ), "Library %s is encrypted. Cannot load", hInst->shortPath ); + COM_PushLibraryError( buf ); Mem_Free( hInst ); return NULL; } if( !( hInst->hInstance = Loader_LoadLibrary( hInst->fullPath ) ) ) { - COM_PushLibraryError( va( "Failed to load DLL with DLL loader: %s", hInst->shortPath ) ); + Q_snprintf( buf, sizeof( buf ), "Failed to load DLL with DLL loader: %s", hInst->shortPath ); + COM_PushLibraryError( buf ); Mem_Free( hInst ); return NULL; } diff --git a/engine/platform/posix/net.h b/engine/platform/posix/net.h index 91ba7562..8353baf1 100644 --- a/engine/platform/posix/net.h +++ b/engine/platform/posix/net.h @@ -17,7 +17,9 @@ GNU General Public License for more details. #include #include +#if !XASH_PSVITA #include +#endif #include #include #include @@ -79,7 +81,7 @@ static int ioctl_stub( int d, unsigned long r, ... ) return 0; } #define ioctlsocket ioctl_stub -#else // XASH_EMSCRIPTEN +#elif !XASH_PSVITA // XASH_EMSCRIPTEN #define ioctlsocket ioctl #endif // XASH_EMSCRIPTEN #define closesocket close diff --git a/engine/platform/posix/sys_posix.c b/engine/platform/posix/sys_posix.c index 3872f8da..364263e6 100644 --- a/engine/platform/posix/sys_posix.c +++ b/engine/platform/posix/sys_posix.c @@ -50,11 +50,12 @@ static qboolean Sys_FindExecutable( const char *baseName, char *buf, size_t size needTrailingSlash = ( envPath[length - 1] == '/' ) ? 0 : 1; if( length + baseNameLength + needTrailingSlash < size ) { - Q_strncpy( buf, envPath, length + 1 ); - if( needTrailingSlash ) - Q_strcpy( buf + length, "/" ); - Q_strcpy( buf + length + needTrailingSlash, baseName ); - buf[length + needTrailingSlash + baseNameLength] = '\0'; + string temp; + + Q_strncpy( temp, envPath, length + 1 ); + Q_snprintf( buf, size, "%s%s%s", + temp, needTrailingSlash ? "/" : "", baseName ); + if( access( buf, X_OK ) == 0 ) return true; } @@ -67,7 +68,7 @@ static qboolean Sys_FindExecutable( const char *baseName, char *buf, size_t size return false; } -#if !XASH_ANDROID && !XASH_NSWITCH +#if !XASH_ANDROID && !XASH_NSWITCH && !XASH_PSVITA void Platform_ShellExecute( const char *path, const char *parms ) { char xdgOpen[128]; diff --git a/engine/platform/psvita/in_psvita.c b/engine/platform/psvita/in_psvita.c new file mode 100644 index 00000000..3d14ade2 --- /dev/null +++ b/engine/platform/psvita/in_psvita.c @@ -0,0 +1,115 @@ +/* +in_psvita.h - psvita-specific input code +Copyright (C) 2021-2023 fgsfds + +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. +*/ + +/* +the SDL fork that we use sometimes fails to call sceImeUpdate, +which leads to input being consumed forever without ever showing the keyboard, +so we just reimplement the whole thing here using SceCommonDialog +*/ + +#include "platform/platform.h" +#include +#include + +extern int SDL_SendKeyboardText( const char *text ); + +static qboolean ime_enabled; +static SceWChar16 ime_string[SCE_IME_MAX_TEXT_LENGTH + 1]; + +/* this is dumb but will probably work fine enough */ +static inline void utf2ascii( char *dst, const SceWChar16 *src, unsigned dstsize ) +{ + if ( !src || !dst || !dstsize ) + return; + while ( *src && ( dstsize-- > 0 ) ) + *(dst++) = (*(src++)) & 0xFF; + *dst = 0x00; +} + +static void IME_Open( void ) +{ + SceInt32 res; + SceImeDialogParam param; + + memset( ime_string, 0, sizeof( ime_string ) ); + + sceImeDialogParamInit( ¶m ); + param.supportedLanguages = SCE_IME_LANGUAGE_ENGLISH; + param.languagesForced = SCE_TRUE; + param.type = SCE_IME_TYPE_BASIC_LATIN; + param.title = u"Input text"; + param.maxTextLength = SCE_IME_MAX_TEXT_LENGTH; + param.initialText = (SceWChar16 *)u""; + param.inputTextBuffer = ime_string; + + res = sceImeDialogInit( ¶m ); + if ( res < 0 ) + { + Con_Reportf( S_WARN "Could not open IME keyboard: %d\n", res ); + } + else + { + ime_enabled = true; + } +} + +static void IME_Close( void ) +{ + if ( ime_enabled ) + { + ime_enabled = false; + sceImeDialogTerm( ); + } +} + +static void IME_Update( void ) +{ + char ascii_string[SCE_IME_MAX_TEXT_LENGTH + 2] = { 0 }; + SceImeDialogResult result; + SceCommonDialogStatus status = sceImeDialogGetStatus( ); + if( status == 2 ) + { + memset( &result, 0, sizeof( SceImeDialogResult ) ); + sceImeDialogGetResult( &result ); + if( result.button == SCE_IME_DIALOG_BUTTON_ENTER ) + { + utf2ascii( ascii_string, ime_string, SCE_IME_MAX_TEXT_LENGTH ); + SDL_SendKeyboardText( ascii_string ); + } + IME_Close(); + } +} + +void Platform_EnableTextInput( qboolean enable ) +{ + if ( enable ) + { + if ( ime_enabled ) + IME_Close(); + IME_Open(); + SDL_EventState( SDL_TEXTINPUT, SDL_ENABLE ); + } + else + { + SDL_EventState( SDL_TEXTINPUT, SDL_DISABLE ); + IME_Close(); + } +} + +void PSVita_InputUpdate( void ) +{ + if ( ime_enabled ) + IME_Update(); +} diff --git a/engine/platform/psvita/net_psvita.h b/engine/platform/psvita/net_psvita.h new file mode 100644 index 00000000..4f111604 --- /dev/null +++ b/engine/platform/psvita/net_psvita.h @@ -0,0 +1,57 @@ +/* +net_psvita.h - psvita network stubs +Copyright (C) 2021-2023 fgsfds + +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 NET_PSVITA_H +#define NET_PSVITA_H + +#include + +/* we're missing IPv6 support; define some trash */ + +#ifndef IN6_IS_ADDR_V4MAPPED +#define IN6_IS_ADDR_V4MAPPED( p ) ( 0 ) +#endif + +#ifndef IPPROTO_IPV6 +#define IPPROTO_IPV6 41 +#endif + +#ifndef IPV6_MULTICAST_LOOP +#define IPV6_MULTICAST_LOOP 19 +#endif + +#ifndef IPV6_V6ONLY +#define IPV6_V6ONLY 26 +#endif + +#ifndef FIONBIO +#define FIONBIO SO_NONBLOCK +#endif + +/* ioctlsocket() is only used to set non-blocking on sockets */ + +static inline int ioctl_psvita( int fd, int req, unsigned int *arg ) +{ + if ( req == FIONBIO ) + { + return setsockopt( fd, SOL_SOCKET, SO_NONBLOCK, arg, sizeof( *arg ) ); + } + return -ENOSYS; +} + +#define ioctlsocket ioctl_psvita + +#endif // NET_PSVITA_H diff --git a/engine/platform/psvita/sce_sys/icon0.png b/engine/platform/psvita/sce_sys/icon0.png new file mode 100644 index 00000000..5203ef32 Binary files /dev/null and b/engine/platform/psvita/sce_sys/icon0.png differ diff --git a/engine/platform/psvita/sce_sys/livearea/contents/bg.png b/engine/platform/psvita/sce_sys/livearea/contents/bg.png new file mode 100644 index 00000000..f1a02015 Binary files /dev/null and b/engine/platform/psvita/sce_sys/livearea/contents/bg.png differ diff --git a/engine/platform/psvita/sce_sys/livearea/contents/startup.png b/engine/platform/psvita/sce_sys/livearea/contents/startup.png new file mode 100644 index 00000000..c53af6f8 Binary files /dev/null and b/engine/platform/psvita/sce_sys/livearea/contents/startup.png differ diff --git a/engine/platform/psvita/sce_sys/livearea/contents/template.xml b/engine/platform/psvita/sce_sys/livearea/contents/template.xml new file mode 100644 index 00000000..16ef0ae9 --- /dev/null +++ b/engine/platform/psvita/sce_sys/livearea/contents/template.xml @@ -0,0 +1,20 @@ + + + + + bg.png + + + + startup.png + + + + + psla:dev + + Developer mode + + + + diff --git a/engine/platform/psvita/sys_psvita.c b/engine/platform/psvita/sys_psvita.c new file mode 100644 index 00000000..a57ca355 --- /dev/null +++ b/engine/platform/psvita/sys_psvita.c @@ -0,0 +1,194 @@ +/* +sys_psvita.c - psvita backend +Copyright (C) 2021-2023 fgsfds + +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 "platform/platform.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define DATA_PATH "data/xash3d" +#define MAX_ARGV 5 // "" -log -dev X NULL + +// 200MB libc heap, 512K main thread stack, 40MB for loading game DLLs +// the rest goes to vitaGL +SceUInt32 sceUserMainThreadStackSize = 512 * 1024; +unsigned int _pthread_stack_default_user = 512 * 1024; +unsigned int _newlib_heap_size_user = 200 * 1024 * 1024; +#define VGL_MEM_THRESHOLD ( 40 * 1024 * 1024 ) + +/* HACKHACK: force-export stuff required by the dynamic libs */ + +extern void *__aeabi_idiv; +extern void *__aeabi_uidiv; +extern void *__aeabi_idivmod; +extern void *__aeabi_uidivmod; +extern void *__aeabi_d2ulz; +extern void *__aeabi_ul2d; + +static const vrtld_export_t aux_exports[] = +{ + VRTLD_EXPORT_SYMBOL( __aeabi_d2ulz ), + VRTLD_EXPORT_SYMBOL( __aeabi_idiv ), + VRTLD_EXPORT_SYMBOL( __aeabi_idivmod ), + VRTLD_EXPORT_SYMBOL( __aeabi_uidivmod ), + VRTLD_EXPORT_SYMBOL( __aeabi_uidiv ), + VRTLD_EXPORT_SYMBOL( __aeabi_ul2d ), + VRTLD_EXPORT_SYMBOL( ctime ), + VRTLD_EXPORT_SYMBOL( vasprintf ), + VRTLD_EXPORT_SYMBOL( vsprintf ), + VRTLD_EXPORT_SYMBOL( vprintf ), + VRTLD_EXPORT_SYMBOL( printf ), + VRTLD_EXPORT_SYMBOL( putchar ), + VRTLD_EXPORT_SYMBOL( puts ), + VRTLD_EXPORT_SYMBOL( tolower ), + VRTLD_EXPORT_SYMBOL( toupper ), + VRTLD_EXPORT_SYMBOL( isalnum ), + VRTLD_EXPORT_SYMBOL( isalpha ), + VRTLD_EXPORT_SYMBOL( strchrnul ), + VRTLD_EXPORT_SYMBOL( stpcpy ), + VRTLD_EXPORT_SYMBOL( rand ), + VRTLD_EXPORT_SYMBOL( srand ), + VRTLD_EXPORT_SYMBOL( sceGxmMapMemory ), // needed by vgl_shim + VRTLD_EXPORT( "dlopen", vrtld_dlopen ), + VRTLD_EXPORT( "dlclose", vrtld_dlclose ), + VRTLD_EXPORT( "dlsym", vrtld_dlsym ), +}; + +const vrtld_export_t *__vrtld_exports = aux_exports; +const size_t __vrtld_num_exports = sizeof( aux_exports ) / sizeof( *aux_exports ); + +/* end of export crap */ + +static const char *PSVita_GetLaunchParameter( char *outbuf ) +{ + SceAppUtilAppEventParam param; + memset( ¶m, 0, sizeof( param ) ); + sceAppUtilReceiveAppEvent( ¶m ); + if( param.type == 0x05 ) + { + sceAppUtilAppEventParseLiveArea( ¶m, outbuf ); + return outbuf; + } + return NULL; +} + +void Platform_ShellExecute( const char *path, const char *parms ) +{ + Con_Reportf( S_WARN "Tried to shell execute ;%s; -- not supported\n", path ); +} + +/* +=========== +PSVita_GetArgv + +On the PS Vita under normal circumstances argv is empty, so we'll construct our own +based on which button the user pressed in the LiveArea launcher. +=========== +*/ +int PSVita_GetArgv( int in_argc, char **in_argv, char ***out_argv ) +{ + static const char *fake_argv[MAX_ARGV] = { "app0:/eboot.bin", NULL }; + int fake_argc = 1; + char tmp[2048] = { 0 }; + SceAppUtilInitParam initParam = { 0 }; + SceAppUtilBootParam bootParam = { 0 }; + + // on the Vita under normal circumstances argv is empty, unless we're launching from Change Game + sceAppUtilInit( &initParam, &bootParam ); + + if( in_argc > 1 ) + { + // probably coming from Change Game, in which case we just need to keep the old args + *out_argv = in_argv; + return in_argc; + } + + // got empty args, which means that we're probably coming from LiveArea + // construct argv based on which button the user pressed in the LiveArea launcher + if( PSVita_GetLaunchParameter( tmp )) + { + if( !Q_strcmp( tmp, "dev" )) + { + // user hit the "Developer Mode" button, inject "-log" and "-dev" arguments + fake_argv[fake_argc++] = "-log"; + fake_argv[fake_argc++] = "-dev"; + fake_argv[fake_argc++] = "2"; + } + } + + *out_argv = (char **)fake_argv; + return fake_argc; +} + +void PSVita_Init( void ) +{ + char xashdir[1024] = { 0 }; + + // cd to the base dir immediately for library loading to work + if( PSVita_GetBasePath( xashdir, sizeof( xashdir ))) + { + chdir( xashdir ); + } + + sceTouchSetSamplingState( SCE_TOUCH_PORT_BACK, SCE_TOUCH_SAMPLING_STATE_STOP ); + scePowerSetArmClockFrequency( 444 ); + scePowerSetBusClockFrequency( 222 ); + scePowerSetGpuClockFrequency( 222 ); + scePowerSetGpuXbarClockFrequency( 166 ); + sceSysmoduleLoadModule( SCE_SYSMODULE_NET ); + + if( vrtld_init( 0 ) < 0 ) + { + Sys_Error( "Could not init vrtld:\n%s\n", vrtld_dlerror() ); + } + + // init vitaGL, leaving some memory for DLL mapping + // TODO: we don't need to do this for ref_soft + vglUseVram( GL_TRUE ); + vglUseExtraMem( GL_TRUE ); + vglInitExtended( 0, 960, 544, VGL_MEM_THRESHOLD, 0 ); +} + +void PSVita_Shutdown( void ) +{ + vrtld_quit( ); +} + +qboolean PSVita_GetBasePath( char *buf, const size_t buflen ) +{ + // check if a xash3d folder exists on one of these drives + // default to the last one (ux0) + static const char *drives[] = { "uma0", "imc0", "ux0" }; + SceUID dir; + size_t i; + + for ( i = 0; i < sizeof( drives ) / sizeof( *drives ); ++i ) + { + Q_snprintf( buf, buflen, "%s:" DATA_PATH, drives[i] ); + dir = sceIoDopen( buf ); + if ( dir >= 0 ) + { + sceIoDclose( dir ); + return true; + } + } + + return false; +} diff --git a/engine/platform/sdl/events.c b/engine/platform/sdl/events.c index bd45bb9d..a71added 100644 --- a/engine/platform/sdl/events.c +++ b/engine/platform/sdl/events.c @@ -671,6 +671,10 @@ void Platform_RunEvents( void ) while( !host.crashed && !host.shutdown_issued && SDL_PollEvent( &event ) ) SDLash_EventFilter( &event ); + +#if XASH_PSVITA + PSVita_InputUpdate(); +#endif } void* Platform_GetNativeObject( const char *name ) diff --git a/engine/platform/sdl/events.h b/engine/platform/sdl/events.h index e42df28e..a47c40a0 100644 --- a/engine/platform/sdl/events.h +++ b/engine/platform/sdl/events.h @@ -25,8 +25,6 @@ void VID_RestoreScreenResolution( void ); qboolean VID_CreateWindow( int width, int height, qboolean fullscreen ); void VID_DestroyWindow( void ); void GL_InitExtensions( void ); -qboolean GL_CreateContext( void ); -qboolean GL_UpdateContext( void ); qboolean GL_DeleteContext( void ); void VID_SaveWindowSize( int width, int height ); diff --git a/engine/platform/sdl/in_sdl.c b/engine/platform/sdl/in_sdl.c index 0603a421..22c32af0 100644 --- a/engine/platform/sdl/in_sdl.c +++ b/engine/platform/sdl/in_sdl.c @@ -126,6 +126,8 @@ void Platform_Vibrate( float time, char flags ) SDL_JoystickRumble( g_joy, 0xFFFF, 0xFFFF, time * 1000.0f ); } +#if !XASH_PSVITA + /* ============= SDLash_EnableTextInput @@ -139,6 +141,8 @@ void Platform_EnableTextInput( qboolean enable ) #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) } +#endif // !XASH_PSVITA + /* ============= SDLash_JoyInit_Old diff --git a/engine/platform/sdl/sys_sdl.c b/engine/platform/sdl/sys_sdl.c index ed1c3661..80d93f92 100644 --- a/engine/platform/sdl/sys_sdl.c +++ b/engine/platform/sdl/sys_sdl.c @@ -63,13 +63,16 @@ void Platform_Init( void ) SDL_StopTextInput(); #endif // XASH_SDL == 2 -#if XASH_NSWITCH - NSwitch_Init(); -#elif XASH_WIN32 +#if XASH_WIN32 Wcon_CreateConsole(); // system console used by dedicated server or show fatal errors #elif XASH_POSIX Posix_Daemonize(); +#if XASH_PSVITA + PSVita_Init(); +#elif XASH_NSWITCH + NSwitch_Init(); #endif +#endif // XASH_POSIX SDLash_InitCursors(); } @@ -82,5 +85,7 @@ void Platform_Shutdown( void ) NSwitch_Shutdown(); #elif XASH_WIN32 Wcon_DestroyConsole(); +#elif XASH_PSVITA + PSVita_Shutdown(); #endif } diff --git a/engine/platform/sdl/vid_sdl.c b/engine/platform/sdl/vid_sdl.c index c40f3228..bf7ae052 100644 --- a/engine/platform/sdl/vid_sdl.c +++ b/engine/platform/sdl/vid_sdl.c @@ -257,6 +257,7 @@ vidmode_t *R_GetVideoMode( int num ) static void R_InitVideoModes( void ) { + char buf[MAX_VA_STRING]; #if SDL_VERSION_ATLEAST( 2, 0, 0 ) int displayIndex = 0; // TODO: handle multiple displays somehow int i, modes; @@ -296,7 +297,8 @@ static void R_InitVideoModes( void ) vidmodes[num_vidmodes].width = mode.w; vidmodes[num_vidmodes].height = mode.h; - vidmodes[num_vidmodes].desc = copystring( va( "%ix%i", mode.w, mode.h )); + Q_snprintf( buf, sizeof( buf ), "%ix%i", mode.w, mode.h ); + vidmodes[num_vidmodes].desc = copystring( buf ); num_vidmodes++; } @@ -331,7 +333,8 @@ static void R_InitVideoModes( void ) vidmodes[num_vidmodes].width = mode->w; vidmodes[num_vidmodes].height = mode->h; - vidmodes[num_vidmodes].desc = copystring( va( "%ix%i", mode->w, mode->h )); + Q_snprintf( buf, sizeof( buf ), "%ix%i", mode->w, mode->h ); + vidmodes[num_vidmodes].desc = copystring( buf ); num_vidmodes++; } @@ -425,6 +428,14 @@ void *GL_GetProcAddress( const char *name ) { void *func = SDL_GL_GetProcAddress( name ); +#if XASH_PSVITA + // try to find in main module + if( !func ) + { + func = dlsym( NULL, name ); + } +#endif + if( !func ) { Con_Reportf( S_ERROR "GL_GetProcAddress failed for %s\n", name ); @@ -481,7 +492,7 @@ qboolean GL_DeleteContext( void ) GL_CreateContext ================= */ -qboolean GL_CreateContext( void ) +static qboolean GL_CreateContext( void ) { #if SDL_VERSION_ATLEAST(2, 0, 0) if( ( glw_state.context = SDL_GL_CreateContext( host.hWnd ) ) == NULL) @@ -498,7 +509,7 @@ qboolean GL_CreateContext( void ) GL_UpdateContext ================= */ -qboolean GL_UpdateContext( void ) +static qboolean GL_UpdateContext( void ) { #if SDL_VERSION_ATLEAST( 2, 0, 0 ) if( SDL_GL_MakeCurrent( host.hWnd, glw_state.context )) @@ -609,7 +620,7 @@ static void WIN_SetWindowIcon( HICON ico ) if( SDL_GetWindowWMInfo( host.hWnd, &wminfo ) ) { - SetClassLongPtr( wminfo.info.win.window, GCLP_HICON, (LONG)ico ); + SetClassLongPtr( wminfo.info.win.window, GCLP_HICON, (LONG_PTR)ico ); } } #endif @@ -733,9 +744,8 @@ qboolean VID_CreateWindow( int width, int height, qboolean fullscreen ) if( !iconLoaded ) { - Q_strcpy( iconpath, GI->iconpath ); - COM_StripExtension( iconpath ); - COM_DefaultExtension( iconpath, ".tga" ); + Q_strncpy( iconpath, GI->iconpath, sizeof( iconpath )); + COM_ReplaceExtension( iconpath, ".tga", sizeof( iconpath )); icon = FS_LoadImage( iconpath, NULL, 0 ); diff --git a/engine/platform/win32/con_win.c b/engine/platform/win32/con_win.c index 63a4102b..dfea3f27 100644 --- a/engine/platform/win32/con_win.c +++ b/engine/platform/win32/con_win.c @@ -29,7 +29,10 @@ WIN32 CONSOLE typedef struct { - char title[64]; + string title; + string previousTitle; + UINT previousCodePage; + UINT previousOutputCodePage; HWND hWnd; HANDLE hInput; HANDLE hOutput; @@ -45,6 +48,7 @@ typedef struct string lineBuffer[COMMAND_HISTORY]; qboolean inputEnabled; qboolean consoleVisible; + qboolean attached; // log stuff qboolean log_active; @@ -129,7 +133,7 @@ static void Wcon_PrintInternal( const char *msg, int length ) void Wcon_ShowConsole( qboolean show ) { - if( !s_wcd.hWnd || show == s_wcd.consoleVisible ) + if( !s_wcd.hWnd || show == s_wcd.consoleVisible || s_wcd.attached ) return; s_wcd.consoleVisible = show; @@ -482,7 +486,8 @@ void Wcon_WinPrint( const char *pMsg ) Wcon_PrintInternal( s_wcd.consoleText, s_wcd.consoleTextLen ); } - Wcon_UpdateStatusLine(); + if( !s_wcd.attached ) + Wcon_UpdateStatusLine(); } /* @@ -509,7 +514,16 @@ void Wcon_CreateConsole( void ) s_wcd.log_active = true; // always make log } - AllocConsole(); + s_wcd.attached = ( AttachConsole( ATTACH_PARENT_PROCESS ) != 0 ); + if( s_wcd.attached ) { + GetConsoleTitle( &s_wcd.previousTitle, sizeof( s_wcd.previousTitle )); + s_wcd.previousCodePage = GetConsoleCP(); + s_wcd.previousOutputCodePage = GetConsoleOutputCP(); + } + else { + AllocConsole(); + } + SetConsoleTitle( s_wcd.title ); SetConsoleCP( CP_UTF8 ); SetConsoleOutputCP( CP_UTF8 ); @@ -525,22 +539,25 @@ void Wcon_CreateConsole( void ) return; } - SetWindowPos( s_wcd.hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOREPOSITION | SWP_SHOWWINDOW ); + if( !s_wcd.attached ) + { + SetWindowPos( s_wcd.hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOREPOSITION | SWP_SHOWWINDOW ); - // show console if needed - if( host.con_showalways ) - { - // make console visible - ShowWindow( s_wcd.hWnd, SW_SHOWDEFAULT ); - UpdateWindow( s_wcd.hWnd ); - SetForegroundWindow( s_wcd.hWnd ); - SetFocus( s_wcd.hWnd ); - s_wcd.consoleVisible = true; - } - else - { - s_wcd.consoleVisible = false; - ShowWindow( s_wcd.hWnd, SW_HIDE ); + // show console if needed + if( host.con_showalways ) + { + // make console visible + ShowWindow( s_wcd.hWnd, SW_SHOWDEFAULT ); + UpdateWindow( s_wcd.hWnd ); + SetForegroundWindow( s_wcd.hWnd ); + SetFocus( s_wcd.hWnd ); + s_wcd.consoleVisible = true; + } + else + { + s_wcd.consoleVisible = false; + ShowWindow( s_wcd.hWnd, SW_HIDE ); + } } } @@ -573,10 +590,21 @@ void Wcon_DestroyConsole( void ) Sys_CloseLog(); - if( s_wcd.hWnd ) + if( !s_wcd.attached ) + { + if( s_wcd.hWnd ) + { + ShowWindow( s_wcd.hWnd, SW_HIDE ); + s_wcd.hWnd = 0; + } + } + else { - ShowWindow( s_wcd.hWnd, SW_HIDE ); - s_wcd.hWnd = 0; + // reverts title & code page for console window that was before starting Xash3D + SetConsoleCP( s_wcd.previousCodePage ); + SetConsoleOutputCP( s_wcd.previousOutputCodePage ); + SetConsoleTitle( &s_wcd.previousTitle ); + Con_Printf( "Press Enter to continue...\n" ); } FreeConsole(); @@ -595,8 +623,8 @@ returned input text */ char *Wcon_Input( void ) { - int i; - int eventsCount; + DWORD i; + DWORD eventsCount; static INPUT_RECORD events[1024]; if( !s_wcd.inputEnabled ) @@ -642,7 +670,7 @@ set server status string in console */ void Wcon_SetStatus( const char *pStatus ) { - if( host.type != HOST_DEDICATED ) + if( host.type != HOST_DEDICATED || s_wcd.attached ) return; Q_strncpy( s_wcd.statusLine, pStatus, sizeof( s_wcd.statusLine ) - 1 ); diff --git a/engine/platform/win32/lib_custom_win.c b/engine/platform/win32/lib_custom_win.c index f4dfe4aa..cede0ac9 100644 --- a/engine/platform/win32/lib_custom_win.c +++ b/engine/platform/win32/lib_custom_win.c @@ -373,21 +373,21 @@ void *MemoryLoadLibrary( const char *name ) if( !data ) { - Q_sprintf( errorstring, "couldn't load %s", name ); + Q_snprintf( errorstring, sizeof( 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 ); + Q_snprintf( errorstring, sizeof( 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 ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s missing PE header", name ); goto library_error; } @@ -402,7 +402,7 @@ void *MemoryLoadLibrary( const char *name ) if( code == NULL ) { - Q_sprintf( errorstring, "%s can't reserve memory", name ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s can't reserve memory", name ); goto library_error; } @@ -436,7 +436,7 @@ void *MemoryLoadLibrary( const char *name ) // load required dlls and adjust function table of imports if( !BuildImportTable( result )) { - Q_sprintf( errorstring, "%s failed to build import table", name ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s failed to build import table", name ); goto library_error; } @@ -450,7 +450,7 @@ void *MemoryLoadLibrary( const char *name ) DllEntry = (DllEntryProc)CALCULATE_ADDRESS( code, result->headers->OptionalHeader.AddressOfEntryPoint ); if( DllEntry == 0 ) { - Q_sprintf( errorstring, "%s has no entry point", name ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s has no entry point", name ); goto library_error; } @@ -458,7 +458,7 @@ void *MemoryLoadLibrary( const char *name ) successfull = (*DllEntry)((HINSTANCE)code, DLL_PROCESS_ATTACH, 0 ); if( !successfull ) { - Q_sprintf( errorstring, "can't attach library %s", name ); + Q_snprintf( errorstring, sizeof( errorstring ), "can't attach library %s", name ); goto library_error; } result->initialized = 1; @@ -475,4 +475,4 @@ library_error: return NULL; } -#endif // XASH_LIB == LIB_WIN32 && XASH_X86 \ No newline at end of file +#endif // XASH_LIB == LIB_WIN32 && XASH_X86 diff --git a/engine/platform/win32/lib_win.c b/engine/platform/win32/lib_win.c index 21ee1fab..69e9cdd4 100644 --- a/engine/platform/win32/lib_win.c +++ b/engine/platform/win32/lib_win.c @@ -101,55 +101,55 @@ qboolean LibraryLoadSymbols( dll_user_t *hInst ) f = FS_Open( hInst->shortPath, "rb", false ); if( !f ) { - Q_sprintf( errorstring, "couldn't load %s", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "couldn't load %s", hInst->shortPath ); goto table_error; } if( FS_Read( f, &dos_header, sizeof( dos_header )) != sizeof( dos_header )) { - Q_sprintf( errorstring, "%s has corrupted EXE header", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s has corrupted EXE header", hInst->shortPath ); goto table_error; } if( dos_header.e_magic != IMAGE_DOS_SIGNATURE ) { - Q_sprintf( errorstring, "%s does not have a valid dll signature", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s does not have a valid dll signature", hInst->shortPath ); goto table_error; } if( FS_Seek( f, dos_header.e_lfanew, SEEK_SET ) == -1 ) { - Q_sprintf( errorstring, "%s error seeking for new exe header", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s error seeking for new exe header", hInst->shortPath ); goto table_error; } if( FS_Read( f, &nt_signature, sizeof( nt_signature )) != sizeof( nt_signature )) { - Q_sprintf( errorstring, "%s has corrupted NT header", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s has corrupted NT header", hInst->shortPath ); goto table_error; } if( nt_signature != IMAGE_NT_SIGNATURE ) { - Q_sprintf( errorstring, "%s does not have a valid NT signature", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s does not have a valid NT signature", hInst->shortPath ); goto table_error; } if( FS_Read( f, &pe_header, sizeof( pe_header )) != sizeof( pe_header )) { - Q_sprintf( errorstring, "%s does not have a valid PE header", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s does not have a valid PE header", hInst->shortPath ); goto table_error; } if( !pe_header.SizeOfOptionalHeader ) { - Q_sprintf( errorstring, "%s does not have an optional header", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s does not have an optional header", hInst->shortPath ); goto table_error; } if( FS_Read( f, &optional_header, sizeof( optional_header )) != sizeof( optional_header )) { - Q_sprintf( errorstring, "%s optional header probably corrupted", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s optional header probably corrupted", hInst->shortPath ); goto table_error; } @@ -159,7 +159,7 @@ qboolean LibraryLoadSymbols( dll_user_t *hInst ) { if( FS_Read( f, §ion_header, sizeof( section_header )) != sizeof( section_header )) { - Q_sprintf( errorstring, "%s error during reading section header", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s error during reading section header", hInst->shortPath ); goto table_error; } @@ -180,13 +180,13 @@ qboolean LibraryLoadSymbols( dll_user_t *hInst ) if( FS_Seek( f, exports_offset, SEEK_SET ) == -1 ) { - Q_sprintf( errorstring, "%s does not have a valid exports section", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s does not have a valid exports section", hInst->shortPath ); goto table_error; } if( FS_Read( f, &export_directory, sizeof( export_directory )) != sizeof( export_directory )) { - Q_sprintf( errorstring, "%s does not have a valid optional header", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s does not have a valid optional header", hInst->shortPath ); goto table_error; } @@ -194,7 +194,7 @@ qboolean LibraryLoadSymbols( dll_user_t *hInst ) if( hInst->num_ordinals > MAX_LIBRARY_EXPORTS ) { - Q_sprintf( errorstring, "%s too many exports %i", hInst->shortPath, hInst->num_ordinals ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s too many exports %i", hInst->shortPath, hInst->num_ordinals ); hInst->num_ordinals = 0; goto table_error; } @@ -203,7 +203,7 @@ qboolean LibraryLoadSymbols( dll_user_t *hInst ) if( FS_Seek( f, ordinal_offset, SEEK_SET ) == -1 ) { - Q_sprintf( errorstring, "%s does not have a valid ordinals section", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s does not have a valid ordinals section", hInst->shortPath ); goto table_error; } @@ -211,7 +211,7 @@ qboolean LibraryLoadSymbols( dll_user_t *hInst ) if( FS_Read( f, hInst->ordinals, hInst->num_ordinals * sizeof( word )) != (hInst->num_ordinals * sizeof( word ))) { - Q_sprintf( errorstring, "%s error during reading ordinals table", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s error during reading ordinals table", hInst->shortPath ); goto table_error; } @@ -219,7 +219,7 @@ qboolean LibraryLoadSymbols( dll_user_t *hInst ) if( FS_Seek( f, function_offset, SEEK_SET ) == -1 ) { - Q_sprintf( errorstring, "%s does not have a valid export address section", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s does not have a valid export address section", hInst->shortPath ); goto table_error; } @@ -227,7 +227,7 @@ qboolean LibraryLoadSymbols( dll_user_t *hInst ) if( FS_Read( f, hInst->funcs, hInst->num_ordinals * sizeof( dword )) != (hInst->num_ordinals * sizeof( dword ))) { - Q_sprintf( errorstring, "%s error during reading export address section", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s error during reading export address section", hInst->shortPath ); goto table_error; } @@ -235,7 +235,7 @@ qboolean LibraryLoadSymbols( dll_user_t *hInst ) if( FS_Seek( f, name_offset, SEEK_SET ) == -1 ) { - Q_sprintf( errorstring, "%s file does not have a valid names section", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s file does not have a valid names section", hInst->shortPath ); goto table_error; } @@ -243,7 +243,7 @@ qboolean LibraryLoadSymbols( dll_user_t *hInst ) if( FS_Read( f, p_Names, hInst->num_ordinals * sizeof( dword )) != (hInst->num_ordinals * sizeof( dword ))) { - Q_sprintf( errorstring, "%s error during reading names table", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s error during reading names table", hInst->shortPath ); goto table_error; } @@ -264,7 +264,7 @@ qboolean LibraryLoadSymbols( dll_user_t *hInst ) if( i != hInst->num_ordinals ) { - Q_sprintf( errorstring, "%s error during loading names section", hInst->shortPath ); + Q_snprintf( errorstring, sizeof( errorstring ), "%s error during loading names section", hInst->shortPath ); goto table_error; } FS_Close( f ); @@ -354,6 +354,7 @@ static void ListMissingModules( dll_user_t *hInst ) PIMAGE_NT_HEADERS peHeader; PIMAGE_IMPORT_DESCRIPTOR importDesc; byte *data; + char buf[MAX_VA_STRING]; if ( !hInst ) return; @@ -374,7 +375,10 @@ static void ListMissingModules( dll_user_t *hInst ) hMod = LoadLibraryEx( importName, NULL, LOAD_LIBRARY_AS_DATAFILE ); if ( !hMod ) - COM_PushLibraryError( va( "%s not found!", importName ) ); + { + Q_snprintf( buf, sizeof( buf ), "%s not found!", importName ); + COM_PushLibraryError( buf ); + } else FreeLibrary( hMod ); } @@ -436,19 +440,22 @@ smart dll loader - can loading dlls from pack or wad files void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean directpath ) { dll_user_t *hInst; + char buf[MAX_VA_STRING]; COM_ResetLibraryError(); hInst = FS_FindLibrary( dllname, directpath ); if( !hInst ) { - COM_PushLibraryError( va( "Failed to find library %s", dllname ) ); + Q_snprintf( buf, sizeof( buf ), "Failed to find library %s", dllname ); + COM_PushLibraryError( buf ); return NULL; } if( hInst->encrypted ) { - COM_PushLibraryError( va( "Library %s is encrypted, cannot load", hInst->shortPath ) ); + Q_snprintf( buf, sizeof( buf ), "Library %s is encrypted, cannot load", hInst->shortPath ); + COM_PushLibraryError( buf ); COM_FreeLibrary( hInst ); return NULL; } @@ -480,7 +487,8 @@ void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean d { if( !LibraryLoadSymbols( hInst )) { - COM_PushLibraryError( va( "Failed to load library %s", dllname ) ); + Q_snprintf( buf, sizeof( buf ), "Failed to load library %s", dllname ); + COM_PushLibraryError( buf ); COM_FreeLibrary( hInst ); return NULL; } diff --git a/engine/ref_api.h b/engine/ref_api.h index 220f4e4d..460adb18 100644 --- a/engine/ref_api.h +++ b/engine/ref_api.h @@ -35,7 +35,8 @@ GNU General Public License for more details. // 1. Initial release // 2. FS functions are removed, instead we have full fs_api_t // 3. SlerpBones, CalcBonePosition/Quaternion calls were moved to libpublic/mathlib -#define REF_API_VERSION 3 +// 4. R_StudioEstimateFrame now has time argument +#define REF_API_VERSION 4 #define TF_SKY (TF_SKYSIDE|TF_NOMIPMAP) @@ -506,7 +507,7 @@ typedef struct ref_interface_s void (*R_ClearAllDecals)( void ); // studio interface - float (*R_StudioEstimateFrame)( cl_entity_t *e, mstudioseqdesc_t *pseqdesc ); + float (*R_StudioEstimateFrame)( cl_entity_t *e, mstudioseqdesc_t *pseqdesc, double time ); void (*R_StudioLerpMovement)( cl_entity_t *e, double time, vec3_t origin, vec3_t angles ); void (*CL_InitStudioAPI)( void ); @@ -631,9 +632,6 @@ typedef struct ref_interface_s typedef int (*REFAPI)( int version, ref_interface_t *pFunctionTable, ref_api_t* engfuncs, ref_globals_t *pGlobals ); #define GET_REF_API "GetRefAPI" -typedef void (*REF_HUMANREADABLE_NAME)( char *out, size_t len ); -#define GET_REF_HUMANREADABLE_NAME "GetRefHumanReadableName" - #ifdef REF_DLL #define DEFINE_ENGINE_SHARED_CVAR( x, y ) cvar_t *x = NULL; #define DECLARE_ENGINE_SHARED_CVAR( x, y ) extern cvar_t *x; diff --git a/engine/sequence.h b/engine/sequence.h index 84854fa0..e7d8aabc 100644 --- a/engine/sequence.h +++ b/engine/sequence.h @@ -195,11 +195,9 @@ struct sentenceGroupEntry_ // Function declarations //--------------------------------------------------------------------------- sequenceEntry_s* Sequence_Get( const char* fileName, const char* entryName ); -void Sequence_ParseFile( const char* fileName, qboolean isGlobal ); void Sequence_OnLevelLoad( const char* mapName ); sentenceEntry_s* Sequence_PickSentence( const char *groupName, int pickMethod, int *picked ); void Sequence_Init( void ); -void Sequence_PurgeEntries( qboolean purgeGlobals ); sentenceEntry_s *Sequence_GetSentenceByIndex( unsigned int index ); #endif // _INCLUDE_SEQUENCE_H_ diff --git a/engine/server/server.h b/engine/server/server.h index 6d488aea..4515d8e6 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -374,9 +374,7 @@ typedef struct entity_state_t *baselines; // [GI->max_edicts] entity_state_t *static_entities; // [MAX_STATIC_ENTITIES]; - double last_heartbeat; challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting - uint heartbeat_challenge; } server_static_t; //============================================================================= @@ -443,6 +441,8 @@ extern convar_t hostname; extern convar_t skill; extern convar_t coop; extern convar_t sv_cheats; +extern convar_t public_server; +extern convar_t sv_nat; extern convar_t *sv_pausable; // allows pause in multiplayer extern convar_t *sv_check_errors; @@ -451,7 +451,6 @@ extern convar_t *sv_lighting_modulate; extern convar_t *sv_novis; extern convar_t *sv_hostmap; extern convar_t *sv_validate_changelevel; -extern convar_t *public_server; //=========================================================== // @@ -588,7 +587,6 @@ void SV_InactivateClients( void ); int SV_FindBestBaselineForStatic( int index, entity_state_t **baseline, entity_state_t *to ); void SV_WriteFrameToClient( sv_client_t *client, sizebuf_t *msg ); void SV_BuildClientFrame( sv_client_t *client ); -void SV_SendMessagesToAll( void ); void SV_SkipUpdates( void ); // diff --git a/engine/server/sv_client.c b/engine/server/sv_client.c index 0894091b..95fc2aaa 100644 --- a/engine/server/sv_client.c +++ b/engine/server/sv_client.c @@ -432,7 +432,7 @@ void SV_ConnectClient( netadr_t from ) // build protinfo answer protinfo[0] = '\0'; - Info_SetValueForKey( protinfo, "ext", va( "%d", newcl->extensions ), sizeof( protinfo ) ); + Info_SetValueForKeyf( protinfo, "ext", sizeof( protinfo ), "%d", newcl->extensions ); // send the connect packet to the client Netchan_OutOfBandPrint( NS_SERVER, from, "client_connect %s", protinfo ); @@ -475,7 +475,7 @@ void SV_ConnectClient( netadr_t from ) Log_Printf( "\"%s<%i><%i><>\" connected, address \"%s\"\n", newcl->name, newcl->userid, i, NET_AdrToString( newcl->netchan.remote_address )); if( count == 1 || count == svs.maxclients ) - svs.last_heartbeat = MAX_HEARTBEAT; + NET_MasterClear(); } /* @@ -542,7 +542,7 @@ edict_t *SV_FakeConnect( const char *netname ) cl->state = cs_spawned; if( count == 1 || count == svs.maxclients ) - svs.last_heartbeat = MAX_HEARTBEAT; + NET_MasterClear(); return cl->edict; } @@ -619,7 +619,7 @@ void SV_DropClient( sv_client_t *cl, qboolean crash ) } if( i == svs.maxclients ) - svs.last_heartbeat = MAX_HEARTBEAT; + NET_MasterClear(); } /* @@ -875,13 +875,13 @@ void SV_Info( netadr_t from, int protocolVersion ) SV_GetPlayerCount( &count, &bots ); // a1ba: send protocol version to distinguish old engine and new - Info_SetValueForKey( s, "p", va( "%i", PROTOCOL_VERSION ), sizeof( s )); + Info_SetValueForKeyf( s, "p", sizeof( s ), "%i", PROTOCOL_VERSION ); Info_SetValueForKey( s, "map", sv.name, sizeof( s )); Info_SetValueForKey( s, "dm", svgame.globals->deathmatch ? "1" : "0", sizeof( s )); Info_SetValueForKey( s, "team", svgame.globals->teamplay ? "1" : "0", sizeof( s )); Info_SetValueForKey( s, "coop", svgame.globals->coop ? "1" : "0", sizeof( s )); - Info_SetValueForKey( s, "numcl", va( "%i", count ), sizeof( s )); - Info_SetValueForKey( s, "maxcl", va( "%i", svs.maxclients ), sizeof( s )); + Info_SetValueForKeyf( s, "numcl", sizeof( s ), "%i", count ); + Info_SetValueForKeyf( s, "maxcl", sizeof( s ), "%i", svs.maxclients ); Info_SetValueForKey( s, "gamedir", GI->gamefolder, sizeof( s )); Info_SetValueForKey( s, "password", have_password ? "1" : "0", sizeof( s )); @@ -980,8 +980,8 @@ void SV_BuildNetAnswer( netadr_t from ) string[0] = '\0'; Info_SetValueForKey( string, "hostname", hostname.string, MAX_INFO_STRING ); Info_SetValueForKey( string, "gamedir", GI->gamefolder, MAX_INFO_STRING ); - Info_SetValueForKey( string, "current", va( "%i", count ), MAX_INFO_STRING ); - Info_SetValueForKey( string, "max", va( "%i", svs.maxclients ), MAX_INFO_STRING ); + Info_SetValueForKeyf( string, "current", MAX_INFO_STRING, "%i", count ); + Info_SetValueForKeyf( string, "max", MAX_INFO_STRING, "%i", svs.maxclients ); Info_SetValueForKey( string, "map", sv.name, MAX_INFO_STRING ); // send serverinfo @@ -1050,8 +1050,8 @@ void SV_RemoteCommand( netadr_t from, sizebuf_t *msg ) remaining[0] = 0; for( i = 2; i < Cmd_Argc(); i++ ) { - Q_strcat( remaining, Cmd_Argv( i )); - Q_strcat( remaining, " " ); + Q_strncat( remaining, Cmd_Argv( i ), sizeof( remaining )); + Q_strncat( remaining, " ", sizeof( remaining )); } Cmd_ExecuteString( remaining ); } @@ -1420,7 +1420,7 @@ void SV_PutClientInServer( sv_client_t *cl ) if( svgame.globals->cdAudioTrack ) { MSG_BeginServerCmd( &msg, svc_stufftext ); - MSG_WriteString( &msg, va( "cd loop %3d\n", svgame.globals->cdAudioTrack )); + MSG_WriteStringf( &msg, "cd loop %3d\n", svgame.globals->cdAudioTrack ); svgame.globals->cdAudioTrack = 0; } @@ -1660,7 +1660,7 @@ static qboolean SV_New_f( sv_client_t *cl ) // server info string MSG_BeginServerCmd( &msg, svc_stufftext ); - MSG_WriteString( &msg, va( "fullserverinfo \"%s\"\n", SV_Serverinfo( ))); + MSG_WriteStringf( &msg, "fullserverinfo \"%s\"\n", SV_Serverinfo( )); // collect the info about all the players and send to me for( i = 0, cur = svs.clients; i < svs.maxclients; i++, cur++ ) @@ -2740,15 +2740,15 @@ static void SV_EntSendVars( sv_client_t *cl, edict_t *ent ) return; MSG_WriteByte( &cl->netchan.message, svc_stufftext ); - MSG_WriteString( &cl->netchan.message, va( "set ent_last_name \"%s\"\n", STRING( ent->v.targetname ) )); + MSG_WriteStringf( &cl->netchan.message, "set ent_last_name \"%s\"\n", STRING( ent->v.targetname )); MSG_WriteByte( &cl->netchan.message, svc_stufftext ); - MSG_WriteString( &cl->netchan.message, va( "set ent_last_num %i\n", NUM_FOR_EDICT( ent ) )); + MSG_WriteStringf( &cl->netchan.message, "set ent_last_num %i\n", NUM_FOR_EDICT( ent )); MSG_WriteByte( &cl->netchan.message, svc_stufftext ); - MSG_WriteString( &cl->netchan.message, va( "set ent_last_inst !%i_%i\n", NUM_FOR_EDICT( ent ), ent->serialnumber )); + MSG_WriteStringf( &cl->netchan.message, "set ent_last_inst !%i_%i\n", NUM_FOR_EDICT( ent ), ent->serialnumber ); MSG_WriteByte( &cl->netchan.message, svc_stufftext ); - MSG_WriteString( &cl->netchan.message, va( "set ent_last_origin \"%f %f %f\"\n", ent->v.origin[0], ent->v.origin[1], ent->v.origin[2])); + MSG_WriteStringf( &cl->netchan.message, "set ent_last_origin \"%f %f %f\"\n", ent->v.origin[0], ent->v.origin[1], ent->v.origin[2] ); MSG_WriteByte( &cl->netchan.message, svc_stufftext ); - MSG_WriteString( &cl->netchan.message, va( "set ent_last_class \"%s\"\n", STRING( ent->v.classname ))); + MSG_WriteStringf( &cl->netchan.message, "set ent_last_class \"%s\"\n", STRING( ent->v.classname )); MSG_WriteByte( &cl->netchan.message, svc_stufftext ); MSG_WriteString( &cl->netchan.message, "ent_getvars_cb\n" ); // why do we need this? } @@ -3116,16 +3116,11 @@ 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" )) + else if( !Q_strcmp( pcmd, "c" ) && sv_nat.value && NET_IsMasterAdr( from )) { - 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, PROTOCOL_VERSION ); - } + netadr_t to; + if( NET_StringToAdr( Cmd_Argv( 1 ), &to ) && !NET_IsReservedAdr( to )) + SV_Info( to, PROTOCOL_VERSION ); } else if( svgame.dllFuncs.pfnConnectionlessPacket( &from, args, buf, &len )) { @@ -3389,8 +3384,8 @@ void SV_ParseCvarValue2( sv_client_t *cl, sizebuf_t *msg ) string name, value; int requestID = MSG_ReadLong( msg ); - Q_strcpy( name, MSG_ReadString( msg )); - Q_strcpy( value, MSG_ReadString( msg )); + Q_strncpy( name, MSG_ReadString( msg ), sizeof( name )); + Q_strncpy( value, MSG_ReadString( msg ), sizeof( value )); if( svgame.dllFuncs2.pfnCvarValue2 != NULL ) svgame.dllFuncs2.pfnCvarValue2( cl->edict, requestID, name, value ); diff --git a/engine/server/sv_cmds.c b/engine/server/sv_cmds.c index 8812811d..99c1c661 100644 --- a/engine/server/sv_cmds.c +++ b/engine/server/sv_cmds.c @@ -34,7 +34,7 @@ void SV_ClientPrintf( sv_client_t *cl, const char *fmt, ... ) return; va_start( argptr, fmt ); - Q_vsprintf( string, fmt, argptr ); + Q_vsnprintf( string, sizeof( string ), fmt, argptr ); va_end( argptr ); MSG_BeginServerCmd( &cl->netchan.message, svc_print ); @@ -56,7 +56,7 @@ void SV_BroadcastPrintf( sv_client_t *ignore, const char *fmt, ... ) int i; va_start( argptr, fmt ); - Q_vsprintf( string, fmt, argptr ); + Q_vsnprintf( string, sizeof( string ), fmt, argptr ); va_end( argptr ); if( sv.state == ss_active ) @@ -97,7 +97,7 @@ void SV_BroadcastCommand( const char *fmt, ... ) return; va_start( argptr, fmt ); - Q_vsprintf( string, fmt, argptr ); + Q_vsnprintf( string, sizeof( string ), fmt, argptr ); va_end( argptr ); MSG_BeginServerCmd( &sv.reliable_datagram, svc_stufftext ); @@ -337,12 +337,12 @@ void SV_NextMap_f( void ) if( Q_stricmp( ext, "bsp" )) continue; - COM_FileBase( t->filenames[i], nextmap ); + COM_FileBase( t->filenames[i], nextmap, sizeof( nextmap )); if( Q_stricmp( sv_hostmap->string, nextmap )) continue; next = ( i + 1 ) % t->numfilenames; - COM_FileBase( t->filenames[next], nextmap ); + COM_FileBase( t->filenames[next], nextmap, sizeof( nextmap )); Cvar_DirectSet( sv_hostmap, nextmap ); // found current point, check for valid @@ -394,7 +394,7 @@ void SV_HazardCourse_f( void ) // special case for Gunman Chronicles: playing avi-file if( FS_FileExists( va( "media/%s.avi", GI->trainmap ), false )) { - Cbuf_AddText( va( "wait; movie %s\n", GI->trainmap )); + Cbuf_AddTextf( "wait; movie %s\n", GI->trainmap ); Host_EndGame( true, DEFAULT_ENDGAME_MESSAGE ); } else COM_NewGame( GI->trainmap ); @@ -749,9 +749,9 @@ void SV_ConSay_f( void ) SV_Heartbeat_f ================== */ -void SV_Heartbeat_f( void ) +static void SV_Heartbeat_f( void ) { - svs.last_heartbeat = MAX_HEARTBEAT; + NET_MasterClear(); } /* diff --git a/engine/server/sv_custom.c b/engine/server/sv_custom.c index ed65820e..cb034efb 100644 --- a/engine/server/sv_custom.c +++ b/engine/server/sv_custom.c @@ -304,7 +304,7 @@ qboolean SV_CheckFile( sizebuf_t *msg, const char *filename ) return true; MSG_BeginServerCmd( msg, svc_stufftext ); - MSG_WriteString( msg, va( "upload \"!MD5%s\"\n", MD5_Print( p.rgucMD5_hash ))); + MSG_WriteStringf( msg, "upload \"!MD5%s\"\n", MD5_Print( p.rgucMD5_hash )); return false; } diff --git a/engine/server/sv_filter.c b/engine/server/sv_filter.c index f6eb8df2..1053facd 100644 --- a/engine/server/sv_filter.c +++ b/engine/server/sv_filter.c @@ -104,51 +104,63 @@ static void SV_BanID_f( void ) return; } - if( !Q_strnicmp( id, "STEAM_", 6 ) || !Q_strnicmp( id, "VALVE_", 6 )) - id += 6; - if( !Q_strnicmp( id, "XASH_", 5 )) - id += 5; - - if( svs.clients ) + if( !svs.clients ) { - if( id[0] == '#' ) - cl = SV_ClientById( Q_atoi( id + 1 )); + Con_Reportf( S_ERROR "banid: no players\n" ); + return; + } + + if( id[0] == '#' ) + { + Con_Printf( S_ERROR "banid: not supported\n" ); + return; +#if 0 + int i = Q_atoi( &id[1] ); + + cl = SV_ClientById( i ); if( !cl ) { - int i; - sv_client_t *cl1; - int len = Q_strlen( id ); + Con_Printf( S_ERROR "banid: no such player with userid %d\n", i ); + return; + } +#endif + } + else + { + size_t len; + int i; - for( i = 0, cl1 = svs.clients; i < sv_maxclients->value; i++, cl1++ ) + if( !Q_strnicmp( id, "STEAM_", 6 ) || !Q_strnicmp( id, "VALVE_", 6 )) + id += 6; + if( !Q_strnicmp( id, "XASH_", 5 )) + id += 5; + + len = Q_strlen( id ); + + for( i = 0; i < sv_maxclients->value; i++ ) + { + if( FBitSet( svs.clients[i].flags, FCL_FAKECLIENT )) + continue; + + if( svs.clients[i].state != cs_spawned ) + continue; + + if( !Q_strncmp( id, Info_ValueForKey( svs.clients[i].useragent, "uuid" ), len )) { - if( !Q_strncmp( id, Info_ValueForKey( cl1->useragent, "uuid" ), len )) - { - cl = cl1; - break; - } + cl = &svs.clients[i]; + break; } } if( !cl ) { - Con_DPrintf( S_WARN "banid: no such player\n" ); - } - else - id = Info_ValueForKey( cl->useragent, "uuid" ); - - if( !id[0] ) - { - Con_DPrintf( S_ERROR "Could not ban, not implemented yet\n" ); + Con_Printf( S_ERROR "banid: no such player with userid %s\n", id ); return; } } - if( !id[0] || id[0] == '#' ) - { - Con_DPrintf( S_ERROR "banid: bad id\n" ); - return; - } + id = Info_ValueForKey( cl->useragent, "uuid" ); SV_RemoveID( id ); @@ -159,7 +171,7 @@ static void SV_BanID_f( void ) cidfilter = filter; if( cl && !Q_stricmp( Cmd_Argv( Cmd_Argc() - 1 ), "kick" )) - Cbuf_AddText( va( "kick #%d \"Kicked and banned\"\n", cl->userid )); + Cbuf_AddTextf( "kick #%d \"Kicked and banned\"\n", cl->userid ); } static void SV_ListID_f( void ) @@ -383,7 +395,7 @@ qboolean SV_CheckIP( netadr_t *adr ) static void SV_AddIP_PrintUsage( void ) { Con_Printf(S_USAGE "addip \n" - S_USAGE_INDENT "addip \n" + S_USAGE_INDENT "addip \n" "Use 0 minutes for permanent\n" "ipaddress A.B.C.D/24 is equivalent to A.B.C.0 and A.B.C\n" "NOTE: IPv6 addresses only support prefix format!\n"); @@ -392,7 +404,7 @@ static void SV_AddIP_PrintUsage( void ) static void SV_RemoveIP_PrintUsage( void ) { Con_Printf(S_USAGE "removeip [removeAll]\n" - S_USAGE_INDENT "removeip [removeAll]\n" + S_USAGE_INDENT "removeip [removeAll]\n" "Use removeAll to delete all ip filters which ipaddress or ipaddress/CIDR includes\n"); } diff --git a/engine/server/sv_frame.c b/engine/server/sv_frame.c index c1452a24..669319fc 100644 --- a/engine/server/sv_frame.c +++ b/engine/server/sv_frame.c @@ -927,30 +927,6 @@ void SV_SendClientMessages( void ) sv.current_client = NULL; } -/* -======================= -SV_SendMessagesToAll - -e.g. before changing level -======================= -*/ -void SV_SendMessagesToAll( void ) -{ - sv_client_t *cl; - int i; - - if( sv.state == ss_dead ) - return; - - for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) - { - if( cl->state >= cs_connected ) - SetBits( cl->flags, FCL_SEND_NET_MESSAGE ); - } - - SV_SendClientMessages(); -} - /* ======================= SV_SkipUpdates diff --git a/engine/server/sv_game.c b/engine/server/sv_game.c index 0f3f85c7..71dfd5cf 100644 --- a/engine/server/sv_game.c +++ b/engine/server/sv_game.c @@ -2208,7 +2208,7 @@ SV_StartMusic void SV_StartMusic( const char *curtrack, const char *looptrack, int position ) { MSG_BeginServerCmd( &sv.multicast, svc_stufftext ); - MSG_WriteString( &sv.multicast, va( "music \"%s\" \"%s\" %d\n", curtrack, looptrack, position )); + MSG_WriteStringf( &sv.multicast, "music \"%s\" \"%s\" %d\n", curtrack, looptrack, position ); SV_Multicast( MSG_ALL, NULL, NULL, false, false ); } @@ -3765,7 +3765,7 @@ void GAME_EXPORT pfnSetClientMaxspeed( const edict_t *pEdict, float fNewMaxspeed return; fNewMaxspeed = bound( -svgame.movevars.maxspeed, fNewMaxspeed, svgame.movevars.maxspeed ); - Info_SetValueForKey( cl->physinfo, "maxspd", va( "%.f", fNewMaxspeed ), MAX_INFO_STRING ); + Info_SetValueForKeyf( cl->physinfo, "maxspd", MAX_INFO_STRING, "%.f", fNewMaxspeed ); cl->edict->v.maxspeed = fNewMaxspeed; } @@ -4886,7 +4886,12 @@ qboolean SV_ParseEdict( char **pfile, edict_t *ent ) pkvd[i].szKeyName = copystring( "angles" ); if( flYawAngle >= 0.0f ) - pkvd[i].szValue = copystring( va( "%g %g %g", ent->v.angles[0], flYawAngle, ent->v.angles[2] )); + { + char temp[MAX_VA_STRING]; + + Q_snprintf( temp, sizeof( temp ), "%g %g %g", ent->v.angles[0], flYawAngle, ent->v.angles[2] ); + pkvd[i].szValue = copystring( temp ); + } else if( flYawAngle == -1.0f ) pkvd[i].szValue = copystring( "-90 0 0" ); else if( flYawAngle == -2.0f ) @@ -4897,11 +4902,14 @@ qboolean SV_ParseEdict( char **pfile, edict_t *ent ) #ifdef HACKS_RELATED_HLMODS if( adjust_origin && !Q_strcmp( pkvd[i].szKeyName, "origin" )) { + char temp[MAX_VA_STRING]; char *pstart = pkvd[i].szValue; COM_ParseVector( &pstart, origin, 3 ); Mem_Free( pkvd[i].szValue ); // release old value, so we don't need these - pkvd[i].szValue = copystring( va( "%g %g %g", origin[0], origin[1], origin[2] - 16.0f )); + + Q_snprintf( temp, sizeof( temp ), "%g %g %g", origin[0], origin[1], origin[2] - 16.0f ); + pkvd[i].szValue = copystring( temp ); } #endif if( !Q_strcmp( pkvd[i].szKeyName, "light" )) diff --git a/engine/server/sv_init.c b/engine/server/sv_init.c index 699b47b9..a5391168 100644 --- a/engine/server/sv_init.c +++ b/engine/server/sv_init.c @@ -19,6 +19,7 @@ GNU General Public License for more details. #include "library.h" #include "voice.h" #include "pm_local.h" +#include "sequence.h" #if XASH_LOW_MEMORY != 2 int SV_UPDATE_BACKUP = SINGLEPLAYER_BACKUP; @@ -276,11 +277,20 @@ model_t *SV_ModelHandle( int modelindex ) return sv.models[modelindex]; } +static resourcetype_t SV_DetermineResourceType( const char *filename ) +{ + if( !Q_strncmp( filename, DEFAULT_SOUNDPATH, sizeof( DEFAULT_SOUNDPATH ) - 1 ) && Sound_SupportedFileFormat( COM_FileExtension( filename ))) + return t_sound; + else + return t_generic; +} + void SV_ReadResourceList( const char *filename ) { - string token; + string token; byte *afile; char *pfile; + resourcetype_t restype; afile = FS_LoadFile( filename, NULL, false ); if( !afile ) return; @@ -295,8 +305,23 @@ void SV_ReadResourceList( const char *filename ) if( !COM_IsSafeFileToDownload( token )) continue; - Con_DPrintf( " %s\n", token ); - SV_GenericIndex( token ); + COM_FixSlashes( token ); + restype = SV_DetermineResourceType( token ); + Con_DPrintf( " %s (%s)\n", token, COM_GetResourceTypeName( restype )); + switch( restype ) + { + // TODO do we need to handle other resource types specifically too? + case t_sound: + { + const char *filepath = token; + filepath += sizeof( DEFAULT_SOUNDPATH ) - 1; // skip "sound/" part + SV_SoundIndex( filepath ); + break; + } + default: + SV_GenericIndex( token ); + break; + } } Con_DPrintf( "----------------------------------\n" ); @@ -315,7 +340,7 @@ void SV_CreateGenericResources( void ) string filename; Q_strncpy( filename, sv.model_precache[1], sizeof( filename )); - COM_ReplaceExtension( filename, ".res" ); + COM_ReplaceExtension( filename, ".res", sizeof( filename )); COM_FixSlashes( filename ); SV_ReadResourceList( filename ); @@ -628,7 +653,7 @@ void SV_ActivateServer( int runPhysics ) const char *cycle = Cvar_VariableString( "mapchangecfgfile" ); if( COM_CheckString( cycle )) - Cbuf_AddText( va( "exec %s\n", cycle )); + Cbuf_AddTextf( "exec %s\n", cycle ); } } @@ -920,6 +945,9 @@ qboolean SV_SpawnServer( const char *mapname, const char *startspot, qboolean ba current_skill = bound( 0, current_skill, 3 ); Cvar_SetValue( "skill", (float)current_skill ); + // enforce hpk_maxsize + HPAK_CheckSize( CUSTOM_RES_PATH ); + // force normal player collisions for single player if( svs.maxclients == 1 ) Cvar_SetValue( "sv_clienttrace", 1 ); @@ -945,7 +973,7 @@ qboolean SV_SpawnServer( const char *mapname, const char *startspot, qboolean ba if( svs.maxclients == 1 ) Cvar_SetValue( "sv_clienttrace", 1 ); // make sure what server name doesn't contain path and extension - COM_FileBase( mapname, sv.name ); + COM_FileBase( mapname, sv.name, sizeof( sv.name )); // precache and static commands can be issued during map initialization Host_SetServerState( ss_loading ); @@ -969,7 +997,7 @@ qboolean SV_SpawnServer( const char *mapname, const char *startspot, qboolean ba for( i = WORLD_INDEX; i < sv.worldmodel->numsubmodels; i++ ) { - Q_sprintf( sv.model_precache[i+1], "*%i", i ); + Q_snprintf( sv.model_precache[i+1], sizeof( sv.model_precache[i+1] ), "*%i", i ); sv.models[i+1] = Mod_ForName( sv.model_precache[i+1], false, false ); SetBits( sv.model_precache_flags[i+1], RES_FATALIFMISSING ); } @@ -987,8 +1015,10 @@ qboolean SV_SpawnServer( const char *mapname, const char *startspot, qboolean ba SV_InitEdict( ent ); } + Sequence_OnLevelLoad( sv.name ); + // heartbeats will always be sent to the id master - svs.last_heartbeat = MAX_HEARTBEAT; // send immediately + NET_MasterClear(); // get actual movevars SV_UpdateMovevars( true ); diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 9ffdedb5..59923634 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -18,8 +18,6 @@ GNU General Public License for more details. #include "net_encode.h" #include "platform/platform.h" -#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" ); @@ -123,13 +121,14 @@ CVAR_DEFINE_AUTO( sv_voicequality, "3", FCVAR_ARCHIVE|FCVAR_SERVER, "voice chat CVAR_DEFINE_AUTO( sv_enttools_enable, "0", FCVAR_ARCHIVE|FCVAR_PROTECTED, "enable powerful and dangerous entity tools" ); CVAR_DEFINE_AUTO( sv_enttools_maxfire, "5", FCVAR_ARCHIVE|FCVAR_PROTECTED, "limit ent_fire actions count to prevent flooding" ); +CVAR_DEFINE( public_server, "public", "0", 0, "change server type from private to public" ); + convar_t *sv_novis; // disable server culling entities by vis convar_t *sv_pausable; convar_t *timeout; // seconds without any message convar_t *sv_lighting_modulate; convar_t *sv_maxclients; convar_t *sv_check_errors; -convar_t *public_server; // should heartbeats be sent convar_t *sv_reconnect_limit; // minimum seconds between connect messages convar_t *sv_validate_changelevel; convar_t *sv_sendvelocity; @@ -141,8 +140,6 @@ convar_t *sv_allow_mouse; convar_t *sv_allow_joystick; convar_t *sv_allow_vr; -static void Master_Heartbeat( void ); - //============================================================================ /* ================ @@ -667,7 +664,7 @@ void Host_ServerFrame( void ) Platform_UpdateStatusLine (); // send a heartbeat to the master if needed - Master_Heartbeat (); + NET_MasterHeartbeat (); } /* @@ -682,68 +679,6 @@ void Host_SetServerState( int state ) } //============================================================================ - -/* -================= -Master_Add -================= -*/ -static void Master_Add( void ) -{ - sizebuf_t msg; - char buf[16]; - uint challenge; - - NET_Config( true, false ); // allow remote - - svs.heartbeat_challenge = challenge = COM_RandomLong( 0, INT_MAX ); - - MSG_Init( &msg, "Master Join", buf, sizeof( buf )); - MSG_WriteBytes( &msg, "q\xFF", 2 ); - MSG_WriteDword( &msg, challenge ); - - if( NET_SendToMasters( NS_SERVER, MSG_GetNumBytesWritten( &msg ), MSG_GetBuf( &msg ))) - svs.last_heartbeat = MAX_HEARTBEAT; -} - -/* -================ -Master_Heartbeat - -Send a message to the master every few minutes to -let it know we are alive, and log information -================ -*/ -static void Master_Heartbeat( void ) -{ - if( !public_server->value || svs.maxclients == 1 ) - return; // only public servers send heartbeats - - // check for time wraparound - if( svs.last_heartbeat > host.realtime ) - svs.last_heartbeat = host.realtime; - - if(( host.realtime - svs.last_heartbeat ) < HEARTBEAT_SECONDS ) - return; // not time to send yet - - svs.last_heartbeat = host.realtime; - - Master_Add(); -} - -/* -================= -Master_Shutdown - -Informs all masters that this server is going down -================= -*/ -static void Master_Shutdown( void ) -{ - NET_Config( true, false ); // allow remote - while( NET_SendToMasters( NS_SERVER, 2, "\x62\x0A" )); -} - /* ================= SV_AddToMaster @@ -754,18 +689,19 @@ Master will validate challenge and this server to public list */ void SV_AddToMaster( netadr_t from, sizebuf_t *msg ) { - uint challenge, challenge2; + uint challenge, challenge2, heartbeat_challenge; char s[MAX_INFO_STRING] = "0\n"; // skip 2 bytes of header int clients, bots; + double last_heartbeat; const int len = sizeof( s ); - if( !NET_IsMasterAdr( from )) + if( !NET_GetMaster( from, &heartbeat_challenge, &last_heartbeat )) { Con_Printf( S_WARN "unexpected master server info query packet from %s\n", NET_AdrToString( from )); return; } - if( svs.last_heartbeat + sv_master_response_timeout.value < host.realtime ) + if( last_heartbeat + sv_master_response_timeout.value < host.realtime ) { Con_Printf( S_WARN "unexpected master server info query packet (too late? try increasing sv_master_response_timeout value)\n"); return; @@ -774,18 +710,18 @@ void SV_AddToMaster( netadr_t from, sizebuf_t *msg ) challenge = MSG_ReadDword( msg ); challenge2 = MSG_ReadDword( msg ); - if( challenge2 != svs.heartbeat_challenge ) + if( challenge2 != heartbeat_challenge ) { Con_Printf( S_WARN "unexpected master server info query packet (wrong challenge!)\n" ); return; } SV_GetPlayerCount( &clients, &bots ); - Info_SetValueForKey( s, "protocol", va( "%d", PROTOCOL_VERSION ), len ); // protocol version - Info_SetValueForKey( s, "challenge", va( "%u", challenge ), len ); // challenge number - Info_SetValueForKey( s, "players", va( "%d", clients ), len ); // current player number, without bots - Info_SetValueForKey( s, "max", va( "%d", svs.maxclients ), len ); // max_players - Info_SetValueForKey( s, "bots", va( "%d", bots ), len ); // bot count + Info_SetValueForKeyf( s, "protocol", len, "%d", PROTOCOL_VERSION ); // protocol version + Info_SetValueForKeyf( s, "challenge", len, "%u", challenge ); // challenge number + Info_SetValueForKeyf( s, "players", len, "%d", clients ); // current player number, without bots + Info_SetValueForKeyf( s, "max", len, "%d", svs.maxclients ); // max_players + Info_SetValueForKeyf( s, "bots", len, "%d", bots ); // bot count Info_SetValueForKey( s, "gamedir", GI->gamefolder, len ); // gamedir Info_SetValueForKey( s, "map", sv.name, len ); // current map Info_SetValueForKey( s, "type", (Host_IsDedicated()) ? "d" : "l", len ); // dedicated or local @@ -874,7 +810,7 @@ void SV_Init( void ) SV_InitHostCommands(); - Cvar_Get( "protocol", va( "%i", PROTOCOL_VERSION ), FCVAR_READ_ONLY, "displays server protocol version" ); + Cvar_Getf( "protocol", FCVAR_READ_ONLY, "displays server protocol version", "%i", PROTOCOL_VERSION ); Cvar_Get( "suitvolume", "0.25", FCVAR_ARCHIVE, "HEV suit volume" ); Cvar_Get( "sv_background", "0", FCVAR_READ_ONLY, "indicate what background map is running" ); Cvar_Get( "gamedir", GI->gamefolder, FCVAR_READ_ONLY, "game folder" ); @@ -935,7 +871,7 @@ void SV_Init( void ) Cvar_RegisterVariable( &sv_stopspeed ); sv_maxclients = Cvar_Get( "maxplayers", "1", FCVAR_LATCH, "server max capacity" ); sv_check_errors = Cvar_Get( "sv_check_errors", "0", FCVAR_ARCHIVE, "check edicts for errors" ); - public_server = Cvar_Get ("public", "0", 0, "change server type from private to public" ); + Cvar_RegisterVariable( &public_server ); sv_lighting_modulate = Cvar_Get( "r_lighting_modulate", "0.6", FCVAR_ARCHIVE, "lightstyles modulate scale" ); sv_reconnect_limit = Cvar_Get ("sv_reconnect_limit", "3", FCVAR_ARCHIVE, "max reconnect attempts" ); Cvar_RegisterVariable( &sv_failuretime ); @@ -983,8 +919,6 @@ void SV_Init( void ) Cvar_RegisterVariable( &sv_enttools_enable ); Cvar_RegisterVariable( &sv_enttools_maxfire ); - Cvar_RegisterVariable( &sv_autosave ); - 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" ); @@ -1114,8 +1048,8 @@ void SV_Shutdown( const char *finalmsg ) if( svs.clients ) SV_FinalMessage( finalmsg, false ); - if( public_server->value && svs.maxclients != 1 ) - Master_Shutdown(); + if( public_server.value && svs.maxclients != 1 ) + NET_MasterShutdown(); NET_Config( false, false ); SV_UnloadProgs (); diff --git a/engine/server/sv_phys.c b/engine/server/sv_phys.c index 069ad1c1..2b5faceb 100644 --- a/engine/server/sv_phys.c +++ b/engine/server/sv_phys.c @@ -718,29 +718,6 @@ void SV_AddGravity( edict_t *ent ) SV_CheckVelocity( ent ); } -/* -============ -SV_AddHalfGravity - -============ -*/ -void SV_AddHalfGravity( edict_t *ent, float timestep ) -{ - float ent_gravity; - - if( ent->v.gravity ) - ent_gravity = ent->v.gravity; - else ent_gravity = 1.0f; - - // Add 1/2 of the total gravitational effects over this timestep - ent->v.velocity[2] -= ( 0.5f * ent_gravity * sv_gravity.value * timestep ); - ent->v.velocity[2] += ( ent->v.basevelocity[2] * sv.frametime ); - ent->v.basevelocity[2] = 0.0f; - - // bound velocity - SV_CheckVelocity( ent ); -} - /* =============================================================================== diff --git a/engine/server/sv_pmove.c b/engine/server/sv_pmove.c index fed1cd8e..6770b5c4 100644 --- a/engine/server/sv_pmove.c +++ b/engine/server/sv_pmove.c @@ -354,11 +354,6 @@ static void GAME_EXPORT pfnParticle( const float *origin, int color, float life, MSG_WriteByte( &sv.reliable_datagram, bound( 0, life * 8, 255 )); } -int SV_TestLine( const vec3_t start, const vec3_t end, int flags ) -{ - return PM_TestLineExt( svgame.pmove, svgame.pmove->physents, svgame.pmove->numphysent, start, end, flags ); -} - static int GAME_EXPORT pfnTestPlayerPosition( float *pos, pmtrace_t *ptrace ) { return PM_TestPlayerPosition( svgame.pmove, pos, ptrace, NULL ); diff --git a/engine/server/sv_save.c b/engine/server/sv_save.c index 2c9da0cf..2ea3d7af 100644 --- a/engine/server/sv_save.c +++ b/engine/server/sv_save.c @@ -1389,7 +1389,7 @@ static void LoadClientState( SAVERESTOREDATA *pSaveData, const char *level, qboo { // NOTE: music is automatically goes across transition, never restore it on changelevel MSG_BeginServerCmd( &sv.signon, svc_stufftext ); - MSG_WriteString( &sv.signon, va( "music \"%s\" \"%s\" %i\n", header.introTrack, header.mainTrack, header.trackPosition )); + MSG_WriteStringf( &sv.signon, "music \"%s\" \"%s\" %i\n", header.introTrack, header.mainTrack, header.trackPosition ); } // don't go camera across the levels @@ -1739,7 +1739,7 @@ static qboolean SaveGameSlot( const char *pSaveName, const char *pSaveComment ) } // pending the preview image for savegame - Cbuf_AddText( va( "saveshot \"%s\"\n", pSaveName )); + Cbuf_AddTextf( "saveshot \"%s\"\n", pSaveName ); Con_Printf( "Saving game to %s...\n", name ); version = SAVEGAME_VERSION; diff --git a/engine/server/sv_world.c b/engine/server/sv_world.c index 71079e65..77a4aa55 100644 --- a/engine/server/sv_world.c +++ b/engine/server/sv_world.c @@ -170,46 +170,6 @@ hull_t *SV_HullForBox( const vec3_t mins, const vec3_t maxs ) return &box_hull; } -/* -================== -SV_HullAutoSelect - -select the apropriate hull automatically -================== -*/ -hull_t *SV_HullAutoSelect( model_t *model, const vec3_t mins, const vec3_t maxs, const vec3_t size, vec3_t offset ) -{ - float curdiff; - float lastdiff = 999; - int i, hullNumber = 0; // assume we fail - vec3_t clip_size; - hull_t *hull; - - // NOTE: this is not matched with hardcoded values in some cases... - for( i = 0; i < MAX_MAP_HULLS; i++ ) - { - VectorSubtract( model->hulls[i].clip_maxs, model->hulls[i].clip_mins, clip_size ); - curdiff = floor( VectorAvg( size )) - floor( VectorAvg( clip_size )); - curdiff = fabs( curdiff ); - - if( curdiff < lastdiff ) - { - hullNumber = i; - lastdiff = curdiff; - } - } - - // TraceHull stuff - hull = &model->hulls[hullNumber]; - - // calculate an offset value to center the origin - // NOTE: never get offset of drawing hull - if( !hullNumber ) VectorCopy( hull->clip_mins, offset ); - else VectorSubtract( hull->clip_mins, mins, offset ); - - return hull; -} - /* ================== SV_HullForBsp diff --git a/engine/wscript b/engine/wscript index d650a457..911cde93 100644 --- a/engine/wscript +++ b/engine/wscript @@ -58,8 +58,20 @@ def configure(conf): if not conf.env.HAVE_SDL2: conf.fatal('SDL2 not availiable! Install switch-sdl2!') conf.define('XASH_SDL', 2) + # then remove them to avoid them getting linked to shared objects conf.env.LDFLAGS.remove('-lstdc++') conf.env.LDFLAGS.remove('-lm') + elif conf.env.DEST_OS == 'psvita': + # allow the SDL2 sanity check to complete properly by linking in some deps missing from the pkg-config file + extra_libs = ['-lstdc++', '-lm', '-lSceShaccCgExt', '-lSceShaccCg_stub', '-ltaihen_stub', '-lSceKernelDmacMgr_stub'] + conf.env.LDFLAGS += extra_libs + conf.load('sdl2') + if not conf.env.HAVE_SDL2: + conf.fatal('SDL2 not availiable! Install switch-sdl2!') + conf.define('XASH_SDL', 2) + # then remove them to avoid them getting linked to shared objects + for lib in extra_libs: + conf.env.LDFLAGS.remove(lib) elif conf.options.FBDEV_SW: # unused, XASH_LINUX without XASH_SDL gives fbdev & alsa support # conf.define('XASH_FBDEV', 1) @@ -114,6 +126,9 @@ def configure(conf): conf.define_cond('DBGHELP', conf.env.DEST_OS == 'win32') conf.define_cond('PSAPI_VERSION', conf.env.DEST_OS == 'win32') # will be defined as 1 + for refdll in conf.refdlls: + refdll.register_define(conf) + def build(bld): # public includes for renderers and utils use bld(name = 'engine_includes', export_includes = '. common common/imagelib', use = 'filesystem_includes') @@ -135,7 +150,7 @@ def build(bld): if bld.env.DEST_OS == 'win32': libs += ['USER32', 'SHELL32', 'GDI32', 'ADVAPI32', 'DBGHELP', 'PSAPI', 'WS2_32' ] source += bld.path.ant_glob(['platform/win32/*.c']) - elif bld.env.DEST_OS != 'dos' and bld.env.DEST_OS != 'nswitch': #posix + elif bld.env.DEST_OS not in ['dos', 'nswitch', 'psvita']: #posix libs += [ 'M', 'RT', 'PTHREAD', 'ASOUND'] if not bld.env.STATIC: libs += ['DL'] @@ -178,6 +193,32 @@ def build(bld): libs += [ 'SOLDER' ] source += bld.path.ant_glob(['platform/posix/*.c']) source += bld.path.ant_glob(['platform/nswitch/*.c']) + # HACK: link in the entirety of libstdc++ so that dynamic libs could use all of it without manual exporting + # we can't do this right away because std::filesystem will complain about not having pathconf(), + # which we have defined in sys_nswitch.c + bld.env.LDFLAGS += ['-Wl,--whole-archive', '-lstdc++', '-Wl,--no-whole-archive', '-lm'] + + if bld.env.DEST_OS == 'psvita': + libs += [ 'VRTLD' ] + source += bld.path.ant_glob(['platform/posix/*.c']) + source += bld.path.ant_glob(['platform/psvita/*.c']) + # HACK: link in the entirety of libstdc++ so that dynamic libs could use all of it without manual exporting + # also link in all the funky dependencies that aren't in SDL2's LDFLAGS + bld.env.LDFLAGS += [ + '-Wl,--whole-archive', + '-lstdc++', + '-lpthread', + '-Wl,--no-whole-archive', + '-lm', + '-lSceShaccCgExt', + '-lkubridge_stub', + '-ltaihen_stub', + '-lSceShaccCg_stub', + '-lSceKernelModulemgr_stub', + '-lSceSblSsMgr_stub', + '-lSceVshBridge_stub', + '-lSceKernelDmacMgr_stub' + ] # add client files if not bld.env.DEDICATED: @@ -189,12 +230,8 @@ def build(bld): includes = ['server', 'client', 'client/vgui' ] - # Switch has custom parameters - if bld.env.DEST_OS == 'nswitch': - # HACK: link in the entirety of libstdc++ so that dynamic libs could use all of it without manual exporting - # we can't do this right away because std::filesystem will complain about not having pathconf(), - # which we have defined in sys_nswitch.c - bld.env.LDFLAGS += ['-Wl,--whole-archive', '-lstdc++', '-Wl,--no-whole-archive', '-lm'] + # Switch and PSVita have custom parameters + if bld.env.DEST_OS in ['nswitch', 'psvita']: bld(source = source, target = 'xash', features = 'c cxxprogram', @@ -203,7 +240,11 @@ def build(bld): install_path = None, nro_install_path = bld.env.BINDIR, nacp = 'platform/nswitch/xash3d-fwgs.nacp', - icon = 'platform/nswitch/icon.jpg') + icon = 'platform/nswitch/icon.jpg', + sce_sys = 'platform/psvita/sce_sys', + title_id = 'XASH10000', + app_name = 'xash3d-fwgs' + ) else: if bld.env.SINGLE_BINARY: install_path = bld.env.BINDIR @@ -222,5 +263,5 @@ def build(bld): use = libs, install_path = install_path, subsystem = bld.env.MSVC_SUBSYSTEM, - rpath = '$ORIGIN' + rpath = '$ORIGIN', ) diff --git a/filesystem/VFileSystem009.cpp b/filesystem/VFileSystem009.cpp index e9a3d54a..250f494d 100644 --- a/filesystem/VFileSystem009.cpp +++ b/filesystem/VFileSystem009.cpp @@ -1,6 +1,6 @@ /* VFileSystem009.h - C++ interface for filesystem_stdio -Copyright (C) 2022 Alibek Omarov +Copyright (C) 2022-2023 Xash3D FWGS contributors 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 @@ -21,9 +21,11 @@ GNU General Public License for more details. #include "filesystem.h" #include "filesystem_internal.h" #include "VFileSystem009.h" +#include "common/com_strings.h" #if __cplusplus < 201103L #define override +#define nullptr NULL #endif // GoldSrc Directories and ID @@ -35,32 +37,47 @@ GNU General Public License for more details. // PLATFORM platform // CONFIG platform/config -static inline qboolean IsIdGamedir( const char *id ) +// This is a macro because pointers returned by alloca +// shouldn't leave current scope +#define FixupPath( var, str ) \ + const size_t var ## _size = Q_strlen(( str )) + 1; \ + char * const var = static_cast( alloca( var ## _size )); \ + CopyAndFixSlashes( var, ( str ), var ## _size ) + +static inline bool 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 ) +static inline const char *IdToDir( char *dir, size_t size, 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" )) + + if( !Q_strcmp( id, "GAMEDOWNLOAD" )) + { + Q_snprintf( dir, size, "%s/downloaded", GI->gamefolder ); + return dir; + } + + if( !Q_strcmp( id, "GAMECONFIG" )) return fs_writepath->filename; // full path here so it's totally our write allowed directory - else if( !Q_strcmp( id, "PLATFORM" )) + + if( !Q_strcmp( id, "PLATFORM" )) return "platform"; // stub - else if( !Q_strcmp( id, "CONFIG" )) + + if( !Q_strcmp( id, "CONFIG" )) return "platform/config"; // stub - else // ROOT || BASE - return fs_rootdir; // give at least root directory + + // ROOT || BASE + return fs_rootdir; // give at least root directory } -static inline void CopyAndFixSlashes( char *p, const char *in ) +static inline void CopyAndFixSlashes( char *p, const char *in, size_t size ) { - Q_strcpy( p, in ); + Q_strncpy( p, in, size ); COM_FixSlashes( p ); } @@ -71,12 +88,11 @@ private: { public: CSearchState( CSearchState **head, search_t *search ) : - next( *head ), search( search ), index( 0 ) + next( *head ), + search( search ), + index( 0 ), + handle( *head ? ( *head )->handle + 1 : 0 ) { - if( *head ) - handle = (*head)->handle + 1; - else handle = 0; - *head = this; } ~CSearchState() @@ -97,17 +113,15 @@ private: 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; + return nullptr; } public: - CXashFS() : searchHead( NULL ) + CXashFS() : searchHead( nullptr ) { } @@ -118,17 +132,13 @@ public: void AddSearchPath( const char *path, const char *id ) override { - char *p = (char *)alloca( Q_strlen( path ) + 1 ); - CopyAndFixSlashes( p, path ); - + FixupPath( 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 ); - + FixupPath( p, path ); FS_AddGameDirectory( p, FS_NOWRITE_PATH | FS_CUSTOM_PATH ); } @@ -145,115 +155,119 @@ public: void CreateDirHierarchy( const char *path, const char *id ) override { - FS_CreatePath( va( "%s/%s", IdToDir( id ), path )); // FS_CreatePath is aware of slashes + char dir[MAX_VA_STRING], fullpath[MAX_VA_STRING]; + + Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", IdToDir( dir, sizeof( dir ), id ), path ); + FS_CreatePath( fullpath ); // FS_CreatePath is aware of slashes } bool FileExists( const char *path ) override { - char *p = (char *)alloca( Q_strlen( path ) + 1 ); - CopyAndFixSlashes( p, path ); - + FixupPath( p, path ); return FS_FileExists( p, false ); } bool IsDirectory( const char *path ) override { - char *p = (char *)alloca( Q_strlen( path ) + 1 ); - CopyAndFixSlashes( p, path ); - + FixupPath( 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; - file_t *fd = FS_Open( p, mode, IsIdGamedir( id ) ); + FixupPath( p, path ); + fd = FS_Open( p, mode, IsIdGamedir( id )); return fd; } void Close( FileHandle_t handle ) override { - FS_Close( (file_t *)handle ); + FS_Close( static_cast( 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; + 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_ ); + FS_Seek( static_cast( handle ), offset, whence_ ); } unsigned int Tell( FileHandle_t handle ) override { - return FS_Tell( (file_t *)handle ); + return FS_Tell( static_cast( handle )); } unsigned int Size( FileHandle_t handle ) override { - file_t *fd = (file_t *)handle; - return fd->real_length; + return static_cast( handle )->real_length; } unsigned int Size( const char *path ) override { - char *p = (char *)alloca( Q_strlen( path ) + 1 ); - CopyAndFixSlashes( p, path ); + FixupPath( 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 ); + FixupPath( p, path ); return FS_FileTime( p, false ); } void FileTimeToString( char *p, int size, long int time ) override { - time_t curtime = time; + const 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 ); + return !FS_Eof( static_cast( handle )); } void Flush( FileHandle_t handle ) override { - FS_Flush( (file_t *)handle ); + FS_Flush( static_cast( handle )); } bool EndOfFile( FileHandle_t handle ) override { - return FS_Eof( (file_t *)handle ); + return FS_Eof( static_cast( handle )); } int Read( void *buf, int size, FileHandle_t handle ) override { - return FS_Read( (file_t *)handle, buf, size ); + return FS_Read( static_cast( handle ), buf, size ); } int Write( const void *buf, int size, FileHandle_t handle ) override { - return FS_Write( (file_t *)handle, buf, size ); + return FS_Write( static_cast( handle ), buf, size ); } char *ReadLine( char *buf, int size, FileHandle_t handle ) override { - int c = FS_Gets( (file_t *)handle, (byte*)buf, size ); + const int c = FS_Gets( static_cast( handle ), buf, size ); - return c >= 0 ? buf : NULL; + return c >= 0 ? buf : nullptr; } int FPrintf( FileHandle_t handle, char *fmt, ... ) override @@ -262,41 +276,41 @@ public: int ret; va_start( ap, fmt ); - ret = FS_VPrintf( (file_t *)handle, fmt, ap ); + ret = FS_VPrintf( static_cast( handle ), fmt, ap ); va_end( ap ); return ret; } - void * GetReadBuffer(FileHandle_t, int *size, bool) override + void *GetReadBuffer( FileHandle_t, int *size, bool ) override { // deprecated by Valve *size = 0; - return NULL; + return nullptr; } - void ReleaseReadBuffer(FileHandle_t, void *) override + void ReleaseReadBuffer( FileHandle_t, void * ) override { // deprecated by Valve return; } - const char *FindFirst(const char *pattern, FileFindHandle_t *handle, const char *id) override + const char *FindFirst( const char *pattern, FileFindHandle_t *handle, const char *id ) override { - if( !handle || !pattern ) - return NULL; + CSearchState *state; + search_t *search; - char *p = (char *)alloca( Q_strlen( pattern ) + 1 ); - CopyAndFixSlashes( p, pattern ); - search_t *search = FS_Search( p, true, IsIdGamedir( id )); + if( !handle || !pattern ) + return nullptr; + + FixupPath( p, pattern ); + search = FS_Search( p, true, IsIdGamedir( id )); if( !search ) - return NULL; - - CSearchState *state = new CSearchState( &searchHead, search ); + return nullptr; + state = new CSearchState( &searchHead, search ); *handle = state->handle; - return state->search->filenames[0]; } @@ -304,10 +318,11 @@ public: { CSearchState *state = GetSearchStateByHandle( handle ); - if( !state ) return NULL; + if( !state ) + return nullptr; if( state->index + 1 >= state->search->numfilenames ) - return NULL; + return nullptr; return state->search->filenames[++state->index]; } @@ -327,32 +342,35 @@ public: void FindClose( FileFindHandle_t handle ) override { - for( CSearchState *state = searchHead, **prev = NULL; - state; - *prev = state, state = state->next ) + CSearchState *prev; + CSearchState *i; + + for( prev = nullptr, i = searchHead; + i != nullptr && i->handle != handle; + prev = i, i = i->next ); + + if( i == nullptr ) { - 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; } - Con_DPrintf( "FindClose: Can't find search state by handle %d\n", handle ); - return; + if( prev != nullptr ) + prev->next = i->next; + else + searchHead = i->next; + + delete i; } - const char * GetLocalPath( const char *name, char *buf, int size ) override + const char *GetLocalPath( const char *name, char *buf, int size ) override { - if( !name ) return NULL; + const char *fullpath; - char *p = (char *)alloca( Q_strlen( name ) + 1 ); - CopyAndFixSlashes( p, name ); + if( !name ) + return nullptr; + + FixupPath( p, name ); #if !XASH_WIN32 if( p[0] == '/' ) @@ -365,10 +383,9 @@ public: return buf; } - - const char *fullpath = FS_GetDiskPath( p, false ); + fullpath = FS_GetDiskPath( p, false ); if( !fullpath ) - return NULL; + return nullptr; Q_strncpy( buf, fullpath, size ); return buf; @@ -377,24 +394,27 @@ public: char *ParseFile( char *buf, char *token, bool *quoted ) override { qboolean qquoted; + char *p; - char *p = COM_ParseFileSafe( buf, token, PFILE_FS_TOKEN_MAX_LENGTH, 0, NULL, &qquoted ); - if( quoted ) *quoted = qquoted; + p = COM_ParseFileSafe( buf, token, PFILE_FS_TOKEN_MAX_LENGTH, 0, nullptr, &qquoted ); + + if( quoted ) + *quoted = qquoted; return p; } bool FullPathToRelativePath( const char *path, char *out ) override { + searchpath_t *sp; + if( !COM_CheckString( path )) { *out = 0; return false; } - char *p = (char *)alloca( Q_strlen( path ) + 1 ); - CopyAndFixSlashes( p, path ); - searchpath_t *sp; + FixupPath( p, path ); for( sp = fs_searchpaths; sp; sp = sp->next ) { @@ -402,12 +422,12 @@ public: if( !Q_strnicmp( sp->filename, p, splen )) { - Q_strcpy( out, p + splen + 1 ); + Q_strncpy( out, p + splen + 1, 512 ); return true; } } - Q_strcpy( out, p ); + Q_strncpy( out, p, 512 ); return false; } @@ -424,13 +444,13 @@ public: return; } - void SetWarningFunc(void (*)(const char *, ...)) override + void SetWarningFunc( void (*)( const char *, ... )) override { // TODO: return; } - void SetWarningLevel(FileWarningLevel_t) override + void SetWarningLevel( FileWarningLevel_t ) override { // TODO: return; @@ -442,23 +462,24 @@ public: return 0; } - void GetInterfaceVersion(char *p, int size) override + 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 ); + char dir[MAX_VA_STRING], fullpath[MAX_VA_STRING]; - return !!FS_AddPak_Fullpath( p, NULL, FS_CUSTOM_PATH ); + Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", IdToDir( dir, sizeof( dir ), id ), path ); + CopyAndFixSlashes( fullpath, path, sizeof( fullpath )); + + return !!FS_AddPak_Fullpath( fullpath, nullptr, 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 ); + FixupPath( p, path ); return FS_OpenReadFile( p, mode, IsIdGamedir( id )); } @@ -466,20 +487,24 @@ public: // 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; } + 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 + 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; + if( pProgress ) + *pProgress = 0; + + if( pOverride ) + *pOverride = true; + return false; } } g_VFileSystem009; @@ -488,22 +513,27 @@ extern "C" void EXPORT *CreateInterface( const char *interface, int *retval ) { if( !Q_strcmp( interface, "VFileSystem009" )) { - if( retval ) *retval = 0; + if( retval ) + *retval = 0; + return &g_VFileSystem009; } if( !Q_strcmp( interface, FS_API_CREATEINTERFACE_TAG )) { - // return a copy, to disallow overriding - static fs_api_t copy = { 0 }; + static fs_api_t copy = { 0 }; // return a copy, to disallow overriding if( !copy.InitStdio ) memcpy( ©, &g_api, sizeof( copy )); - if( retval ) *retval = 0; + if( retval ) + *retval = 0; + return © } - if( retval ) *retval = 1; - return NULL; + if( retval ) + *retval = 1; + + return nullptr; } diff --git a/filesystem/VFileSystem009.h b/filesystem/VFileSystem009.h index a0b73123..e48f4b7a 100644 --- a/filesystem/VFileSystem009.h +++ b/filesystem/VFileSystem009.h @@ -1,6 +1,6 @@ /* VFileSystem009.h - C++ interface for filesystem_stdio -Copyright (C) 2022 Alibek Omarov +Copyright (C) 2022-2023 Xash3D FWGS contributors 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 diff --git a/filesystem/dir.c b/filesystem/dir.c index 550754a5..910790dc 100644 --- a/filesystem/dir.c +++ b/filesystem/dir.c @@ -1,6 +1,7 @@ /* dir.c - caseinsensitive directory operations Copyright (C) 2022 Alibek Omarov, Velaron +Copyright (C) 2023 Xash3D FWGS contributors 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 @@ -21,8 +22,10 @@ GNU General Public License for more details. #include #if XASH_POSIX #include +#if !XASH_PSVITA #include #endif +#endif #if XASH_LINUX #include #ifndef FS_CASEFOLD_FL // for compatibility with older distros @@ -52,7 +55,7 @@ typedef struct dir_s static qboolean Platform_GetDirectoryCaseSensitivity( const char *dir ) { -#if XASH_WIN32 +#if XASH_WIN32 || XASH_PSVITA || XASH_NSWITCH return false; #elif XASH_LINUX && defined( FS_IOC_GETFLAGS ) int flags = 0; diff --git a/filesystem/filesystem.c b/filesystem/filesystem.c index 805bcf11..9c574cac 100644 --- a/filesystem/filesystem.c +++ b/filesystem/filesystem.c @@ -1,6 +1,9 @@ /* filesystem.c - game filesystem based on DP fs +Copyright (C) 2003-2006 Mathieu Olivier +Copyright (C) 2000-2007 DarkPlaces contributors Copyright (C) 2007 Uncle Mike +Copyright (C) 2015-2023 Xash3D FWGS contributors 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 @@ -30,9 +33,6 @@ GNU General Public License for more details. #endif #include #include -#if XASH_LINUX -#include -#endif #include "port.h" #include "const.h" #include "crtlib.h" @@ -655,7 +655,7 @@ void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, const qbool { pfile = COM_ParseFile( pfile, GameInfo->iconpath, sizeof( GameInfo->iconpath )); COM_FixSlashes( GameInfo->iconpath ); - COM_DefaultExtension( GameInfo->iconpath, ".ico" ); + COM_DefaultExtension( GameInfo->iconpath, ".ico", sizeof( GameInfo->iconpath )); } else if( !Q_stricmp( token, "type" )) { @@ -819,7 +819,8 @@ void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, const qbool } // make sure what gamedir is really exist - if( !FS_SysFolderExists( va( "%s/%s", fs_rootdir, GameInfo->falldir ))) + Q_snprintf( token, sizeof( token ), "%s/%s", fs_rootdir, GameInfo->falldir ); + if( !FS_SysFolderExists( token )) GameInfo->falldir[0] = '\0'; } @@ -906,28 +907,57 @@ static qboolean FS_ReadGameInfo( const char *filepath, const char *gamedir, game /* ================ -FS_CheckForGameDir +FS_CheckForQuakeGameDir + +Checks if game directory resembles Quake Engine game directory +(some of checks may as well work with Xash gamedirs, it's not a bug) ================ */ -static qboolean FS_CheckForGameDir( const char *gamedir ) +static qboolean FS_CheckForQuakeGameDir( const char *gamedir, qboolean direct ) { - // if directory contain config.cfg it's 100% gamedir - if( FS_FileExists( va( "%s/config.cfg", gamedir ), false )) - return true; - - // if directory contain progs.dat it's 100% gamedir - if( FS_FileExists( va( "%s/progs.dat", gamedir ), false )) - return true; - + // if directory contain config.cfg or progs.dat it's 100% gamedir // quake mods probably always archived but can missed config.cfg before first running - if( FS_FileExists( va( "%s/pak0.pak", gamedir ), false )) - return true; + const char *files[] = { "config.cfg", "progs.dat", "pak0.pak" }; + int i; - // NOTE; adds here some additional checks if you wished + for( i = 0; i < sizeof( files ) / sizeof( files[0] ); i++ ) + { + char buf[MAX_VA_STRING]; + + Q_snprintf( buf, sizeof( buf ), "%s/%s", gamedir, files[i] ); + if( direct ? FS_SysFileExists( buf ) : FS_FileExists( buf, false )) + return true; + } return false; } +/* +=============== +FS_CheckForXashGameDir + +Checks if game directory resembles Xash3D game directory +=============== +*/ +static qboolean FS_CheckForXashGameDir( const char *gamedir, qboolean direct ) +{ + // if directory contain gameinfo.txt or liblist.gam it's 100% gamedir + const char *files[] = { "gameinfo.txt", "liblist.gam" }; + int i; + + for( i = 0; i < sizeof( files ) / sizeof( files[0] ); i++ ) + { + char buf[MAX_SYSPATH]; + + Q_snprintf( buf, sizeof( buf ), "%s/%s", gamedir, files[i] ); + if( direct ? FS_SysFileExists( buf ) : FS_FileExists( buf, false )) + return true; + } + + return false; + +} + /* ================ FS_ParseGameInfo @@ -984,7 +1014,7 @@ static qboolean FS_ParseGameInfo( const char *gamedir, gameinfo_t *GameInfo ) FS_ConvertGameInfo( gamedir, gameinfo_path, liblist_path ); // force to create gameinfo for specified game if missing - if(( FS_CheckForGameDir( gamedir ) || !Q_stricmp( fs_gamedir, gamedir )) && !FS_FileExists( gameinfo_path, false )) + if(( FS_CheckForQuakeGameDir( gamedir, false ) || !Q_stricmp( fs_gamedir, gamedir )) && !FS_FileExists( gameinfo_path, false )) { gameinfo_t tmpGameInfo; memset( &tmpGameInfo, 0, sizeof( tmpGameInfo )); @@ -1014,6 +1044,7 @@ void FS_AddGameHierarchy( const char *dir, uint flags ) { int i; qboolean isGameDir = flags & FS_GAMEDIR_PATH; + char buf[MAX_VA_STRING]; GI->added = true; @@ -1046,15 +1077,23 @@ void FS_AddGameHierarchy( const char *dir, uint flags ) newFlags |= FS_GAMERODIR_PATH; FS_AllowDirectPaths( true ); - FS_AddGameDirectory( va( "%s/%s/", fs_rodir, dir ), newFlags ); + Q_snprintf( buf, sizeof( buf ), "%s/%s/", fs_rodir, dir ); + FS_AddGameDirectory( buf, newFlags ); FS_AllowDirectPaths( false ); } if( isGameDir ) - FS_AddGameDirectory( va( "%s/downloaded/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); - FS_AddGameDirectory( va( "%s/", dir ), flags ); + { + Q_snprintf( buf, sizeof( buf ), "%s/downloaded/", dir ); + FS_AddGameDirectory( buf, FS_NOWRITE_PATH | FS_CUSTOM_PATH ); + } + Q_snprintf( buf, sizeof( buf ), "%s/", dir ); + FS_AddGameDirectory( buf, flags ); if( isGameDir ) - FS_AddGameDirectory( va( "%s/custom/", dir ), FS_NOWRITE_PATH | FS_CUSTOM_PATH ); + { + Q_snprintf( buf, sizeof( buf ), "%s/custom/", dir ); + FS_AddGameDirectory( buf, FS_NOWRITE_PATH | FS_CUSTOM_PATH ); + } } /* @@ -1072,8 +1111,12 @@ void FS_Rescan( void ) #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 ); + char buf[MAX_VA_STRING]; + + Q_snprintf( buf, sizeof( buf ), "%sextras.pak", SDL_GetBasePath() ); + FS_AddPak_Fullpath( buf, NULL, extrasFlags ); + Q_snprintf( buf, sizeof( buf ), "%sextras_%s.pak", SDL_GetBasePath(), GI->gamefolder ); + FS_AddPak_Fullpath( buf, NULL, extrasFlags ); } #else str = getenv( "XASH3D_EXTRAS_PAK1" ); @@ -1107,7 +1150,7 @@ void FS_LoadGameInfo( const char *rootfolder ) // lock uplevel of gamedir for read\write fs_ext_path = false; - if( rootfolder ) Q_strcpy( fs_gamedir, rootfolder ); + if( rootfolder ) Q_strncpy( fs_gamedir, rootfolder, sizeof( fs_gamedir )); Con_Reportf( "FS_LoadGameInfo( %s )\n", fs_gamedir ); // clear any old pathes @@ -1186,7 +1229,7 @@ static qboolean FS_FindLibrary( const char *dllname, qboolean directpath, fs_dll } dllInfo->shortPath[i] = '\0'; - COM_DefaultExtension( dllInfo->shortPath, "."OS_LIB_EXT ); // apply ext if forget + COM_DefaultExtension( dllInfo->shortPath, "."OS_LIB_EXT, sizeof( dllInfo->shortPath )); // apply ext if forget search = FS_FindFile( dllInfo->shortPath, &index, NULL, 0, false ); @@ -1293,6 +1336,7 @@ qboolean FS_InitStdio( qboolean caseinsensitive, const char *rootdir, const char qboolean hasBaseDir = false; qboolean hasGameDir = false; int i; + char buf[MAX_VA_STRING]; FS_InitMemory(); #if !XASH_WIN32 @@ -1320,17 +1364,21 @@ qboolean FS_InitStdio( qboolean caseinsensitive, const char *rootdir, const char for( i = 0; i < dirs.numstrings; i++ ) { char roPath[MAX_SYSPATH]; - char rwPath[MAX_SYSPATH]; Q_snprintf( roPath, sizeof( roPath ), "%s/%s/", fs_rodir, dirs.strings[i] ); - Q_snprintf( rwPath, sizeof( rwPath ), "%s/%s/", 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 ); + // check if it's gamedir + if( FS_CheckForXashGameDir( roPath, true ) || FS_CheckForQuakeGameDir( roPath, true )) + { + char rwPath[MAX_SYSPATH]; + + Q_snprintf( rwPath, sizeof( rwPath ), "%s/%s/", fs_rootdir, dirs.strings[i] ); + FS_CreatePath( rwPath ); + } } stringlistfreecontents( &dirs ); @@ -1358,7 +1406,10 @@ qboolean FS_InitStdio( qboolean caseinsensitive, const char *rootdir, const char // build list of game directories here if( COM_CheckStringEmpty( fs_rodir )) - FS_AddGameDirectory( va( "%s/", fs_rodir ), FS_STATIC_PATH|FS_NOWRITE_PATH ); + { + Q_snprintf( buf, sizeof( buf ), "%s/", fs_rodir ); + FS_AddGameDirectory( buf, FS_STATIC_PATH|FS_NOWRITE_PATH ); + } FS_AddGameDirectory( "./", FS_STATIC_PATH ); for( i = 0; i < dirs.numstrings; i++ ) @@ -2013,7 +2064,7 @@ int FS_VPrintf( file_t *file, const char *format, va_list ap ) while( 1 ) { tempbuff = (char *)Mem_Malloc( fs_mempool, buff_size ); - len = Q_vsprintf( tempbuff, format, ap ); + len = Q_vsnprintf( tempbuff, buff_size, format, ap ); if( len >= 0 && len < buff_size ) break; @@ -2052,7 +2103,7 @@ FS_UnGetc Put a character back into the read buffer (only supports one character!) ==================== */ -int FS_UnGetc( file_t *file, byte c ) +int FS_UnGetc( file_t *file, char c ) { // If there's already a character waiting to be read if( file->ungetc != EOF ) @@ -2069,7 +2120,7 @@ FS_Gets Same as fgets ==================== */ -int FS_Gets( file_t *file, byte *string, size_t bufsize ) +int FS_Gets( file_t *file, char *string, size_t bufsize ) { int c, end = 0; @@ -2091,7 +2142,7 @@ int FS_Gets( file_t *file, byte *string, size_t bufsize ) c = FS_Getc( file ); if( c != '\n' ) - FS_UnGetc( file, (byte)c ); + FS_UnGetc( file, c ); } return c; diff --git a/filesystem/filesystem.h b/filesystem/filesystem.h index 762c11fd..e38ebe03 100644 --- a/filesystem/filesystem.h +++ b/filesystem/filesystem.h @@ -1,6 +1,9 @@ /* filesystem.h - engine FS +Copyright (C) 2003-2006 Mathieu Olivier +Copyright (C) 2000-2007 DarkPlaces contributors Copyright (C) 2007 Uncle Mike +Copyright (C) 2015-2023 Xash3D FWGS contributors 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 @@ -152,8 +155,8 @@ typedef struct fs_api_t 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 (*Gets)( file_t *file, char *string, size_t bufsize ); + int (*UnGetc)( file_t *file, char 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 ); diff --git a/filesystem/filesystem_internal.h b/filesystem/filesystem_internal.h index b60c271e..51748b22 100644 --- a/filesystem/filesystem_internal.h +++ b/filesystem/filesystem_internal.h @@ -1,6 +1,9 @@ /* filesystem.h - engine FS +Copyright (C) 2003-2006 Mathieu Olivier +Copyright (C) 2000-2007 DarkPlaces contributors Copyright (C) 2007 Uncle Mike +Copyright (C) 2015-2023 Xash3D FWGS contributors 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 @@ -141,8 +144,8 @@ 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_Gets( file_t *file, char *string, size_t bufsize ); +int FS_UnGetc( file_t *file, char 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 ); diff --git a/filesystem/fscallback.h b/filesystem/fscallback.h index bebfdd29..8a22962d 100644 --- a/filesystem/fscallback.h +++ b/filesystem/fscallback.h @@ -1,6 +1,9 @@ /* fscallback.h - common filesystem callbacks -Copyright (C) 2022 Alibek Omarov +Copyright (C) 2003-2006 Mathieu Olivier +Copyright (C) 2000-2007 DarkPlaces contributors +Copyright (C) 2007 Uncle Mike +Copyright (C) 2015-2023 Xash3D FWGS contributors 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 diff --git a/filesystem/pak.c b/filesystem/pak.c index 4efc3191..476207ae 100644 --- a/filesystem/pak.c +++ b/filesystem/pak.c @@ -1,7 +1,9 @@ /* pak.c - PAK support for filesystem +Copyright (C) 2003-2006 Mathieu Olivier +Copyright (C) 2000-2007 DarkPlaces contributors Copyright (C) 2007 Uncle Mike -Copyright (C) 2022 Alibek Omarov +Copyright (C) 2015-2023 Xash3D FWGS contributors 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 diff --git a/filesystem/tests/interface.cpp b/filesystem/tests/interface.cpp new file mode 100644 index 00000000..889c59a2 --- /dev/null +++ b/filesystem/tests/interface.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include "port.h" +#include "build.h" +#include "VFileSystem009.h" +#include "filesystem.h" + +#if XASH_POSIX +#include +#define LoadLibrary( x ) dlopen( x, RTLD_NOW ) +#define GetProcAddress( x, y ) dlsym( x, y ) +#define FreeLibrary( x ) dlclose( x ) +typedef void *HMODULE; +#elif XASH_WIN32 +#include +#endif + +HMODULE g_hModule; +FSAPI g_pfnGetFSAPI; +typedef void *(*pfnCreateInterface_t)( const char *, int * ); +pfnCreateInterface_t g_pfnCreateInterface; +fs_api_t g_fs; +fs_globals_t *g_nullglobals; + +static bool LoadFilesystem() +{ + int temp = -1; + + g_hModule = LoadLibrary( "filesystem_stdio." OS_LIB_EXT ); + if( !g_hModule ) + return false; + + // check our C-style interface existence + g_pfnGetFSAPI = reinterpret_cast( GetProcAddress( g_hModule, GET_FS_API )); + if( !g_pfnGetFSAPI ) + return false; + + g_nullglobals = NULL; + if( !g_pfnGetFSAPI( FS_API_VERSION, &g_fs, &g_nullglobals, NULL )) + return false; + + if( !g_nullglobals ) + return false; + + // check Valve-style interface existence + g_pfnCreateInterface = reinterpret_cast( GetProcAddress( g_hModule, "CreateInterface" )); + if( !g_pfnCreateInterface ) + return false; + + if( !g_pfnCreateInterface( "VFileSystem009", &temp ) || temp != 0 ) + return false; + + temp = -1; + + if( !g_pfnCreateInterface( FS_API_CREATEINTERFACE_TAG, &temp ) || temp != 0 ) + return false; + + return true; +} + +int main() +{ + if( !LoadFilesystem() ) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} diff --git a/filesystem/wad.c b/filesystem/wad.c index a456e49f..7095108c 100644 --- a/filesystem/wad.c +++ b/filesystem/wad.c @@ -1,7 +1,9 @@ /* wad.c - WAD support for filesystem +Copyright (C) 2003-2006 Mathieu Olivier +Copyright (C) 2000-2007 DarkPlaces contributors Copyright (C) 2007 Uncle Mike -Copyright (C) 2022 Alibek Omarov +Copyright (C) 2015-2023 Xash3D FWGS contributors 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 @@ -457,17 +459,20 @@ static int FS_FindFile_WAD( searchpath_t *search, const char *path, char *fixedn COM_ExtractFilePath( path, wadname ); wadfolder[0] = '\0'; - if( COM_CheckStringEmpty( wadname ) ) + if( COM_CheckStringEmpty( wadname )) { - COM_FileBase( wadname, wadname ); - Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); - COM_DefaultExtension( wadname, ".wad" ); + string wadbasename; + + COM_FileBase( wadname, wadbasename, sizeof( wadbasename )); + + Q_strncpy( wadfolder, wadbasename, sizeof( wadfolder )); + Q_snprintf( wadname, sizeof( wadname ), "%s.wad", wadbasename ); anywadname = false; } // make wadname from wad fullpath - COM_FileBase( search->filename, shortname ); - COM_DefaultExtension( shortname, ".wad" ); + COM_FileBase( search->filename, shortname, sizeof( shortname )); + COM_DefaultExtension( shortname, ".wad", sizeof( shortname )); // quick reject by wadname if( !anywadname && Q_stricmp( wadname, shortname )) @@ -475,7 +480,7 @@ static int FS_FindFile_WAD( searchpath_t *search, const char *path, char *fixedn // NOTE: we can't using long names for wad, // because we using original wad names[16]; - COM_FileBase( path, shortname ); + COM_FileBase( path, shortname, sizeof( shortname )); lump = W_FindLump( search->wad, shortname, type ); @@ -503,26 +508,30 @@ static void FS_Search_WAD( searchpath_t *search, stringlist_t *list, const char string wadfolder, temp; int j, i; const char *slash, *backslash, *colon, *separator; + char buf[MAX_VA_STRING]; // quick reject by filetype if( type == TYP_NONE ) return; COM_ExtractFilePath( pattern, wadname ); - COM_FileBase( pattern, wadpattern ); + COM_FileBase( pattern, wadpattern, sizeof( wadpattern )); wadfolder[0] = '\0'; if( COM_CheckStringEmpty( wadname )) { - COM_FileBase( wadname, wadname ); - Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); - COM_DefaultExtension( wadname, ".wad" ); + string wadbasename; + + COM_FileBase( wadname, wadbasename, sizeof( wadbasename )); + + Q_strncpy( wadfolder, wadbasename, sizeof( wadfolder )); + Q_snprintf( wadname, sizeof( wadname ), "%s.wad", wadbasename ); anywadname = false; } // make wadname from wad fullpath - COM_FileBase( search->filename, temp2 ); - COM_DefaultExtension( temp2, ".wad" ); + COM_FileBase( search->filename, temp2, sizeof( temp2 )); + COM_DefaultExtension( temp2, ".wad", sizeof( temp2 )); // quick reject by wadname if( !anywadname && Q_stricmp( wadname, temp2 )) @@ -550,8 +559,9 @@ static void FS_Search_WAD( searchpath_t *search, stringlist_t *list, const char 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( search->wad->lumps[i].type ))); + Q_snprintf( temp2, sizeof( temp2 ), "%s/%s", wadfolder, temp ); + Q_snprintf( buf, sizeof( buf ), ".%s", W_ExtFromType( search->wad->lumps[i].type )); + COM_DefaultExtension( temp2, buf, sizeof( temp2 )); stringlistappend( list, temp2 ); } } diff --git a/filesystem/watch.c b/filesystem/watch.c deleted file mode 100644 index 1d4cf7ea..00000000 --- a/filesystem/watch.c +++ /dev/null @@ -1,117 +0,0 @@ -#if 0 -#include "build.h" -#if XASH_LINUX -#include -#include -#include -#endif -#include "filesystem_internal.h" -#include "common/com_strings.h" - -#define MAX_FS_WATCHES 256 - -struct -{ -#if XASH_LINUX - int fd; - int count; - struct - { - fs_event_callback_t callback; - int fd; - } watch[MAX_FS_WATCHES]; -#endif // XASH_LINUX -} fsnotify; - -#if XASH_LINUX -static qboolean FS_InotifyInit( void ) -{ - int fd; - - if(( fd = inotify_init1( IN_NONBLOCK )) < 0 ) - { - Con_Printf( S_ERROR "inotify_init1 failed: %s", strerror( errno )); - return false; - } - - fsnotify.fd = fd; - return true; -} - -static qboolean FS_InotifyWasInit( void ) -{ - return fsnotify.fd >= 0; -} -#endif - -/* -=============== -FS_AddWatch - -Adds on-disk path to filesystem watcher list -Every file modification will call back -=============== -*/ -int FS_AddWatch( const char *path, fs_event_callback_t callback ) -{ -#if XASH_LINUX - int fd; - const uint mask = IN_CREATE | IN_DELETE | IN_MODIFY; - - if( !FS_InotifyWasInit() && !FS_InotifyInit()) - return false; - - if(( fd = inotify_add_watch( fsnotify.fd, path, mask )) < 0 ) - { - Con_Printf( S_ERROR "inotify_add_watch failed: %s", strerror( errno )); - return false; - } - - fsnotify.watch[fsnotify.count].fd = fd; - fsnotify.watch[fsnotify.count].callback = callback; - - return true; -#else - return false; -#endif -} - -/* -=============== -FS_WatchFrame - -Polls any changes and runs call backs -=============== -*/ -void FS_WatchFrame( void ) -{ -#if XASH_LINUX - int i; - - for( i = 0; i < fsnotify.count; i++ ) - { - struct inotify_event events; - } - -#endif -} - -/* -=============== -FS_WatchInitialize - -initializes filesystem watcher subsystem -=============== -*/ -qboolean FS_WatchInitialize( void ) -{ -#if XASH_LINUX - fsnotify.fd = -1; // only call inotify init when requested - fsnotify.count = 0; - - return true; -#else - return false; -#endif -} -#endif // 0 diff --git a/filesystem/wscript b/filesystem/wscript index 362892b1..52da7f49 100644 --- a/filesystem/wscript +++ b/filesystem/wscript @@ -1,12 +1,7 @@ #!/usr/bin/env python -from waflib.Tools import waf_unit_test - def options(opt): - grp = opt.add_option_group('filesystem_stdio options') - - grp.add_option('--enable-fs-tests', action='store_true', dest = 'FS_TESTS', default = False, - help = 'enable filesystem_stdio tests') + pass def configure(conf): nortti = { @@ -15,30 +10,40 @@ def configure(conf): } conf.env.append_unique('CXXFLAGS', conf.get_flags_by_compiler(nortti, conf.env.COMPILER_CC)) - conf.env.FS_TESTS = conf.options.FS_TESTS - 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(name = 'filesystem_includes', export_includes = '.') + + libs = [ 'filesystem_includes' ] + # on PSVita do not link any libraries that are already in the main executable, but add the includes target + if bld.env.DEST_OS == 'psvita': + libs += [ 'sdk_includes' ] + else: + libs += [ 'public' ] + bld.shlib(target = 'filesystem_stdio', - features = 'cxx', + features = 'cxx seq', source = bld.path.ant_glob(['*.c', '*.cpp']), - use = 'filesystem_includes public', + use = libs, install_path = bld.env.LIBDIR, subsystem = bld.env.MSVC_SUBSYSTEM) - if bld.env.FS_TESTS: + if bld.env.TESTS: # build in same module, so dynamic linking will work # for now (until we turn libpublic to shared module lol) - bld.program(features = 'test', - source = 'tests/caseinsensitive.c', - target = 'test_caseinsensitive', - use = 'filesystem_includes public DL', - rpath = '$ORIGIN', - subsystem = bld.env.CONSOLE_SUBSYSTEM, - install_path = None) - bld.add_post_fun(waf_unit_test.summary) - bld.add_post_fun(waf_unit_test.set_exit_code) + tests = { + 'interface' : 'tests/interface.cpp', + 'caseinsensitive' : 'tests/caseinsensitive.c', + } + + for i in tests: + bld.program(features = 'test seq', + source = tests[i], + target = 'test_%s' % i, + use = libs + ['DL'], + rpath = '$ORIGIN', + subsystem = bld.env.CONSOLE_SUBSYSTEM, + install_path = None) diff --git a/filesystem/zip.c b/filesystem/zip.c index 6bfbdb34..cfb73bd8 100644 --- a/filesystem/zip.c +++ b/filesystem/zip.c @@ -1,7 +1,7 @@ /* zip.c - ZIP support for filesystem Copyright (C) 2019 Mr0maks -Copyright (C) 2022 Alibek Omarov +Copyright (C) 2019-2023 Xash3D FWGS contributors 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 diff --git a/public/build.c b/public/build.c index 74146dae..b356a491 100644 --- a/public/build.c +++ b/public/build.c @@ -16,27 +16,16 @@ GNU General Public License for more details. #include "crtlib.h" #include "buildenums.h" -static const char *date = __DATE__ ; static const char *mon[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static const char mond[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; -/* -=============== -Q_buildnum - -returns days since Apr 1 2015 -=============== -*/ -int Q_buildnum( void ) +int Q_buildnum_date( const char *date ) { - static int b = 0; + int b; int m = 0; int d = 0; int y = 0; - if( b != 0 ) - return b; - for( m = 0; m < 11; m++ ) { if( !Q_strnicmp( &date[0], mon[m], 3 )) @@ -57,6 +46,23 @@ int Q_buildnum( void ) return b; } +/* +=============== +Q_buildnum + +returns days since Apr 1 2015 +=============== +*/ +int Q_buildnum( void ) +{ + static int b = 0; + + if( !b ) + b = Q_buildnum_date( __DATE__ ); + + return b; +} + /* ============= Q_buildnum_compat @@ -89,6 +95,8 @@ const char *Q_PlatformStringByID( const int platform ) return "win32"; case PLATFORM_ANDROID: return "android"; + case PLATFORM_LINUX_UNKNOWN: + return "linuxunkabi"; case PLATFORM_LINUX: return "linux"; case PLATFORM_APPLE: @@ -111,6 +119,8 @@ const char *Q_PlatformStringByID( const int platform ) return "irix"; case PLATFORM_NSWITCH: return "nswitch"; + case PLATFORM_PSVITA: + return "psvita"; } assert( 0 ); @@ -160,8 +170,8 @@ const char *Q_ArchitectureStringByID( const int arch, const uint abi, const int // no support for big endian ARM here if( endianness == ENDIANNESS_LITTLE ) { - const int ver = ( abi >> ARCHITECTURE_ARM_VER_SHIFT ) & ARCHITECTURE_ARM_VER_MASK; - const qboolean hardfp = FBitSet( abi, ARCHITECTURE_ARM_HARDFP ); + const uint ver = ( abi >> ARCH_ARM_VER_SHIFT ) & ARCH_ARM_VER_MASK; + const qboolean hardfp = FBitSet( abi, ARCH_ARM_HARDFP ); if( is64 ) return "arm64"; // keep as arm64, it's not aarch64! @@ -184,12 +194,12 @@ const char *Q_ArchitectureStringByID( const int arch, const uint abi, const int case ARCHITECTURE_RISCV: switch( abi ) { - case ARCHITECTURE_RISCV_FP_SOFT: + case ARCH_RISCV_FP_SOFT: return is64 ? "riscv64" : "riscv32"; - case ARCHITECTURE_RISCV_FP_SINGLE: + case ARCH_RISCV_FP_SINGLE: return is64 ? "riscv64f" : "riscv32f"; - case ARCHITECTURE_RISCV_FP_DOUBLE: - return is64 ? "riscv64d" : "riscv64f"; + case ARCH_RISCV_FP_DOUBLE: + return is64 ? "riscv64d" : "riscv32d"; } break; } diff --git a/public/build.h b/public/build.h index 8a4db711..636d97a4 100644 --- a/public/build.h +++ b/public/build.h @@ -69,6 +69,7 @@ Then you can use another oneliner to query all variables: #undef XASH_IRIX #undef XASH_JS #undef XASH_LINUX +#undef XASH_LINUX_UNKNOWN #undef XASH_LITTLE_ENDIAN #undef XASH_MIPS #undef XASH_MOBILE_PLATFORM @@ -83,6 +84,7 @@ Then you can use another oneliner to query all variables: #undef XASH_WIN32 #undef XASH_X86 #undef XASH_NSWITCH +#undef XASH_PSVITA //================================================================ // @@ -98,10 +100,17 @@ Then you can use another oneliner to query all variables: #else // POSIX compatible #define XASH_POSIX 1 #if defined __linux__ - #define XASH_LINUX 1 #if defined __ANDROID__ #define XASH_ANDROID 1 + #else + #include + // if our system libc has features.h header + // try to detect it to not confuse other libcs with built with glibc game libraries + #if !defined __GLIBC__ + #define XASH_LINUX_UNKNOWN 1 + #endif #endif + #define XASH_LINUX 1 #elif defined __FreeBSD__ #define XASH_FREEBSD 1 #elif defined __NetBSD__ @@ -122,12 +131,14 @@ Then you can use another oneliner to query all variables: #endif // TARGET_OS_IOS #elif defined __SWITCH__ #define XASH_NSWITCH 1 + #elif defined __vita__ + #define XASH_PSVITA 1 #else #error #endif #endif -#if XASH_ANDROID || defined XASH_IOS || defined XASH_NSWITCH +#if XASH_ANDROID || defined XASH_IOS || defined XASH_NSWITCH || defined XASH_PSVITA #define XASH_MOBILE_PLATFORM 1 #endif diff --git a/public/buildenums.h b/public/buildenums.h index ed7a0c58..cd798eb7 100644 --- a/public/buildenums.h +++ b/public/buildenums.h @@ -28,10 +28,10 @@ GNU General Public License for more details. // //================================================================ #define PLATFORM_WIN32 1 -#define PLATFORM_ANDROID 2 -#define PLATFORM_LINUX 3 -#define PLATFORM_APPLE 4 -#define PLATFORM_FREEBSD 5 +#define PLATFORM_LINUX 2 +#define PLATFORM_FREEBSD 3 +#define PLATFORM_ANDROID 4 +#define PLATFORM_APPLE 5 #define PLATFORM_NETBSD 6 #define PLATFORM_OPENBSD 7 #define PLATFORM_EMSCRIPTEN 8 @@ -40,11 +40,15 @@ GNU General Public License for more details. #define PLATFORM_SERENITY 11 #define PLATFORM_IRIX 12 #define PLATFORM_NSWITCH 13 +#define PLATFORM_PSVITA 14 +#define PLATFORM_LINUX_UNKNOWN 15 #if XASH_WIN32 #define XASH_PLATFORM PLATFORM_WIN32 #elif XASH_ANDROID #define XASH_PLATFORM PLATFORM_ANDROID +#elif XASH_LINUX_UNKNOWN + #define XASH_PLATFORM PLATFORM_LINUX_UNKNOWN #elif XASH_LINUX #define XASH_PLATFORM PLATFORM_LINUX #elif XASH_APPLE @@ -67,6 +71,8 @@ GNU General Public License for more details. #define XASH_PLATFORM PLATFORM_IRIX #elif XASH_NSWITCH #define XASH_PLATFORM PLATFORM_NSWITCH +#elif XASH_PSVITA + #define XASH_PLATFORM PLATFORM_PSVITA #else #error #endif @@ -76,8 +82,8 @@ GNU General Public License for more details. // CPU ARCHITECTURE DEFINES // //================================================================ -#define ARCHITECTURE_AMD64 1 -#define ARCHITECTURE_X86 2 +#define ARCHITECTURE_X86 1 +#define ARCHITECTURE_AMD64 2 #define ARCHITECTURE_ARM 3 #define ARCHITECTURE_MIPS 4 #define ARCHITECTURE_JS 6 @@ -125,27 +131,27 @@ GNU General Public License for more details. //================================================================ #define BIT( n ) ( 1U << ( n )) -#define ARCHITECTURE_ARM_VER_MASK ( BIT( 5 ) - 1 ) -#define ARCHITECTURE_ARM_VER_SHIFT 0 -#define ARCHITECTURE_ARM_HARDFP BIT( 5 ) +#define ARCH_ARM_VER_MASK ( BIT( 5 ) - 1 ) +#define ARCH_ARM_VER_SHIFT 0 +#define ARCH_ARM_HARDFP BIT( 5 ) -#define ARCHITECTURE_RISCV_FP_SOFT 0 -#define ARCHITECTURE_RISCV_FP_SINGLE 1 -#define ARCHITECTURE_RISCV_FP_DOUBLE 2 +#define ARCH_RISCV_FP_SOFT 0 +#define ARCH_RISCV_FP_SINGLE 1 +#define ARCH_RISCV_FP_DOUBLE 2 #if XASH_ARCHITECTURE == ARCHITECTURE_ARM #if XASH_ARM_HARDFP - #define XASH_ARCHITECTURE_ABI ( ARCHITECTURE_ARM_HARDFP | XASH_ARM ) + #define XASH_ARCHITECTURE_ABI ( ARCH_ARM_HARDFP | XASH_ARM ) #else #define XASH_ARCHITECTURE_ABI ( XASH_ARM ) #endif #elif XASH_ARCHITECTURE == ARCHITECTURE_RISCV #if XASH_RISCV_SOFTFP - #define XASH_ARCHITECTURE_ABI ARCHITECTURE_RISCV_FP_SOFT + #define XASH_ARCHITECTURE_ABI ARCH_RISCV_FP_SOFT #elif XASH_RISCV_SINGLEFP - #define XASH_ARCHITECTURE_ABI ARCHITECTURE_RISCV_FP_SINGLE + #define XASH_ARCHITECTURE_ABI ARCH_RISCV_FP_SINGLE #elif XASH_RISCV_DOUBLEFP - #define XASH_ARCHITECTURE_ABI ARCHITECTURE_RISCV_FP_DOUBLE + #define XASH_ARCHITECTURE_ABI ARCH_RISCV_FP_DOUBLE #else #error #endif diff --git a/public/crclib.c b/public/crclib.c index 95013b58..0312f820 100644 --- a/public/crclib.c +++ b/public/crclib.c @@ -457,10 +457,11 @@ returns hash key for string */ uint COM_HashKey( const char *string, uint hashSize ) { - uint i, hashKey = 0; + int hashKey = 5381; + unsigned char i; - for( i = 0; string[i]; i++ ) - hashKey = (hashKey + i) * 37 + Q_tolower( string[i] ); + while(( i = *string++ )) + hashKey = ( hashKey << 5 ) + hashKey + ( i & 0xDF ); - return (hashKey % hashSize); + return hashKey & ( hashSize - 1 ); } diff --git a/public/crtlib.c b/public/crtlib.c index a0867558..b88d2c10 100644 --- a/public/crtlib.c +++ b/public/crtlib.c @@ -1,6 +1,7 @@ /* crtlib.c - internal stdlib Copyright (C) 2011 Uncle Mike +Copyright (c) QuakeSpasm contributors 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 @@ -23,20 +24,6 @@ GNU General Public License for more details. #include "crtlib.h" #include "xash3d_mathlib.h" -void Q_strnupr( const char *in, char *out, size_t size_out ) -{ - if( size_out == 0 ) return; - - while( *in && size_out > 1 ) - { - if( *in >= 'a' && *in <= 'z' ) - *out++ = *in++ + 'A' - 'a'; - else *out++ = *in++; - size_out--; - } - *out = '\0'; -} - void Q_strnlwr( const char *in, char *out, size_t size_out ) { if( size_out == 0 ) return; @@ -504,18 +491,6 @@ int Q_snprintf( char *buffer, size_t buffersize, const char *format, ... ) return result; } -int Q_sprintf( char *buffer, const char *format, ... ) -{ - va_list args; - int result; - - va_start( args, format ); - result = Q_vsnprintf( buffer, 99999, format, args ); - va_end( args ); - - return result; -} - void COM_StripColors( const char *in, char *out ) { while ( *in ) @@ -527,26 +502,6 @@ void COM_StripColors( const char *in, char *out ) *out = '\0'; } -uint Q_hashkey( const char *string, uint hashSize, qboolean caseinsensitive ) -{ - uint i, hashKey = 0; - - if( caseinsensitive ) - { - for( i = 0; string[i]; i++) - hashKey += (i * 119) * Q_tolower( string[i] ); - } - else - { - for( i = 0; string[i]; i++ ) - hashKey += (i + 119) * (int)string[i]; - } - - hashKey = ((hashKey ^ (hashKey >> 10)) ^ (hashKey >> 20)) & (hashSize - 1); - - return hashKey; -} - char *Q_pretifymem( float value, int digitsafterdecimal ) { static char output[8][32]; @@ -564,14 +519,14 @@ char *Q_pretifymem( float value, int digitsafterdecimal ) if( value > onemb ) { value /= onemb; - Q_strcpy( suffix, " Mb" ); + Q_strncpy( suffix, " Mb", sizeof( suffix )); } else if( value > onekb ) { value /= onekb; - Q_strcpy( suffix, " Kb" ); + Q_strncpy( suffix, " Kb", sizeof( suffix )); } - else Q_strcpy( suffix, " bytes" ); + else Q_strncpy( suffix, " bytes", sizeof( suffix )); // clamp to >= 0 digitsafterdecimal = Q_max( digitsafterdecimal, 0 ); @@ -579,15 +534,15 @@ char *Q_pretifymem( float value, int digitsafterdecimal ) // if it's basically integral, don't do any decimals if( fabs( value - (int)value ) < 0.00001f ) { - Q_sprintf( val, "%i%s", (int)value, suffix ); + Q_snprintf( val, sizeof( val ), "%i%s", (int)value, suffix ); } else { char fmt[32]; // otherwise, create a format string for the decimals - Q_sprintf( fmt, "%%.%if%s", digitsafterdecimal, suffix ); - Q_sprintf( val, fmt, (double)value ); + Q_snprintf( fmt, sizeof( fmt ), "%%.%if%s", digitsafterdecimal, suffix ); + Q_snprintf( val, sizeof( val ), fmt, (double)value ); } // copy from in to out @@ -619,69 +574,42 @@ char *Q_pretifymem( float value, int digitsafterdecimal ) return out; } -/* -============ -va - -does a varargs printf into a temp buffer, -so I don't need to have varargs versions -of all text functions. -============ -*/ -char *va( const char *format, ... ) -{ - va_list argptr; - static char string[16][1024], *s; - static int stringindex = 0; - - s = string[stringindex]; - stringindex = (stringindex + 1) & 15; - va_start( argptr, format ); - Q_vsnprintf( s, sizeof( string[0] ), format, argptr ); - va_end( argptr ); - - return s; -} - /* ============ COM_FileBase Extracts the base name of a file (no path, no extension, assumes '/' as path separator) +a1ba: adapted and simplified version from QuakeSpasm ============ */ -void COM_FileBase( const char *in, char *out ) +void COM_FileBase( const char *in, char *out, size_t size ) { - int len, start, end; + const char *dot, *slash, *s; + size_t len; - len = Q_strlen( in ); - if( !len ) return; + if( unlikely( !COM_CheckString( in ) || size <= 1 )) + { + out[0] = 0; + return; + } - // scan backward for '.' - end = len - 1; + slash = in; + dot = NULL; + for( s = in; *s; s++ ) + { + if( *s == '/' || *s == '\\' ) + slash = s + 1; - while( end && in[end] != '.' && in[end] != '/' && in[end] != '\\' ) - end--; + if( *s == '.' ) + dot = s; + } - if( in[end] != '.' ) - end = len-1; // no '.', copy to end - else end--; // found ',', copy to left of '.' + if( dot == NULL || dot < slash ) + dot = s; - // scan backward for '/' - start = len - 1; + len = Q_min( size - 1, dot - slash ); - while( start >= 0 && in[start] != '/' && in[start] != '\\' ) - start--; - - if( start < 0 || ( in[start] != '/' && in[start] != '\\' )) - start = 0; - else start++; - - // length of new sting - len = end - start + 1; - - // Copy partial string - Q_strncpy( out, &in[start], len + 1 ); + memcpy( out, slash, len ); out[len] = 0; } @@ -786,7 +714,7 @@ void COM_StripExtension( char *path ) COM_DefaultExtension ================== */ -void COM_DefaultExtension( char *path, const char *extension ) +void COM_DefaultExtension( char *path, const char *extension, size_t size ) { const char *src; size_t len; @@ -803,7 +731,7 @@ void COM_DefaultExtension( char *path, const char *extension ) src--; } - Q_strcpy( &path[len], extension ); + Q_strncpy( &path[len], extension, size - len ); } /* @@ -811,10 +739,10 @@ void COM_DefaultExtension( char *path, const char *extension ) COM_ReplaceExtension ================== */ -void COM_ReplaceExtension( char *path, const char *extension ) +void COM_ReplaceExtension( char *path, const char *extension, size_t size ) { COM_StripExtension( path ); - COM_DefaultExtension( path, extension ); + COM_DefaultExtension( path, extension, size ); } /* @@ -861,7 +789,10 @@ void COM_PathSlashFix( char *path ) len = Q_strlen( path ); if( path[len - 1] != '\\' && path[len - 1] != '/' ) - Q_strcpy( &path[len], "/" ); + { + path[len] = '/'; + path[len + 1] = '\0'; + } } /* diff --git a/public/crtlib.h b/public/crtlib.h index 4f159584..b90fc182 100644 --- a/public/crtlib.h +++ b/public/crtlib.h @@ -48,6 +48,7 @@ enum // build.c // int Q_buildnum( void ); +int Q_buildnum_date( const char *date ); int Q_buildnum_compat( void ); const char *Q_PlatformStringByID( const int platform ); const char *Q_buildos( void ); @@ -58,19 +59,13 @@ const char *Q_buildcommit( void ); // // crtlib.c // -#define Q_strupr( in, out ) Q_strnupr( in, out, 99999 ) -void Q_strnupr( const char *in, char *out, size_t size_out ); -#define Q_strlwr( in, out ) Q_strnlwr( in, out, 99999 ) void Q_strnlwr( const char *in, char *out, size_t size_out ); #define Q_strlen( str ) (( str ) ? strlen(( str )) : 0 ) size_t Q_colorstr( const char *string ); char Q_toupper( const char in ); char Q_tolower( const char in ); -#define Q_strcat( dst, src ) Q_strncat( dst, src, 99999 ) size_t Q_strncat( char *dst, const char *src, size_t siz ); -#define Q_strcpy( dst, src ) Q_strncpy( dst, src, 99999 ) 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 ); @@ -82,19 +77,16 @@ qboolean Q_stricmpext( const char *pattern, const char *text ); qboolean Q_strnicmpext( const char *pattern, const char *text, size_t minimumlen ); const byte *Q_memmem( const byte *haystack, size_t haystacklen, const byte *needle, size_t needlelen ); const char *Q_timestamp( int format ); -#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 ); #define Q_strpbrk strpbrk 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 ); -void COM_FileBase( const char *in, char *out ); +void COM_FileBase( const char *in, char *out, size_t size ); const char *COM_FileExtension( const char *in ); -void COM_DefaultExtension( char *path, const char *extension ); -void COM_ReplaceExtension( char *path, const char *extension ); +void COM_DefaultExtension( char *path, const char *extension, size_t size ); +void COM_ReplaceExtension( char *path, const char *extension, size_t size ); void COM_ExtractFilePath( const char *path, char *dest ); const char *COM_FileWithoutPath( const char *in ); void COM_StripExtension( char *path ); diff --git a/public/matrixlib.c b/public/matrixlib.c index 435474d5..a783c24b 100644 --- a/public/matrixlib.c +++ b/public/matrixlib.c @@ -19,7 +19,7 @@ GNU General Public License for more details. #include "com_model.h" #include "xash3d_mathlib.h" -const matrix3x4 matrix3x4_identity = +const matrix3x4 m_matrix3x4_identity = { { 1, 0, 0, 0 }, // PITCH [forward], org[0] { 0, 1, 0, 0 }, // YAW [right] , org[1] @@ -217,62 +217,6 @@ void Matrix3x4_CreateFromEntity( matrix3x4 out, const vec3_t angles, const vec3_ } } -void Matrix3x4_TransformPositivePlane( const matrix3x4 in, const vec3_t normal, float d, vec3_t out, float *dist ) -{ - float scale = sqrt( in[0][0] * in[0][0] + in[0][1] * in[0][1] + in[0][2] * in[0][2] ); - float iscale = 1.0f / scale; - - out[0] = (normal[0] * in[0][0] + normal[1] * in[0][1] + normal[2] * in[0][2]) * iscale; - out[1] = (normal[0] * in[1][0] + normal[1] * in[1][1] + normal[2] * in[1][2]) * iscale; - out[2] = (normal[0] * in[2][0] + normal[1] * in[2][1] + normal[2] * in[2][2]) * iscale; - *dist = d * scale + ( out[0] * in[0][3] + out[1] * in[1][3] + out[2] * in[2][3] ); -} - -void Matrix3x4_Invert_Simple( matrix3x4 out, const matrix3x4 in1 ) -{ - // we only support uniform scaling, so assume the first row is enough - // (note the lack of sqrt here, because we're trying to undo the scaling, - // this means multiplying by the inverse scale twice - squaring it, which - // makes the sqrt a waste of time) - float scale = 1.0f / (in1[0][0] * in1[0][0] + in1[0][1] * in1[0][1] + in1[0][2] * in1[0][2]); - - // invert the rotation by transposing and multiplying by the squared - // recipricol of the input matrix scale as described above - out[0][0] = in1[0][0] * scale; - out[0][1] = in1[1][0] * scale; - out[0][2] = in1[2][0] * scale; - out[1][0] = in1[0][1] * scale; - out[1][1] = in1[1][1] * scale; - out[1][2] = in1[2][1] * scale; - out[2][0] = in1[0][2] * scale; - out[2][1] = in1[1][2] * scale; - out[2][2] = in1[2][2] * scale; - - // invert the translate - out[0][3] = -(in1[0][3] * out[0][0] + in1[1][3] * out[0][1] + in1[2][3] * out[0][2]); - out[1][3] = -(in1[0][3] * out[1][0] + in1[1][3] * out[1][1] + in1[2][3] * out[1][2]); - out[2][3] = -(in1[0][3] * out[2][0] + in1[1][3] * out[2][1] + in1[2][3] * out[2][2]); -} - -void Matrix3x4_Transpose( matrix3x4 out, const matrix3x4 in1 ) -{ - // transpose only rotational component - out[0][0] = in1[0][0]; - out[0][1] = in1[1][0]; - out[0][2] = in1[2][0]; - out[1][0] = in1[0][1]; - out[1][1] = in1[1][1]; - out[1][2] = in1[2][1]; - out[2][0] = in1[0][2]; - out[2][1] = in1[1][2]; - out[2][2] = in1[2][2]; - - // copy origin - out[0][3] = in1[0][3]; - out[1][3] = in1[1][3]; - out[2][3] = in1[2][3]; -} - /* ================== Matrix3x4_TransformAABB @@ -295,7 +239,7 @@ void Matrix3x4_TransformAABB( const matrix3x4 world, const vec3_t mins, const ve VectorAdd( worldCenter, worldExtents, absmax ); } -const matrix4x4 matrix4x4_identity = +const matrix4x4 m_matrix4x4_identity = { { 1, 0, 0, 0 }, // PITCH { 0, 1, 0, 0 }, // YAW @@ -360,40 +304,6 @@ void Matrix4x4_ConcatTransforms( matrix4x4 out, const matrix4x4 in1, const matri out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + in1[2][2] * in2[2][3] + in1[2][3]; } -void Matrix4x4_SetOrigin( matrix4x4 out, float x, float y, float z ) -{ - out[0][3] = x; - out[1][3] = y; - out[2][3] = z; -} - -void Matrix4x4_OriginFromMatrix( const matrix4x4 in, float *out ) -{ - out[0] = in[0][3]; - out[1] = in[1][3]; - out[2] = in[2][3]; -} - -void Matrix4x4_FromOriginQuat( matrix4x4 out, const vec4_t quaternion, const vec3_t origin ) -{ - out[0][0] = 1.0f - 2.0f * quaternion[1] * quaternion[1] - 2.0f * quaternion[2] * quaternion[2]; - out[1][0] = 2.0f * quaternion[0] * quaternion[1] + 2.0f * quaternion[3] * quaternion[2]; - out[2][0] = 2.0f * quaternion[0] * quaternion[2] - 2.0f * quaternion[3] * quaternion[1]; - out[0][3] = origin[0]; - out[0][1] = 2.0f * quaternion[0] * quaternion[1] - 2.0f * quaternion[3] * quaternion[2]; - out[1][1] = 1.0f - 2.0f * quaternion[0] * quaternion[0] - 2.0f * quaternion[2] * quaternion[2]; - out[2][1] = 2.0f * quaternion[1] * quaternion[2] + 2.0f * quaternion[3] * quaternion[0]; - out[1][3] = origin[1]; - out[0][2] = 2.0f * quaternion[0] * quaternion[2] + 2.0f * quaternion[3] * quaternion[1]; - out[1][2] = 2.0f * quaternion[1] * quaternion[2] - 2.0f * quaternion[3] * quaternion[0]; - out[2][2] = 1.0f - 2.0f * quaternion[0] * quaternion[0] - 2.0f * quaternion[1] * quaternion[1]; - out[2][3] = origin[2]; - out[3][0] = 0.0f; - out[3][1] = 0.0f; - out[3][2] = 0.0f; - out[3][3] = 1.0f; -} - void Matrix4x4_CreateFromEntity( matrix4x4 out, const vec3_t angles, const vec3_t origin, float scale ) { float angle, sr, sp, sy, cr, cp, cy; @@ -525,17 +435,6 @@ void Matrix4x4_TransformPositivePlane( const matrix4x4 in, const vec3_t normal, *dist = d * scale + ( out[0] * in[0][3] + out[1] * in[1][3] + out[2] * in[2][3] ); } -void Matrix4x4_TransformStandardPlane( const matrix4x4 in, const vec3_t normal, float d, vec3_t out, float *dist ) -{ - float scale = sqrt( in[0][0] * in[0][0] + in[0][1] * in[0][1] + in[0][2] * in[0][2] ); - float iscale = 1.0f / scale; - - out[0] = (normal[0] * in[0][0] + normal[1] * in[0][1] + normal[2] * in[0][2]) * iscale; - out[1] = (normal[0] * in[1][0] + normal[1] * in[1][1] + normal[2] * in[1][2]) * iscale; - out[2] = (normal[0] * in[2][0] + normal[1] * in[2][1] + normal[2] * in[2][2]) * iscale; - *dist = d * scale - ( out[0] * in[0][3] + out[1] * in[1][3] + out[2] * in[2][3] ); -} - void Matrix4x4_Invert_Simple( matrix4x4 out, const matrix4x4 in1 ) { // we only support uniform scaling, so assume the first row is enough @@ -568,26 +467,6 @@ void Matrix4x4_Invert_Simple( matrix4x4 out, const matrix4x4 in1 ) out[3][3] = 1.0f; } -void Matrix4x4_Transpose( matrix4x4 out, const matrix4x4 in1 ) -{ - out[0][0] = in1[0][0]; - out[0][1] = in1[1][0]; - out[0][2] = in1[2][0]; - out[0][3] = in1[3][0]; - out[1][0] = in1[0][1]; - out[1][1] = in1[1][1]; - out[1][2] = in1[2][1]; - out[1][3] = in1[3][1]; - out[2][0] = in1[0][2]; - out[2][1] = in1[1][2]; - out[2][2] = in1[2][2]; - out[2][3] = in1[3][2]; - out[3][0] = in1[0][3]; - out[3][1] = in1[1][3]; - out[3][2] = in1[2][3]; - out[3][3] = in1[3][3]; -} - qboolean Matrix4x4_Invert_Full( matrix4x4 out, const matrix4x4 in1 ) { float *temp; diff --git a/public/tests/test_build.c b/public/tests/test_build.c new file mode 100644 index 00000000..b28c7641 --- /dev/null +++ b/public/tests/test_build.c @@ -0,0 +1,177 @@ +#include +#include "build.h" +#include "buildenums.h" +#include "crtlib.h" + +// THESE DEFINITIONS MUST NEVER CHANGE +static struct +{ + int id; + const char *string; +} expected_platforms[] = +{ +{ PLATFORM_WIN32, "win32" }, +{ PLATFORM_ANDROID, "android" }, +{ PLATFORM_LINUX_UNKNOWN, "linuxunkabi" }, +{ PLATFORM_LINUX, "linux" }, +{ PLATFORM_APPLE, "apple" }, +{ PLATFORM_FREEBSD, "freebsd" }, +{ PLATFORM_NETBSD, "netbsd" }, +{ PLATFORM_OPENBSD, "openbsd" }, +{ PLATFORM_EMSCRIPTEN, "emscripten" }, +{ PLATFORM_DOS4GW, "DOS4GW" }, +{ PLATFORM_HAIKU, "haiku" }, +{ PLATFORM_SERENITY, "serenity" }, +{ PLATFORM_IRIX, "irix" }, +{ PLATFORM_NSWITCH, "nswitch" }, +{ PLATFORM_PSVITA, "psvita" }, +}; + +static struct +{ + int id; + int abi; + int endianness; + int is64; + const char *string; +} expected_architectures[] = +{ +{ ARCHITECTURE_AMD64, 0, -1, -1, "amd64" }, +{ ARCHITECTURE_X86, 0, -1, -1, "i386" }, +{ ARCHITECTURE_E2K, 0, -1, -1, "e2k" }, +{ ARCHITECTURE_JS, 0, -1, -1, "javascript" }, + +// all possible MIPS names +{ ARCHITECTURE_MIPS, 0, ENDIANNESS_BIG, true, "mips64" }, +{ ARCHITECTURE_MIPS, 0, ENDIANNESS_BIG, false, "mips" }, +{ ARCHITECTURE_MIPS, 0, ENDIANNESS_LITTLE, true, "mips64el" }, +{ ARCHITECTURE_MIPS, 0, ENDIANNESS_LITTLE, false, "mipsel" }, + +// All ARM is little endian only (for now?) +// Arm64 is always arm64, no matter the version (for now) +// Arm64 don't care about hardfp bit +{ ARCHITECTURE_ARM, 8 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, true, "arm64" }, +{ ARCHITECTURE_ARM, 8, ENDIANNESS_LITTLE, true, "arm64" }, +{ ARCHITECTURE_ARM, 0 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, true, "arm64" }, +{ ARCHITECTURE_ARM, 0, ENDIANNESS_LITTLE, true, "arm64" }, + +// ARMv6 and below don't care about hardfp bit +{ ARCHITECTURE_ARM, 4 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, false, "armv4l" }, +{ ARCHITECTURE_ARM, 4, ENDIANNESS_LITTLE, false, "armv4l" }, +{ ARCHITECTURE_ARM, 5 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, false, "armv5l" }, +{ ARCHITECTURE_ARM, 5, ENDIANNESS_LITTLE, false, "armv5l" }, +{ ARCHITECTURE_ARM, 6 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, false, "armv6l" }, +{ ARCHITECTURE_ARM, 6, ENDIANNESS_LITTLE, false, "armv6l" }, +{ ARCHITECTURE_ARM, 6 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, false, "armv6l" }, +{ ARCHITECTURE_ARM, 6, ENDIANNESS_LITTLE, false, "armv6l" }, + +// ARMv7 and ARMv8 in 32-bit mode, hardfp bit is important +{ ARCHITECTURE_ARM, 7 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, false, "armv7hf" }, +{ ARCHITECTURE_ARM, 7, ENDIANNESS_LITTLE, false, "armv7l" }, +{ ARCHITECTURE_ARM, 8 | ARCH_ARM_HARDFP, ENDIANNESS_LITTLE, false, "armv8_32hf" }, +{ ARCHITECTURE_ARM, 8, ENDIANNESS_LITTLE, false, "armv8_32l" }, + +{ ARCHITECTURE_RISCV, ARCH_RISCV_FP_SOFT, -1, true, "riscv64" }, +{ ARCHITECTURE_RISCV, ARCH_RISCV_FP_SOFT, -1, false, "riscv32" }, +{ ARCHITECTURE_RISCV, ARCH_RISCV_FP_SINGLE, -1, true, "riscv64f" }, +{ ARCHITECTURE_RISCV, ARCH_RISCV_FP_SINGLE, -1, false, "riscv32f" }, +{ ARCHITECTURE_RISCV, ARCH_RISCV_FP_DOUBLE, -1, true, "riscv64d" }, +{ ARCHITECTURE_RISCV, ARCH_RISCV_FP_DOUBLE, -1, false, "riscv32d" }, +}; + +static int TestPlatformString( void ) +{ + int i; + + for( i = 0; i < sizeof( expected_platforms ) / sizeof( expected_platforms[0] ); i++ ) + { + if( Q_strcmp( Q_PlatformStringByID( expected_platforms[i].id ), expected_platforms[i].string )) + return i + 1; + } + + return 0; +} + +static qboolean TestArchitectureString_NoEndianness( int id, int abi, int is64, const char *string ) +{ + return !Q_strcmp( Q_ArchitectureStringByID( id, abi, ENDIANNESS_LITTLE, is64 ), string ) && + !Q_strcmp( Q_ArchitectureStringByID( id, abi, ENDIANNESS_BIG, is64 ), string ); +} + +static qboolean TestArchitectureString_No64( int id, int abi, int endianness, const char *string ) +{ + if( endianness == -1 ) + { + return TestArchitectureString_NoEndianness( id, abi, false, string ) && + TestArchitectureString_NoEndianness( id, abi, true, string ); + } + + return !Q_strcmp( Q_ArchitectureStringByID( id, abi, endianness, true ), string ) && + !Q_strcmp( Q_ArchitectureStringByID( id, abi, endianness, false ), string ); +} + +static int TestArchitectureString( void ) +{ + int i; + + for( i = 0; i < sizeof( expected_architectures ) / sizeof( expected_architectures[0] ); i++ ) + { + if( expected_architectures[i].is64 == -1 ) + { + if( !TestArchitectureString_No64( + expected_architectures[i].id, + expected_architectures[i].abi, + expected_architectures[i].endianness, + expected_architectures[i].string )) + return i + 1; + } + else if( expected_architectures[i].endianness == -1 ) + { + if( !TestArchitectureString_NoEndianness( + expected_architectures[i].id, + expected_architectures[i].abi, + expected_architectures[i].is64, + expected_architectures[i].string )) + { + abort(); + return i + 1; + } + } + else + { + if( Q_strcmp( Q_ArchitectureStringByID( + expected_architectures[i].id, + expected_architectures[i].abi, + expected_architectures[i].endianness, + expected_architectures[i].is64 + ), expected_architectures[i].string )) + return i + 1; + } + } + + return 0; +} + +int main( void ) +{ + int res; + + res = TestPlatformString(); + if( res != 0 ) + return res; + + res = TestArchitectureString(); + if( res != 0 ) + return res + 100; + + if( Q_buildnum_compat() != 4529 ) + return 200; + + if( Q_buildnum_date( "Apr 02 2015" ) != 1 ) + return 201; + + if( Q_buildnum_date( "Apr 17 2023" ) != 2938 ) + return 202; + + return EXIT_SUCCESS; +} diff --git a/public/tests/test_filebase.c b/public/tests/test_filebase.c new file mode 100644 index 00000000..89fee044 --- /dev/null +++ b/public/tests/test_filebase.c @@ -0,0 +1,112 @@ +#include +#include +#include "xash3d_mathlib.h" +#include "crtlib.h" + +static void COM_FileBase_old( const char *in, char *out, size_t size ) +{ + int len, start, end; + + len = Q_strlen( in ); + if( !len || !size ) return; + + // scan backward for '.' + end = len - 1; + + while( end && in[end] != '.' && in[end] != '/' && in[end] != '\\' ) + end--; + + if( in[end] != '.' ) + end = len-1; // no '.', copy to end + else end--; // found ',', copy to left of '.' + + // scan backward for '/' + start = len - 1; + + while( start >= 0 && in[start] != '/' && in[start] != '\\' ) + start--; + + if( start < 0 || ( in[start] != '/' && in[start] != '\\' )) + start = 0; + else start++; + + // length of new sting + len = end - start + 1; + + if( size > len ) // patched to somewhat support size limit + { + // Copy partial string + Q_strncpy( out, &in[start], len + 1 ); + out[len] = 0; + } + else + { + Q_strncpy( out, &in[start], size ); + } +} + +#define COM_FILEBASE_CHECK( in, out, size, expected ) \ + COM_FileBase( in, out, size ); \ + if( Q_strcmp( out, expected )) \ + { \ + printf( "%d: fail with libpublic impl, got: %s, expected: %s\n", __LINE__, out, expected ); \ + return 1; \ + } \ + COM_FileBase_old( in, out, size ); \ + if( Q_strcmp( out, expected )) \ + { \ + printf( "%d: fail with old impl, got: %s, expected: %s\n", __LINE__, out, expected ); \ + return 2; \ + } + +static int Test_FileBase( void ) +{ + string s; + + // test slashless case + COM_FILEBASE_CHECK( "asdf", s, sizeof( s ), "asdf" ); + + // test slashless case with extension + COM_FILEBASE_CHECK( "sdf.wad", s, sizeof( s ), "sdf" ); + + // test case with one slash + COM_FILEBASE_CHECK( "zxcv/asdfqwer", s, sizeof( s ), "asdfqwer" ); + + // test case with multiple slashes + COM_FILEBASE_CHECK( "zxc/asd/qwert", s, sizeof( s ), "qwert" ); + + // test case with full path + COM_FILEBASE_CHECK( "zxc/asd/qvert.lkjefgkljh", s, sizeof( s ), "qvert" ); + + // test case where dot placed before last slash + COM_FILEBASE_CHECK( "pak0.pk3/texture", s, sizeof( s ), "texture" ); + + // test case of directory path + COM_FILEBASE_CHECK( "pak0.pk3/", s, sizeof( s ), "" ); + + // test case of file with no name + COM_FILEBASE_CHECK( "blep/.nomedia", s, sizeof( s ), "" ); + + // test idiot cases + COM_FILEBASE_CHECK( NULL, s, sizeof( s ), "" ); + COM_FILEBASE_CHECK( "", s, sizeof( s ), "" ); + COM_FILEBASE_CHECK( "jhnwrgkujihrgwfikouj", s, 0, "" ); + + // test length limit + COM_FILEBASE_CHECK( "qwertyuiop", s, 3, "qw" ); + COM_FILEBASE_CHECK( "qwertyuiop", s, 1, "" ); + + return 0; +} + +int main( void ) +{ + int ret; + + ret = Test_FileBase(); + + if( ret ) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} diff --git a/public/tests/test_strings.c b/public/tests/test_strings.c new file mode 100644 index 00000000..e9f63d17 --- /dev/null +++ b/public/tests/test_strings.c @@ -0,0 +1,39 @@ +#include +#include "crtlib.h" + +int Test_Strcpycatcmp( void ) +{ + char buf[] = "meowmeowmeow", buf2[] = "barkbark"; + char dst[64]; + + if( Q_strncpy( dst, buf, sizeof( dst )) != sizeof( buf ) - 1 ) + return 1; + + if( Q_strcmp( dst, buf )) + return 2; + + if( Q_strcmp( dst, buf )) + return 4; + + if( !Q_strcmp( dst, buf2 )) + return 5; + + if( Q_strncat( dst, buf2, sizeof( dst )) != sizeof( buf ) + sizeof( buf2 ) - 2 ) + return 6; + + if( Q_strcmp( dst, "meowmeowmeowbarkbark" )) + return 7; + + if( Q_strncmp( dst, buf, sizeof( buf ) - 1 )) + return 8; + + return 0; +} + +int main( void ) +{ + if( Test_Strcpycatcmp( )) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} diff --git a/public/wscript b/public/wscript index cc20f0c5..1243fd20 100644 --- a/public/wscript +++ b/public/wscript @@ -22,3 +22,19 @@ def build(bld): features = 'c', use = 'sdk_includes', subsystem = bld.env.MSVC_SUBSYSTEM) + + if bld.env.TESTS: + tests = { + 'strings': 'tests/test_strings.c', + 'build': 'tests/test_build.c', + 'filebase': 'tests/test_filebase.c', + } + + for i in tests: + bld.program(features = 'test', + source = tests[i], + target = 'test_%s' % i, + use = 'public', + rpath = '$ORIGIN', + subsystem = bld.env.CONSOLE_SUBSYSTEM, + install_path = None) diff --git a/public/xash3d_mathlib.c b/public/xash3d_mathlib.c index 39dc3089..fb066c22 100644 --- a/public/xash3d_mathlib.c +++ b/public/xash3d_mathlib.c @@ -53,27 +53,9 @@ float anglemod( float a ) return a; } -/* -================= -SimpleSpline - -NOTE: ripped from hl2 source -hermite basis function for smooth interpolation -Similar to Gain() above, but very cheap to call -value should be between 0 & 1 inclusive -================= -*/ -float SimpleSpline( float value ) -{ - float valueSquared = value * value; - - // nice little ease-in, ease-out spline-like curve - return (3.0f * valueSquared - 2.0f * valueSquared * value); -} - word FloatToHalf( float v ) { - unsigned int i = *((unsigned int *)&v); + unsigned int i = FloatAsUint( v ); unsigned int e = (i >> 23) & 0x00ff; unsigned int m = i & 0x007fffff; unsigned short h; @@ -115,7 +97,7 @@ float HalfToFloat( word h ) } } - return *((float *)&f); + return UintAsFloat( f ); } /* @@ -201,49 +183,6 @@ int PlaneTypeForNormal( const vec3_t normal ) return PLANE_NONAXIAL; } -/* -================= -PlanesGetIntersectionPoint - -================= -*/ -qboolean PlanesGetIntersectionPoint( const mplane_t *plane1, const mplane_t *plane2, const mplane_t *plane3, vec3_t out ) -{ - vec3_t n1, n2, n3; - vec3_t n1n2, n2n3, n3n1; - float denom; - - VectorNormalize2( plane1->normal, n1 ); - VectorNormalize2( plane2->normal, n2 ); - VectorNormalize2( plane3->normal, n3 ); - - CrossProduct( n1, n2, n1n2 ); - CrossProduct( n2, n3, n2n3 ); - CrossProduct( n3, n1, n3n1 ); - - denom = DotProduct( n1, n2n3 ); - VectorClear( out ); - - // check if the denominator is zero (which would mean that no intersection is to be found - if( denom == 0.0f ) - { - // no intersection could be found, return <0,0,0> - return false; - } - - // compute intersection point -#if 0 - VectorMAMAM( plane1->dist, n2n3, plane2->dist, n3n1, plane3->dist, n1n2, out ); -#else - VectorMA( out, plane1->dist, n2n3, out ); - VectorMA( out, plane2->dist, n3n1, out ); - VectorMA( out, plane3->dist, n1n2, out ); -#endif - VectorScale( out, ( 1.0f / denom ), out ); - - return true; -} - /* ================= NearestPOW @@ -296,9 +235,9 @@ float rsqrt( float number ) return 0.0f; x = number * 0.5f; - i = *(int *)&number; // evil floating point bit level hacking + i = FloatAsInt( number ); // evil floating point bit level hacking i = 0x5f3759df - (i >> 1); // what the fuck? - y = *(float *)&i; + y = IntAsFloat( i ); y = y * (1.5f - (x * y * y)); // first iteration return y; @@ -764,53 +703,6 @@ void QuaternionSlerp( const vec4_t p, const vec4_t q, float t, vec4_t qt ) QuaternionSlerpNoAlign( p, q2, t, qt ); } -/* -==================== -V_CalcFov -==================== -*/ -float V_CalcFov( float *fov_x, float width, float height ) -{ - float x, half_fov_y; - - if( *fov_x < 1.0f || *fov_x > 179.0f ) - *fov_x = 90.0f; // default value - - x = width / tan( DEG2RAD( *fov_x ) * 0.5f ); - half_fov_y = atan( height / x ); - - return RAD2DEG( half_fov_y ) * 2; -} - -/* -==================== -V_AdjustFov -==================== -*/ -void V_AdjustFov( float *fov_x, float *fov_y, float width, float height, qboolean lock_x ) -{ - float x, y; - - if( width * 3 == 4 * height || width * 4 == height * 5 ) - { - // 4:3 or 5:4 ratio - return; - } - - if( lock_x ) - { - *fov_y = 2 * atan((width * 3) / (height * 4) * tan( *fov_y * M_PI_F / 360.0f * 0.5f )) * 360 / M_PI_F; - return; - } - - y = V_CalcFov( fov_x, 640, 480 ); - x = *fov_x; - - *fov_x = V_CalcFov( &y, height, width ); - if( *fov_x < x ) *fov_x = x; - else *fov_y = y; -} - /* ================== BoxOnPlaneSide diff --git a/public/xash3d_mathlib.h b/public/xash3d_mathlib.h index 02c10c3e..fa7e354a 100644 --- a/public/xash3d_mathlib.h +++ b/public/xash3d_mathlib.h @@ -22,6 +22,8 @@ GNU General Public License for more details. #endif #include "build.h" +#include "xash3d_types.h" +#include "const.h" #include "com_model.h" #include "studio.h" @@ -135,11 +137,46 @@ GNU General Public License for more details. #define PlaneDiff(point,plane) (((plane)->type < 3 ? (point)[(plane)->type] : DotProduct((point), (plane)->normal)) - (plane)->dist) #define bound( min, num, max ) ((num) >= (min) ? ((num) < (max) ? (num) : (max)) : (min)) +// horrible cast but helps not breaking strict aliasing in mathlib +// as union type punning should be fine in C but not in C++ +// so don't carry over this to C++ code +typedef union +{ + float fl; + uint32_t u; + int32_t i; +} float_bits_t; + +static inline uint32_t FloatAsUint( float v ) +{ + float_bits_t bits = { v }; + return bits.u; +} + +static inline int32_t FloatAsInt( float v ) +{ + float_bits_t bits = { v }; + return bits.i; +} + +static inline float IntAsFloat( int32_t i ) +{ + float_bits_t bits; + bits.i = i; + return bits.fl; +} + +static inline float UintAsFloat( uint32_t u ) +{ + float_bits_t bits; + bits.u = u; + return bits.fl; +} + float rsqrt( float number ); float anglemod( float a ); word FloatToHalf( float v ); float HalfToFloat( word h ); -float SimpleSpline( float value ); void RoundUpHullSize( vec3_t size ); int SignbitsForPlane( const vec3_t normal ); int PlaneTypeForNormal( const vec3_t normal ); @@ -151,7 +188,6 @@ void VectorVectors( const vec3_t forward, vec3_t right, vec3_t up ); void VectorAngles( const float *forward, float *angles ); void AngleVectors( const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up ); void VectorsAngles( const vec3_t forward, const vec3_t right, const vec3_t up, vec3_t angles ); -qboolean PlanesGetIntersectionPoint( const struct mplane_s *plane1, const struct mplane_s *plane2, const struct mplane_s *plane3, vec3_t out ); void PlaneIntersect( const struct mplane_s *plane, const vec3_t p0, const vec3_t p1, vec3_t out ); void ClearBounds( vec3_t mins, vec3_t maxs ); @@ -171,7 +207,7 @@ float ApproachVal( float target, float value, float speed ); // // matrixlib.c // -#define Matrix3x4_LoadIdentity( mat ) Matrix3x4_Copy( mat, matrix3x4_identity ) +#define Matrix3x4_LoadIdentity( mat ) Matrix3x4_Copy( mat, m_matrix3x4_identity ) #define Matrix3x4_Copy( out, in ) memcpy( out, in, sizeof( matrix3x4 )) void Matrix3x4_VectorTransform( const matrix3x4 in, const float v[3], float out[3] ); @@ -181,15 +217,12 @@ void Matrix3x4_VectorIRotate( const matrix3x4 in, const float v[3], float out[3] void Matrix3x4_ConcatTransforms( matrix3x4 out, const matrix3x4 in1, const matrix3x4 in2 ); void Matrix3x4_FromOriginQuat( matrix3x4 out, const vec4_t quaternion, const vec3_t origin ); void Matrix3x4_CreateFromEntity( matrix3x4 out, const vec3_t angles, const vec3_t origin, float scale ); -void Matrix3x4_TransformPositivePlane( const matrix3x4 in, const vec3_t normal, float d, vec3_t out, float *dist ); void Matrix3x4_TransformAABB( const matrix3x4 world, const vec3_t mins, const vec3_t maxs, vec3_t absmin, vec3_t absmax ); void Matrix3x4_SetOrigin( matrix3x4 out, float x, float y, float z ); -void Matrix3x4_Invert_Simple( matrix3x4 out, const matrix3x4 in1 ); void Matrix3x4_OriginFromMatrix( const matrix3x4 in, float *out ); void Matrix3x4_AnglesFromMatrix( const matrix3x4 in, vec3_t out ); -void Matrix3x4_Transpose( matrix3x4 out, const matrix3x4 in1 ); -#define Matrix4x4_LoadIdentity( mat ) Matrix4x4_Copy( mat, matrix4x4_identity ) +#define Matrix4x4_LoadIdentity( mat ) Matrix4x4_Copy( mat, m_matrix4x4_identity ) #define Matrix4x4_Copy( out, in ) memcpy( out, in, sizeof( matrix4x4 )) void Matrix4x4_VectorTransform( const matrix4x4 in, const float v[3], float out[3] ); @@ -197,20 +230,12 @@ void Matrix4x4_VectorITransform( const matrix4x4 in, const float v[3], float out void Matrix4x4_VectorRotate( const matrix4x4 in, const float v[3], float out[3] ); void Matrix4x4_VectorIRotate( const matrix4x4 in, const float v[3], float out[3] ); void Matrix4x4_ConcatTransforms( matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2 ); -void Matrix4x4_FromOriginQuat( matrix4x4 out, const vec4_t quaternion, const vec3_t origin ); void Matrix4x4_CreateFromEntity( matrix4x4 out, const vec3_t angles, const vec3_t origin, float scale ); void Matrix4x4_TransformPositivePlane( const matrix4x4 in, const vec3_t normal, float d, vec3_t out, float *dist ); -void Matrix4x4_TransformStandardPlane( const matrix4x4 in, const vec3_t normal, float d, vec3_t out, float *dist ); void Matrix4x4_ConvertToEntity( const matrix4x4 in, vec3_t angles, vec3_t origin ); -void Matrix4x4_SetOrigin( matrix4x4 out, float x, float y, float z ); void Matrix4x4_Invert_Simple( matrix4x4 out, const matrix4x4 in1 ); -void Matrix4x4_OriginFromMatrix( const matrix4x4 in, float *out ); -void Matrix4x4_Transpose( matrix4x4 out, const matrix4x4 in1 ); qboolean Matrix4x4_Invert_Full( matrix4x4 out, const matrix4x4 in1 ); -float V_CalcFov( float *fov_x, float width, float height ); -void V_AdjustFov( float *fov_x, float *fov_y, float width, float height, qboolean lock_x ); - void R_StudioSlerpBones( int numbones, vec4_t q1[], float pos1[][3], const vec4_t q2[], const float pos2[][3], float s ); void R_StudioCalcBoneQuaternion( int frame, float s, const mstudiobone_t *pbone, const mstudioanim_t *panim, const float *adj, vec4_t q ); void R_StudioCalcBonePosition( int frame, float s, const mstudiobone_t *pbone, const mstudioanim_t *panim, const vec3_t adj, vec3_t pos ); @@ -234,8 +259,8 @@ int BoxOnPlaneSide( const vec3_t emins, const vec3_t emaxs, const mplane_t *p ); extern vec3_t vec3_origin; extern int boxpnt[6][4]; -extern const matrix3x4 matrix3x4_identity; -extern const matrix4x4 matrix4x4_identity; +extern const matrix3x4 m_matrix3x4_identity; +extern const matrix4x4 m_matrix4x4_identity; extern const float m_bytenormals[NUMVERTEXNORMALS][3]; #endif // XASH3D_MATHLIB_H diff --git a/ref/gl/gl_alias.c b/ref/gl/gl_alias.c index d9766755..538cb16c 100644 --- a/ref/gl/gl_alias.c +++ b/ref/gl/gl_alias.c @@ -292,8 +292,8 @@ void BuildTris( void ) t = (t + 0.5f) / m_pAliasHeader->skinheight; // Carmack use floats and Valve use shorts here... - *(float *)&g_commands[g_numcommands++] = s; - *(float *)&g_commands[g_numcommands++] = t; + g_commands[g_numcommands++] = FloatAsInt( s ); + g_commands[g_numcommands++] = FloatAsInt( t ); } } @@ -443,7 +443,7 @@ rgbdata_t *Mod_CreateSkinData( model_t *mod, byte *data, int width, int height ) } } - COM_FileBase( loadmodel->name, name ); + COM_FileBase( loadmodel->name, name, sizeof( name )); // for alias models only player can have remap textures if( mod != NULL && !Q_stricmp( name, "player" )) @@ -627,6 +627,7 @@ void Mod_LoadAliasModel( model_t *mod, const void *buffer, qboolean *loaded ) daliasframetype_t *pframetype; daliasskintype_t *pskintype; int i, j, size; + char poolname[MAX_VA_STRING]; if( loaded ) *loaded = false; pinmodel = (daliashdr_t *)buffer; @@ -641,7 +642,8 @@ void Mod_LoadAliasModel( model_t *mod, const void *buffer, qboolean *loaded ) if( pinmodel->numverts <= 0 || pinmodel->numtris <= 0 || pinmodel->numframes <= 0 ) return; // how to possible is make that? - mod->mempool = Mem_AllocPool( va( "^2%s^7", mod->name )); + Q_snprintf( poolname, sizeof( poolname ), "^2%s^7", mod->name ); + mod->mempool = Mem_AllocPool( poolname ); // allocate space for a working header, plus all the data except the frames, // skin and group info diff --git a/ref/gl/gl_backend.c b/ref/gl/gl_backend.c index f3f4e2a5..b0914aef 100644 --- a/ref/gl/gl_backend.c +++ b/ref/gl/gl_backend.c @@ -55,7 +55,7 @@ void R_Speeds_Printf( const char *msg, ... ) char text[2048]; va_start( argptr, msg ); - Q_vsprintf( text, msg, argptr ); + Q_vsnprintf( text, sizeof( text ), msg, argptr ); va_end( argptr ); Q_strncat( r_speeds_msg, text, sizeof( r_speeds_msg )); @@ -116,18 +116,6 @@ void GL_BackendEndFrame( void ) memset( &r_stats, 0, sizeof( r_stats )); } -/* -================= -GL_LoadTexMatrix -================= -*/ -void GL_LoadTexMatrix( const matrix4x4 m ) -{ - pglMatrixMode( GL_TEXTURE ); - GL_LoadMatrix( m ); - glState.texIdentityMatrix[glState.activeTMU] = false; -} - /* ================= GL_LoadTexMatrixExt @@ -590,9 +578,8 @@ qboolean VID_CubemapShot( const char *base, uint size, const float *vieworg, qbo r_shot->buffer = buffer; // make sure what we have right extension - Q_strncpy( basename, base, MAX_STRING ); - COM_StripExtension( basename ); - COM_DefaultExtension( basename, ".tga" ); + Q_strncpy( basename, base, sizeof( basename )); + COM_ReplaceExtension( basename, ".tga", sizeof( basename )); // write image as 6 sides result = gEngfuncs.FS_SaveImage( basename, r_shot ); @@ -701,7 +688,7 @@ rebuild_page: if( FBitSet( image->flags, TF_DEPTHMAP ) && !FBitSet( image->flags, TF_NOCOMPARE )) pglTexParameteri( image->target, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB ); - COM_FileBase( image->name, shortname ); + COM_FileBase( image->name, shortname, sizeof( shortname )); if( Q_strlen( shortname ) > 18 ) { // cutoff too long names, it looks ugly diff --git a/ref/gl/gl_context.c b/ref/gl/gl_context.c index c95c0155..2673c93e 100644 --- a/ref/gl/gl_context.c +++ b/ref/gl/gl_context.c @@ -512,16 +512,3 @@ int EXPORT GetRefAPI( int version, ref_interface_t *funcs, ref_api_t *engfuncs, return REF_API_VERSION; } - -void EXPORT GetRefHumanReadableName( char *out, size_t size ) -{ -#if defined XASH_NANOGL - Q_strncpy( out, "GLES1(NanoGL)", size ); -#elif defined XASH_WES - Q_strncpy( out, "GLES2(gl-wes-v2)", size ); -#elif defined XASH_GL4ES - Q_strncpy( out, "GLES2(gl4es)", size ); -#else - Q_strncpy( out, "OpenGL", size ); -#endif -} diff --git a/ref/gl/gl_cull.c b/ref/gl/gl_cull.c index 3cd3af1d..b13b9638 100644 --- a/ref/gl/gl_cull.c +++ b/ref/gl/gl_cull.c @@ -35,18 +35,6 @@ qboolean R_CullBox( const vec3_t mins, const vec3_t maxs ) return GL_FrustumCullBox( &RI.frustum, mins, maxs, 0 ); } -/* -================= -R_CullSphere - -Returns true if the sphere is completely outside the frustum -================= -*/ -qboolean R_CullSphere( const vec3_t centre, const float radius ) -{ - return GL_FrustumCullSphere( &RI.frustum, centre, radius, 0 ); -} - /* ============= R_CullModel @@ -65,10 +53,6 @@ int R_CullModel( cl_entity_t *e, const vec3_t absmin, const vec3_t absmax ) return 1; } - // local client can't view himself if camera or thirdperson is not active - if( RP_LOCALCLIENT( e ) && !ENGINE_GET_PARM( PARM_THIRDPERSON ) && CL_IsViewEntityLocalPlayer()) - return 1; - if( R_CullBox( absmin, absmax )) return 1; diff --git a/ref/gl/gl_decals.c b/ref/gl/gl_decals.c index 2b3f82b8..2356a7bf 100644 --- a/ref/gl/gl_decals.c +++ b/ref/gl/gl_decals.c @@ -1188,7 +1188,7 @@ int R_CreateDecalList( decallist_t *pList ) pList[total].scale = decal->scale; R_DecalUnProject( decal, &pList[total] ); - COM_FileBase( R_GetTexture( decal->texture )->name, pList[total].name ); + COM_FileBase( R_GetTexture( decal->texture )->name, pList[total].name, sizeof( pList[total].name )); // check to see if the decal should be added total = DecalListAdd( pList, total ); diff --git a/ref/gl/gl_frustum.c b/ref/gl/gl_frustum.c index 92e995f0..46dd2be9 100644 --- a/ref/gl/gl_frustum.c +++ b/ref/gl/gl_frustum.c @@ -16,21 +16,6 @@ GNU General Public License for more details. #include "gl_local.h" #include "xash3d_mathlib.h" -void GL_FrustumEnablePlane( gl_frustum_t *out, int side ) -{ - Assert( side >= 0 && side < FRUSTUM_PLANES ); - - // make sure what plane is ready - if( !VectorIsNull( out->planes[side].normal )) - SetBits( out->clipFlags, BIT( side )); -} - -void GL_FrustumDisablePlane( gl_frustum_t *out, int side ) -{ - Assert( side >= 0 && side < FRUSTUM_PLANES ); - ClearBits( out->clipFlags, BIT( side )); -} - void GL_FrustumSetPlane( gl_frustum_t *out, int side, const vec3_t vecNormal, float flDist ) { Assert( side >= 0 && side < FRUSTUM_PLANES ); @@ -43,30 +28,6 @@ void GL_FrustumSetPlane( gl_frustum_t *out, int side, const vec3_t vecNormal, fl SetBits( out->clipFlags, BIT( side )); } -void GL_FrustumNormalizePlane( gl_frustum_t *out, int side ) -{ - float length; - - Assert( side >= 0 && side < FRUSTUM_PLANES ); - - // normalize - length = VectorLength( out->planes[side].normal ); - - if( length ) - { - float ilength = (1.0f / length); - out->planes[side].normal[0] *= ilength; - out->planes[side].normal[1] *= ilength; - out->planes[side].normal[2] *= ilength; - out->planes[side].dist *= ilength; - } - - out->planes[side].type = PlaneTypeForNormal( out->planes[side].normal ); - out->planes[side].signbits = SignbitsForPlane( out->planes[side].normal ); - - SetBits( out->clipFlags, BIT( side )); -} - void GL_FrustumInitProj( gl_frustum_t *out, float flZNear, float flZFar, float flFovX, float flFovY ) { float xs, xc; @@ -135,136 +96,6 @@ void GL_FrustumInitOrtho( gl_frustum_t *out, float xLeft, float xRight, float yT GL_FrustumSetPlane( out, FRUSTUM_BOTTOM, iup, -yBottom - orgOffset ); } -void GL_FrustumInitBox( gl_frustum_t *out, const vec3_t org, float radius ) -{ - vec3_t normal; - int i; - - for( i = 0; i < FRUSTUM_PLANES; i++ ) - { - // setup normal for each direction - VectorClear( normal ); - normal[((i >> 1) + 1) % 3] = (i & 1) ? 1.0f : -1.0f; - GL_FrustumSetPlane( out, i, normal, DotProduct( org, normal ) - radius ); - } -} - -void GL_FrustumInitProjFromMatrix( gl_frustum_t *out, const matrix4x4 projection ) -{ - int i; - - // left - out->planes[FRUSTUM_LEFT].normal[0] = projection[0][3] + projection[0][0]; - out->planes[FRUSTUM_LEFT].normal[1] = projection[1][3] + projection[1][0]; - out->planes[FRUSTUM_LEFT].normal[2] = projection[2][3] + projection[2][0]; - out->planes[FRUSTUM_LEFT].dist = -(projection[3][3] + projection[3][0]); - - // right - out->planes[FRUSTUM_RIGHT].normal[0] = projection[0][3] - projection[0][0]; - out->planes[FRUSTUM_RIGHT].normal[1] = projection[1][3] - projection[1][0]; - out->planes[FRUSTUM_RIGHT].normal[2] = projection[2][3] - projection[2][0]; - out->planes[FRUSTUM_RIGHT].dist = -(projection[3][3] - projection[3][0]); - - // bottom - out->planes[FRUSTUM_BOTTOM].normal[0] = projection[0][3] + projection[0][1]; - out->planes[FRUSTUM_BOTTOM].normal[1] = projection[1][3] + projection[1][1]; - out->planes[FRUSTUM_BOTTOM].normal[2] = projection[2][3] + projection[2][1]; - out->planes[FRUSTUM_BOTTOM].dist = -(projection[3][3] + projection[3][1]); - - // top - out->planes[FRUSTUM_TOP].normal[0] = projection[0][3] - projection[0][1]; - out->planes[FRUSTUM_TOP].normal[1] = projection[1][3] - projection[1][1]; - out->planes[FRUSTUM_TOP].normal[2] = projection[2][3] - projection[2][1]; - out->planes[FRUSTUM_TOP].dist = -(projection[3][3] - projection[3][1]); - - // near - out->planes[FRUSTUM_NEAR].normal[0] = projection[0][3] + projection[0][2]; - out->planes[FRUSTUM_NEAR].normal[1] = projection[1][3] + projection[1][2]; - out->planes[FRUSTUM_NEAR].normal[2] = projection[2][3] + projection[2][2]; - out->planes[FRUSTUM_NEAR].dist = -(projection[3][3] + projection[3][2]); - - // far - out->planes[FRUSTUM_FAR].normal[0] = projection[0][3] - projection[0][2]; - out->planes[FRUSTUM_FAR].normal[1] = projection[1][3] - projection[1][2]; - out->planes[FRUSTUM_FAR].normal[2] = projection[2][3] - projection[2][2]; - out->planes[FRUSTUM_FAR].dist = -(projection[3][3] - projection[3][2]); - - for( i = 0; i < FRUSTUM_PLANES; i++ ) - { - GL_FrustumNormalizePlane( out, i ); - } -} - -void GL_FrustumComputeCorners( gl_frustum_t *out, vec3_t corners[8] ) -{ - memset( corners, 0, sizeof( vec3_t ) * 8 ); - - PlanesGetIntersectionPoint( &out->planes[FRUSTUM_LEFT], &out->planes[FRUSTUM_TOP], &out->planes[FRUSTUM_FAR], corners[0] ); - PlanesGetIntersectionPoint( &out->planes[FRUSTUM_RIGHT], &out->planes[FRUSTUM_TOP], &out->planes[FRUSTUM_FAR], corners[1] ); - PlanesGetIntersectionPoint( &out->planes[FRUSTUM_LEFT], &out->planes[FRUSTUM_BOTTOM], &out->planes[FRUSTUM_FAR], corners[2] ); - PlanesGetIntersectionPoint( &out->planes[FRUSTUM_RIGHT], &out->planes[FRUSTUM_BOTTOM], &out->planes[FRUSTUM_FAR], corners[3] ); - - if( FBitSet( out->clipFlags, BIT( FRUSTUM_NEAR ))) - { - PlanesGetIntersectionPoint( &out->planes[FRUSTUM_LEFT], &out->planes[FRUSTUM_TOP], &out->planes[FRUSTUM_NEAR], corners[4] ); - PlanesGetIntersectionPoint( &out->planes[FRUSTUM_RIGHT], &out->planes[FRUSTUM_TOP], &out->planes[FRUSTUM_NEAR], corners[5] ); - PlanesGetIntersectionPoint( &out->planes[FRUSTUM_LEFT], &out->planes[FRUSTUM_BOTTOM], &out->planes[FRUSTUM_NEAR], corners[6] ); - PlanesGetIntersectionPoint( &out->planes[FRUSTUM_RIGHT], &out->planes[FRUSTUM_BOTTOM], &out->planes[FRUSTUM_NEAR], corners[7] ); - } - else - { - PlanesGetIntersectionPoint( &out->planes[FRUSTUM_LEFT], &out->planes[FRUSTUM_RIGHT], &out->planes[FRUSTUM_TOP], corners[4] ); - VectorCopy( corners[4], corners[5] ); - VectorCopy( corners[4], corners[6] ); - VectorCopy( corners[4], corners[7] ); - } -} - -void GL_FrustumComputeBounds( gl_frustum_t *out, vec3_t mins, vec3_t maxs ) -{ - vec3_t corners[8]; - int i; - - GL_FrustumComputeCorners( out, corners ); - - ClearBounds( mins, maxs ); - - for( i = 0; i < 8; i++ ) - AddPointToBounds( corners[i], mins, maxs ); -} - -void GL_FrustumDrawDebug( gl_frustum_t *out ) -{ - vec3_t bbox[8]; - int i; - - GL_FrustumComputeCorners( out, bbox ); - - // g-cont. frustum must be yellow :-) - pglColor4f( 1.0f, 1.0f, 0.0f, 1.0f ); - pglDisable( GL_TEXTURE_2D ); - pglBegin( GL_LINES ); - - for( i = 0; i < 2; i += 1 ) - { - pglVertex3fv( bbox[i+0] ); - pglVertex3fv( bbox[i+2] ); - pglVertex3fv( bbox[i+4] ); - pglVertex3fv( bbox[i+6] ); - pglVertex3fv( bbox[i+0] ); - pglVertex3fv( bbox[i+4] ); - pglVertex3fv( bbox[i+2] ); - pglVertex3fv( bbox[i+6] ); - pglVertex3fv( bbox[i*2+0] ); - pglVertex3fv( bbox[i*2+1] ); - pglVertex3fv( bbox[i*2+4] ); - pglVertex3fv( bbox[i*2+5] ); - } - - pglEnd(); - pglEnable( GL_TEXTURE_2D ); -} - // cull methods qboolean GL_FrustumCullBox( gl_frustum_t *out, const vec3_t mins, const vec3_t maxs, int userClipFlags ) { diff --git a/ref/gl/gl_frustum.h b/ref/gl/gl_frustum.h index 0b310a80..520ebff8 100644 --- a/ref/gl/gl_frustum.h +++ b/ref/gl/gl_frustum.h @@ -33,20 +33,10 @@ typedef struct gl_frustum_s void GL_FrustumInitProj( gl_frustum_t *out, float flZNear, float flZFar, float flFovX, float flFovY ); void GL_FrustumInitOrtho( gl_frustum_t *out, float xLeft, float xRight, float yTop, float yBottom, float flZNear, float flZFar ); -void GL_FrustumInitBox( gl_frustum_t *out, const vec3_t org, float radius ); // used for pointlights -void GL_FrustumInitProjFromMatrix( gl_frustum_t *out, const matrix4x4 projection ); void GL_FrustumSetPlane( gl_frustum_t *out, int side, const vec3_t vecNormal, float flDist ); -void GL_FrustumNormalizePlane( gl_frustum_t *out, int side ); -void GL_FrustumComputeBounds( gl_frustum_t *out, vec3_t mins, vec3_t maxs ); -void GL_FrustumComputeCorners( gl_frustum_t *out, vec3_t bbox[8] ); -void GL_FrustumDrawDebug( gl_frustum_t *out ); // cull methods qboolean GL_FrustumCullBox( gl_frustum_t *out, const vec3_t mins, const vec3_t maxs, int userClipFlags ); qboolean GL_FrustumCullSphere( gl_frustum_t *out, const vec3_t centre, float radius, int userClipFlags ); -// plane manipulating -void GL_FrustumEnablePlane( gl_frustum_t *out, int side ); -void GL_FrustumDisablePlane( gl_frustum_t *out, int side ); - #endif//GL_FRUSTUM_H diff --git a/ref/gl/gl_image.c b/ref/gl/gl_image.c index 5c4aed51..d3329771 100644 --- a/ref/gl/gl_image.c +++ b/ref/gl/gl_image.c @@ -1587,7 +1587,7 @@ int GL_LoadTextureArray( const char **names, int flags ) // create complexname from layer names for( i = 0; i < numLayers - 1; i++ ) { - COM_FileBase( names[i], basename ); + COM_FileBase( names[i], basename, sizeof( basename )); ret = Q_snprintf( &name[len], sizeof( name ) - len, "%s|", basename ); if( ret == -1 ) @@ -1596,7 +1596,7 @@ int GL_LoadTextureArray( const char **names, int flags ) len += ret; } - COM_FileBase( names[i], basename ); + COM_FileBase( names[i], basename, sizeof( basename )); ret = Q_snprintf( &name[len], sizeof( name ) - len, "%s[%i]", basename, numLayers ); if( ret == -1 ) @@ -1931,23 +1931,6 @@ void GL_ProcessTexture( int texnum, float gamma, int topColor, int bottomColor ) gEngfuncs.FS_FreeImage( pic ); } -/* -================ -GL_TexMemory - -return size of all uploaded textures -================ -*/ -int GL_TexMemory( void ) -{ - int i, total = 0; - - for( i = 0; i < gl_numTextures; i++ ) - total += gl_textures[i].size; - - return total; -} - /* ============================================================================== diff --git a/ref/gl/gl_local.h b/ref/gl/gl_local.h index deac6e94..e50cf7f6 100644 --- a/ref/gl/gl_local.h +++ b/ref/gl/gl_local.h @@ -35,6 +35,12 @@ GNU General Public License for more details. #include "gl_export.h" #include "wadfile.h" +#if XASH_PSVITA +int VGL_ShimInit( void ); +void VGL_ShimShutdown( void ); +void VGL_ShimEndFrame( void ); +#endif + #ifndef offsetof #ifdef __GNUC__ #define offsetof(s,m) __builtin_offsetof(s,m) @@ -288,7 +294,6 @@ void GL_CleanUpTextureUnits( int last ); void GL_Bind( GLint tmu, GLenum texnum ); void GL_MultiTexCoord2f( GLenum texture, GLfloat s, GLfloat t ); void GL_SetTexCoordArrayMode( GLenum mode ); -void GL_LoadTexMatrix( const matrix4x4 m ); void GL_LoadTexMatrixExt( const float *glmatrix ); void GL_LoadMatrix( const matrix4x4 source ); void GL_TexGen( GLenum coord, GLenum mode ); @@ -313,7 +318,6 @@ qboolean R_BeamCull( const vec3_t start, const vec3_t end, qboolean pvsOnly ); // int R_CullModel( cl_entity_t *e, const vec3_t absmin, const vec3_t absmax ); qboolean R_CullBox( const vec3_t mins, const vec3_t maxs ); -qboolean R_CullSphere( const vec3_t centre, const float radius ); int R_CullSurface( msurface_t *surf, gl_frustum_t *frustum, uint clipflags ); // @@ -362,7 +366,6 @@ void R_InitDlightTexture( void ); void R_TextureList_f( void ); void R_InitImages( void ); void R_ShutdownImages( void ); -int GL_TexMemory( void ); // // gl_rlight.c @@ -375,7 +378,6 @@ void R_MarkLights( dlight_t *light, int bit, mnode_t *node ); colorVec R_LightVec( const vec3_t start, const vec3_t end, vec3_t lightspot, vec3_t lightvec ); int R_CountSurfaceDlights( msurface_t *surf ); colorVec R_LightPoint( const vec3_t p0 ); -int R_CountDlights( void ); // // gl_rmain.c @@ -401,16 +403,9 @@ int CL_FxBlend( cl_entity_t *e ); // gl_rmath.c // void Matrix4x4_ToArrayFloatGL( const matrix4x4 in, float out[16] ); -void Matrix4x4_FromArrayFloatGL( matrix4x4 out, const float in[16] ); void Matrix4x4_Concat( matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2 ); void Matrix4x4_ConcatTranslate( matrix4x4 out, float x, float y, float z ); void Matrix4x4_ConcatRotate( matrix4x4 out, float angle, float x, float y, float z ); -void Matrix4x4_ConcatScale( matrix4x4 out, float x ); -void Matrix4x4_ConcatScale3( matrix4x4 out, float x, float y, float z ); -void Matrix4x4_CreateTranslate( matrix4x4 out, float x, float y, float z ); -void Matrix4x4_CreateRotate( matrix4x4 out, float angle, float x, float y, float z ); -void Matrix4x4_CreateScale( matrix4x4 out, float x ); -void Matrix4x4_CreateScale3( matrix4x4 out, float x, float y, float z ); void Matrix4x4_CreateProjection(matrix4x4 out, float xMax, float xMin, float yMax, float yMin, float zNear, float zFar); void Matrix4x4_CreateOrtho(matrix4x4 m, float xLeft, float xRight, float yBottom, float yTop, float zNear, float zFar); void Matrix4x4_CreateModelview( matrix4x4 out ); @@ -461,16 +456,13 @@ void R_DrawSpriteModel( cl_entity_t *e ); // gl_studio.c // void R_StudioInit( void ); -void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded ); void R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles ); -float CL_GetSequenceDuration( cl_entity_t *ent, int sequence ); struct mstudiotex_s *R_StudioGetTexture( cl_entity_t *e ); -float CL_GetStudioEstimatedFrame( cl_entity_t *ent ); int R_GetEntityRenderMode( cl_entity_t *ent ); void R_DrawStudioModel( cl_entity_t *e ); player_info_t *pfnPlayerInfo( int index ); void R_GatherPlayerLight( void ); -float R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc ); +float R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc, double time ); void R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles ); void R_StudioResetPlayerModels( void ); void CL_InitStudioAPI( void ); diff --git a/ref/gl/gl_opengl.c b/ref/gl/gl_opengl.c index f0a33d9c..d16738fd 100644 --- a/ref/gl/gl_opengl.c +++ b/ref/gl/gl_opengl.c @@ -335,6 +335,7 @@ qboolean GL_CheckExtension( const char *name, const dllfunc_t *funcs, const char const dllfunc_t *func; cvar_t *parm = NULL; const char *extensions_string; + char desc[MAX_VA_STRING]; gEngfuncs.Con_Reportf( "GL_CheckExtension: %s ", name ); GL_SetExtension( r_ext, true ); @@ -342,7 +343,8 @@ qboolean GL_CheckExtension( const char *name, const dllfunc_t *funcs, const char if( cvarname ) { // system config disable extensions - parm = gEngfuncs.Cvar_Get( cvarname, "1", FCVAR_GLCONFIG|FCVAR_READ_ONLY, va( CVAR_GLCONFIG_DESCRIPTION, name )); + Q_snprintf( desc, sizeof( desc ), CVAR_GLCONFIG_DESCRIPTION, name ); + parm = gEngfuncs.Cvar_Get( cvarname, "1", FCVAR_GLCONFIG|FCVAR_READ_ONLY, desc ); } if(( parm && !CVAR_TO_BOOL( parm )) || ( !CVAR_TO_BOOL( gl_extensions ) && r_ext != GL_OPENGL_110 )) @@ -370,7 +372,10 @@ qboolean GL_CheckExtension( const char *name, const dllfunc_t *funcs, const char { // functions are cleared before all the extensions are evaluated if((*func->func = (void *)gEngfuncs.GL_GetProcAddress( func->name )) == NULL ) - GL_SetExtension( r_ext, false ); // one or more functions are invalid, extension will be disabled + { + // one or more functions are invalid, extension will be disabled + GL_SetExtension( r_ext, false ); + } } #endif @@ -746,11 +751,22 @@ void GL_InitExtensionsBigGL( void ) // this won't work without extended context if( glw_state.extended ) GL_CheckExtension( "GL_ARB_debug_output", debugoutputfuncs, "gl_debug_output", GL_DEBUG_OUTPUT ); + +#if XASH_PSVITA + // not all GL1.1 functions are implemented in vitaGL, but there's enough + GL_SetExtension( GL_OPENGL_110, true ); + // NPOT textures are actually supported, but the extension is not listed in GL_EXTENSIONS + GL_SetExtension( GL_ARB_TEXTURE_NPOT_EXT, true ); + // init our immediate mode override + VGL_ShimInit(); +#endif } #endif void GL_InitExtensions( void ) { + char value[MAX_VA_STRING]; + GL_OnContextCreated(); // initialize gl extensions @@ -791,8 +807,9 @@ void GL_InitExtensions( void ) if( GL_Support( GL_TEXTURE_2D_RECT_EXT )) pglGetIntegerv( GL_MAX_RECTANGLE_TEXTURE_SIZE_EXT, &glConfig.max_2d_rectangle_size ); - gEngfuncs.Cvar_Get( "gl_max_size", va( "%i", glConfig.max_2d_texture_size ), 0, "opengl texture max dims" ); - gEngfuncs.Cvar_Set( "gl_anisotropy", va( "%f", bound( 0, gl_texture_anisotropy->value, glConfig.max_texture_anisotropy ))); + Q_snprintf( value, sizeof( value ), "%i", glConfig.max_2d_texture_size ); + gEngfuncs.Cvar_Get( "gl_max_size", value, 0, "opengl texture max dims" ); + gEngfuncs.Cvar_SetValue( "gl_anisotropy", bound( 0, gl_texture_anisotropy->value, glConfig.max_texture_anisotropy )); if( GL_Support( GL_TEXTURE_COMPRESSION_EXT )) gEngfuncs.Image_AddCmdFlags( IL_DDS_HARDWARE ); @@ -814,6 +831,10 @@ void GL_ClearExtensions( void ) // now all extensions are disabled memset( glConfig.extension, 0, sizeof( glConfig.extension )); glw_state.initialized = false; +#if XASH_PSVITA + // deinit our immediate mode override + VGL_ShimShutdown(); +#endif } //======================================================================= diff --git a/ref/gl/gl_rlight.c b/ref/gl/gl_rlight.c index 89679989..514b58b4 100644 --- a/ref/gl/gl_rlight.c +++ b/ref/gl/gl_rlight.c @@ -171,29 +171,6 @@ void R_PushDlights( void ) } } -/* -============= -R_CountDlights -============= -*/ -int R_CountDlights( void ) -{ - dlight_t *l; - int i, numDlights = 0; - - for( i = 0; i < MAX_DLIGHTS; i++ ) - { - l = gEngfuncs.GetDynamicLight( i ); - - if( l->die < gpGlobals->time || !l->radius ) - continue; - - numDlights++; - } - - return numDlights; -} - /* ============= R_CountSurfaceDlights diff --git a/ref/gl/gl_rmain.c b/ref/gl/gl_rmain.c index be5cd3d2..bc8f9229 100644 --- a/ref/gl/gl_rmain.c +++ b/ref/gl/gl_rmain.c @@ -1124,6 +1124,9 @@ R_EndFrame */ void R_EndFrame( void ) { +#if XASH_PSVITA + VGL_ShimEndFrame(); +#endif // flush any remaining 2D bits R_Set2DMode( false ); gEngfuncs.GL_SwapBuffers(); diff --git a/ref/gl/gl_rmath.c b/ref/gl/gl_rmath.c index 69fb4eb2..0406a0c3 100644 --- a/ref/gl/gl_rmath.c +++ b/ref/gl/gl_rmath.c @@ -119,26 +119,6 @@ void Matrix4x4_ToArrayFloatGL( const matrix4x4 in, float out[16] ) out[15] = in[3][3]; } -void Matrix4x4_FromArrayFloatGL( matrix4x4 out, const float in[16] ) -{ - out[0][0] = in[0]; - out[1][0] = in[1]; - out[2][0] = in[2]; - out[3][0] = in[3]; - out[0][1] = in[4]; - out[1][1] = in[5]; - out[2][1] = in[6]; - out[3][1] = in[7]; - out[0][2] = in[8]; - out[1][2] = in[9]; - out[2][2] = in[10]; - out[3][2] = in[11]; - out[0][3] = in[12]; - out[1][3] = in[13]; - out[2][3] = in[14]; - out[3][3] = in[15]; -} - void Matrix4x4_CreateTranslate( matrix4x4 out, float x, float y, float z ) { out[0][0] = 1.0f; @@ -190,46 +170,6 @@ void Matrix4x4_CreateRotate( matrix4x4 out, float angle, float x, float y, float out[3][3]=1.0f; } -void Matrix4x4_CreateScale( matrix4x4 out, float x ) -{ - out[0][0] = x; - out[0][1] = 0.0f; - out[0][2] = 0.0f; - out[0][3] = 0.0f; - out[1][0] = 0.0f; - out[1][1] = x; - out[1][2] = 0.0f; - out[1][3] = 0.0f; - out[2][0] = 0.0f; - out[2][1] = 0.0f; - out[2][2] = x; - out[2][3] = 0.0f; - out[3][0] = 0.0f; - out[3][1] = 0.0f; - out[3][2] = 0.0f; - out[3][3] = 1.0f; -} - -void Matrix4x4_CreateScale3( matrix4x4 out, float x, float y, float z ) -{ - out[0][0] = x; - out[0][1] = 0.0f; - out[0][2] = 0.0f; - out[0][3] = 0.0f; - out[1][0] = 0.0f; - out[1][1] = y; - out[1][2] = 0.0f; - out[1][3] = 0.0f; - out[2][0] = 0.0f; - out[2][1] = 0.0f; - out[2][2] = z; - out[2][3] = 0.0f; - out[3][0] = 0.0f; - out[3][1] = 0.0f; - out[3][2] = 0.0f; - out[3][3] = 1.0f; -} - void Matrix4x4_ConcatTranslate( matrix4x4 out, float x, float y, float z ) { matrix4x4 base, temp; @@ -247,21 +187,3 @@ void Matrix4x4_ConcatRotate( matrix4x4 out, float angle, float x, float y, float Matrix4x4_CreateRotate( temp, angle, x, y, z ); Matrix4x4_Concat( out, base, temp ); } - -void Matrix4x4_ConcatScale( matrix4x4 out, float x ) -{ - matrix4x4 base, temp; - - Matrix4x4_Copy( base, out ); - Matrix4x4_CreateScale( temp, x ); - Matrix4x4_Concat( out, base, temp ); -} - -void Matrix4x4_ConcatScale3( matrix4x4 out, float x, float y, float z ) -{ - matrix4x4 base, temp; - - Matrix4x4_Copy( base, out ); - Matrix4x4_CreateScale3( temp, x, y, z ); - Matrix4x4_Concat( out, base, temp ); -} diff --git a/ref/gl/gl_rmisc.c b/ref/gl/gl_rmisc.c index 9a5b7443..9c5da914 100644 --- a/ref/gl/gl_rmisc.c +++ b/ref/gl/gl_rmisc.c @@ -119,39 +119,11 @@ void R_NewMap( void ) Q_strncpy( mapname, WORLDMODEL->name, sizeof( mapname )); COM_StripExtension( mapname ); - Q_sprintf( filepath, "%s_detail.txt", mapname ); + Q_snprintf( filepath, sizeof( filepath ), "%s_detail.txt", mapname ); R_ParseDetailTextures( filepath ); } - if( gEngfuncs.pfnGetCvarFloat( "v_dark" )) - { - screenfade_t *sf = gEngfuncs.GetScreenFade(); - float fadetime = 5.0f; - client_textmessage_t *title; - - title = gEngfuncs.pfnTextMessageGet( "GAMETITLE" ); - if( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE )) - fadetime = 1.0f; - - if( title ) - { - // get settings from titles.txt - sf->fadeEnd = title->holdtime + title->fadeout; - sf->fadeReset = title->fadeout; - } - else sf->fadeEnd = sf->fadeReset = fadetime; - - sf->fadeFlags = FFADE_IN; - sf->fader = sf->fadeg = sf->fadeb = 0; - sf->fadealpha = 255; - sf->fadeSpeed = (float)sf->fadealpha / sf->fadeReset; - sf->fadeReset += gpGlobals->time; - sf->fadeEnd += sf->fadeReset; - - gEngfuncs.Cvar_SetValue( "v_dark", 0.0f ); - } - // clear out efrags in case the level hasn't been reloaded for( i = 0; i < WORLDMODEL->numleafs; i++ ) WORLDMODEL->leafs[i+1].efrags = NULL; diff --git a/ref/gl/gl_sprite.c b/ref/gl/gl_sprite.c index 006fe135..7ed990f4 100644 --- a/ref/gl/gl_sprite.c +++ b/ref/gl/gl_sprite.c @@ -242,6 +242,7 @@ void Mod_LoadMapSprite( model_t *mod, const void *buffer, size_t size, qboolean int linedelta, numframes; mspriteframe_t *pspriteframe; msprite_t *psprite; + char poolname[MAX_VA_STRING]; if( loaded ) *loaded = false; Q_snprintf( texname, sizeof( texname ), "#%s", mod->name ); @@ -275,7 +276,8 @@ void Mod_LoadMapSprite( model_t *mod, const void *buffer, size_t size, qboolean // determine how many frames we needs numframes = (pix->width * pix->height) / (w * h); - mod->mempool = Mem_AllocPool( va( "^2%s^7", mod->name )); + Q_snprintf( poolname, sizeof( poolname ), "^2%s^7", mod->name ); + mod->mempool = Mem_AllocPool( poolname ); psprite = Mem_Calloc( mod->mempool, sizeof( msprite_t ) + ( numframes - 1 ) * sizeof( psprite->frames )); mod->cache.data = psprite; // make link to extradata diff --git a/ref/gl/gl_studio.c b/ref/gl/gl_studio.c index 1c0aa6a4..3618faf7 100644 --- a/ref/gl/gl_studio.c +++ b/ref/gl/gl_studio.c @@ -150,6 +150,11 @@ void R_StudioInit( void ) r_studio_sort_textures = gEngfuncs.Cvar_Get( "r_studio_sort_textures", "0", FCVAR_GLCONFIG, "change draw order for additive meshes" ); r_studio_drawelements = gEngfuncs.Cvar_Get( "r_studio_drawelements", "1", FCVAR_GLCONFIG, "use glDrawElements for studiomodels" ); +#if XASH_PSVITA + // don't do the same array-building work twice since that's what our FFP shim does anyway + gEngfuncs.Cvar_FullSet( "r_studio_drawelements", "0", FCVAR_READ_ONLY ); +#endif + Matrix3x4_LoadIdentity( g_studio.rotationmatrix ); // g-cont. cvar disabled by Valve @@ -173,7 +178,7 @@ static void R_StudioSetupTimings( void ) { // synchronize with server time g_studio.time = gpGlobals->time; - g_studio.frametime = gpGlobals->time - gpGlobals->oldtime; + g_studio.frametime = gpGlobals->time - gpGlobals->oldtime; } else { @@ -621,14 +626,14 @@ StudioEstimateFrame ==================== */ -float R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc ) +float R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc, double time ) { double dfdt, f; if( g_studio.interpolate ) { - if( g_studio.time < e->curstate.animtime ) dfdt = 0.0; - else dfdt = (g_studio.time - e->curstate.animtime) * e->curstate.framerate * pseqdesc->fps; + if( time < e->curstate.animtime ) dfdt = 0.0; + else dfdt = (time - e->curstate.animtime) * e->curstate.framerate * pseqdesc->fps; } else dfdt = 0; @@ -671,35 +676,6 @@ float R_StudioEstimateInterpolant( cl_entity_t *e ) return dadt; } -/* -==================== -CL_GetSequenceDuration - -==================== -*/ -float CL_GetSequenceDuration( cl_entity_t *ent, int sequence ) -{ - studiohdr_t *pstudiohdr; - mstudioseqdesc_t *pseqdesc; - - if( ent->model != NULL && ent->model->type == mod_studio ) - { - pstudiohdr = (studiohdr_t *)gEngfuncs.Mod_Extradata( mod_studio, ent->model ); - - if( pstudiohdr ) - { - sequence = bound( 0, sequence, pstudiohdr->numseq - 1 ); - pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence; - - if( pseqdesc->numframes > 1 && pseqdesc->fps > 0 ) - return (float)pseqdesc->numframes / (float)pseqdesc->fps; - } - } - - return 0.1f; -} - - /* ==================== StudioFxTransform @@ -878,7 +854,7 @@ void R_StudioMergeBones( cl_entity_t *e, model_t *m_pSubModel ) pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence; - f = R_StudioEstimateFrame( e, pseqdesc ); + f = R_StudioEstimateFrame( e, pseqdesc, g_studio.time ); panim = gEngfuncs.R_StudioGetAnim( m_pStudioHeader, m_pSubModel, pseqdesc ); R_StudioCalcRotations( e, pos, q, pseqdesc, panim, f ); @@ -944,7 +920,7 @@ void R_StudioSetupBones( cl_entity_t *e ) pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence; - f = R_StudioEstimateFrame( e, pseqdesc ); + f = R_StudioEstimateFrame( e, pseqdesc, g_studio.time ); panim = gEngfuncs.R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc ); R_StudioCalcRotations( e, pos, q, pseqdesc, panim, f ); @@ -1701,47 +1677,56 @@ R_LightLambert ==================== */ -void R_LightLambert( vec4_t light[MAX_LOCALLIGHTS], vec3_t normal, vec3_t color, byte *out ) +static void R_LightLambert( vec4_t light[MAX_LOCALLIGHTS], const vec3_t normal, const vec3_t color, byte *out ) { vec3_t finalLight; - vec3_t localLight; int i; + if( !g_studio.numlocallights ) + { + VectorScale( color, 255.0f, out ); + return; + } + VectorCopy( color, finalLight ); for( i = 0; i < g_studio.numlocallights; i++ ) { - float r, r2; + float r; - if( tr.fFlipViewModel ) - r = DotProduct( normal, light[i] ); - else r = -DotProduct( normal, light[i] ); + r = DotProduct( normal, light[i] ); + if( likely( !tr.fFlipViewModel )) + r = -r; if( r > 0.0f ) { + vec3_t localLight; + float temp; + if( light[i][3] == 0.0f ) { - r2 = DotProduct( light[i], light[i] ); + float r2 = DotProduct( light[i], light[i] ); if( r2 > 0.0f ) light[i][3] = g_studio.locallightR2[i] / ( r2 * sqrt( r2 )); else light[i][3] = 0.0001f; } - localLight[0] = Q_min( g_studio.locallightcolor[i].r * r * light[i][3], 255.0f ); - localLight[1] = Q_min( g_studio.locallightcolor[i].g * r * light[i][3], 255.0f ); - localLight[2] = Q_min( g_studio.locallightcolor[i].b * r * light[i][3], 255.0f ); - VectorScale( localLight, ( 1.0f / 255.0f ), localLight ); + temp = Q_min( r * light[i][3] / 255.0f, 1.0f ); - finalLight[0] = Q_min( finalLight[0] + localLight[0], 1.0f ); - finalLight[1] = Q_min( finalLight[1] + localLight[1], 1.0f ); - finalLight[2] = Q_min( finalLight[2] + localLight[2], 1.0f ); + localLight[0] = (float)g_studio.locallightcolor[i].r * temp; + localLight[1] = (float)g_studio.locallightcolor[i].g * temp; + localLight[2] = (float)g_studio.locallightcolor[i].b * temp; + + VectorAdd( finalLight, localLight, finalLight ); } } - out[0] = finalLight[0] * 255; - out[1] = finalLight[1] * 255; - out[2] = finalLight[2] * 255; + VectorScale( finalLight, 255.0f, finalLight ); + + out[0] = Q_min( (int)( finalLight[0] ), 255 ); + out[1] = Q_min( (int)( finalLight[1] ), 255 ); + out[2] = Q_min( (int)( finalLight[2] ), 255 ); } static void R_StudioSetColorArray(short *ptricmds, vec3_t *pstudionorms, byte *color ) @@ -2784,7 +2769,7 @@ static void R_StudioClientEvents( void ) if( pseqdesc->numevents == 0 ) return; - end = R_StudioEstimateFrame( e, pseqdesc ); + end = R_StudioEstimateFrame( e, pseqdesc, g_studio.time ); start = end - e->curstate.framerate * gpGlobals->frametime * pseqdesc->fps; pevent = (mstudioevent_t *)((byte *)m_pStudioHeader + pseqdesc->eventindex); @@ -3772,7 +3757,7 @@ static void R_StudioLoadTexture( model_t *mod, studiohdr_t *phdr, mstudiotexture } Q_strncpy( mdlname, mod->name, sizeof( mdlname )); - COM_FileBase( ptexture->name, name ); + COM_FileBase( ptexture->name, name, sizeof( name )); COM_StripExtension( mdlname ); if( FBitSet( ptexture->flags, STUDIO_NF_NOMIPS )) diff --git a/ref/gl/gl_warp.c b/ref/gl/gl_warp.c index 3144324d..5bb9703d 100644 --- a/ref/gl/gl_warp.c +++ b/ref/gl/gl_warp.c @@ -64,7 +64,7 @@ static qboolean CheckSkybox( const char *name, char out[6][MAX_STRING] ) { const char *skybox_ext[3] = { "dds", "tga", "bmp" }; int i, j, num_checked_sides; - const char *sidename; + char sidename[MAX_VA_STRING]; // search for skybox images for( i = 0; i < 3; i++ ) @@ -74,7 +74,7 @@ static qboolean CheckSkybox( const char *name, char out[6][MAX_STRING] ) for( j = 0; j < 6; j++ ) { // build side name - sidename = va( "%s%s.%s", name, r_skyBoxSuffix[j], skybox_ext[i] ); + Q_snprintf( sidename, sizeof( sidename ), "%s%s.%s", name, r_skyBoxSuffix[j], skybox_ext[i] ); if( gEngfuncs.fsapi->FileExists( sidename, false )) { Q_strncpy( out[j], sidename, sizeof( out[j] )); @@ -90,7 +90,7 @@ static qboolean CheckSkybox( const char *name, char out[6][MAX_STRING] ) for( j = 0; j < 6; j++ ) { // build side name - sidename = va( "%s_%s.%s", name, r_skyBoxSuffix[j], skybox_ext[i] ); + Q_snprintf( sidename, sizeof( sidename ), "%s_%s.%s", name, r_skyBoxSuffix[j], skybox_ext[i] ); if( gEngfuncs.fsapi->FileExists( sidename, false )) { Q_strncpy( out[j], sidename, sizeof( out[j] )); diff --git a/ref/gl/vgl_shim/vgl_shaders/fragment.cg.inc b/ref/gl/vgl_shim/vgl_shaders/fragment.cg.inc new file mode 100644 index 00000000..6aa0a092 --- /dev/null +++ b/ref/gl/vgl_shim/vgl_shaders/fragment.cg.inc @@ -0,0 +1,58 @@ +R""( + +#if ATTR_TEXCOORD0 +uniform sampler2D uTex0 : TEXUNIT0; +#endif +#if ATTR_TEXCOORD1 +uniform sampler2D uTex1 : TEXUNIT1; +#endif +#if FEAT_ALPHA_TEST +uniform float uAlphaTest; +#endif +#if FEAT_FOG +// color + density, mode always GL_EXP +uniform float4 uFog; +#endif + +float4 main( + uniform float4 uColor +#if ATTR_COLOR + , float4 vColor : COLOR +#endif +#if ATTR_TEXCOORD0 + , float2 vTexCoord0 : TEXCOORD0 +#endif +#if ATTR_TEXCOORD1 + , float2 vTexCoord1 : TEXCOORD1 +#endif +#if ATTR_NORMAL + , float3 vNormal : TEXCOORD2 +#endif +#if FEAT_FOG + , float4 vPosition : WPOS +#endif +) { +#if ATTR_COLOR + float4 c = vColor; +#else + float4 c = uColor; +#endif +#if ATTR_TEXCOORD0 + c *= tex2D(uTex0, vTexCoord0); +#endif +#if ATTR_TEXCOORD1 + c *= tex2D(uTex1, vTexCoord1); +#endif +#if FEAT_ALPHA_TEST + if (c.a <= uAlphaTest) + discard; +#endif +#if FEAT_FOG + float fogDist = vPosition.z / vPosition.w; + float fogRate = clamp(exp(-uFog.w * fogDist), 0.f, 1.f); + c.rgb = lerp(uFog.rgb, c.rgb, fogRate); +#endif + return c; +} + +)"" diff --git a/ref/gl/vgl_shim/vgl_shaders/vertex.cg.inc b/ref/gl/vgl_shim/vgl_shaders/vertex.cg.inc new file mode 100644 index 00000000..75a9919a --- /dev/null +++ b/ref/gl/vgl_shim/vgl_shaders/vertex.cg.inc @@ -0,0 +1,49 @@ +R""( + +// has to be called this for VGL to fill it in automatically +uniform float4x4 gl_ModelViewProjectionMatrix; + +void main( + float3 inPosition +#if ATTR_COLOR + , float4 inColor +#endif +#if ATTR_NORMAL + , float3 inNormal +#endif +#if ATTR_TEXCOORD0 + , float2 inTexCoord0 +#endif +#if ATTR_TEXCOORD1 + , float2 inTexCoord1 +#endif + , float4 out vPosition : POSITION +#if ATTR_COLOR + , float4 out vColor : COLOR +#endif +#if ATTR_TEXCOORD0 + , float2 out vTexCoord0 : TEXCOORD0 +#endif +#if ATTR_TEXCOORD1 + , float2 out vTexCoord1 : TEXCOORD1 +#endif +#if ATTR_NORMAL + , float3 out vNormal : TEXCOORD2 +#endif +) { + vPosition = mul(gl_ModelViewProjectionMatrix, float4(inPosition, 1.f)); +#if ATTR_COLOR + vColor = inColor; +#endif +#if ATTR_NORMAL + vNormal = inNormal; +#endif +#if ATTR_TEXCOORD0 + vTexCoord0 = inTexCoord0; +#endif +#if ATTR_TEXCOORD1 + vTexCoord1 = inTexCoord1; +#endif +} + +)"" diff --git a/ref/gl/vgl_shim/vgl_shim.c b/ref/gl/vgl_shim/vgl_shim.c new file mode 100644 index 00000000..456b3b25 --- /dev/null +++ b/ref/gl/vgl_shim/vgl_shim.c @@ -0,0 +1,573 @@ +/* +vgl_shim.c - vitaGL custom immediate mode shim +Copyright (C) 2023 fgsfds + +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. +*/ + +/* + this is a "replacement" for vitaGL's immediate mode tailored specifically for xash + this will only provide performance gains if vitaGL is built with DRAW_SPEEDHACK=1 + since that makes it assume that all vertex data pointers are GPU-mapped +*/ + +#include +#include +#include +#include +#include + +#include "port.h" +#include "xash3d_types.h" +#include "cvardef.h" +#include "const.h" +#include "com_model.h" +#include "cl_entity.h" +#include "render_api.h" +#include "protocol.h" +#include "dlight.h" +#include "ref_api.h" +#include "com_strings.h" +#include "crtlib.h" +#include "vgl_shim.h" + +#define MAX_SHADERLEN 4096 +// increase this when adding more attributes +#define MAX_PROGS 32 + +extern ref_api_t gEngfuncs; + +enum vgl_attrib_e +{ + VGL_ATTR_POS = 0, // 1 + VGL_ATTR_COLOR = 1, // 2 + VGL_ATTR_TEXCOORD0 = 2, // 4 + VGL_ATTR_TEXCOORD1 = 3, // 8 + VGL_ATTR_MAX +}; + +// continuation of previous enum +enum vgl_flag_e +{ + VGL_FLAG_ALPHA_TEST = VGL_ATTR_MAX, // 16 + VGL_FLAG_FOG, // 32 + VGL_FLAG_MAX +}; + +typedef struct +{ + GLuint flags; + GLint attridx[VGL_ATTR_MAX]; + GLuint glprog; + GLint ucolor; + GLint ualpha; + GLint utex0; + GLint utex1; + GLint ufog; +} vgl_prog_t; + +static const char *vgl_vert_src = +#include "vgl_shaders/vertex.cg.inc" +; + +static const char *vgl_frag_src = +#include "vgl_shaders/fragment.cg.inc" +; + +static int vgl_init = 0; + +static struct +{ + GLfloat *attrbuf[VGL_ATTR_MAX]; + GLuint cur_flags; + GLint begin; + GLint end; + GLenum prim; + GLfloat color[4]; + GLfloat fog[4]; // color + density + GLfloat alpharef; + vgl_prog_t progs[MAX_PROGS]; + vgl_prog_t *cur_prog; + GLboolean uchanged; +} vgl; + +static const int vgl_attr_size[VGL_ATTR_MAX] = { 3, 4, 2, 2 }; + +static const char *vgl_flag_name[VGL_FLAG_MAX] = +{ + "ATTR_POSITION", + "ATTR_COLOR", + "ATTR_TEXCOORD0", + "ATTR_TEXCOORD1", + "FEAT_ALPHA_TEST", + "FEAT_FOG", +}; + +static const char *vgl_attr_name[VGL_ATTR_MAX] = +{ + "inPosition", + "inColor", + "inTexCoord0", + "inTexCoord1", +}; + +// HACK: borrow alpha test and fog flags from internal vitaGL state +extern GLboolean alpha_test_state; +extern GLboolean fogging; + +static GLuint VGL_GenerateShader( const vgl_prog_t *prog, GLenum type ) +{ + char *shader, shader_buf[MAX_SHADERLEN + 1]; + char tmp[256]; + int i; + GLint status, len; + GLuint id; + + shader = shader_buf; + shader[0] = '\n'; + shader[1] = 0; + + for ( i = 0; i < VGL_FLAG_MAX; ++i ) + { + Q_snprintf( tmp, sizeof( tmp ), "#define %s %d\n", vgl_flag_name[i], prog->flags & ( 1 << i ) ); + Q_strncat( shader, tmp, MAX_SHADERLEN ); + } + + if ( type == GL_FRAGMENT_SHADER ) + Q_strncat( shader, vgl_frag_src, MAX_SHADERLEN ); + else + Q_strncat( shader, vgl_vert_src, MAX_SHADERLEN ); + + id = glCreateShader( type ); + len = Q_strlen( shader ); + glShaderSource( id, 1, (const void *)&shader, &len ); + glCompileShader( id ); + glGetShaderiv( id, GL_COMPILE_STATUS, &status ); + if ( status == GL_FALSE ) + { + gEngfuncs.Con_Reportf( S_ERROR "VGL_GenerateShader( 0x%04x, 0x%x ): compile failed:\n", prog->flags, type ); + gEngfuncs.Con_DPrintf( "Shader text:\n%s\n\n", shader ); + glDeleteShader( id ); + return 0; + } + + return id; +} + +static vgl_prog_t *VGL_GetProg( const GLuint flags ) +{ + int i, loc, status; + GLuint vp, fp, glprog; + vgl_prog_t *prog; + + // try to find existing prog matching this feature set + + if ( vgl.cur_prog && vgl.cur_prog->flags == flags ) + return vgl.cur_prog; + + for ( i = 0; i < MAX_PROGS; ++i ) + { + if ( vgl.progs[i].flags == flags ) + return &vgl.progs[i]; + else if ( vgl.progs[i].flags == 0 ) + break; + } + + if ( i == MAX_PROGS ) + { + gEngfuncs.Host_Error( "VGL_GetProg(): Ran out of program slots for 0x%04x\n", flags ); + return NULL; + } + + // new prog; generate shaders + + gEngfuncs.Con_DPrintf( S_NOTE "VGL_GetProg(): Generating progs for 0x%04x\n", flags ); + prog = &vgl.progs[i]; + prog->flags = flags; + + vp = VGL_GenerateShader( prog, GL_VERTEX_SHADER ); + fp = VGL_GenerateShader( prog, GL_FRAGMENT_SHADER ); + if ( !vp || !fp ) + { + prog->flags = 0; + return NULL; + } + + glprog = glCreateProgram(); + glAttachShader( glprog, vp ); + glAttachShader( glprog, fp ); + + loc = 0; + for ( i = 0; i < VGL_ATTR_MAX; ++i ) + { + if ( flags & ( 1 << i ) ) + { + prog->attridx[i] = loc; + glBindAttribLocation( glprog, loc++, vgl_attr_name[i] ); + } + else + { + prog->attridx[i] = -1; + } + } + + glLinkProgram( glprog ); + glDeleteShader( vp ); + glDeleteShader( fp ); + + glGetProgramiv( glprog, GL_LINK_STATUS, &status ); + if ( status == GL_FALSE ) + { + gEngfuncs.Con_Reportf( S_ERROR "VGL_GetProg(): Failed linking progs for 0x%04x!\n", prog->flags ); + prog->flags = 0; + glDeleteProgram( glprog ); + return NULL; + } + + prog->ucolor = glGetUniformLocation( glprog, "uColor" ); + prog->ualpha = glGetUniformLocation( glprog, "uAlphaTest" ); + prog->utex0 = glGetUniformLocation( glprog, "uTex0" ); + prog->utex1 = glGetUniformLocation( glprog, "uTex1" ); + prog->ufog = glGetUniformLocation( glprog, "uFog" ); + + // these never change + if ( prog->utex0 >= 0 ) + glUniform1i( prog->utex0, 0 ); + if ( prog->utex1 >= 0 ) + glUniform1i( prog->utex1, 1 ); + + prog->glprog = glprog; + + gEngfuncs.Con_DPrintf( S_NOTE "VGL_GetProg(): Generated progs for 0x%04x\n", flags ); + + return prog; +} + +static vgl_prog_t *VGL_SetProg( const GLuint flags ) +{ + vgl_prog_t *prog = NULL; + + if ( flags && ( prog = VGL_GetProg( flags ) ) ) + { + if ( prog != vgl.cur_prog ) + { + glUseProgram( prog->glprog ); + vgl.uchanged = GL_TRUE; + } + if ( vgl.uchanged ) + { + if ( prog->ualpha >= 0 ) + glUniform1f( prog->ualpha, vgl.alpharef ); + if ( prog->ucolor >= 0 ) + glUniform4fv( prog->ucolor, 1, vgl.color ); + if ( prog->ufog >= 0 ) + glUniform4fv( prog->ufog, 1, vgl.fog ); + vgl.uchanged = GL_FALSE; + } + } + else + { + glUseProgram( 0 ); + } + + vgl.cur_prog = prog; + return prog; +} + +int VGL_ShimInit( void ) +{ + int i; + GLuint total, size; + static const GLuint precache_progs[] = { + 0x0001, // out = ucolor + 0x0005, // out = tex0 * ucolor + 0x0007, // out = tex0 * vcolor + 0x0015, // out = tex0 * ucolor + FEAT_ALPHA_TEST + 0x0021, // out = ucolor + FEAT_FOG + 0x0025, // out = tex0 * ucolor + FEAT_FOG + 0x0027, // out = tex0 * vcolor + FEAT_FOG + 0x0035, // out = tex0 * ucolor + FEAT_ALPHA_TEST + FEAT_FOG + }; + + if ( vgl_init ) + return 0; + + memset( &vgl, 0, sizeof( vgl ) ); + + vgl.color[0] = 1.f; + vgl.color[1] = 1.f; + vgl.color[2] = 1.f; + vgl.color[3] = 1.f; + vgl.uchanged = GL_TRUE; + + total = 0; + for ( i = 0; i < VGL_ATTR_MAX; ++i ) + { + size = VGL_MAX_VERTS * vgl_attr_size[i] * sizeof( GLfloat ); + vgl.attrbuf[i] = memalign( 0x100, size ); + total += size; + } + + VGL_ShimInstall(); + + gEngfuncs.Con_DPrintf( S_NOTE "VGL_ShimInit(): %u bytes allocated for vertex buffer\n", total ); + gEngfuncs.Con_DPrintf( S_NOTE "VGL_ShimInit(): Pre-generating %u progs...\n", sizeof( precache_progs ) / sizeof( *precache_progs ) ); + for ( i = 0; i < (int)( sizeof( precache_progs ) / sizeof( *precache_progs ) ); ++i ) + VGL_GetProg( precache_progs[i] ); + + vgl_init = 1; + return 0; +} + +void VGL_ShimShutdown( void ) +{ + int i; + + if ( !vgl_init ) + return; + + glFinish(); + glUseProgram( 0 ); + + /* + // FIXME: this sometimes causes the game to block on glDeleteProgram for up to a minute + // but since this is only called on shutdown or game change, it should be fine to skip + for ( i = 0; i < MAX_PROGS; ++i ) + { + if ( vgl.progs[i].flags ) + glDeleteProgram( vgl.progs[i].glprog ); + } + */ + + for ( i = 0; i < VGL_ATTR_MAX; ++i ) + free( vgl.attrbuf[i] ); + + memset( &vgl, 0, sizeof( vgl ) ); + + vgl_init = 0; +} + +void VGL_ShimEndFrame( void ) +{ + vgl.end = vgl.begin = 0; +} + +void VGL_Begin( GLenum prim ) +{ + int i; + vgl.prim = prim; + vgl.begin = vgl.end; + // pos always enabled + vgl.cur_flags = 1 << VGL_ATTR_POS; + // disable all vertex attrib pointers + for ( i = 0; i < VGL_ATTR_MAX; ++i ) + glDisableVertexAttribArray( i ); +} + +void VGL_End( void ) +{ + int i; + vgl_prog_t *prog; + GLuint flags = vgl.cur_flags; + GLint count = vgl.end - vgl.begin; + + if ( !vgl.prim || !count ) + goto _leave; // end without begin + + // enable alpha test and fog if needed + if ( alpha_test_state ) + flags |= 1 << VGL_FLAG_ALPHA_TEST; + if ( fogging ) + flags |= 1 << VGL_FLAG_FOG; + + prog = VGL_SetProg( flags ); + if ( !prog ) + { + gEngfuncs.Host_Error( "VGL_End(): Could not find program for flags 0x%04x!\n", flags ); + goto _leave; + } + + for ( i = 0; i < VGL_ATTR_MAX; ++i ) + { + if ( prog->attridx[i] >= 0 ) + { + glEnableVertexAttribArray( prog->attridx[i] ); + glVertexAttribPointer( prog->attridx[i], vgl_attr_size[i], GL_FLOAT, GL_FALSE, 0, vgl.attrbuf[i] + vgl_attr_size[i] * vgl.begin ); + } + } + + glDrawArrays( vgl.prim, 0, count ); + +_leave: + vgl.prim = GL_NONE; + vgl.begin = vgl.end; + vgl.cur_flags = 0; +} + +void VGL_Vertex3f( GLfloat x, GLfloat y, GLfloat z ) +{ + GLfloat *p = vgl.attrbuf[VGL_ATTR_POS] + vgl.end * 3; + *p++ = x; + *p++ = y; + *p++ = z; + ++vgl.end; + if ( vgl.end >= VGL_MAX_VERTS ) + { + gEngfuncs.Con_DPrintf( S_ERROR "VGL_Vertex3f(): Vertex buffer overflow!\n" ); + vgl.end = vgl.begin = 0; + } +} + +void VGL_Vertex2f( GLfloat x, GLfloat y ) +{ + VGL_Vertex3f( x, y, 0.f ); +} + +void VGL_Vertex3fv( const GLfloat *v ) +{ + VGL_Vertex3f( v[0], v[1], v[2] ); +} + +void VGL_Color4f( GLfloat r, GLfloat g, GLfloat b, GLfloat a ) +{ + vgl.color[0] = r; + vgl.color[1] = g; + vgl.color[2] = b; + vgl.color[3] = a; + vgl.uchanged = GL_TRUE; + if ( vgl.prim ) + { + // HACK: enable color attribute if we're using color inside a Begin-End pair + GLfloat *p = vgl.attrbuf[VGL_ATTR_COLOR] + vgl.end * 4; + vgl.cur_flags |= 1 << VGL_ATTR_COLOR; + *p++ = r; + *p++ = g; + *p++ = b; + *p++ = a; + } +} + +void VGL_Color3f( GLfloat r, GLfloat g, GLfloat b ) +{ + VGL_Color4f( r, g, b, 1.f ); +} + +void VGL_Color4ub( GLubyte r, GLubyte g, GLubyte b, GLubyte a ) +{ + VGL_Color4f( (GLfloat)r / 255.f, (GLfloat)g / 255.f, (GLfloat)b / 255.f, (GLfloat)a / 255.f ); +} + +void VGL_Color4ubv( const GLubyte *v ) +{ + VGL_Color4ub( v[0], v[1], v[2], v[3] ); +} + +void VGL_TexCoord2f( GLfloat u, GLfloat v ) +{ + // by spec glTexCoord always updates texunit 0 + GLfloat *p = vgl.attrbuf[VGL_ATTR_TEXCOORD0] + vgl.end * 2; + vgl.cur_flags |= 1 << VGL_ATTR_TEXCOORD0; + *p++ = u; + *p++ = v; +} + +void VGL_MultiTexCoord2f( GLenum tex, GLfloat u, GLfloat v ) +{ + GLfloat *p; + // assume there can only be two + if ( tex == GL_TEXTURE0 ) + { + p = vgl.attrbuf[VGL_ATTR_TEXCOORD0] + vgl.end * 2; + vgl.cur_flags |= 1 << VGL_ATTR_TEXCOORD0; + } + else + { + p = vgl.attrbuf[VGL_ATTR_TEXCOORD1] + vgl.end * 2; + vgl.cur_flags |= 1 << VGL_ATTR_TEXCOORD1; + } + *p++ = u; + *p++ = v; +} + +void VGL_Normal3fv( const GLfloat *v ) +{ + /* this does not seem to be necessary */ +} + +void VGL_ShadeModel( GLenum unused ) +{ + /* this doesn't do anything in vitaGL except spit errors in debug mode, so stub it out */ +} + +void VGL_AlphaFunc( GLenum mode, GLfloat ref ) +{ + vgl.alpharef = ref; + vgl.uchanged = GL_TRUE; + // mode is always GL_GREATER +} + +void VGL_Fogf( GLenum param, GLfloat val ) +{ + if ( param == GL_FOG_DENSITY ) + { + vgl.fog[3] = val; + vgl.uchanged = GL_TRUE; + } +} + +void VGL_Fogfv( GLenum param, const GLfloat *val ) +{ + if ( param == GL_FOG_COLOR ) + { + vgl.fog[0] = val[0]; + vgl.fog[1] = val[1]; + vgl.fog[2] = val[2]; + vgl.uchanged = GL_TRUE; + } +} + +void VGL_DrawBuffer( GLenum mode ) +{ + /* unsupported */ +} + +void VGL_Hint( GLenum hint, GLenum val ) +{ + /* none of the used hints are supported; stub to prevent errors */ +} + +#define VGL_OVERRIDE_PTR( name ) \ +{ \ + extern void *pgl ## name; \ + pgl ## name = VGL_ ## name; \ +} + +void VGL_ShimInstall( void ) +{ + VGL_OVERRIDE_PTR( Vertex2f ) + VGL_OVERRIDE_PTR( Vertex3f ) + VGL_OVERRIDE_PTR( Vertex3fv ) + VGL_OVERRIDE_PTR( Color3f ) + VGL_OVERRIDE_PTR( Color4f ) + VGL_OVERRIDE_PTR( Color4ub ) + VGL_OVERRIDE_PTR( Color4ubv ) + VGL_OVERRIDE_PTR( Normal3fv ) + VGL_OVERRIDE_PTR( TexCoord2f ) + VGL_OVERRIDE_PTR( MultiTexCoord2f ) + VGL_OVERRIDE_PTR( ShadeModel ) + VGL_OVERRIDE_PTR( DrawBuffer ) + VGL_OVERRIDE_PTR( AlphaFunc ) + VGL_OVERRIDE_PTR( Fogf ) + VGL_OVERRIDE_PTR( Fogfv ) + VGL_OVERRIDE_PTR( Hint ) + VGL_OVERRIDE_PTR( Begin ) + VGL_OVERRIDE_PTR( End ) +} diff --git a/ref/gl/vgl_shim/vgl_shim.h b/ref/gl/vgl_shim/vgl_shim.h new file mode 100644 index 00000000..e5107d4e --- /dev/null +++ b/ref/gl/vgl_shim/vgl_shim.h @@ -0,0 +1,24 @@ +/* +vgl_shim.h - vitaGL custom immediate mode shim +Copyright (C) 2023 fgsfds + +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 + +// max verts in a single frame +#define VGL_MAX_VERTS 32768 + +int VGL_ShimInit( void ); +void VGL_ShimInstall( void ); +void VGL_ShimShutdown( void ); +void VGL_ShimEndFrame( void ); diff --git a/ref/gl/vgl_shim/wscript b/ref/gl/vgl_shim/wscript new file mode 100644 index 00000000..13926864 --- /dev/null +++ b/ref/gl/vgl_shim/wscript @@ -0,0 +1,25 @@ +#! /usr/bin/env python +# encoding: utf-8 + +import os + +def options(opt): + pass + +def configure(conf): + conf.define('REF_DLL', 1) + +def build(bld): + source = bld.path.ant_glob( [ '*.c' ] ) + includes = [ '.' ] + libs = [ 'engine_includes', 'sdk_includes' ] + bld.env.LDFLAGS += ['-fPIC'] + bld.env.CFLAGS += ['-fPIC'] + bld.stlib( + source = source, + target = 'vgl_shim', + features = 'c', + includes = includes, + use = libs, + subsystem = bld.env.MSVC_SUBSYSTEM + ) diff --git a/ref/gl/wscript b/ref/gl/wscript index b8678e46..16266c9e 100644 --- a/ref/gl/wscript +++ b/ref/gl/wscript @@ -32,7 +32,12 @@ def configure(conf): conf.check_cc(lib='log') def build(bld): - libs = [ 'engine_includes', 'public', 'M' ] + libs = [ 'engine_includes' ] + # on PSVita do not link any libraries that are already in the main executable, but add the includes target + if bld.env.DEST_OS == 'psvita': + libs += [ 'sdk_includes', 'vgl_shim' ] + else: + libs += [ 'public', 'M' ] source = bld.path.ant_glob(['*.c']) includes = '.' diff --git a/ref/soft/r_context.c b/ref/soft/r_context.c index d02ff9e8..c455fbbb 100644 --- a/ref/soft/r_context.c +++ b/ref/soft/r_context.c @@ -26,23 +26,6 @@ static void GAME_EXPORT R_ClearScreen( void ) } -static qboolean GAME_EXPORT IsNormalPass( void ) -{ - return RP_NORMALPASS(); -} - -static void GAME_EXPORT R_IncrementSpeedsCounter( int type ) -{ - switch( type ) - { - case RS_ACTIVE_TENTS: - r_stats.c_active_tents_count++; - break; - default: - gEngfuncs.Host_Error( "R_IncrementSpeedsCounter: unsupported type %d\n", type ); - } -} - static const byte * GAME_EXPORT R_GetTextureOriginalBuffer( unsigned int idx ) { image_t *glt = R_GetTexture( idx ); @@ -582,8 +565,3 @@ int EXPORT GAME_EXPORT GetRefAPI( int version, ref_interface_t *funcs, ref_api_t return REF_API_VERSION; } - -void EXPORT GetRefHumanReadableName( char *out, size_t size ) -{ - Q_strncpy( out, "Software", size ); -} diff --git a/ref/soft/r_decals.c b/ref/soft/r_decals.c index e3d4805a..57be64d2 100644 --- a/ref/soft/r_decals.c +++ b/ref/soft/r_decals.c @@ -1200,7 +1200,7 @@ int GAME_EXPORT R_CreateDecalList( decallist_t *pList ) pList[total].scale = decal->scale; R_DecalUnProject( decal, &pList[total] ); - COM_FileBase( R_GetTexture( decal->texture )->name, pList[total].name ); + COM_FileBase( R_GetTexture( decal->texture )->name, pList[total].name, sizeof( pList[total].name )); // check to see if the decal should be added total = DecalListAdd( pList, total ); diff --git a/ref/soft/r_edge.c b/ref/soft/r_edge.c index e5b8e571..36f69b5d 100644 --- a/ref/soft/r_edge.c +++ b/ref/soft/r_edge.c @@ -25,14 +25,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. void R_SurfacePatch (void) { } - -void R_EdgeCodeStart (void) -{ -} - -void R_EdgeCodeEnd (void) -{ -} #endif @@ -973,36 +965,6 @@ void D_TurbulentSurf (surf_t *s) } } -/* -============== -D_SkySurf -============== -*/ -void D_SkySurf (surf_t *s) -{ - pface = s->msurf; - miplevel = 0; - if (!pface->texinfo->texture) - return; - cacheblock = R_GetTexture(pface->texinfo->texture->gl_texturenum)->pixels[0]; - cachewidth = 256; - - d_zistepu = s->d_zistepu; - d_zistepv = s->d_zistepv; - d_ziorigin = s->d_ziorigin; - - D_CalcGradients (pface); - - D_DrawSpans16 (s->spans); - -// set up a gradient for the background surface that places it -// effectively at infinity distance from the viewpoint - d_zistepu = 0; - d_zistepv = 0; - d_ziorigin = -0.9; - - D_DrawZSpans (s->spans); -} qboolean alphaspans; diff --git a/ref/soft/r_light.c b/ref/soft/r_light.c index 1e84d77d..aa341aff 100644 --- a/ref/soft/r_light.c +++ b/ref/soft/r_light.c @@ -174,29 +174,6 @@ void R_PushDlights( void ) } } -/* -============= -R_CountDlights -============= -*/ -int R_CountDlights( void ) -{ - dlight_t *l; - int i, numDlights = 0; - - for( i = 0; i < MAX_DLIGHTS; i++ ) - { - l = gEngfuncs.GetDynamicLight( i ); - - if( l->die < gpGlobals->time || !l->radius ) - continue; - - numDlights++; - } - - return numDlights; -} - /* ============= R_CountSurfaceDlights diff --git a/ref/soft/r_local.h b/ref/soft/r_local.h index 3ee559cc..2940d6fe 100644 --- a/ref/soft/r_local.h +++ b/ref/soft/r_local.h @@ -374,7 +374,6 @@ void GL_BackendStartFrame( void ); void GL_BackendEndFrame( void ); void GL_CleanUpTextureUnits( int last ); void GL_Bind( int tmu, unsigned int texnum ); -void GL_LoadTexMatrix( const matrix4x4 m ); void GL_LoadTexMatrixExt( const float *glmatrix ); void GL_LoadMatrix( const matrix4x4 source ); void GL_TexGen( unsigned int coord, unsigned int mode ); @@ -465,7 +464,6 @@ void R_MarkLights( dlight_t *light, int bit, mnode_t *node ); colorVec R_LightVec( const vec3_t start, const vec3_t end, vec3_t lightspot, vec3_t lightvec ); int R_CountSurfaceDlights( msurface_t *surf ); colorVec R_LightPoint( const vec3_t p0 ); -int R_CountDlights( void ); #endif // // gl_rmain.c @@ -488,17 +486,11 @@ void R_DrawFog( void ); // // gl_rmath.c // -void Matrix4x4_ToArrayFloatGL( const matrix4x4 in, float out[16] ); -void Matrix4x4_FromArrayFloatGL( matrix4x4 out, const float in[16] ); void Matrix4x4_Concat( matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2 ); void Matrix4x4_ConcatTranslate( matrix4x4 out, float x, float y, float z ); void Matrix4x4_ConcatRotate( matrix4x4 out, float angle, float x, float y, float z ); -void Matrix4x4_ConcatScale( matrix4x4 out, float x ); -void Matrix4x4_ConcatScale3( matrix4x4 out, float x, float y, float z ); void Matrix4x4_CreateTranslate( matrix4x4 out, float x, float y, float z ); void Matrix4x4_CreateRotate( matrix4x4 out, float angle, float x, float y, float z ); -void Matrix4x4_CreateScale( matrix4x4 out, float x ); -void Matrix4x4_CreateScale3( matrix4x4 out, float x, float y, float z ); void Matrix4x4_CreateProjection(matrix4x4 out, float xMax, float xMin, float yMax, float yMin, float zNear, float zFar); void Matrix4x4_CreateOrtho(matrix4x4 m, float xLeft, float xRight, float yBottom, float yTop, float zNear, float zFar); void Matrix4x4_CreateModelview( matrix4x4 out ); @@ -522,7 +514,6 @@ texture_t *R_TextureAnimation( msurface_t *s ); void GL_SetupFogColorForSurfaces( void ); void R_DrawAlphaTextureChains( void ); void GL_RebuildLightmaps( void ); -void GL_InitRandomTable( void ); void GL_BuildLightmaps( void ); void GL_ResetFogColor( void ); void R_GenerateVBO(); @@ -551,14 +542,12 @@ void R_DrawSpriteModel( cl_entity_t *e ); void R_StudioInit( void ); void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded ); void R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles ); -float CL_GetSequenceDuration( cl_entity_t *ent, int sequence ); struct mstudiotex_s *R_StudioGetTexture( cl_entity_t *e ); -float CL_GetStudioEstimatedFrame( cl_entity_t *ent ); int R_GetEntityRenderMode( cl_entity_t *ent ); void R_DrawStudioModel( cl_entity_t *e ); player_info_t *pfnPlayerInfo( int index ); void R_GatherPlayerLight( void ); -float R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc ); +float R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc, double time ); void R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles ); void R_StudioResetPlayerModels( void ); void CL_InitStudioAPI( void ); @@ -1010,8 +999,6 @@ extern affinetridesc_t r_affinetridesc; void D_DrawSurfaces (void); void R_DrawParticle( void ); void D_ViewChanged (void); -void D_WarpScreen (void); -void R_PolysetUpdateTables (void); //=======================================================================// @@ -1199,6 +1186,7 @@ void R_ScanEdges (void); // // r_surf.c // +void GL_InitRandomTable( void ); void D_FlushCaches( void ); // diff --git a/ref/soft/r_main.c b/ref/soft/r_main.c index 243429ad..379c0dcc 100644 --- a/ref/soft/r_main.c +++ b/ref/soft/r_main.c @@ -1311,7 +1311,7 @@ void R_DrawBrushModel(cl_entity_t *pent) if (!topnode) return; // no part in a visible leaf - alphaspans = true; + alphaspans = true; VectorCopy (RI.currententity->origin, r_entorigin); VectorSubtract (RI.vieworg, r_entorigin, tr.modelorg); //VectorSubtract (r_origin, RI.currententity->origin, modelorg); @@ -1971,6 +1971,7 @@ qboolean GAME_EXPORT R_Init( void ) R_StudioInit(); R_SpriteInit(); R_InitTurb(); + GL_InitRandomTable(); return true; } diff --git a/ref/soft/r_math.c b/ref/soft/r_math.c index 4c835623..90600ead 100644 --- a/ref/soft/r_math.c +++ b/ref/soft/r_math.c @@ -99,46 +99,6 @@ void Matrix4x4_CreateModelview( matrix4x4 out ) out[1][2] = 1.0f; } -void Matrix4x4_ToArrayFloatGL( const matrix4x4 in, float out[16] ) -{ - out[ 0] = in[0][0]; - out[ 1] = in[1][0]; - out[ 2] = in[2][0]; - out[ 3] = in[3][0]; - out[ 4] = in[0][1]; - out[ 5] = in[1][1]; - out[ 6] = in[2][1]; - out[ 7] = in[3][1]; - out[ 8] = in[0][2]; - out[ 9] = in[1][2]; - out[10] = in[2][2]; - out[11] = in[3][2]; - out[12] = in[0][3]; - out[13] = in[1][3]; - out[14] = in[2][3]; - out[15] = in[3][3]; -} - -void Matrix4x4_FromArrayFloatGL( matrix4x4 out, const float in[16] ) -{ - out[0][0] = in[0]; - out[1][0] = in[1]; - out[2][0] = in[2]; - out[3][0] = in[3]; - out[0][1] = in[4]; - out[1][1] = in[5]; - out[2][1] = in[6]; - out[3][1] = in[7]; - out[0][2] = in[8]; - out[1][2] = in[9]; - out[2][2] = in[10]; - out[3][2] = in[11]; - out[0][3] = in[12]; - out[1][3] = in[13]; - out[2][3] = in[14]; - out[3][3] = in[15]; -} - void Matrix4x4_CreateTranslate( matrix4x4 out, float x, float y, float z ) { out[0][0] = 1.0f; @@ -247,21 +207,3 @@ void Matrix4x4_ConcatRotate( matrix4x4 out, float angle, float x, float y, float Matrix4x4_CreateRotate( temp, angle, x, y, z ); Matrix4x4_Concat( out, base, temp ); } - -void Matrix4x4_ConcatScale( matrix4x4 out, float x ) -{ - matrix4x4 base, temp; - - Matrix4x4_Copy( base, out ); - Matrix4x4_CreateScale( temp, x ); - Matrix4x4_Concat( out, base, temp ); -} - -void Matrix4x4_ConcatScale3( matrix4x4 out, float x, float y, float z ) -{ - matrix4x4 base, temp; - - Matrix4x4_Copy( base, out ); - Matrix4x4_CreateScale3( temp, x, y, z ); - Matrix4x4_Concat( out, base, temp ); -} diff --git a/ref/soft/r_misc.c b/ref/soft/r_misc.c index e984a27e..d385c167 100644 --- a/ref/soft/r_misc.c +++ b/ref/soft/r_misc.c @@ -157,22 +157,6 @@ void TransformVector (vec3_t in, vec3_t out) out[2] = DotProduct(in,RI.vforward); } -/* -================ -R_TransformPlane -================ -*/ -void R_TransformPlane (mplane_t *p, float *normal, float *dist) -{ - float d; - - d = DotProduct (RI.vieworg, p->normal); - *dist = p->dist - d; -// TODO: when we have rotating entities, this will need to use the view matrix - TransformVector (p->normal, normal); -} - - /* =============== R_SetUpFrustumIndexes diff --git a/ref/soft/r_polyse.c b/ref/soft/r_polyse.c index 0d36d386..93861f85 100644 --- a/ref/soft/r_polyse.c +++ b/ref/soft/r_polyse.c @@ -123,28 +123,6 @@ void R_RasterizeAliasPolySmooth (void); void R_PolysetScanLeftEdge(int height); qboolean R_PolysetScanLeftEdge_C(int height); -/* -================ -R_PolysetUpdateTables -================ -*/ -void R_PolysetUpdateTables (void) -{ - int i; - byte *s; - - if (r_affinetridesc.skinwidth != skinwidth || - r_affinetridesc.pskin != skinstart) - { - skinwidth = r_affinetridesc.skinwidth; - skinstart = r_affinetridesc.pskin; - s = skinstart; - for (i=0 ; iname ); @@ -275,7 +276,8 @@ void Mod_LoadMapSprite( model_t *mod, const void *buffer, size_t size, qboolean // determine how many frames we needs numframes = (pix->width * pix->height) / (w * h); - mod->mempool = Mem_AllocPool( va( "^2%s^7", mod->name )); + Q_snprintf( poolname, sizeof( poolname ), "^2%s^7", mod->name ); + mod->mempool = Mem_AllocPool( poolname ); psprite = Mem_Calloc( mod->mempool, sizeof( msprite_t ) + ( numframes - 1 ) * sizeof( psprite->frames )); mod->cache.data = psprite; // make link to extradata diff --git a/ref/soft/r_studio.c b/ref/soft/r_studio.c index e3df3fa3..e3b3b82f 100644 --- a/ref/soft/r_studio.c +++ b/ref/soft/r_studio.c @@ -617,14 +617,14 @@ StudioEstimateFrame ==================== */ -float GAME_EXPORT R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc ) +float GAME_EXPORT R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc, double time ) { double dfdt, f; if( g_studio.interpolate ) { - if( g_studio.time < e->curstate.animtime ) dfdt = 0.0; - else dfdt = (g_studio.time - e->curstate.animtime) * e->curstate.framerate * pseqdesc->fps; + if( time < e->curstate.animtime ) dfdt = 0.0; + else dfdt = (time - e->curstate.animtime) * e->curstate.framerate * pseqdesc->fps; } else dfdt = 0; @@ -667,35 +667,6 @@ float R_StudioEstimateInterpolant( cl_entity_t *e ) return dadt; } -/* -==================== -CL_GetSequenceDuration - -==================== -*/ -float CL_GetSequenceDuration( cl_entity_t *ent, int sequence ) -{ - studiohdr_t *pstudiohdr; - mstudioseqdesc_t *pseqdesc; - - if( ent->model != NULL && ent->model->type == mod_studio ) - { - pstudiohdr = (studiohdr_t *)gEngfuncs.Mod_Extradata( mod_studio, ent->model ); - - if( pstudiohdr ) - { - sequence = bound( 0, sequence, pstudiohdr->numseq - 1 ); - pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence; - - if( pseqdesc->numframes > 1 && pseqdesc->fps > 0 ) - return (float)pseqdesc->numframes / (float)pseqdesc->fps; - } - } - - return 0.1f; -} - - /* ==================== StudioFxTransform @@ -874,7 +845,7 @@ void R_StudioMergeBones( cl_entity_t *e, model_t *m_pSubModel ) pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence; - f = R_StudioEstimateFrame( e, pseqdesc ); + f = R_StudioEstimateFrame( e, pseqdesc, g_studio.time ); panim = gEngfuncs.R_StudioGetAnim( m_pStudioHeader, m_pSubModel, pseqdesc ); R_StudioCalcRotations( e, pos, q, pseqdesc, panim, f ); @@ -940,7 +911,7 @@ void R_StudioSetupBones( cl_entity_t *e ) pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence; - f = R_StudioEstimateFrame( e, pseqdesc ); + f = R_StudioEstimateFrame( e, pseqdesc, g_studio.time ); panim = gEngfuncs.R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc ); R_StudioCalcRotations( e, pos, q, pseqdesc, panim, f ); @@ -1704,47 +1675,56 @@ R_LightLambert ==================== */ -void R_LightLambert( vec4_t light[MAX_LOCALLIGHTS], vec3_t normal, vec3_t color, byte *out ) +static void R_LightLambert( vec4_t light[MAX_LOCALLIGHTS], const vec3_t normal, const vec3_t color, byte *out ) { vec3_t finalLight; - vec3_t localLight; int i; + if( !g_studio.numlocallights ) + { + VectorScale( color, 255.0f, out ); + return; + } + VectorCopy( color, finalLight ); for( i = 0; i < g_studio.numlocallights; i++ ) { - float r, r2; + float r; - if( tr.fFlipViewModel ) - r = DotProduct( normal, light[i] ); - else r = -DotProduct( normal, light[i] ); + r = DotProduct( normal, light[i] ); + if( likely( !tr.fFlipViewModel )) + r = -r; if( r > 0.0f ) { + vec3_t localLight; + float temp; + if( light[i][3] == 0.0f ) { - r2 = DotProduct( light[i], light[i] ); + float r2 = DotProduct( light[i], light[i] ); if( r2 > 0.0f ) light[i][3] = g_studio.locallightR2[i] / ( r2 * sqrt( r2 )); else light[i][3] = 0.0001f; } - localLight[0] = Q_min( g_studio.locallightcolor[i].r * r * light[i][3], 255.0f ); - localLight[1] = Q_min( g_studio.locallightcolor[i].g * r * light[i][3], 255.0f ); - localLight[2] = Q_min( g_studio.locallightcolor[i].b * r * light[i][3], 255.0f ); - VectorScale( localLight, ( 1.0f / 255.0f ), localLight ); + temp = Q_min( r * light[i][3] / 255.0f, 1.0f ); - finalLight[0] = Q_min( finalLight[0] + localLight[0], 1.0f ); - finalLight[1] = Q_min( finalLight[1] + localLight[1], 1.0f ); - finalLight[2] = Q_min( finalLight[2] + localLight[2], 1.0f ); + localLight[0] = (float)g_studio.locallightcolor[i].r * temp; + localLight[1] = (float)g_studio.locallightcolor[i].g * temp; + localLight[2] = (float)g_studio.locallightcolor[i].b * temp; + + VectorAdd( finalLight, localLight, finalLight ); } } - out[0] = finalLight[0] * 255; - out[1] = finalLight[1] * 255; - out[2] = finalLight[2] * 255; + VectorScale( finalLight, 255.0f, finalLight ); + + out[0] = Q_min( (int)( finalLight[0] ), 255 ); + out[1] = Q_min( (int)( finalLight[1] ), 255 ); + out[2] = Q_min( (int)( finalLight[2] ), 255 ); } static void R_StudioSetColorBegin(short *ptricmds, vec3_t *pstudionorms ) @@ -2561,7 +2541,7 @@ static void R_StudioClientEvents( void ) if( pseqdesc->numevents == 0 ) return; - end = R_StudioEstimateFrame( e, pseqdesc ); + end = R_StudioEstimateFrame( e, pseqdesc, g_studio.time ); start = end - e->curstate.framerate * gpGlobals->frametime * pseqdesc->fps; pevent = (mstudioevent_t *)((byte *)m_pStudioHeader + pseqdesc->eventindex); @@ -3557,7 +3537,7 @@ static void R_StudioLoadTexture( model_t *mod, studiohdr_t *phdr, mstudiotexture } Q_strncpy( mdlname, mod->name, sizeof( mdlname )); - COM_FileBase( ptexture->name, name ); + COM_FileBase( ptexture->name, name, sizeof( name )); COM_StripExtension( mdlname ); if( FBitSet( ptexture->flags, STUDIO_NF_NOMIPS )) diff --git a/ref/soft/r_surf.c b/ref/soft/r_surf.c index e1d44681..48b7fc31 100644 --- a/ref/soft/r_surf.c +++ b/ref/soft/r_surf.c @@ -353,7 +353,7 @@ void R_BuildLightMap (void) #endif -void R_InitRandomTable( void ) +void GL_InitRandomTable( void ) { int tu, tv; @@ -1105,52 +1105,6 @@ surfcache_t *D_SCAlloc (int width, int size) return new; } - -/* -================= -D_SCDump -================= -*/ -void D_SCDump (void) -{ - surfcache_t *test; - - for (test = sc_base ; test ; test = test->next) - { - if (test == sc_rover) - gEngfuncs.Con_Printf ("ROVER:\n"); - gEngfuncs.Con_Printf ("%p : %i bytes %i width\n",test, test->size, test->width); - } -} - -//============================================================================= - -// if the num is not a power of 2, assume it will not repeat - -int MaskForNum (int num) -{ - if (num==128) - return 127; - if (num==64) - return 63; - if (num==32) - return 31; - if (num==16) - return 15; - return 255; -} - -int D_log2 (int num) -{ - int c; - - c = 0; - - while (num>>=1) - c++; - return c; -} - //============================================================================= void R_DecalComputeBasis( msurface_t *surf, int flags, vec3_t textureSpaceBasis[3] ); void R_DrawSurfaceDecals( void ) diff --git a/ref/soft/r_trialias.c b/ref/soft/r_trialias.c index 51e7bfdc..9595a6c4 100644 --- a/ref/soft/r_trialias.c +++ b/ref/soft/r_trialias.c @@ -25,7 +25,6 @@ float r_avertexnormals[NUMVERTEXNORMALS][3] = { void R_AliasSetUpTransform (void); -void R_AliasTransformVector (vec3_t in, vec3_t out, float m[3][4] ); void R_AliasProjectAndClipTestFinalVert (finalvert_t *fv); void R_AliasTransformFinalVerts( int numpoints, finalvert_t *fv, dtrivertx_t *oldv, dtrivertx_t *newv ); @@ -42,19 +41,6 @@ R_AliasCheckBBox #define BBOX_MUST_CLIP_Z 2 #define BBOX_TRIVIAL_REJECT 8 - -/* -================ -R_AliasTransformVector -================ -*/ -void R_AliasTransformVector(vec3_t in, vec3_t out, float xf[3][4] ) -{ - out[0] = DotProduct(in, xf[0]) + xf[0][3]; - out[1] = DotProduct(in, xf[1]) + xf[1][3]; - out[2] = DotProduct(in, xf[2]) + xf[2][3]; -} - void VectorInverse (vec3_t v) { v[0] = -v[0]; @@ -238,13 +224,6 @@ void R_AliasProjectAndClipTestFinalVert( finalvert_t *fv ) fv->flags |= ALIAS_BOTTOM_CLIP; } -void R_AliasWorldToScreen( const float *v, float *out ) -{ - out[0] = DotProduct(v, aliastransform[0]) + aliastransform[0][3]; - out[1] = DotProduct(v, aliastransform[1]) + aliastransform[1][3]; - out[2] = DotProduct(v, aliastransform[2]) + aliastransform[2][3]; -} - void R_SetupFinalVert( finalvert_t *fv, float x, float y, float z, int light, int s, int t ) { vec3_t v = {x, y, z}; diff --git a/ref/soft/wscript b/ref/soft/wscript index e5cf7ca2..78de52d3 100644 --- a/ref/soft/wscript +++ b/ref/soft/wscript @@ -22,6 +22,13 @@ def configure(conf): conf.env.append_unique('DEFINES', 'REF_DLL') def build(bld): + libs = [ 'engine_includes' ] + # on PSVita do not link any libraries that are already in the main executable, but add the includes target + if bld.env.DEST_OS == 'psvita': + libs += [ 'sdk_includes' ] + else: + libs += [ 'public', 'M' ] + if bld.env.DEDICATED: return @@ -29,7 +36,7 @@ def build(bld): target = 'ref_soft', features = 'c', includes = '.', - use = 'engine_includes public M', + use = libs, install_path = bld.env.LIBDIR, subsystem = bld.env.MSVC_SUBSYSTEM ) diff --git a/scripts/cirrus/build_freebsd.sh b/scripts/cirrus/build_freebsd.sh index 02fc7b80..554313d0 100755 --- a/scripts/cirrus/build_freebsd.sh +++ b/scripts/cirrus/build_freebsd.sh @@ -14,9 +14,9 @@ build_engine() cd "$CIRRUS_WORKING_DIR" || die if [ "$APP" = "xashds" ]; then - ./waf configure -T release -d --enable-fs-tests || die_configure + ./waf configure -T release -d --enable-tests || die_configure elif [ "$APP" = "xash3d-fwgs" ]; then - ./waf configure -T release --enable-stb --enable-utils --enable-gl4es --enable-gles1 --enable-gles2 --enable-fs-tests || die_configure + ./waf configure -T release --enable-stb --enable-utils --enable-gl4es --enable-gles1 --enable-gles2 --enable-tests || die_configure else die fi diff --git a/scripts/continious_upload.sh b/scripts/continious_upload.sh deleted file mode 100755 index fb91611b..00000000 --- a/scripts/continious_upload.sh +++ /dev/null @@ -1,283 +0,0 @@ -#!/bin/bash - -# MIT License -# -# Copyright (c) 2016 Simon Peter -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -set +x # Do not leak information - -# Exit immediately if one of the files given as arguments is not there -# because we don't want to delete the existing release if we don't have -# the new files that should be uploaded -for file in "$@" -do - if [ ! -e "$file" ] - then echo "$file is missing, giving up." >&2; exit 1 - fi -done - -if [ $# -eq 0 ]; then - echo "No artifacts to use for release, giving up." - exit 0 -fi - -if command -v sha256sum >/dev/null 2>&1 ; then - shatool="sha256sum" -elif command -v shasum >/dev/null 2>&1 ; then - shatool="shasum -a 256" # macOS fallback -else - echo "Neither sha256sum nor shasum is available, cannot check hashes" -fi - -RELEASE_BODY="" -GIT_REPO_SLUG="$REPO_SLUG" - -if [ ! -z "$GITHUB_ACTIONS" ] ; then - GIT_COMMIT="$GITHUB_SHA" - GIT_REPO_SLUG="$GITHUB_REPOSITORY" - if [[ "$GITHUB_REF" == "refs/tags/"* ]] ; then - GIT_TAG="${GITHUB_REF#refs/tags/}" - fi - RELEASE_BODY="GitHub Actions build log: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" -fi - -if [ ! -z "$UPLOADTOOL_BODY" ] ; then - RELEASE_BODY="$UPLOADTOOL_BODY" -fi - -# The calling script (usually .travis.yml) can set a suffix to be used for -# the tag and release name. This way it is possible to have a release for -# the output of the CI/CD pipeline (marked as 'continuous') and also test -# builds for other branches. -# If this build was triggered by a tag, call the result a Release -if [ ! -z "$UPLOADTOOL_SUFFIX" ] ; then - if [ "$UPLOADTOOL_SUFFIX" = "$GIT_TAG" ] ; then - RELEASE_NAME="$GIT_TAG" - RELEASE_TITLE="Release build ($GIT_TAG)" - is_prerelease="false" - else - RELEASE_NAME="continuous-$UPLOADTOOL_SUFFIX" - RELEASE_TITLE="Continuous build ($UPLOADTOOL_SUFFIX)" - if [ -z "$UPLOADTOOL_ISPRERELEASE" ] ; then - is_prerelease="false" - else - is_prerelease="true" - fi - - fi -else - 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 - 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 -if [ "$GITHUB_EVENT_NAME" == "pull_request" ] ; then - echo "Release uploading disabled for pull requests, uploading to transfer.sh instead" - rm -f ./uploaded-to - for FILE in "$@" ; do - BASENAME="$(basename "${FILE}")" - curl --upload-file $FILE "https://transfer.sh/$BASENAME" > ./one-upload - echo "$(cat ./one-upload)" # this way we get a newline - echo -n "$(cat ./one-upload)\\n" >> ./uploaded-to # this way we get a \n but no newline - done - $shatool "$@" - exit 0 -fi - -if [ ! -z "$GITHUB_ACTIONS" ] ; then - echo "Running on GitHub Actions" - if [ -z "$GITHUB_TOKEN" ] ; then - echo "\$GITHUB_TOKEN missing, please add the following to your run action:" - echo "env:" - echo " GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}" - exit 1 - fi -else - echo "Not running on known CI" - if [ -z "$GIT_REPO_SLUG" ] ; then - read -r -p "Repo Slug (GitHub username/reponame): " GIT_REPO_SLUG - fi - if [ -z "$GITHUB_TOKEN" ] ; then - read -r -s -p "Token (https://github.com/settings/tokens): " GITHUB_TOKEN - fi -fi - -tag_url="https://api.github.com/repos/$GIT_REPO_SLUG/git/refs/tags/$RELEASE_NAME" -tag_infos=$(curl -XGET --header "Authorization: token ${GITHUB_TOKEN}" "${tag_url}") -echo "tag_infos: $tag_infos" -tag_sha=$(echo "$tag_infos" | grep '"sha":' | head -n 1 | cut -d '"' -f 4 | cut -d '{' -f 1) -echo "tag_sha: $tag_sha" - -release_url="https://api.github.com/repos/$GIT_REPO_SLUG/releases/tags/$RELEASE_NAME" -echo "Getting the release ID..." -echo "release_url: $release_url" -release_infos=$(curl -XGET --header "Authorization: token ${GITHUB_TOKEN}" "${release_url}") -echo "release_infos: $release_infos" -release_id=$(echo "$release_infos" | grep "\"id\":" | head -n 1 | tr -s " " | cut -f 3 -d" " | cut -f 1 -d ",") -echo "release ID: $release_id" -upload_url=$(echo "$release_infos" | grep '"upload_url":' | head -n 1 | cut -d '"' -f 4 | cut -d '{' -f 1) -echo "upload_url: $upload_url" -release_url=$(echo "$release_infos" | grep '"url":' | head -n 1 | cut -d '"' -f 4 | cut -d '{' -f 1) -echo "release_url: $release_url" -target_commit_sha=$(echo "$release_infos" | grep '"target_commitish":' | head -n 1 | cut -d '"' -f 4 | cut -d '{' -f 1) -echo "target_commit_sha: $target_commit_sha" - -if [ "$GIT_COMMIT" != "$target_commit_sha" ] ; then - - echo "GIT_COMMIT != target_commit_sha, hence deleting $RELEASE_NAME..." - - if [ ! -z "$release_id" ]; then - delete_url="https://api.github.com/repos/$GIT_REPO_SLUG/releases/$release_id" - echo "Delete the release..." - echo "delete_url: $delete_url" - curl -XDELETE \ - --header "Authorization: token ${GITHUB_TOKEN}" \ - "${delete_url}" - fi - - # echo "Checking if release with the same name is still there..." - # echo "release_url: $release_url" - # curl -XGET --header "Authorization: token ${GITHUB_TOKEN}" \ - # "$release_url" - - if [ "$RELEASE_NAME" == "continuous" ] ; then - # if this is a continuous build tag, then delete the old tag - # in preparation for the new release - echo "Delete the tag..." - delete_url="https://api.github.com/repos/$GIT_REPO_SLUG/git/refs/tags/$RELEASE_NAME" - echo "delete_url: $delete_url" - curl -XDELETE \ - --header "Authorization: token ${GITHUB_TOKEN}" \ - "${delete_url}" - fi - - echo "Create release..." - - release_infos=$(curl -H "Authorization: token ${GITHUB_TOKEN}" \ - --data '{"tag_name": "'"$RELEASE_NAME"'","target_commitish": "'"$GIT_COMMIT"'","name": "'"$RELEASE_TITLE"'","body": "'"$RELEASE_BODY"'","draft": false,"prerelease": '$is_prerelease'}' "https://api.github.com/repos/$GIT_REPO_SLUG/releases") - - echo "$release_infos" - - unset upload_url - upload_url=$(echo "$release_infos" | grep '"upload_url":' | head -n 1 | cut -d '"' -f 4 | cut -d '{' -f 1) - echo "upload_url: $upload_url" - - unset release_url - release_url=$(echo "$release_infos" | grep '"url":' | head -n 1 | cut -d '"' -f 4 | cut -d '{' -f 1) - echo "release_url: $release_url" - -fi # if [ "$GIT_COMMIT" != "$tag_sha" ] - -if [ -z "$release_url" ] ; then - echo "Cannot figure out the release URL for $RELEASE_NAME" - exit 1 -fi - -echo "Upload binaries to the release..." - -# Need to URL encode the basename, so we have this function to do so -urlencode() { - # urlencode - old_lc_collate=$LC_COLLATE - LC_COLLATE=C - local length="${#1}" - for (( i = 0; i < length; i++ )); do - local c="${1:$i:1}" - case $c in - [a-zA-Z0-9.~_-]) printf '%s' "$c" ;; - *) printf '%%%02X' "'$c" ;; - esac - done - LC_COLLATE=$old_lc_collate -} - -for FILE in "$@" ; do - FULLNAME="${FILE}" - BASENAME="$(basename "${FILE}")" - curl -H "Authorization: token ${GITHUB_TOKEN}" \ - -H "Accept: application/vnd.github.manifold-preview" \ - -H "Content-Type: application/octet-stream" \ - --data-binary "@$FULLNAME" \ - "$upload_url?name=$(urlencode "$BASENAME")" - echo "" -done - -$shatool "$@" - -if [ "$GIT_COMMIT" != "$tag_sha" ] ; then - echo "Publish the release..." - - release_infos=$(curl -H "Authorization: token ${GITHUB_TOKEN}" \ - --data '{"draft": false}' "$release_url") - - echo "$release_infos" -fi # if [ "$GIT_COMMIT" != "$tag_sha" ] diff --git a/scripts/gha/build_linux.sh b/scripts/gha/build_linux.sh index b2660b59..2f3b40c5 100755 --- a/scripts/gha/build_linux.sh +++ b/scripts/gha/build_linux.sh @@ -50,9 +50,9 @@ build_engine() fi if [ "$1" = "dedicated" ]; then - ./waf configure -T release -d $AMD64 --enable-fs-tests || die_configure + ./waf configure -T release -d $AMD64 --enable-tests || die_configure elif [ "$1" = "full" ]; then - ./waf configure --sdl2=SDL2_linux -T release --enable-stb $AMD64 --enable-utils --enable-fs-tests || die_confgure + ./waf configure --sdl2=SDL2_linux -T release --enable-stb $AMD64 --enable-utils --enable-tests || die_confgure else die fi diff --git a/scripts/gha/build_motomagx.sh b/scripts/gha/build_motomagx.sh index fda2c356..e38aadbe 100755 --- a/scripts/gha/build_motomagx.sh +++ b/scripts/gha/build_motomagx.sh @@ -3,12 +3,12 @@ . scripts/lib.sh . /opt/toolchains/motomagx/setenv-z6.sh -cd $GITHUB_WORKSPACE +cd $GITHUB_WORKSPACE || die mkdir -p Xash/valve/cl_dlls mkdir -p Xash/valve/dlls -pushd hlsdk +pushd hlsdk || die ./waf configure -T fast --enable-magx --enable-simple-mod-hacks build install --destdir=../Xash || die popd diff --git a/scripts/gha/build_nswitch.sh b/scripts/gha/build_nswitch.sh index 03f0353d..1f841354 100644 --- a/scripts/gha/build_nswitch.sh +++ b/scripts/gha/build_nswitch.sh @@ -15,6 +15,6 @@ docker run --name xash-build --rm -v `pwd`:`pwd` -w `pwd` devkitpro/devkita64:la echo "Packaging artifacts..." -pushd pkgtemp +pushd pkgtemp || die 7z a -t7z ../artifacts/xash3d-fwgs-nswitch.7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on -r xash3d/ popd diff --git a/scripts/gha/build_nswitch_docker.sh b/scripts/gha/build_nswitch_docker.sh index 18aa6dec..7b24b1b1 100644 --- a/scripts/gha/build_nswitch_docker.sh +++ b/scripts/gha/build_nswitch_docker.sh @@ -6,9 +6,10 @@ build_hlsdk() { echo "Building HLSDK: $1 branch..." git checkout $1 - ./waf configure -T release --nswitch || die_configure + + # This is not our bug if HLSDK doesn't build with -Werrors enabled + ./waf configure -T release --nswitch --disable-werror || die_configure ./waf build install --destdir=../pkgtemp/xash3d || die - ./waf clean } echo "Setting up environment..." @@ -44,8 +45,7 @@ echo "Building engine..." echo "Building HLSDK..." -# TODO: replace with hlsdk-portable when PRs are merged -pushd hlsdk-portable +pushd hlsdk-portable || die build_hlsdk mobile_hacks valve build_hlsdk opfor gearbox build_hlsdk bshift bshift diff --git a/scripts/gha/build_psvita.sh b/scripts/gha/build_psvita.sh new file mode 100644 index 00000000..ae9b1bba --- /dev/null +++ b/scripts/gha/build_psvita.sh @@ -0,0 +1,98 @@ +#!/bin/bash + +. scripts/lib.sh + +build_hlsdk() +{ + echo "Building HLSDK: $1 branch..." + git checkout $1 + + # This is not our bug if HLSDK doesn't build with -Werrors enabled + ./waf configure -T release --psvita --disable-werror || die_configure + ./waf build install --destdir=../pkgtemp/data/xash3d || die +} + +export VITASDK=/usr/local/vitasdk +export PATH=$VITASDK/bin:$PATH + +cd "$BUILDDIR" || die + +rm -rf artifacts build pkgtemp + +mkdir -p pkgtemp/data/xash3d/{valve,gearbox,bshift}/{dlls,cl_dlls} || die +mkdir -p artifacts/ || die + +echo "Building vitaGL..." + +make -C vitaGL NO_TEX_COMBINER=1 HAVE_UNFLIPPED_FBOS=1 HAVE_PTHREAD=1 MATH_SPEEDHACK=1 DRAW_SPEEDHACK=1 HAVE_CUSTOM_HEAP=1 -j2 install || die + +echo "Building vrtld..." + +pushd vita-rtld || die +cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release || die_configure +cmake --build build -- -j2 || die +cmake --install build || die +popd + +echo "Building SDL..." + +pushd SDL || die +cmake -S. -Bbuild -DCMAKE_TOOLCHAIN_FILE=${VITASDK}/share/vita.toolchain.cmake -DCMAKE_BUILD_TYPE=Release -DVIDEO_VITA_VGL=ON || die_configure +cmake --build build -- -j2 || die +cmake --install build || die +popd + +echo "Building engine..." + +./waf configure -T release --psvita || die_configure +./waf build install --destdir=pkgtemp/data/xash3d || die +cp build/engine/xash.vpk pkgtemp/ + +echo "Building HLSDK..." + +pushd hlsdk-portable || die +build_hlsdk mobile_hacks valve +build_hlsdk opfor gearbox +build_hlsdk bshift bshift +popd + +echo "Generating default config files..." + +pushd pkgtemp/data/xash3d/valve + +touch config.cfg +echo 'unbindall' >> config.cfg +echo 'bind A_BUTTON "+use"' >> config.cfg +echo 'bind B_BUTTON "+jump"' >> config.cfg +echo 'bind X_BUTTON "+reload"' >> config.cfg +echo 'bind Y_BUTTON "+duck"' >> config.cfg +echo 'bind L1_BUTTON "+attack2"' >> config.cfg +echo 'bind R1_BUTTON "+attack"' >> config.cfg +echo 'bind START "escape"' >> config.cfg +echo 'bind DPAD_UP "lastinv"' >> config.cfg +echo 'bind DPAD_DOWN "impulse 100"' >> config.cfg +echo 'bind DPAD_LEFT "invprev"' >> config.cfg +echo 'bind DPAD_RIGHT "invnext"' >> config.cfg +echo 'gl_vsync "1"' >> config.cfg +echo 'sv_autosave "0"' >> config.cfg +echo 'voice_enable "0"' >> config.cfg + +touch video.cfg +echo 'fullscreen "1"' >> video.cfg +echo 'width "960"' >> video.cfg +echo 'height "544"' >> video.cfg +echo 'r_refdll "gl"' >> video.cfg + +touch opengl.cfg +echo 'gl_nosort "1"' >> opengl.cfg + +cp *.cfg ../gearbox/ +cp *.cfg ../bshift/ + +popd + +echo "Packaging artifacts..." + +pushd pkgtemp || die +7z a -t7z ../artifacts/xash3d-fwgs-psvita.7z -m0=lzma2 -mx=9 -mfb=64 -md=32m -ms=on -r xash.vpk data/ +popd diff --git a/scripts/gha/build_win32.sh b/scripts/gha/build_win32.sh index 0b868938..46584e02 100755 --- a/scripts/gha/build_win32.sh +++ b/scripts/gha/build_win32.sh @@ -14,9 +14,9 @@ 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 "release" --enable-utils --enable-fs-tests --prefix=`pwd` $AMD64 || die_configure +./waf.bat configure -s "SDL2_VC" -T "release" --enable-utils --enable-tests $AMD64 || die_configure ./waf.bat build || die -./waf.bat install || die +./waf.bat install --destdir=. || die if [ "$ARCH" = "i386" ]; then cp SDL2_VC/lib/x86/SDL2.dll . # Install SDL2 diff --git a/scripts/gha/deps_psvita.sh b/scripts/gha/deps_psvita.sh new file mode 100644 index 00000000..35795a36 --- /dev/null +++ b/scripts/gha/deps_psvita.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +cd $GITHUB_WORKSPACE + +echo "Downloading vitasdk..." + +export VITASDK=/usr/local/vitasdk + +VITAGL_SRCREV="c52391378c2bf1a00a0194c4fd88c35492d104b8" # lock vitaGL version to avoid compilation errors + +install_package() +{ + ./vdpm $1 || exit 1 +} + +git clone https://github.com/vitasdk/vdpm.git --depth=1 || exit 1 +pushd vdpm +./bootstrap-vitasdk.sh || exit 1 +install_package taihen +install_package kubridge +install_package zlib +install_package SceShaccCgExt +install_package vitaShaRK +install_package libmathneon +popd + +echo "Downloading vitaGL..." + +git clone https://github.com/Rinnegatamante/vitaGL.git || exit 1 +pushd vitaGL +git checkout $VITAGL_SRCREV || exit 1 +popd + +echo "Downloading vitaGL fork of SDL2..." + +git clone https://github.com/Northfear/SDL.git --depth=1 || exit 1 + +echo "Downloading vita-rtld..." + +git clone https://github.com/fgsfdsfgs/vita-rtld.git --depth=1 || exit 1 + +echo "Downloading HLSDK..." + +rm -rf hlsdk-xash3d hlsdk-portable +git clone --recursive https://github.com/FWGS/hlsdk-portable || exit 1 diff --git a/scripts/waifulib/compiler_optimizations.py b/scripts/waifulib/compiler_optimizations.py index 110b0762..9cc81b7c 100644 --- a/scripts/waifulib/compiler_optimizations.py +++ b/scripts/waifulib/compiler_optimizations.py @@ -118,7 +118,7 @@ POLLY_CFLAGS = { def options(opt): grp = opt.add_option_group('Compiler optimization options') - grp.add_option('-T', '--build-type', action='store', dest='BUILD_TYPE', default=None, + grp.add_option('-T', '--build-type', action='store', dest='BUILD_TYPE', default='release', help = 'build type: debug, release or none(custom flags)') grp.add_option('--enable-lto', action = 'store_true', dest = 'LTO', default = False, @@ -129,12 +129,11 @@ def options(opt): def configure(conf): conf.start_msg('Build type') - if conf.options.BUILD_TYPE == None: - conf.end_msg('not set', color='RED') - conf.fatal('Set a build type, for example "-T release"') - elif not conf.options.BUILD_TYPE in VALID_BUILD_TYPES: + + if not conf.options.BUILD_TYPE in VALID_BUILD_TYPES: conf.end_msg(conf.options.BUILD_TYPE, color='RED') conf.fatal('Invalid build type. Valid are: %s' % ', '.join(VALID_BUILD_TYPES)) + conf.end_msg(conf.options.BUILD_TYPE) conf.msg('LTO build', 'yes' if conf.options.LTO else 'no') @@ -172,5 +171,10 @@ def get_optimization_flags(conf): if conf.env.DEST_OS == 'nswitch' and conf.options.BUILD_TYPE == 'debug': # enable remote debugger cflags.append('-DNSWITCH_DEBUG') + elif conf.env.DEST_OS == 'psvita': + # this optimization is broken in vitasdk + cflags.append('-fno-optimize-sibling-calls') + # remove fvisibility to allow everything to be exported by default + cflags.remove('-fvisibility=hidden') return cflags, linkflags diff --git a/scripts/waifulib/psvita.py b/scripts/waifulib/psvita.py new file mode 100644 index 00000000..68e7af18 --- /dev/null +++ b/scripts/waifulib/psvita.py @@ -0,0 +1,114 @@ +# encoding: utf-8 +# psvita.py -- PSVita VPK task +# Copyright (C) 2023 fgsfds +# 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. + +from waflib.Tools import ccroot +from waflib import * + +def add_source_file(ctx, nodes, f): + if f: + if isinstance(f, str): + node = ctx.path.make_node(f) + elif isinstance(f, Node.Node): + node = f + nodes += [node] + return node + return None + +def configure(conf): + conf.find_program('vita-elf-create', var='ELF_CREATE') + conf.find_program('vita-make-fself', var='MAKE_FSELF') + conf.find_program('vita-mksfoex', var='MKSFOEX') + conf.find_program('vita-pack-vpk', var='PACKVPK') + +class mkvelf(Task.Task): + color = 'CYAN' + run_str = '${ELF_CREATE} -g ${YMLFILE} ${ELFFILE} ${TGT}' + +class mkfself(Task.Task): + color = 'CYAN' + run_str = '${MAKE_FSELF} ${VELFFILE} ${TGT}' + +class mksfoex(Task.Task): + color = 'CYAN' + # ATTRIBUTE2=12 enables the biggest extended memory mode + run_str = '${MKSFOEX} -s TITLE_ID=${TITLEID} -d ATTRIBUTE2=12 ${APPNAME} ${TGT}' + +class mkvpk(Task.Task): + color = 'CYAN' + run_str = '${PACKVPK} -s ${SFOFILE} -b ${FSELFFILE} -a ${SCESYS}=sce_sys ${TGT}' + +@TaskGen.feature('cxxprogram') +@TaskGen.after_method('apply_link') +def apply_velf(self): + elffile = self.link_task.outputs[0] + in_nodes = [elffile] + + ymlfile = elffile.change_ext('.yml') + velffile = elffile.change_ext('.velf') + out_nodes = [velffile, ymlfile] + + self.env.ELFFILE = str(elffile) + self.env.VELFFILE = str(velffile) + self.env.YMLFILE = str(ymlfile) + + self.velf_task = self.create_task('mkvelf', in_nodes) + self.velf_task.set_outputs(out_nodes) + +@TaskGen.feature('cxxprogram') +@TaskGen.after_method('apply_velf') +def apply_fself(self): + velffile = self.velf_task.outputs[0] + in_nodes = [velffile] + + fselffile = velffile.change_ext('.bin') + out_nodes = [fselffile] + + self.env.FSELFFILE = str(fselffile) + + self.fself_task = self.create_task('mkfself', in_nodes) + self.fself_task.set_outputs(out_nodes) + +@TaskGen.feature('cxxprogram') +@TaskGen.after_method('apply_fself') +def apply_sfo(self): + fselffile = self.fself_task.outputs[0] + in_nodes = [fselffile] + scetitleid = getattr(self, 'title_id', None) + sceappname = getattr(self, 'app_name', None) + + sfofile = fselffile.change_ext('.sfo') + out_nodes = [sfofile] + + if scetitleid: self.env.TITLEID = scetitleid + if sceappname: self.env.APPNAME = sceappname + self.env.SFOFILE = str(sfofile) + + self.sfo_task = self.create_task('mksfoex', in_nodes) + self.sfo_task.set_outputs(out_nodes) + +@TaskGen.feature('cxxprogram') +@TaskGen.after_method('apply_sfo') +def apply_vpk(self): + fselffile = self.fself_task.outputs[0] + sfofile = self.sfo_task.outputs[0] + in_nodes = [fselffile, sfofile] + scesysdir = add_source_file(self, in_nodes, getattr(self, 'sce_sys', None)) + + vpkfile = sfofile.change_ext('.vpk') + out_nodes = [vpkfile] + + if scesysdir: self.env.SCESYS = str(scesysdir) + self.env.VPKFILE = str(vpkfile) + + self.vpk_task = self.create_task('mkvpk', in_nodes) + self.vpk_task.set_outputs(out_nodes) diff --git a/scripts/waifulib/vgui.py b/scripts/waifulib/vgui.py new file mode 100644 index 00000000..acba762a --- /dev/null +++ b/scripts/waifulib/vgui.py @@ -0,0 +1,80 @@ +#! /usr/bin/env python +# encoding: utf-8 +# mittorn, 2018 + +from waflib.Configure import conf +from waflib import Logs +import os + +VGUI_SUPPORTED_OS = ['win32', 'darwin', 'linux'] + +VGUI_FRAGMENT = '''#include +int main() { return 0; }''' + +def options(opt): + grp = opt.add_option_group('VGUI options') + + vgui_dev_path = os.path.join(opt.path.path_from(opt.root), 'vgui-dev') + + grp.add_option('--vgui', action = 'store', dest = 'VGUI_DEV', default=vgui_dev_path, + help = 'path to vgui-dev repo [default: %default]') + + grp.add_option('--skip-vgui-sanity-check', action = 'store_false', dest = 'VGUI_SANITY_CHECK', default=True, + help = 'skip checking VGUI sanity [default: %default]' ) + return + +@conf +def check_vgui(conf): + 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)) + return False + 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)) + return False + 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 can\'t be linked with MinGW') + return False + else: conf.end_msg('yes') + + conf.start_msg('Configuring VGUI by provided path') + vgui_dev = conf.options.VGUI_DEV + + if conf.env.DEST_OS == 'win32': + conf.env.LIB_VGUI = ['vgui'] + conf.env.LIBPATH_VGUI = [os.path.abspath(os.path.join(vgui_dev, 'lib/win32_vc6/'))] + else: + libpath = os.path.abspath(os.path.join(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(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=VGUI_FRAGMENT, + 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.") + + return True diff --git a/scripts/waifulib/xcompile.py b/scripts/waifulib/xcompile.py index 21631d38..08e90228 100644 --- a/scripts/waifulib/xcompile.py +++ b/scripts/waifulib/xcompile.py @@ -32,6 +32,8 @@ ANDROID_64BIT_API_MIN = 21 # minimal API level that supports 64-bit targets NSWITCH_ENVVARS = ['DEVKITPRO'] +PSVITA_ENVVARS = ['VITASDK'] + # This class does support ONLY r10e and r19c/r20 NDK class Android: ctx = None # waf context @@ -432,6 +434,70 @@ class NintendoSwitch: ldflags = [] # ['-lm', '-lstdc++'] return ldflags +class PSVita: + ctx = None # waf context + arch ='armeabi-v7a-hard' + vitasdk_dir = None + + def __init__(self, ctx): + self.ctx = ctx + + for i in PSVITA_ENVVARS: + self.vitasdk_dir = os.getenv(i) + if self.vitasdk_dir != None: + break + else: + ctx.fatal('Set %s environment variable pointing to the VitaSDK directory!' % + ' or '.join(PSVITA_ENVVARS)) + + def gen_toolchain_prefix(self): + return 'arm-vita-eabi-' + + def gen_gcc_toolchain_path(self): + return os.path.join(self.vitasdk_dir, 'bin', self.gen_toolchain_prefix()) + + def cc(self): + return self.gen_gcc_toolchain_path() + 'gcc' + + def cxx(self): + return self.gen_gcc_toolchain_path() + 'g++' + + def strip(self): + return self.gen_gcc_toolchain_path() + 'strip' + + def ar(self): + return self.gen_gcc_toolchain_path() + 'ar' + + def pkgconfig(self): + return self.gen_gcc_toolchain_path() + 'pkg-config' + + def cflags(self, cxx = False): + cflags = [] + # arch flags + cflags += ['-D__vita__', '-mtune=cortex-a9', '-mfpu=neon'] + # necessary linker flags + cflags += ['-Wl,-q', '-Wl,-z,nocopyreloc'] + # this optimization is broken in vitasdk + cflags += ['-fno-optimize-sibling-calls'] + # disable some ARM bullshit + cflags += ['-fno-short-enums', '-Wno-attributes'] + # base include dir + cflags += ['-isystem %s/arm-vita-eabi/include' % self.vitasdk_dir] + # SDL include dir + cflags += ['-I%s/arm-vita-eabi/include/SDL2' % self.vitasdk_dir] + return cflags + + # they go before object list + def linkflags(self): + linkflags = ['-Wl,--hash-style=sysv', '-Wl,-q', '-Wl,-z,nocopyreloc', '-mtune=cortex-a9', '-mfpu=neon'] + # enforce no-short-enums again + linkflags += ['-Wl,-no-enum-size-warning', '-fno-short-enums'] + return linkflags + + def ldflags(self): + ldflags = [] + return ldflags + def options(opt): xc = opt.add_option_group('Cross compile options') xc.add_option('--android', action='store', dest='ANDROID_OPTS', default=None, @@ -442,6 +508,8 @@ def options(opt): help='enable building with MSVC using Wine [default: %default]') xc.add_option('--nswitch', action='store_true', dest='NSWITCH', default = False, help ='enable building for Nintendo Switch [default: %default]') + xc.add_option('--psvita', action='store_true', dest='PSVITA', default = False, + help ='enable building for PlayStation Vita [default: %default]') def configure(conf): if conf.options.ANDROID_OPTS: @@ -507,10 +575,25 @@ def configure(conf): conf.env.HAVE_M = True conf.env.LIB_M = ['m'] conf.env.DEST_OS = 'nswitch' + elif conf.options.PSVITA: + conf.psvita = psvita = PSVita(conf) + conf.environ['CC'] = psvita.cc() + conf.environ['CXX'] = psvita.cxx() + conf.environ['STRIP'] = psvita.strip() + conf.environ['AR'] = psvita.ar() + conf.env.PKGCONFIG = psvita.pkgconfig() + conf.env.CFLAGS += psvita.cflags() + conf.env.CXXFLAGS += psvita.cflags(True) + conf.env.LINKFLAGS += psvita.linkflags() + conf.env.LDFLAGS += psvita.ldflags() + conf.env.HAVE_M = True + conf.env.LIB_M = ['m'] + conf.env.VRTLD = ['vrtld'] + conf.env.DEST_OS = 'psvita' conf.env.MAGX = conf.options.MAGX conf.env.MSVC_WINE = conf.options.MSVC_WINE - MACRO_TO_DESTOS = OrderedDict({ '__ANDROID__' : 'android', '__SWITCH__' : 'nswitch' }) + MACRO_TO_DESTOS = OrderedDict({ '__ANDROID__' : 'android', '__SWITCH__' : 'nswitch', '__vita__' : 'psvita' }) for k in c_config.MACRO_TO_DESTOS: MACRO_TO_DESTOS[k] = c_config.MACRO_TO_DESTOS[k] # ordering is important c_config.MACRO_TO_DESTOS = MACRO_TO_DESTOS diff --git a/utils/mdldec/mdldec.c b/utils/mdldec/mdldec.c index c137be93..58c69241 100644 --- a/utils/mdldec/mdldec.c +++ b/utils/mdldec/mdldec.c @@ -86,7 +86,7 @@ static void BoneNameFix( void ) bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + i; if( bone->name[0] == '\0' ) - Q_sprintf( bone->name, "MDLDEC_Bone%i", ++counter ); + Q_snprintf( bone->name, sizeof( bone->name ), "MDLDEC_Bone%i", ++counter ); } if( counter ) @@ -179,8 +179,8 @@ static qboolean LoadMDL( const char *modelname ) if( !model_hdr->numtextures ) { - Q_strcpy( texturename, modelname ); - Q_strcpy( &texturename[len], "t.mdl" ); + Q_strncpy( texturename, modelname, sizeof( texturename )); + Q_strncpy( &texturename[len], "t.mdl", sizeof( texturename ) - len ); texture_hdr = (studiohdr_t *)LoadFile( texturename ); @@ -222,11 +222,11 @@ static qboolean LoadMDL( const char *modelname ) if( model_hdr->numseqgroups > 1 ) { - Q_strcpy( seqgroupname, modelname ); + Q_strncpy( seqgroupname, modelname, sizeof( seqgroupname )); for( i = 1; i < model_hdr->numseqgroups; i++ ) { - Q_sprintf( &seqgroupname[len], "%02d.mdl", i ); + Q_snprintf( &seqgroupname[len], sizeof( seqgroupname ) - len, "%02d.mdl", i ); anim_hdr[i] = (studiohdr_t *)LoadFile( seqgroupname ); @@ -244,7 +244,7 @@ static qboolean LoadMDL( const char *modelname ) } } - COM_FileBase( modelname, modelfile ); + COM_FileBase( modelname, modelfile, sizeof( modelfile )); SequenceNameFix(); @@ -283,7 +283,7 @@ int main( int argc, char *argv[] ) goto end; } - Q_strcpy( destdir, argv[2] ); + Q_strncpy( destdir, argv[2], sizeof( destdir )); } if( !LoadActivityList( argv[0] ) || !LoadMDL( argv[1] ) ) diff --git a/utils/mdldec/qc.c b/utils/mdldec/qc.c index 9a8aef82..d482de21 100644 --- a/utils/mdldec/qc.c +++ b/utils/mdldec/qc.c @@ -110,7 +110,7 @@ static const char *FindActivityName( int type ) GetMotionTypeString ============ */ -static void GetMotionTypeString( int type, char *str, qboolean is_composite ) +static void GetMotionTypeString( int type, char *str, size_t size, qboolean is_composite ) { const char *p = NULL; @@ -119,46 +119,46 @@ static void GetMotionTypeString( int type, char *str, qboolean is_composite ) if( is_composite ) { if( type & STUDIO_X ) - Q_strcat( str, " X" ); + Q_strncat( str, " X", size ); if( type & STUDIO_Y ) - Q_strcat( str, " Y" ); + Q_strncat( str, " Y", size ); if( type & STUDIO_Z ) - Q_strcat( str, " Z" ); + Q_strncat( str, " Z", size ); if( type & STUDIO_XR ) - Q_strcat( str, " XR" ); + Q_strncat( str, " XR", size ); if( type & STUDIO_YR ) - Q_strcat( str, " YR" ); + Q_strncat( str, " YR", size ); if( type & STUDIO_ZR ) - Q_strcat( str, " ZR" ); + Q_strncat( str, " ZR", size ); if( type & STUDIO_LX ) - Q_strcat( str, " LX" ); + Q_strncat( str, " LX", size ); if( type & STUDIO_LY ) - Q_strcat( str, " LY" ); + Q_strncat( str, " LY", size ); if( type & STUDIO_LZ ) - Q_strcat( str, " LZ" ); + Q_strncat( str, " LZ", size ); if( type & STUDIO_LXR ) - Q_strcat( str, " LXR" ); + Q_strncat( str, " LXR", size ); if( type & STUDIO_LYR ) - Q_strcat( str, " LYR" ); + Q_strncat( str, " LYR", size ); if( type & STUDIO_LZR ) - Q_strcat( str, " LZR" ); + Q_strncat( str, " LZR", size ); if( type & STUDIO_LINEAR ) - Q_strcat( str, " LM" ); + Q_strncat( str, " LM", size ); if( type & STUDIO_QUADRATIC_MOTION ) - Q_strcat( str, " LQ" ); + Q_strncat( str, " LQ", size ); return; } @@ -185,7 +185,7 @@ static void GetMotionTypeString( int type, char *str, qboolean is_composite ) } if( p ) - Q_strcpy( str, p ); + Q_strncpy( str, p, size ); } /* @@ -329,9 +329,9 @@ static void WriteBodyGroupInfo( FILE *fp ) { model = (mstudiomodel_t *)( (byte *)model_hdr + bodypart->modelindex ); - COM_FileBase( model->name, modelname ); + COM_FileBase( model->name, modelname, sizeof( modelname )); - fprintf( fp, "$body \"%s\" \"%s\"\n\n", bodypart->name, model->name ); + fprintf( fp, "$body \"%s\" \"%s\"\n\n", bodypart->name, modelname ); continue; } @@ -349,7 +349,7 @@ static void WriteBodyGroupInfo( FILE *fp ) continue; } - COM_FileBase( model->name, modelname ); + COM_FileBase( model->name, modelname, sizeof( modelname )); fprintf( fp, "studio \"%s\"\n", modelname ); } @@ -380,7 +380,7 @@ static void WriteControllerInfo( FILE *fp ) bonecontroller = (mstudiobonecontroller_t *)( (byte *)model_hdr + model_hdr->bonecontrollerindex ) + i; bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + bonecontroller->bone; - GetMotionTypeString( bonecontroller->type & ~STUDIO_RLOOP, motion_types, false ); + GetMotionTypeString( bonecontroller->type & ~STUDIO_RLOOP, motion_types, sizeof( motion_types ), false ); fprintf( fp, "$controller %i \"%s\" %s %f %f\n", bonecontroller->index, bone->name, motion_types, @@ -464,7 +464,7 @@ static void WriteSequenceInfo( FILE *fp ) fprintf( fp, "\"%s_blend2\" ", seqdesc->label ); } - GetMotionTypeString( seqdesc->blendtype[0], motion_types, false ); + GetMotionTypeString( seqdesc->blendtype[0], motion_types, sizeof( motion_types ), false ); fprintf( fp, "blend %s %.0f %.0f", motion_types, seqdesc->blendstart[0], seqdesc->blendend[0] ); @@ -476,7 +476,7 @@ static void WriteSequenceInfo( FILE *fp ) if( seqdesc->motiontype ) { - GetMotionTypeString( seqdesc->motiontype, motion_types, true ); + GetMotionTypeString( seqdesc->motiontype, motion_types, sizeof( motion_types ), true ); fprintf( fp, "%s", motion_types ); } diff --git a/utils/mdldec/smd.c b/utils/mdldec/smd.c index 4e80e3b0..beaff9d6 100644 --- a/utils/mdldec/smd.c +++ b/utils/mdldec/smd.c @@ -465,7 +465,7 @@ static void WriteReferences( void ) if( !Q_strncmp( model->name, "blank", 5 ) ) continue; - COM_FileBase( model->name, name ); + COM_FileBase( model->name, name, sizeof( name )); len = Q_snprintf( filename, MAX_SYSPATH, "%s%s.smd", destdir, name ); diff --git a/utils/mdldec/texture.c b/utils/mdldec/texture.c index d38062f0..5643393e 100644 --- a/utils/mdldec/texture.c +++ b/utils/mdldec/texture.c @@ -39,7 +39,7 @@ static void WriteBMP( mstudiotexture_t *texture ) bmp_t bmp_hdr = {0,}; size_t texture_size; - COM_FileBase( texture->name, texturename ); + COM_FileBase( texture->name, texturename, sizeof( texturename )); len = Q_snprintf( filename, MAX_SYSPATH, "%s%s.bmp", destdir, texturename ); if( len == -1 ) diff --git a/utils/run-fuzzer/wscript b/utils/run-fuzzer/wscript index 11174c8a..ba471aab 100644 --- a/utils/run-fuzzer/wscript +++ b/utils/run-fuzzer/wscript @@ -33,6 +33,7 @@ def add_runner_target(bld, lib, func): def build(bld): add_runner_target(bld, 'libxash.so', 'Sound_LoadMPG') + add_runner_target(bld, 'libxash.so', 'Sound_ParseID3Tag') add_runner_target(bld, 'libxash.so', 'Sound_LoadWAV') add_runner_target(bld, 'libxash.so', 'Image_LoadBMP') add_runner_target(bld, 'libxash.so', 'Image_LoadPNG') diff --git a/waf b/waf index 82ac5125..f93cde51 100755 --- a/waf +++ b/waf @@ -32,13 +32,13 @@ POSSIBILITY OF SUCH DAMAGE. import os, sys, inspect -VERSION="2.0.24" -REVISION="99462435aaeec331fa35f40b2179472b" -GIT="c140c3f538c4a21f3d88bab9403b42c696759dcb" +VERSION="2.0.25" +REVISION="9dbe1df4d0e92facbc1de0486ec78fed" +GIT="ee63711f9792665ffd31c3397ab0e770300b938d" INSTALL='' -C1='#5' -C2='#/' -C3='#+' +C1='#n' +C2='#Y' +C3='#X' cwd = os.getcwd() join = os.path.join @@ -171,5 +171,5 @@ if __name__ == '__main__': Scripting.waf_entry_point(cwd, VERSION, wafdir) #==> -#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/ﯫc[ٻvvlqVڧYF{=J޾{}fKnncw<ͧwp;jl{}>-}qǧ7׸>usbBMڱUpvx]aJ{[f{olz#X ;kJD3N۷(#Yn8}#X%T#X\vR(6hCCZd 6tE9QUZ˵uoCǗ֤#nv}h0+J-c,{2)kɬy껮%#X=x)iR"s0;a^Wd.Vi#n#n()#XTUBo#n;:NîQu'Eo64G,'﷞L@ҩEu#X#X#X#X#X#Xj=B#n9#XªQh#nkzѠրk:vf۸Ik@Bm#Xnc#X*c(PT#X#XR#X;(4#ndR{Ev^n5GM+&}D}+X(4&s'#SR>ݏ-}w}}y_Tyswz=G}ݛy&zj=>n| vu8PjUH.nmE;]kKhaSBx瞶F=upyϽWmGzط_o.}{|;q}bκ.ޞwu b=_]}:wy2+.^ϝom{]So7tko=t6v}J]oN"@#X\mj׸j[vz;X'_1Is}^ם՝}v쮝3y(wg%תv:EJ=>{;Z![5tm={ֆ͝-Չ]-\{n]JPU^w^P9qkxy]Xwjn]nݕSlH=+B95rvunּ>vNN#X0l#X$D.Iwe[r궪!ج%vҶI^L>|u6ϝ}^o8km;nfwEسP^P#X#X=)/mt&]d:;ywMsSU]s#Xn6ʵ#X(#X#n6GQܰ:=;_n0Ş{xƎ#n#YT\]wr@#XWk}U(-פo'jM>[#XT#XNήĀ͕ÍaTtklw\Ӳ@)zD-8KlOR67nal l{B{t@tJ"L Xܮ#XW3m[o޾oMVyiOy:{k햴+goX|;:B#X@M#X4#X&M#X#XhhFzAj4fdP=M#Y4ڞQ@ d44dʟ=I䇤Ҟ#X#X#X#X#X#X#X#X#X 4h4iMS)OQy)=L4z=h#X#X#XH@#Fؚd{zI?IhP#YPA#X#X#X#X#X#XR2#X & `=&mM2TDF#Y#Y#X#X#X#X5#XL h#X `4ʦ= )zO҇H#X#X#X#X@T*t?SC©ES#YZ"#nfB$l @#XH$GE~ H5X|`qcU)sD`x˨tCKDMԼAPqfA$fqV_"r$="ziACPv#X2&Șbv2Qnp8#n%#n#X(*)#Y- Шb(E&t*B"!߾n o " @L#nBA"RĐ1Dʙ (V%$^#n"3*$GQx@MD444S+lGS#nab"GSBAUr"#n% RP#XP8#nV E"("*b(#nb "&*I"hH"&&"#nf#nJ*jfb$*jf"*h#n)`(Id(bh" $(DB)RPPfXRBI`("XaEJUdA"j bX(#n&* #n"Y#n))#nb&B*H Z)j"ibHJ*f)I(Ia)H*h* !hjY` `R BV) R*"Jj"#n#nbBJ)"jRh*i%(I "`Q&R*&* hh! I(R *f B fh*h"I#n (#n(ZXHR*d)(#n*(#nh*Hbd *#n)**X#na"B!(%*b" jf*("BffJhb abB&(b"!"d("bIf*#nf&ib #nJ"(hf"e"*" "* h&H&ZH#n&#n"$J)!""(!(#nJH( *#n()*()"( b(Z)`& &H j*&"hh""((f"#n%bb)h* $""bARSICTDC4SDE4Q4Pı4% Q5QD$QE),ITKDAM)CQQQ)S0U4PI5E+30T %H0TK4TTK1A$I2%RDTTL$DTR0EQTQT0QTIQDTTMQK0Q,A0ҥ4DTQ!$MSPEM0SA!!EUQ,MQPEKMLTK%A@TERPSDIITDDLHDPI IL35QDE2MD-STSALL30E!Q,S@PQM0EMCE HR%$RARM,UQ-DL$)T0U0EDTLA4REDT-EEQA4M!,E@PS@čDDESD@DPM1U,S@14 QQ,HD4 1DIM$AUDHRTT1DQP4ML1E4T0STUA5QRCEPH)#X2PTL$MA%DLDAS5%RB 2EDIDP )04MTPAAELI ')Ȫ4ԬMD441PDEDEM(L 2PDA%LA1 CESKMDDPU$1%LTRQKRU-SATUE-U4LQDU$UM CIRDDDE-4ME%ML4LQR44MH0YV*j^8MDD 25(PJLQAAII#4LA#XAD4 $UHD L$QKCUI35AE@IAL4C#XPCDJCMI,AIAQ4T1EE$ṮEP$0A!T%EE DTD 4RUUJR$H@K K1UT1QRPU#Y RDPJD(@Hi)h(h&(iJ#nV* ifjJh`#n$҄²E3RULU1IEP%TEDTQS@PM%3L̥#YH#Y(55LDC4 EQSADI254S251IEḎU2TULM%T0ԴPCE15HDUE0AUL)1LRQ$T05DDMBEJPKQHDHQA4BQKE5UD43TM4 I1LC T%!4DQJЅ#n 4-S4BPRS4T%1$Q!)M,#YAUBJ D!@R  D%+%#B@$1,C$PQ-#X4Q@$M0 @0DHD5D LT45MKIL,RQ0TBPMU!$M#AED0IDP!H4-RQ#CADBBRPҔ%+CU0 U-U LIL4JU)3UAKE%%Dą1Q1MT QT%)4HЄE- JHPMQDQEDL+CU T M$2HL3@*1DHD,%T#3M-1$Q#XD, EBERE4EPUHLDM!UIE#YIUMDQTD%DS-T@SDDPİEPQ1P1IAAPPMTE#Y,Q$ 30HTQU1S EQBPUԤISDIPULMMU1#PI3EMPQTU#YKRQELBĴRQ45M DE#ACDA1I4 3UPЕUTIK5@A,ED LTUU4QKK0DM#YTM4@PMCJDR#YIHAKLRJ5PQQPSRPELRPI0THEPCBQSHE@%2QJLU%TE1PLSD3DD,EUAHA$1SLDT,H!$C,KC@MQDE1)12#DSPE5C0!$S@Q@D1RAUED%R5%#Y4#LԕEKLDK0#Y45HD#YUTM!AKUPPSIC,@AR!BQER#D@RSTC REEDBUP#YS3DUID35Q54! ELQ @IE%$U4DDD%4SJ@QB*HAU +K2QE IUC3@%5UU@DILAE 2 HQH5D)1@sf!)& hZHV*DhX"%("VFHh#n( i &ihHh(V $"&f*(J@#nH$&RhHjHBb#n)R@a"JiRTJZbP""!J(h"j&(&"f)I bF$J*@)#n`BedJB&h !J(Y #n#nHJi(j&JX"#Xd"@@ &@*#nI &)iiU#XF &("覚*#nZI$JVh(*"IhZU&AJi *abT$%d"(`#nJJBB`&f&Xjh"&"b)#n)Ba(J.PqrAS?!/T?7mDST?ITWvx}í3r,?kzI'T3[!Ltj#n}z#`|'4ז?mǩ{Bb#niTꢴZ㍔̈́C~&< AfhIOag:grgTrr#Y]?50q;)%\FwU_֫.9Nf4kRgГ,Ou}I< N>:`U-?˥Wʛ%1>to3@y#QLWU4 9-M3"T)N؊) &*ۗț$DGY- `2%P((kT'E?Ə[9k>uVYvmthJW*0nr#naf}zfұ@q$;|6bSNmn8ΰ7U`!85yϛh٘{wLkAf} >-9fz}F Ą*^9DELEl0P;8#YǸF9ӻF( [L|vCZumoGZҖ #rOuC2o(dC+&B(xu6C }!jowmP䆈^(X#nL4l_f.~AQ/J(0Ϋ鬳=!P4tK:sŇ=%gk'>#niB $ C{]:Ƞ̙ HC=*(;QC4U$D#nڦx'#X6RE=dPu4xse`8F֚;YLJw`[[G*>Ïgrz隅SL=&ҡ|Rpx]tA;P;ߏupd3T%(vhUR uJ^l>]KYE S$G[Z#nEi)$+CK;s˲9;6~5:`D98樷xXù1J30՜snRcuFo6yQ9nߛz\.T+˒cf-֢yǶ\-7;IMQaU%ʚmg_w6ikf_ g"G|Bs#YYF7˔<"D9t_l\hkqsbX<9ݓ,8ANoy>/bPAeݜoׄGPA(!ܷFPF]*">CT، nSq$:2K:3V>h]LX#Y·YсT(Bٖ+B7+]aXDa!2n\t%+u=KF^r_&!Ҕf[DNK#nzՂ@*4L<&۠qtWX*N+("|ز"0S.pR6pQ_3a'z4`#n?],xpw>@||!.QI#Y=P4w~{Gq)>y=bua#n};s:2^j"?#n951?֏O{:vζ+5_lFa7;Ao]zo"Բ/* /_,!de*P|ou.hF#n4TтMn,Teg2Ye+jc6k#nVt;D zMp#np,u?sDM&YTFJsRbљ](&&lKC,0î%/}|(_'ư~>vzmBFn*Ǭ#Y6 3놗M1(ʰP^{2_=ϓ&2I_n1h0 /xu?(d$yq8(+ Dݙe0;xFfuc6#YmUeа'!Y#Y.Z:dU`CBuBEJ| iaRQ֪fe7Ϙ8,De c90"2V$_ً0C#\yPů]~(xC{2zDgI'~ex}U/9`>%xHae5!8N=aB(ƤŽ$J4'L}+?Is/ yD$lߏ=N/O\l=k JA#n7*_{`ӷo>sC>J|rPod&8Nªpޕ<'EP3ɼ2lV1nS"-<SqբA~*4!3:Iϖk K8;BK9R ٓdF0X\.5МK*pF"tqbzp~g?K;\J$Bxx%G'?;oV/SU.Ιozrf!HS*cS#Y/ 9#n_^PçOyQinPB.D x=FfC5s_7|H>Ugi:ec6qQ32tJw$cv4$#n^׷,g- W3v[ci`XMbPV"zzrIoZYUTdu0u38̉YKy`&1m!#Yq|V#XAȡ2_tKz|}.-lyo{[P\.ƺŒĕT(8íM%[Xg,Xn#YG~m4+\YӦLˉܚѩ_)AqBP&=X7mH`x\w} {R2tb]L*ԦD`OYOƳ,n]'#YdvԐ݋=/ۦl,ȵ)gokn^\$HÜ޾{7rr_\"ȡ HT$͏io_:[ܦа@\Ҿ%XG\,Ngu=qp3FV#Xp'XoMH.|08)ʥKzAՠ h a?ߨ{ߗ:2;E7ˆ/#nPzMޒow 8y.#Y|z5vN”|*Wg5~~`ʝ!]ʰ2#Yج">dXvoBY똎ޜe;a,n"&i#Y{!lZ2GTU#X$D( 0¡9]UQŝ[q6QuquH1~#XSh1Tfvw8GCY ,Y^ONȽϬFQOiӬ'jxmpAC!N\7a@CzKF H$*-.jL2"Ky?D_愿y"EH6X>H6~sO=IaFREFPxGlc(-Yo'{2#XwR3#XْcaX)l-zFi}8vKݾ "ld`)xS$b$ k_Zl2ɔsI<9p +UNV\ 4^(+΅e\]A#Xo}ly s;2%&URL} \QGunR®e/̸d.heE"Htۚx i7a|PxGFf஡F|D{(g@5#n+[C, oГID'H:(2 "#n蒑QOcP֨$(%0vO'}0wNMКzMY2#X;WNr9n m>mX$c}RAIl$ϥvd2zlH+E#n7T*Z&ƫ$I&p}:& Ż@J{EJXYXǽhV,0'maKm\:}ڕeno'|yv8߇&}ClvMϊFc7FNp.YCE;䀖KKթOڍsѢ_6&hOOdX U~|U^$BcX퐀,A:bkgd8MjB'>6>8dwU;R248dUĞ2>w?/~ͼjOWM aICذi8QMIP$DiiR+y/Rjbnsq%~`u#X5|ɖkTkySJk+0μ(:kWypx"]TV!MSr5i!~W_Bxsog&61\vAX?cKBq܏ fd2sE%rR!AïJAWTX/n{)e:wsofF2.7FN4(@7>7B]P;5D#Yqkt®W榲t~623Fqۊ8b* THBpt/#nt]:ϙ9mz0!cQÜiP8ko ;:yõp7![qryxrEwPL++E[̯ ;2~Gh;,7V#YՓI%JI痮6[v~"`9=P B(am0C#YZ[j1O‰)"6puhrSarŹVSUngO0(D|j>F5ax0q;q#66d2E?YnOJε+4yC#Yh3OL:gLє\ s#n!d:;#nB`Po)ɸf@;%%0ӠdU#NBLk%V#9ؠDB^haYN'$r|S%h0T$Կ%99цf|SQ6FPSCĶUiɆd{S6:öW/Vpg_r8IQq6yeDP"`p\̬@(xh?bndpeN|Mj/%{NsX+qTHmTK0"DJ宕P0Ņ/}aTdq i6 #mvޞ36QvˈO`2Lto&Eđl)F(;>R}K33h6,PgEK7o)›M+G ;\bxv˷Bnf;W'(GG,ɮ&k|e1bCh1R*f"$'Tme% 4qY9wM:C:(3(ɵmCשU~g'N?}bTȘi o/SF܊2*Ņj5wG*BC(bFqaj/_m®S7\VJ6ֻШϠ:vl?̇f #EVpii͹d德w>׺,PT}Hb2_F0/Ay~ 2iwnPE#nKF6,#X~X0q43PU=pf[R,G=*̀QS#Y%$b.PH0;OHkGX4E4diͫ׭5Zs{oDcA.K(.Z\>jP_^~8Ebz*`Q9M椺_WP^MA#XI;{e%` '[kUMDz (0tW#nvgaC5}-9v_H:uDLJ#02}҈j/ێrW: P#-S#so w>kqL$9vf2t[!kbaoш|`~99zwi|2qyw?9M'qXʑJgJjwF#Y/Jg%Av: J[) G3Ӓ#nxPXpLH#ngeqr>ټ1ޅ86#Y$jK}Uq59;b+neUZp]:˜K>mTBԨ#n?cs߈.jܷ2#YMr,:haӱm0>h^Y6uy RCr}8{qB݀h+>j@%-;AHf7!G嬘 Mn*#n2!DlX2T[4~m+C燉<y vFM?#hzhuq,ɿpL#X9Om Qd`hgP$c`;{gŋ !9~q?Oz%ד)<(ŌA` #n|O傹 ѓ|(x%"v!H30ŧ֋{{]~C֞IɧչAhʃ m,XX,/:~ƿC(=#C(y:^ϧhj$@9 Z8cs]Ai6hxQx Glh((BK#n=,2hr9}^L4m2@ERrB-261SO@h6 cHVvs(\hk.Z!2zXBùdq-Ddør("c DATq ny?On/->%9m-#n55JIk]iȤtsZ-[,V#Y4۹_o(nqmi|#Y>UHS`rB `n,dʨ#j1ݺûF62ab;u*GHV #n#YI Hz*:K#XHHqs*V&vn3Q~/e&[umXyHv{h4yfk,[Y B*9T2UI*{/GOAplNQQ#XM#X~xuU(~+2$!NtEOR_R$1oǻ1XQ~6"锯LF:A{}#Y1?4A[]߆7?'Bjq|l{lϷCG'nRhj}S}p3L] a鸂J^{lv^X1O6#n9O?* u#n$}#493_>B՟q zPOsSa85)-#VR_ #{c>.<Oz`'ah#YC?7ģ%#nS:0ap%A 1#X#qNrJq)2q% *<`aF΍Y#n~Kl]/1#XbҨ35ҽsǓs\^jO6`]#Y9.:8Hg}#vYx#XuoIsa͕r,"h?X#Ye(#nZeHV:"a#LeJ|ܖj}6ut7aXvd%jL'U-coOwb? >}_2,fq rHmGuLeQg$V} WhQުgAzIC"@ ~ {5?aӦԑ6(>ͻNb^-\pWD`kTII,J`=ّ\g[ێpii*9I C)a!$HyH`u#Y'DДBQ!9R!u۴ATJV*#nd#Y`)5Nwf=h@s)X̫E\pA9ijPݹfK>#Y[^JĿa(XmpyGNi#Y8$Cs~%,2˃Ì%$l#X8卓<7|#YL^#_Dž3EdnNz >Ğ;g tf Hǯcf`TVN:C"Ph60:]0X(m6)ɭ'VnɩS:)(ly4$y`#tl"A]o6?~5[#Y4ch>/F1b0<#Y İ*mw&ҵR z%"b$X٩V!7tfݖ(G:80v,iފH3bEiQpOH46D"ΛFYEY@)2:iu5jQvaBdvgjg6tu1{f#noG#YD##YRA1\D&j;4!5i2蜱#n87LR !! p`HDǫ%z#YwXݲy¾N\<8Ҭƻ2rΔ/?AQ9'W_0t^p`.u"5 * tahV(Ɏiu\o^Dǥh҄q!d BE^ x_9#Xl?-O=:Xxv0,iv+r܎3U<8/hln{ׇ>Bi.KԡjȵCt\XD,TmrqI<nlZ\#nQ#Y0vND퍠ݵZ0ŧj#綃 GUEA"ES(p^lfTsMc3#*U0<%$YPUϟ#XQFAT&JwoQ{H~#X~>\􍞞o7֋w|ZOjkf*r^Ӿ(7M~a6ZyS&;t.Trj<=.>p-#YSvFɈv/S`5=?*jLI%XA)H*.Knf.[F ?Zt*6@?6>jžҟ4aD(85]m~@o~8I 0wѫ?Ll~QPt>#nv1osؕX .Jh>$ɱɅ/ğ&>=#XDz8S*{@З4j,ӯ0xm)ǘ{J#Y: :N[hx+!#XNL<'HdALcBvŴ]F`G@nK"4ZԘ~b=eezܠ![s"nOvIe݀h_ɿI mV5ԃh}](mcI6Kd?õ#X?%IH U#n*#Y) H. <$p9.^U 8Tbq2,d#YkU}auF*&7hW,FP#X>RD[->h?{fYF#nF+2~x]Bd~Y~wZe,sł$:ں;YC.0rĘQCͩJB]17u@(P ѡWc>wR!hH7#n]$'qi~vO#X1H3ƈ`zuA1)+/4ijp3|8) @WP s 76Ō xK9;#Yo+VY-GNtvsKN\20ΰ3MTLoTkwPPIΕ7"/) ' <6p~4GT$dmf]!% o4poʛEsv=BM-}L}lhaD'V=3ׯN3W#YّJj ^G1)0F23U1#Y8on_aG_ Lrͳ򾣩c{^$$ r7܄^c93'wE  2_Me˱= ,a(mf˦+et<3Xj3XkMkci$C5:n7ê¢go1ַ˔jؕ'x,1\>M\c֊"Io7̻ͷ&3p̎,* p/5=^%d%#YeQ=#nol̢;X޴Aj$CK3lG^J2#YA*m9#Y>>0@FR[\0s}]嬎qڶFZ(xlOiU뿣#wyw)> (#X T[\gFB]ꘀR)f5EY35#e`\`k8ˈC8 Ʀ@sTC{ᅩӛ5._]iں*xCd13:iR3 gU.! ;vF86e<.$ cb;;-הnq (1𐔧J#YtWLW~M:<3B2ojmE_L`l)(N<}h\6**5=K}ԫ9η9 tθ}u$E&F8c a#Xi|e'GK.yɜ{U|H0f/O;2ZvUFM#nF+}3R0K($Xx$ &#Y~Qf~ʩ+~[fZ;,$&\dN'diƑ<'1O9~*0x;2Z1vdku߇--9̩;KAA7'iyDa4k.Rfq~}ax!Te.Q"QbvJoe{&1bxʠ]JĊ模t^圄fv4lӘJ2ن_Ӑ.ϋah @r H%05.tSeevGp{#X.ey,Q E_}:BTLSN3]*nH-uq&h Гq`G pk"fec!sNMLJk"Za٣xP"^?I0^ExY~zBy_|G=3O_|I\hA72'l0<#n%mD`Ju%#nQ=y(|2E5t۴~Fq'QԵ끸&cH,3m`/ {`Kwj:.>Hq[ŇJ#XCqUsUC}Og=~f6Q-LHl?!N}5aASBs'#X3Xd#YNO]ut)1Qw6.j>c/H.Em(Y(΂ tUyzEaL ML^of'9GFh5[,PE7'Wo;DcUFUaAώ!!b(( Fk9ʖ#X}5#Y , .ڀ&)9}m{\r<0HLQ?}Gl5 ,ө֡hfF2%@P78!N|,]ȅʒf&xJ"On;oW]7;0 f>(pwR0 ~/ka<,0 Y5"'b$Q [s$}>*F#!^Z& Gu=#n+(I;?i #X. )ΉF PnɍESZ|6/G)tg^*U#Y-ozŽ _H  $rbZ@(^?wW?ZN>Hn[vot89ޢ_F( ,xDfMj!RD/wH#n.#Xbn'A3)vA0>c=㐽ÊHu#YYkw&b{誘#QV+ eLILY\zCpb{C2#`Ez/#YJ#X / 7/;]gHR(Z(*Jq%z'/@9r2o^fDP~,#Y=#X*LwJH"$Һ:Ă^c.ϡ1-+HiPϋ󿐉WӟqfTn]L3#Ym #XMx1h lLE|}_6k19!g$-tHZ~",Q__&}6cJ0B#X';}~?ֵ_|tf?#XG\#XāDڋAQ#0`%C1';UMtb&l]m~_TRsF[RG x_#X}BW=2WN_ߧA"S$\c32(9As^k9EPBilm<9h#X8~#n";DN􈢘(y|"FbļM[Y4'uL>;,HiTQ"~#X|#XT1h7SU QqI/&B#+e0:- ꩘A~>n)G1ҍFnJQJs @LnqÆu" 4&pͱ^A "f.RA r^ʟcj"]k#Ywiͳ\m9#XB'O Dng@@Mh(&vj;>Y;sb^nqo%l4Ɔ̱"0PrI"7S|s8G?S=_u3@vz Ե2r1,Hރ~~/`+4.HZo.pse92ܷM+<$zI*Ѷa rє*]r^a2~^*Ҽľ>Xh5}uXu;"QJy&r<!N`{s>Gh |g4lGT`@tX6n;4$Q܎tҫyU&-o]*#nFqUJ9^WMqShkBo g)\B#s5R&9V#Y[58U,䦔fRS;8[ Fljm6%G D޲fjssNqTT5h!S,tAΡ𕹭P>k0w  ݷЩUǗW#nܣ9>vPp<<#XlO;SSN1w׌EۀzyX}c/.R\oHmd?qNub\}31N*Qo\#-Ǯ>'rk)pL'ξ]{xy/. X!C u"G1>|ÐЎ_'f2\/#X7O(_,ЇͻA0.BZ[ޘs ۇ&E#qvGY\jNzp10D#ne: |鍥P_l[CO ճCbb7(?GΡ>|ec?a{~LƱ֨Ҭ]UTWY5ui:mMc#Yt@?BtcBD JrL+6fLF:ϝ<#nMzhM$~SםP"*oƟǖiӤ#n>az:-N:'nrDzy]?L! _}#s=ɛ  R/("i3(Sg6 ~S4Ӌ<NX07&qYgrs~VOb#XQ#X9n#IPngy~qz>3%1C!O7Px&zZ\V]MK28ӯOP3{b…s#ĩl0vLhm2<4<=i[VBF8jiֵV*k5k$ŸpMf}K ri˟Z*MRF=٫&$Na|> QFAh >X~Qk0=aǢ?gt#Xq#Yߵf7梥-Lߢ<χ@*+HKMbz$:C`r f¢vfZxqI֞KМFĜ/bhDAL2:/w]d$EOBUwf'Y9 wWQjRșa#yИmAcx.2#Y21(MbWy#X#YH*$^\fB#XLuzA{xLhޫ}b22i0PU؄OE56k51oϧ-OxcFonE vgl  aFOܴx̚,֜Lr#XmrjF$)%#n[z#nf}:9Hi3tQ]? ZD.:;<hx#ng >˖X*HzUtj^#ܰtyFsIZufo*Yv#nI.`Z.2$Cڿrp>PCߠLm}F6RB\z [;=,zU1|_ۊ"FK T-.A:YvkVO7SJ.]NϐyT/0te'2oEtz)?i!2;K${eh+LY59 T;2+Q]҅Ba[ࠇPX% N :}8O>l#pU*:L}exS#YVĴesc2o:q#nJK JuV+gc"2m晨 )A: JLxrɾSF@vi>GAvD >C yC^xK#Ysf$`, :0kU0QĝB/)f:`0D!k;0RNgI73 i͸E)l#Y$C-_ .1@%pD oJ=);JݷC%R ),n#wN!/5*Ya& cMA󇏅G}j>3_i&ewT@bUQ~?kco H0ތU@wE0=v$IT8]˲QHF'=PgѕHq^T/:3|[JRgl@Zucc.cܿzܢu6q%gYگ|b#XٍaQYD)5;pyka0<t`+ƍ?Z9qGoVkYپR\۫diI(Ty4lp7#n 'ݴ/x0~܌}x]!|up\hdV:h"6QdQŷ(gePYǓT=}{`F.Qa{\h+Q8X>9Ks$.&LDOs\6cһez:r#Yna3$4$Y80;JkFY P4#YΗ_?E.AzKygF#nCV%!rx4Eh"%yx)yAu-~2u&t닯9g޴7n80Z!"%c1kXHV#Y~ Fnٍ΄1N(M{Lc<@s*]RȌ/UUUs^]_IWw1 #YB% rT^P.\f0<6ǚF@I-D* bu2BҠ#nI8n1IDů=#X04okEƚs)S{B1pB#nγTU_oeԝiJ$!r7Zl9P-T#X!n'69m#YT!ժ7rʘ}N`,*rAskp5iZ#AA3!,I淔~s 횋#n5UsaQeEwJ#XUe.f2XYW "b퉽ݶ†G5HFJ?)ۍoqSJ2b6eA+jvzCN ދ= -H'&J-V* P{<a!o) 1JHJ [XgH~#=h#q>T@8mL)\#Y:櫃^a|#X=p`'QBwOu;'3#Y:VD^JBE(#YvA-FT%RAFlV'd]"'XUVLW<﷝{]բ䞤;qAh!}]֫Y{sB,P2e$Z)+ xY8u>?b֫BxO PKoЪgg5F<2 v'('EpHⶱU1K`/[x!k #Y`*M-&QM`u"LU@H׶y+7(8#X{Mf-#X` t>{lP0V_W "'(l1Ԗz$(s-ٖQ*e }C.u ְ"?+j!H(έU"]1Q)n#Y@#R|B<n=FVbkg#n"H'sPm(DGzNEKê u'"^Bʣ.7nT4ԁU8WuSjR,A-=uP$"S8j0g#w#Y6^<0Ro:I̖N)+|_4r?^f 㗕OtsB#Y8.EZ8NFGMcYRt+f%<|97 TA='] HKesB'7}>qfEvץ>_-䀡Tq-fAIN|_G/X<{wÓ{ 9SGnw]BkF80mE+aC'Ob{duk*gG Ź\WRj7PsQM#YXiip=ӝŚrj#Y A#YHf ^x}81s#Y={_,2yF3uģ'+qPcx  C` d/j;8#a{`}nql-#Yy>)xx+`e2Ԕ@MYc:9>5BVߊJ־cig;0WIЙ!$(~}qWʝPzRI`2)h/}j{Jm0 ) xN|.8["ݮz96t#Gb+u.BϤz(vY6YE5;#YMg hp`E'?r-%Ta k#7=[})|}(o{; .gӒ #Yy)j<#YA ʂ`5&;"bʬ#n0v)Ncg1xS6'_>ۖbueQd"ݯ0ok1y?fJϺSNXP0EՀGo =VX0YNk lq7Ѱ/ӦhOu3Qp y 5O.رMBHKyt<4#@e#Y.#n̨IMi:8(F({1i ڀۇ~rCBeWN=BuSҴa,'أjl"~$#r$ Nc2aYm#YES5#nL_tT׫o7Ή¯<&FV9azbeYJNǹ0]0y9EΞ#na۲ШJ:":r9V#7x9yu3a|=#yM/UjW18d0ˢZ2;(UU5a']fKTyvp0e(ݹGN7:D@^.eTl 3qq0m]MȇϚu/J Raљ[}HnL?muF09`8x*Bp3Gdk82?io 8!xT,-uc jp@5 X%A&WׂO,%p5tܿ#nBXJzYaiQw(y6j/m#Xb )#XCXވGW@YTT[FV>||e\Lz}feWѢ*Iԃo^(ɖGЅ¨F*4SgGiԼDw#Y@{x 27 G]f.wFWN#nF>KRETP&tM3:sw9^^ocF8$@mCg< [wY$w#YC.ԏ#JdD7(iA#Y PKQltk넔g4&eCy晜iC)(J^lӔ-#XB1eI, ]맿663\7y/,01݅1i)"ϴ&Bقp3;Lώ%>#YQgLn5"=ݷ`t3g;g$LZ#U9l7>4\BUvɝ"-BB3ӻ8|erΡ\4p5bQoQcSɴf06!7(FO'}~}#K*[%gC4-mXfZ2'QAi9[Ń2\ ZA69 U*v~㡰Ё!to:-I&xCٜLCKryU16!9vdոbh .لǥ-9n]VF6LpL´˓c9w#Y&pw9*dm7. є2 H&# HuʙrҟΌwM'/Geٲsݘ^"aջzA^:~УǿZΆ8yGZ,X`]HL#XigqdN@rR6'~WwCʓHh:wAFDn [i]hV#c,֭#+Wqtm8x;JOyKQR#KnAsL@]m4ar<åP&GWM&Kl#XoJј$1CiLs) Jr޴<P-GɄL|wiô,+1IvX=z;W_UU]]#Y(vj@FPTKM3 鷣@/Imr>EK*iMB]Y %VX1:ܤw]IXr[s|0z/Lwy |PQ;:<0h'&sBIv),#GA?-\۵"7q1b"1#XtV0@D?pYn8BCq7p1S{#nΫg #Y#n-^12D{(b )TNx -X>\% n_xZz0$#Y~O~FƟ#n#nf hݜ7pjpc02(Ў?H#X .ۄL2V5RLǧEQ9Pdz-ⶠW&~sݏO3Ca@yԗe)B}]"ؾq[S|#nۢP[ʸK# Íc-(~798>mm&&;=qKoa*)z0G&9L L [Ag>DOwpI.3.@k B(k_R^kULR?hvB-:ǾpGE=@g>CU)C,?ݛ[LerSUE7,m-|,t3| vBU#XVTx;:-;?9e}-53;z}x -xd _cp:F* d:1AP{#X7[Jڵ/T9i4}aaI|b^;qMs4O7! iL%ɒt`pl~׌qFlqT`+{QV`wN">YofSe'~ZF8|=_I#n1Gn~RzrBMg*^IH]#n1B8w3= "qܪ[{{ qB艊<;:#Y(M+oextLw#Y y;V-̧ѧ8ehzE*XX,1nҲ&,C8"+6e5fq D]lXLlc6nX{Nb9eiвpELj#t^YuTQHT麧6aÚjMĵ{`^0Ⴥ돔bV5[/=s(>uׂ#nx-xIЦ}TLңԹ4U#XDJ~,3 dӾ G}p!ziĴpoΔOx0)_M_}H[vt}1=C42wuMͿ!.A~-`-g7Ǒn'#XqOi:'z|5S~>ν U-/k2fJH%$8xq G"~C5.>g K{5~ߩO8jl8@#Y2o80OinG<Fvx2n4C7F.d#X`M1'\85"]*;M0/`x$vDF*32%6q77<\s)#YyYt:P6&uy.I^zv&{D+k[OQťeQ:}ϝB(O#nFWlJ('/imK#Y,$D*񅶔 TV)7#n#YqQ)#!Kz+wej8,JqwkOz?SFz#m,#YCFhVckYMo႒0陽by)gK96Us:x5((hp/{ahS5"@0"[ 3a r$5y2wC`a sPj eFo #EIIiC(ǔKcJcqBbw@#Xɔ.6䟣#7> 47{3y|gBK{ތ#v{Q-\jԩpi)8D}RNd+ fS>(TbVn vZͤU+l#Y-w*)a\w`0(8HwidTsGQv7?V!71Wibi 7ÚY51*qȆ n?)8%Fvl5 ߘ彅~Bsp9O#n>ɿ~|(ri5Vt@c_7xK!Qrv,;c1iS/xu 8;u 0ߊ L%948P\Qߪ⒈nAiVUX-ջNXe'8#XbLHˏ&_qh3%#ysFBŠ؟8w;U.Ptr߇ ,w5PRR#G9&Vo.։q-pc201l6xn?#YN"5xxp@p)Fqϟo^j#Y/nd>7oeG|1_k|pݒHAB:}Z&Tc1 AIKM)q۱VTN?DV#Y9-&2\AdվKp)qL_ؘ0(7R2~P%٧T]i]׫;-TXݖK !6Z'4ULP%(4{`-ZT%%VH]A#Y$y**O;``Q6KX^_T\Z72#Yœ,ePZ#na,RjȠ#XelPVt0#n$ޒx7XN4WDs.]>n$=6)؁!z<@d#n&ޒՄ6NHs_#nQVb!D*z0л#n ]Ô*6Q̰fYܢkQvJy7",,MYJLG@`r$&bq<^Ng y|4Lb9np.P#Y@ 1W_jo-M_ǐy=clS_ByA)\iL6Oy\~pQV軩V4`po ,;o@f+aIψf;A<|is$s\ 6*#X-NA>T߫~ MQIR|PYnvNO)3/^FHNjjJ%'J?=ԨQ=gwNgoNͯV`fG& Qx,wL9chfC#nn*FU(;ch[{w~gW$!F3dGh} ڻN+cATȝ3t}Ld6"oɹSu$j.`F6@m6MCb/#X#XYdd R00LJ#nEyJ\4D]%@#XaY᫼JQc48u۟х05s? = 9i`š8$SQ ޲#D2=4toxg钎kXZ|b7]}LW7y"b-d˕0qCƺ!ai|}ZkhK9ԙ1/[un/#X$Zk,D?%b[sĘNZBq#nZ&]/Nx;#L2uMUJr(: }V3viS90'EqY臾cHܪ%~V`zuY)q5Z֧8z ͹w q9_-mqҡnRsdAx \1Z+yi_q|n؞G7<kD,0qWkIA]Epf>19GrtCܧ'HBZZj-K'+WJrSzf55 꺇̜#.t;k7#({#~o9Mm'Ax|ӌu\G3diu/NY#0sػiY9(7x=ດLM˝~eL*UZb.Nq#nSՍu^@>7ʹ9v?꣕9ߜZ5VqYU¬aBGnۜ}0Y ( gF:EM$h*/eZ] !l/YZ ^,W+NpW-)ӭ "R1gqqLF1#Yk|uzPs~2t>#Yx٥x9>ߕی̈֯&1hHt0zZm•T?ҟdLl|6PaRnr\F/)\9=eai4ZHgԘΑJ6uW^ۇ5qo3{˧#9ŒbG͏懏ɬ^=oF<ׇ'.yC^&0a9YF> 1P#nF*F4+ž];w)uG?(i!tE7tF"0xHByXҾ?3)4l.$9CM/"Ui@CZ#n^$1Rk`JNBvB$#k%״^@H5ܘi1H 8wf+~kj1,A#YL#nH&!ĝqQ\,4BF *8tQe.S,ي3hJu7+z%gK.1')hD@1ߍ/_>汏O8 0epIcue3Xd#nY(BS,Xa#YImq$[/s'# /)V.{]ڭҠTkMD<ͰC\3)+fNA^ruD sB;dLq.ML ߍ%o,IJ[K'&9ud=e#XnpPr]\,4 g_E#Y'3Q\.2'v¿j?f|&>/Cx7Gڭ༚^vhyhg 9i;UYm|'6 ZTH"\m&f!x]zVMC I `#X}b#(r4& xl?#Yo{Uű板ÇZ5:. AR]D@."1$?( ݩ,#XWba%*Nwt%4Ofy;Rh՘_v(_^|zK5EqN\o3g |k7[YO1EF V|5"HU|#Y]csCx7뻩C{OT7JG{.#Yf;WuM#n?$yW<97o`:Lg4}H]ױG 6YXg)C4DE&RWܛbAL$-Oanf] f@NnWtT%3H/ϖIHs߷_qRF s_2  7x[e'q{V9 &d6sR+XwRoas^\bXJ@2qu}g[X`#n:$Ta#Ys{/BiF2ei+ILRpSO?f8,k38ZMJ4lqhȒ50xV6ZqF] vEI#fqW#n5!O}2kB+("~wně zeBiS-39'NX/Ҕ#Y!4ZF&AMq?N(a#nH?q) 6s9d@ȵh4 na㋽<1 6SїلH-Uso}W Qpx-d44kE\*辑-Q9>(/MM#n?Xb128ڕYtV*˴iR*R;hmz`ܵAAZo&cfX#ۘVriHF9Z[ԇ-X1vVlrⅻD4I#nї*#Y(׫G>ܨNO\Oqaŵ_TCu+z'=;8F?F 3H|bk˧oN;@qƭڵc=)#X#|( uB#}G_^,_:@eNZ"}xXB%"J"hR4 Ȃe$`=|cqp4;3<˰1AϦu{#Y^vi{T$6I=I#X퐊|XU9wnOaM5Rt@%2QtKGߧGpQn(E AJU#Tb"ć!/ T<|#Fs|;<+XU،NANZ!;z|5(߸FR:*ْB1sKSp#nu}Ɵ еQ&K3~%/R%XvQeqq]N!1P!S#F1oX5܎y,-=! 0b] Z ņ>AU?UqxH8/1%tv"'Vp%f@ƛ:VJ>]LT,9sPG5C,#>]?[w#oӯV?֙k=;A7-kь`.*viQ֕$syM=_Pe8{?~]6UN{4>1HP,7Etxv1qgN‰ 7k"o`ݺj9Svſ#kƟO';!3n*O#B#Yc9mUHgPQPB[c?/20"Zڏ>S{xyaJ>}i4a"[[$5uż{:z]A"0)BK(#X\0Z#X}ΐ6rTР(!H@U}#Yl4 x2w@t/x_<-HDԧ}:Jx4@!׭?H0/`#X]tz{mQX7_:v"No\g25S‚꟟r%--#nڵHXePMzP JQ/"XmNˡv(0xoWY/xoA|&B`_6*ĀRa޻5pi?`dS>6MQe|_NǁN##Jq_KIdRT9>#Y'Yܳ=P }H2Obw'u'4Jm̆+J4՗#X)0 胣hrOQχ8z~/Ug~Ξhm0_Wn;fM>wRhkߍ0.Q> w4M/Lf;zJ=1":kWg5>ve/:zGH HS^ru3:9Yash !0jB"[&MjJMI#dvNdF#Xݡ̆Bx,N MG+E TD4U@A5Tb F,W~gG'yI?pd:=,?ށ9)IJG?͜Il_&"P=w'yG;:Y$}O;#nN#XP jW0;~vOw\5oĚ\vO 4װ}~mk|yyy~X/u×>CG3r#ْ*S^װxO-$_?|[OiRó}X#n"F|qQt-{qq}J+B_HOTߩ[^=Ow+aˆyڋXퟟQ˜y>nݗѬ|yaxj>DQJ#՗ɖ'7u'\8wm:mdYSd*@~iD>]v>]ث0’(\4w݂omqy{:ȇ~:#Y)D0:DzS?6~M:4Qޜ~j.-]OFݹa)~L]ϧ~|&Rr:#Yݟo|q/-8ѮPO!/F}6\kvp9, ]ƣpǗ,Z0o&l1o.Z?wmכ~>kFӴy䨰v;ۏѶ#X^/Kt`z9]}Ki!dZO1.b#2͂S7])l5[.0Ɂwwv{&HI[ }C]0wp)ց̅q._vf͙{ݿ_bTn}_tk!t7x?Zizu8 q??3H~?u>#Oi|rHWzna:Ud/WH`a40= <8[DPxR>B}~A]PZ@mc͌o8,_Mkm~άD|_S#nxW~X3|x1tHhWCO&%^|>|#Y?=f NaGll*;^0n BsauWlGo5Ѷw2cg#Yp3=7长Z<߷K)ՄJm,!~m'?w7>L3B4f))#Y4w0B8?#ݳ!/?Us/h[׵g>#'e;dR'p϶fZ<>D(hmx`AQ-ӈA?]z>|?#Y{n#Ytf"4=.~̻"/l2<z.bQѳ GP8TFOG~O@##nHO7.~Њ#Xp$?mB|I?mȗ'X|rS}Gy=+2_h|7~F{N۱Ͻ)뚝#XK5D1R}nnyg!Yj1aaa`L ut(2=cq s +p#XNRbaTʋݳ6G 8#YwffgxLvsjߢiSp])qW~ 3E- "pslKoNq>HGH{_[P/XEM3©7oT׭\i\t>ah-3?G<S]" a\ciHz9=z1w2]?c|:śp/@6ɿaL,n:%*ShosE=.^HR -*Y8a8ݭoRu`|7 oPFѧ'r(ܩ8 1;jUξ]3l>c2BSgjk0=#X0翄*:xGY9GBAI$usp5 V\a>ƈm5:x0^YGDGJΰy9MX͹yn_y>>ȍ٪s5nܧjxB݈}xK#nRze!MuCi0aA +ğa[C#|Ox9f8awvNp8Ps޸wIM;U#nb6󟟣o\F<|s=a)V]g{A`o=*RØ\@[|&ݒ^/֓ HN*9"ϙ|cA}eUd:s16h؜w<6 D\ٖ(bITє&#ni1\9p1΀8.VҘ*$I8Xb64Lij<uqޞt 'h#Y1E#_HWM,l|F.-r?"%U QjV VBçC.}Y t&?!"iJ{2]~c'( ӅE^7$N@AHI0eq/`#X4BC@Fp4@( (A#XL}zw5q5t^{>ח'ָ)Id >9n8wq_ʽxKW0x~q'VH-!EԧDJ6&UTZD*kGЎDU%t+hchPh;#n8qWidADPbw3D'tSHչ׉Ew̜2bKFޖ!!\,`ʮ3#nmbƆWY ,w1Zƈj38D#n>lYF&g[d LN,C("\>6J8pQfI!s7B8g#T^q0$C٣]n׎~>vs3;87/O/7Ňe8c%;rQ;ڽG.JRp* #n! pȰnҐqdqX\.D/z=EE '2:L]QͶ%C"=~\y`x2#nֽC0{#YݳsK^u焁keN@^HOA5:0pQ#͹?G7\ԏ$Gng6!珤qI%I*m`$8u'0J'9&s?z1vm_ѹGa-oDZ_L[rÅxjg?W-1P?\llj@j6Aa6J6s݌W7ѸGk!HDEz^'\Μ7bbIMØmp5 jSũ*#nA$4-K7?_Gvb4$^a?,M8#-Sq"n:@ghKtM !5pNruSp5Z= *LnEȼJ=}=E5$C٦N } OSz{b4RU6 VS:yzu^\#nRF9*qr\^+탱N)Jq '^iH>C djvﲠ}VQCj"KAL3m z6p#Eh6#FqƠ4BZecBXkFA64DQh|V1vpmB2&Ru'G?^b{)O`"쵵-mwX`IdƅU#X1빂l&Bf#D9atb#Y'0=i:<؂]LIEݶ1'o|lfuxXIN#n=(,iK48#Vy"60@s0TAS\EhMjcY6TcN>K6 PmZ<>fc;;7l Tl0+&(zrpqڎ-m;j-۳ELT~LSɶX2DfQ9HP֢#Ybbj2S?:#nʱΉ[;85آ =U’;rV؍d6lr11'?YW'P#Y)QGNCv҇pO-5K1c )\cGa1#nTzь:~&EױywJ=R$?_O@!!E߅1:&%1o{ϑ{9犍w5֦H1J0Ce`FDp3ӷnOҙYF,34Fsgbw'wa̰[m0a1c'ݎ#&d>m̅v&h}XqrDlCMQj)Igf8Fnd,*]=fc$܍hG0.nlv1*8<s(:h&h׿vYaO9"D#q4 aẹIPviAbȓBzjuz8?m a88 99@eONSHmXqkY6 fsFC†V,@MD^#Y鱶3u9!F#nhD7}4RJZoT$΢"ʜCs9m'N7A") kbcON(c&vv̥jՅrhr 61paYMx{[l3(iG%475n [#Y{uӝiX.>ZƼ8g/~tIeɇEE=ݡ5*\J"N';Fx$grQ{-Sgy참sb&fyr\.>Y]3n?=gprtVy]U,RYTfnD%51'L/Q+V3C!fBWFM4:1\kF bӅ/cgm;KKC|N-cNb&lN`LLF)1 )3;Q-_D!QB{ob,c ˬ׈,Q1iuT1,k=шwroҾr֏ub}*̣{hMgO8Wtm݄ljʼnz /_5t2o ͠MQᾪΪXجA\Zda#Y4RٙTz%e%!.l 읩TRWpZN: Lz{/xmj4HnȤJy[) we7l(Qbgp0#.'lF*A dV#Y43m!5vڝb^Q  $^p jޑe]ޝ7#AO`yX TSto0~JAf[/W҄Wʣr~G>ep=p#D#n,{E#Y6"q@R _2&a3HYc_*Ǹeb(OhW@Az#Xj 1pT7u홚Qo݉LϓZēԹQ#nXG`V,~V4Æ򈤣TZ%"H;+g둨;qѥȑ'z#Yvhx1b|jfc*dH&>Ia[jpꔜ5|dV7G6v-rs8dZtmC烆ʚljd]fJNvzܴ7oLfYU=d+Y|DiSKC`EdzA+&TՕb~n-Cfu8x{CRuH ֆ2sD$%L0y&rd(t"#nh0@JzT|S[@ei}<+${#t'w3q,og o'x;b;' ҠIv! S]٣gCK>fξ]q)/Y琦_41kȒ4[X1Ƙ8[0|mii;ۜꖹ>s4Z;b#YeAk}y>ϫ+LJ|2vNv6I|F3qϧ$a(*\ * A}c/e7P>2H",oKI٪P7CtzԽ,ԅcZ>ߢzgGKLP?jluI6fv4[Ki{(!;/"=PøpOv9B$M#nb L#O"]S~6|M;q#7$nq}Sύ+j^ɡL#9֠#ΈNcfFIo@(Uz5,!hp\&q71nNN212Bn1B&'Ԗ[OTUc[.$:<(vXl]#otHǜDDei0Z0y9>~ٿtpB]#;,a0*/ᑂk#Yb)D2G+j9k918aABNɡE5ߔY@UǨ%| Ai ;/Ey*jC^2,ީK!(Qאee=$Qa7v7':QgM0?: ?XU^Kbwc&\Yi7{jB [PɖUQ'#X嵳ƶ*eE+sRmTN5wa<$f!κ]8E˾yH˕]!,<`CĶgR}QMJ57e ;KOQ3#\Af2|\c9t4៏=w8]#XFmeAU"IР\죿O M׾-^#Mأ1H֟}Qhw/.O#Ym&{- >p/fe<i#Y2-i#YҎQjG]ԷeÜ>˻E#hP;a-w z~5#44pCÔK|x(4䤰oR7_ʣOMБNrFk˜&d ?5@žX^^"Z_ďdlx(SǩݳËgSA/a=5>KdG:ƳXAX36u+g4Y.4 ZCӋL{)e?fn=.g8M:mSbMo:ۛѿtx:FZD%0VlWx<ӽHiaiᝄ*2l&A4˫RE ܆K24f^@&SkB4rsn:n4(J&almj,;E ;0GJ!dbh[\ƽg[e1c9ٮj4jȸ}Y52}z{'^/w^MF02؟n>0 |bJIqe3p*xrqόos?X[Zg yVpC?P~9@90x12݈$|;e9!o &gCG'H&#Y)ϸSn2ӋsiaAs[iVq %3g  D ta^}j)-#97(- -R: q8VY4=y鷏ҝܲ6h|\ zȺ!Wm b-|]}<xx^.ՆJ)tɁ^QWF3مberv]60;†,5ܸV>ofWm0VDGWFjnq9=*vcob,F;{h.F7 MU-]1@QTD z IsR_?/&soMrz}^F]R!ܤ^QΦZa.~O Sn|cg(pnot7Y} + \~xOo]xּr6LE-b `6[@s΂ux˄!E}+YU6#VKJanlfK!dg*vsZ d_ϝhcO<fҍ@7Q0Ung:s<[,Hֳ/#nqcJZdQY3ӓ"dE#YҗYk;u1|b;J2DhQ 4^sʎb!$H?:0P?.b:!Vﰑ4m3WZ M50x0ͧqqUePR(B<:6_8nkiRd,ctq倍ĨeL7SzQ3a-5ƪ(5®楃Z o`w+y; h)=Fs"|LE EX]8sJ'&#YʘPG=ZV.[ЙwU[|GrOCL80tjm,B17 \>aQi4IPWJXc#X\f!,^>2#T*%д,SO0[ Jm`t>pa"h-3EpWOTb0x.״9>`i| qƯ|eVѝhΡViOVZdϱ›ﭣHV0t%|zNG°g^b#6yT-i+9\q6Ҵq#n#XL/`<1`u9OgX w%?7FPƋ7V(Cm*UD t_#Ybr5؊2@e=yZ̨j۾;UUSq6qZle銛֦޶#Ya҂l1DDR`#Yv-}˔[l[YV:/j;Υ(lZ)Y4oK5m6Y2sW<x!ݗ.xDGj"|%bxQJDoü]z\WʛIц #\^ :;ۆ炃U 2l#nJ_9WcݔOJhK8=_&3!ςr.5'$$6f`@"s GzCw>!x:BlN5Oar/ K^#X7GC:k& uR"_U~g$rVSqF&NY`}4$^SZM tJH#n[kaԃ0<^.}{:b'7Q)` ]`b3A#X{#njc 3+z(db䜖5 Nj%l GPn=@,d4G34F>w hDϳ/QK"fX'x]*q&lW_.lP(Đ_|Tw|B!sOuɿd&od*ԳP?(xW18ΎS)b_O2Z}Z,Q+-X}ݴ[[wzǺC>0z~G̍9vm*>_"KΣ1j)7(q;`z׮]D(wϖ]pJ9nM?xsQ''w gK^ek*v#n 9sUaJvGsЏ-q#YXI俣=Bqv?xwq5ߍVuQտSьj\ >X31[O Ew0-==GHᝊ2/!CC,t ? ٳ9tJO#Xenv5dz#Xpe/MKS{lcW"i4ل!ozҐ%SPܝF6f=ac+Jtyے+擞/Q1)U;{sR0GU7K:czȷYL.^ tV~!@|= ]+'lc*gO]E%MT3(PR98z$y%DbZþ4#X㒀0U32=he6Kڷ|bO)eRY>{k-v$?7,S#X!/5Rh9V~5y59r͈.>,7.xUo]`ޑ񘘝#t'\k﹨txg?wvXb#j*t ~=5xiupnFtlPE=sJ.utu|}K&!O0#X\֒TSk)3#X,f7*ƭC7cVV6+AT@9a^_hDVWly)12.=³g#Y .x$\Go}Տϡ=ϓ;!#z36w_*FRuMuVl.vح[_F&pBJ`Omnt ‹vc[Ye[暻0* L?)uz٪ QS1s"jB) >hFcŋ\*mBJ6$ m֣9jLF[z^у^;(#4G,G03EoA,?DIx=vo0{K|/<2lCъ.rd"k?'D:sd#]J#{TDi(&(";7RONA%DSj5{ql!֐D$O6_Ch:L5v׍*5}{"CHz.尤JIO=z9{㛼(񓽱#Y_w{9o#Y"0r$.ObLv87OJ@$)XX),pK@=vgo.ywFg3=,J{t'77#R; uCfj#X+`DytR h ƃݺ/0o6K߻a;zG}W?^F,̶@ad>'G*pwߕ{{3ï.#YJ.ʷ'1I/6Adp9[.F{Ձ$R~B?.?0 @}^&#XȒ"G'+]=~e4N>N7 Db2>7B,Fzf{*\zHvݏuqj:[[6^d1$@Rp7 j%bE!{yZ;qst7^;<70}=y(|aBNNYsXL'؂B#n3}w.+Uo:٠Aځͩ%ymDs (?C&rL,H CI@-(4gCjc@XH>{'!dpM(bCAK]Vʌ#19VI!:Tq_חExjt҈񧂈@|~(RQƒܾ+b q׍VDkX|^Ϸͳ݄#7mU?=#nT`bUI~tȴ,&>#Yt:kN.t:>%nW8<#Yˑď#XI4*SUE;5L_%l]#n,֖߭d&,drf$Ld!}uqqی @Û*\TC^#n 2q;p 4Qy( ǎ/l HJmkxJ@"?#n<%݁ d<~:kJ@5d!lA7Jw hA^i/W̓ߪϧ'mP;5TOp=p`BF !t?]SWKZOvX%nrKyZ0cHr\7dMV5Nt8'gW~S*ohM|Hs#Y0 zKes}]a_zoxtǩL!&oPPtAŠ\H!R~z#X}APJ#X`T]r#X |z!w W`IU4H IwzZx.H?))?nش%O':_o/~8;#mT#RS15rы$4#YidnCx_{k$:~(ZqvBtBLUuP Q}䲐muáǀI^Q, ;@{ rjj1 f|+Ob;$ mb˗JXX,x 3(҃g(xOXWArnyO;u0 #X#n#X'#,o>Q\0;B#YG RzQ}NulŠ$5}@DO7Dzq|a@}1:cŠ<\Zb}3E!6#n0VTUj/>n]E03cv<#nS"ќ[GX7S>ţ&TH#Y]86CՂAzaDEYD5ޘ !Kw:br"9Q?(~6z7ڝJeAW}a7+Nχ"T<9ȡV^%|@ ~+Fˆ8MT&Z~| #Y$LR+ĄpXla~K?o0߁mK"8d~A2̅B# Nu1T`"v8__RjPxVy6԰ HEzgFOf䊮<*Վt#YJv*\lFw*Wn N>!I{:7ê$y[<|#n=hTy`0A]v56S(Lpß; &j˵8q\\h4|y}jZ.#X4Ub%};̺8}jòÃh :*ؕ\(Ĝ]#X [rU#nUq?Y|j]gV"Dَ#YݨȒad_N"G/r MHASREMtH"=!&dfu]%-!!Y#Yîq՘OC'Ifh,9G㯇k{؊%OqކDc¸︡% b*Bu8yg3ﱭrV3Ê_B%Glys0dG`яVM SSQ⣓+x`GWoXI\ctuG7[m 0a3h#Yn LJ]Wƻ`>O~a2lm#(6IO\F^xJyG.^H#Vsc U&7#Y(aCSl#YIB! (a+4C{N"f_54W&İփÅ459!Zu8T m7LY*^c|`E#nx2wCi&dz{DAx!}IQ#Ydc:i>,I0בT#X}D)vA?MWN'#Y|!L .HI8zqF#MIܟ/0Bp~#X1ˑLqgg4o1ݭRH--g @w,߲[F]g66Cc5Z*#3sҞb)%]qW8{qp1on&[w!s_dIЗn YPĩ2%l}X#n7m:lʧRf'Y\ǒ>IV-\:Nz57oc^Gbq:uz)X3.ev;Vpfы/.KcR<_J#nfL,؄1DlbF&׆!0wmA%@A巶w'LagMՙ50`!b8PD UXn3֩ g.|Ekkv#{uڒ[WHepvN6%xYﹼ.j‡ 1ޫM⫄4 ?9ռ&RvdВIf~ܝ.zF_ůt #Xz E0 #nk?iq 'A'5^|O/$OA>{D;&'_?_Mg?gWO:ofu{h>/sŗdZ-;& ~Ծn#i0VphGz% <e7EfJWg#n#n7~RlGΎm *]ăUL)0bɠend߼4MxX IYYW*{цxm +|c0nب Gh|No-- s@zŞᳲ!y?@NE*R ^A4dM>2vY)=z;a~G4QAԚ}a {D3#XBPleb fH&#;m6?|*#.ө9e#K'J^xqN}`sGE$%c'q#X5`o͒bBm/ˆXC~ CC!E. `yBz&,Qw<;f#X"J9GaEJ5!2Ed;^#ʇgvޏ gQ8EdDE`d0!fvvx'C:bA[bzrn@&%}H`7qrSzz#Y8e g۸5C{݃i)#XU|3̻NɲFq0d{7g u:vI ԁGŸmMP:Xf:S Kizއa|ԝڞP|AI5[uυ @5Y{b\yALl>axԊ[z,tԄ|nb<3!.y-˘T@vЦ-:A4 ː/`;pehJxs`^!9Ăi1W! 6=JUIMPhPDLGσ:`gK(;$p4`C6hr;AY2{<Z˻7f*ckฐrz6ǿ:ے 2#>Cѝ%Ĭzjt>$\<\;@m 3 Q8u&f@J2yܟN`*Pyf8H)Ǟa0S "B#nBZBT8@HΕ$@U"C]Q8gKI Ab׎L@%d5&_M7Hva;y`_[rhlQnEbQ,00LeM[H`:ao.w%(PXT#nډ9`oDAHv#FE!JwdV+$.vQ7b6T[VYv̈́D#X #nΧ@~A/I3ȝPĹ2q M^JwiFCP3$güF #nM$0$axs#Y7' [(<:UO0d}.^#XL7f x#X{J9xogɾ'nխ3^YP8+1;I _t68Wk!N9ƫnc0؂"RZ,*s;uzC^a3a3RPpӕnۿD3Wrlӄ5TNF~<)wiܻv9&- #nهwu0ue%k#YC^2DFM榓1#X!F"6iH |M@^ׂ)6R,.O'`h|FC* -Q.W{YD.;/}:9.5O5[J0}J5'L4~kw5?8C"TuN4BZr#Xj)Nq87jqmU v 1C\5u8?2`\#Yp2xKژ#Z/(6@lX8DD0ɱR 2?}J jJRiiSzye}q؇ĆZB) 1O**5z1Q`iYM~|tUd>{oO#nr]t}TY#Xhal6^3s3b0෠pC(|Ow2;t`={,k!ω A lxCMΝ60,t\¹G!ށW|_DJ"_#'YxM%UVO*JMgWg~}#YƺQh]ҋ6#toMg連uU`\chFg}r^>U#az3QwcϧԣGE|q\=oRp#,LYȪj|i{E8@r{0vȏ2ƀ&pHΣJ^M`ʸS~OOI?N]Gx )̓D1i bDTCQ4MBlDYETJݔL'puJ1c~(['h0#W I+,`dvY>d#X>j&3k-" 97y396IHpeXxEc(zd H} El@'iпLC7]̹c~Pxo3d;ܣqpqGO_}H&DTAjbPxhh8ფ?a&OIZ~o2h[hUܵD-2zv' GSzʹw´V<<+3;w.OSws;9o%;,JQG(SߘAJXzslve5IA9(#Y93X`xf'c%!fu)SR])"iRcKmdw\)+6lR{|yHôn'@}P#YhPߧjmlHJHyM#Yo_+8h|u'* {O`uzv(;/A~,7a^W<@v_)wYL#nw*Ӊ~,)ӚM;;,XzX{˿&"b}z8, DU-&3(MdJ&8>+mPy}.2XQ@V 6uKVh~׋@݂%~M2aW A@XExu{k1J^#Y?oV#Y~df=, #YXl?q>Mc:\0fzaВT W7S#X,*?|`̈́!PHHz=Û/ϩη#BxC͍F"jp6ƶug;; aCpۺ@qQTTPF&ч""#n-n'a7X> 6m#XJ]yR_];z ^ܪIqI4r196ޗG|3ʸr"@ޜS7a:\F?$$U"rOh x>|ઞ:#Y}sU>"~~^mc 0Յ&% ,!l-iE;f2@w]ٹPv8waM잭]4P]ΌeHJ^x z?˂>P{᭏I8.My#nNEj $)0HBwZv*̀1˜o~ ՕZŝ#YLY4{B#Y?r;|97X:QM3 rM~f.^d'{j|}v8yw&I ?34 l_Gw5ۿvIu^ҭ0=d)Y?4i<`Ju|5?_TZf 8[#n#>¢lDg/cU!bbrSG<Rn|t3z7cLnr#n",XN?C9pz naHMg}^ (|6#Y4k51n; PWZ2COd;&G_ꚾUP8/X2D@[SHCuOwEOwf#6D#nvkNs`@kXa]{\B#X@jrhH\̰?)>Lb߀eGNG[D#ff?Mk?ys6IL-9ȒA7#EV(<}{deF,vN:r|~p ""`:11?{:#X #n#Y\_#ooX k^2tzJ诿?I~(C͐qhNҹkWqqC)_ZYʿ{:mi&I䝞] {?ZGxRV6 #Y{HȋҬd*AޡG>rýWe7G_\j!lZF"Y>8#Y T(;Lzrwd#X$:B'=A>G W ; Tq0),;*GG2k^o$$dvdفlH?g3nûCwW#Xv,~+lEvL5 #\)%'8h_6Xޫ/a oT@oݩr3IZK/&@#n- 8@ކ&ڟNH)O@£(5^?%P>4@\~NO#ƾL#(c =ØwJ''=zr+# __|ʷ7t]c$I /O1H DNt!:y]gA4}hD#nG[^ʁ0&Х`#XcU+ӟU˲_krg`\_u^tKy1"J_`r{Ac30}S##n8|0HPJw}1:o,a)0K#Xxsbm#3e\&:`;Dž0T)FrgUM([6Yh@u'˞9d 8l*1g??r!WAD]|,͍qռ'żjS !AKW`薓i첛 `oKra[UG尯*lQj4yӧ/ŜgbI \l,xi2#X=_!sHd+ A!Y%Ġh:&9n? EA@j7S0eHN)ͺ#X" 髡}d1E]KjW8_#Y #Y18QEra} VcQ=Ag”hY\cc,S0[>#n#Y}of‹Q?g}^~}6@#nR2#X 7 #z@!Iq>nqO|)9 8A$nCa/>ɧl D`.8B$nˉ?)lt#Mg"F;Dɼ۱sRp&Hgp+@}ӡXuCnoq̠%G %~lĨA |J>_{[Kf#Ky89#X2? FYD|x=%jR #YXfP r @4.6UDXpa!mZ:v (UM؟3Y(X5pUrl5B.Ԋg~.j^a@4!#X#Q Hڟ#NmeJ z-&"܄ϣ!&S")HfB$ԚKuDs־od:lG8UmGό53B;K9F#E/ޒJln7cy6W\hB8.*#Y_zt3Y^r_q_$ 972ή(Ϧ{l 4uH )҇DޢQ墫qf_|y]w'%9S.[1)>răm{Sܶ":eNoIV]W&]W#dv|N;#ŶN|jЄ{;x?ǔ )rG3vŰ []uT}]<õif\Ah.W˘\Pc߱JWi]9w|q{1%|f98u~LNQpbA;cB*p*Ii1S|Bc]c6ΖhŠ. `/)X-׵Q<F7!+a֫nQ.񓃎><~]pڸb^KK}G4ԓ(5_^*^x"5?k#Y1蝈ޮa>X8=b;ju;rfhON8_s]k.ĝxHgI!#Y_)ݻVk ANnR1B-TԐl>63|0fBqZpo}yǞ4Eɐ0/;5?WEvJ݂ZY2zo^28L9䠎wTd}As0e6?"D^4B {^ӕx%8{T!LJd/+CZ7<6IFX1Ö0#׹z(+g;/*lV"QĚhq)CTX5걝Cȑ6iEȱ{3ʔ78{xFK`׺s;6;r#TÜoɕ|sI+]ZեO.X=-[Tft4J(v' ٗ eItNfv#Y/F`yNQ3_&nZ-E]C~ۯ}}qT*D3`#XnG w{z- dɕ;mLj?n"Et3z]3 *3R!f+:̠v}np`QbH Vi!4 n^m>YLo`\l_&'9k9yb>BG3w"EY^#[3{y mj[GOK4coY)@+cSAωT$rEr^\Mx#XD=#HA;';֥,8EDc.vLJfj|nFqc#"wn67_׭}5Cpձ6n!|Vad7c ҘΝ-xaєb%~La0ⰊlyZA?t{ZA:˂F#Y~V\)3u3 ~~|ٳ#Y?M_6@vOX2\R" $K;Yd<@G!5%5) ׾6_'qH(Jy3{dU9?O:3EK|p21'D1z z6Y-TOR~#Xmya>BB?dBWl26i5`G! Fm}|&|\dY]zp$ҟۋM #nH.ia&˸`tڿ(}2Q$3GCH~"&_TVz#YvѣӯӇ#nfO8_f.Q#Y#nG (`I%b #nUҡzYbރEb珫u^LdCȃC#XTB I G1HjC#Xޟ`4o=uni!\{gu>0|0LjG'э<87ڐ$G밇݊ 2~qۀz/KeBʽkU E3j&3S|QbB m.4 #Y2 T#nx1L`2HAyRBr"LUsۅ#fRŁ q Q.IvBߛ.'"?p 2i-߁y?eesS7I?=7M#YLU$OSrRXw⪚t"g .#Y۹#XOAvy IJb{\#Y/AȈr9w9I7F }8G#YEݚwW?[#X#nQK@'圕#XrJ9 iBhSA#na&B#n?=)ʃ^797N{D" \((5tpMh'< C #L!~#X|2~2u{?#}?~=vm`ȈkT,$_OF4}>LyQnPTBcC>)HLxzU嗋Z:khO14(%@#Yʒ((`š>#n0DIfHNbMfy"#Y tЖ,AzHr QG<# Ez`ȡv#XFJbX$\[ײxd%iE`vCK,{OVr>{ !0 46Yr LQlE˜SfK/z4jCɏVz_o,< +gݚ_v#Y}LADDzw:M[`{ݍXmg'AA茔?wxwF*"4kˣEHv''rK}/2b)_ʡSx#XcbqKK&/Ώu?dD`#LIFTDmFyp lFN#՘a $dNO}!#n#XЇ*>gj>'W̍PA~\yw$rϗ#UOP ꒀ]"8't |;#XhY#X콑ď bPHPSsXC2=YZQa))#X>1)"~T^)~sXh|r"NABԀ@U{#XCc{#X#XxQ Pk#nz?_X5wg7_\ݴG^"2G"iDkzy(|*3 !ۿt?y}ϛUp!7=\ng% ^SmK~Y(|2Vp5g6|x) Gو4vd4?lc.g[%Jtr"4gw6ޔBD]kSׅiWUl1Bo{k D<&*mޡJj?ܨ G4I:ad>,ílJWAL!p.ٮ$1a$]pY$ΉQb1A:X>(PJF#DfKUuv\]xDP$n;q-gx&>@,PeAx@ [\O_}zjf̂?b?2ǏCA?sFy{<(Ω28^:YDAӸE4̌mup4Fxkbzzxyp|v26d'vql)DMCPj#X)'tP^*x5f_C8on#Xˋq P6ٯ#Y?_~?o1p7g)@%臯2=3uR:1}h#n6GK3D@߇DP _&ߠ #YxJ%#YP?oQ5hәot:kY_yt1b+GqotzvcԿܯ M?˪5#X?[C].cgתW>ar:i @Л#X}^GF[ y@eC)  @2"NG#X\!#YgZ^Alm3dso%nf21##n[|7MSg$"but#XSAatBGwws#Xy#XP{.Ngi9>q=48ǒzұhSTCƽIF6(qIcipGb#n#XDȑX(<2t0yM#X*(Y G 3QݭHϘPGWtj@l)#nses)P`bPvdh֧=Fxg &Bvš's&hs't_W_l쁷r>`ȄΙ5>ΖCB}Aa8Tpl?k~s#9Fes_ fQ1=`fK'x0u]`%t^7PF(,a`,2SXZP~+|:9Nê'm:x;C#r**D{f6:o=:'4.b]3Zƫ^ Z{#n=@~_@#X_]5o<Œsߜ'2r02طf2 6x' )Tq5#Y0BJ;撔(c[4m :=栽\S#Y4H>9n' q:*‘c$0pFU4ǼIh.OIN1v5%#n.i%#n<ժJ j5k:.i-ԓ^ =%F#ך2`8}9q_O5 tnCf<" #nPMӇ) z_HJ 6xEik:HVWMDD5_ɳVr~7G02@@u3D]_;sO#ngk/%)+#Y'Gk#Y^J{#Y [Z_HeQ_Rﻴ%q"6%7*{7}_{BrWvCZֶUg0X(>Nͳ}-3qɿن#`8Ra~8N_JPkG#nj2,Lw.{4z@#n'R:ذRNG&c,;?JmD2ml.:D y, IA#nRyZg Si_.ͱGcDxi*U]wTi _T>2y'#c˔j6}E-"p$EL%A4eξGsmo#Ym#E7I 8;x"0-}|c9 tJ,#n'E~Ѣchwp`d7p/#STI}Lدb=$( Uߗ_V'Ɵìx15kl~PVj`Xa$#??E-#Y2 AN?q˗n]kC=.n -=L<Ncop4O=afr"'$K9Rl_( )0>?/ç3z,a - g#X Clp54d`v/Z/ 殍0T U<]#nlɲG^I3tZk˭FmIUkUK[V`LUɸd%z ְ͔240c5K2=wS4yT>^u}5R#XL_J64C[431K||8GCM$)#X2;5tYYJlbs(ה&2׌$8{| 4lM`CG?=~(vIGA &?W3O3$ J/-(NH=#l11^/d#Y|~@5#Y0#X8O$!}#X>>?BU9n>>Z^J)D@j8W8c$m57d;ƺWEAx>f+WB#X*˾l}_hXlo:^p;D HhG䚩4!"K~ñ#B9a?bNW`?\JeMƺ:yi|78\#nA#Yh'~3CY{GOtT&ә|IeSP?JHz9Ǟgw8z4%OO&Sm#X1z?Uo-H7 'g3>MSH#Y EAD\Ϝv#Y:#X#Y^esۜzaM̍ YCc KÒ$ #X֙~-&ED(ݛ0T S.44#nXzd,e蠗Ygy'gs#I_ˡ2Jx! ji +!rE*0z83iH}N7`aKF'I]'Ȱ:43NO_@>sm>i!~ /JXO_$ѦD֧#Y,sT^Rk![`r+ql\p0A8zx% #gκ>_G>#n]>9]וԝ_Po& 9Lx'U8zQ%c/D&]܇aMo 8XdĜ|q7.֏RkT+#3i괆#YM@lBŽ#nUSQ #XӜU;TN[; L̰Lِ""{r;]ci#XQ#&<: \#YC64UQo4B/ӿY !\#n-19@}Mb aWrv#YĴxxt)dO@~r|HzH^b>3Ծܙ{@u"g~1ź'#oZ{~Nx[ @3qMN`67`}hLCH704nu+8r-c*[8i$j>#Yk94Mh񥦍2<؍MjXpO :aq!-p#nw~*UԦKv4UBXnn*~ú"w#Y0#n(<[?Ԩ^ u@#n?Hh0Ai o,Ѝ;uU{3+#YzG$-z?fvc"h,&4h2@;! cWOÿeO_wIA&c,W"08BdzW#X$^r辒ϣL݃xYW ||}]6$_Yf$ 9BwV\ƑuHA1!i﹏V琤dzICTDR2$Ai2#XAz#n#:CxO'W/`VJy0g^5:NϤ:T7a9wGCMI>c%؃ٱ:8jzy$:'éϬ#YYvGUGo:fROrKAX(8AԱ#ޙJ-6p{1I cYgJ Gٙ &I1C!8t#n?Ӱ.'49ubilzXBCqt\#X^KsҜc]HeCG领ͧ{AfGS=#X7r*zP#n$۬;#X:O?W &&y#Yہz ȃiw=C0U]4Ƿ:!6ɬ#X)d?aHb $R. o ;vʽ;=#{ԴP***X~~*X{_/50ԲƲ?B܊#n&Ilk^.0a$% GπѾ:4cM,F[c!TM>?5`|zHf Yag#n5nl,]s?)T:lt8zGhTE9pCÌ'`y Opξ=?yA¿W7?z]y#Wx!yYoF{tOH_zӏ(LgrK>(ɦ=rW @HH`3јKL# e*t,DCEdWgs/6m-#Y?E̅YB$tӗu?\€(au9#XMzH7+h9DDrC0I(#X=#X>K;,vZ_N*8_2fu!,g!(Bw|#Yu5|f'|xtHj4U[Yڬբ?ҊϣAHaM$# 7߆F~RNVV9rnAgٕ)Lʁm&SFA{y;yws$''WA~@w96#YJ(fNFUT 66D,CHAx:D88A2Cg=IݧSuOΛt2/9^cO5#X`]T>~?rxo7{CМ^{{ڗLd-*@#Y`WvaR`6yn;4+jId˸zrrs@0۹aK*N#XzP9WQK!TWr2xZ_RWƣ#ag:^kNU'Aj~!F]3x嚰wd)`8TGDz8w\rrUֿ_NlJCt b^=/#XA(q1DwHӭaDD3mU*6gdƍF OdPD.#Yx!h#YZX**vuUQt*͗P 9"R`V9%.=‹1o<6A%_3ٶ֟s/{`_HRCHv!97$N~ϏwoS$#nS#Ymԥ n6;$&=ej5FCz/?f倈'`8m_ ,8>T?IWIGU?<Ĕ,`D W bw~z_=ch(Oa׉p$tAӀ9Jۂ#*=#x$6(h<}&uX|EǗR:cH~8l`ԝƠxs#[Q.l:gf_ Dȹ~tFx?v٤:~9oҷ+|@,Ku^(MHzR 6|~x#Xh&&C,{Ba&8n05#Y d,D%j蟣3]@ &?/EL-!<*DI"BdO?A}l >ň{_|Wǐ>'Ψn(B!Eb/C.&^4;?]Xs'8p ΁̎9#nY@k Ќ9}sr[gÏmR+&;#H_׺seS 6|OF:!2'8M#nsr^*2 n3 S#n!}H9VM-!=Ng 'U#~f #Y1:~n=_HIF 4c#XC=3!pW> 0+g$$9:g@ C}@5Wq^}vI2 Ֆjp;OB(J$0ÿWT/HtLlOb|$#Yw>E?BJ`e+YI$QVY$/k/(<ΖuC2/ذ#YHc&΂iA0T1ez*96ev|X+f>A8Hd'#YWMA7Q$hTGHm?eHKH9mDqqac'?~?W4>Plr+~*,#X`!5=S:^֒bu`@߸55k#n;"WryӰ<=P#YBo.lXڀ<"/MwaZ˦G1*poFhm0X Ї#Yab2!?XT?ro5m%&<~t.L6($!E#XLQ^?"("6#nkv~W'R QzV Lӓ#XgWʸi#">HA "ʠLC$߾|1I|:$_ΗF%#XR#)Ez#Y*la`#n}QGJ.iP3Cw _@%H#YሸoPWgn[JمQFx`;ߧ>!){.ab*P;&ҠD kXFğDcQTX#nE#Yz}_g. XC~hOׯ,4ߩa?)Q'~C!ݴwC9/)w :}xE#}k80)HŅe2=" I\Rz{.mV}-bh䘂RS NQF4@J1b^Ļ:Ppk*(ɪIOsױZƲ#Yue z6 Z#X*^9dQTdP'@QoRDRҊ  /~0Þ2}M~'P#@?n٧n3 ~#Yb?_.^l/v|'䖞HqoBwT\rĂE`6#YeD".[2)H]>m=v%rRb#noLE[x26xa^ѭ7";Sj08[xc0 8d#n&s&If)Q7N?u-٭l01`!0u7 aցtP8}Z?=ӊoǶA~)#T 'C#n.*6AØo#2~PpAL BtVl#X^#Y :C#Yc-0q¢,3z5N8QH+ľHTQ>-E,㮚kק"H>6I2#R~"=}t흺_b268L@AI,@R e%krE8'R"4glq8({Ƌ >'".f :W>qym5Oc(]#nJi"8sEɭE3t(Lzpг2RNּﭛ tG5|gb#Yʖe#XcW3[#H0V`dP)s22tWl4Zxr#n@ 07A*)Z+tZ2|խ,j@d뛸TdpDH|7b#q++=ı{"-ښ*:{g9̑.#n61JIByƫ*f ~l"K!bn+j>9'ז#no>Q_|sIEiza/5 BjM ;RI}jܲ|&58M=#Y]2^4x1tCKbcƼyw,KL4a%0jNK#Y U:cw1,S2f }}tUTVjQFs&%:$r΋꬙hɌUElwZP2Kc.P2t> @%V>'l8pC*!__y~Jdzzyi#nI;:燞l%yʆ!> G*z}ß;D9Ӈ"&p^"lX 'P O9'(@xDExK#4S CO;UDRAS򦁑h>?y'{чTU.<Lg#}*z>IdDd8dk#X[7!ANETEX+3*&X`N͙ X4#YJBޞ)m[A( }}I@l*w"b:Qmk!uPsYvlC!SܑF8ۜ[m i`|#Xh*XxFBWh<0O3O0cyMvxma .a//Dƛ(KI S~d0hcl7&a<8I#Y:aٓ#MYņ+L4Q׾62 m B`li6]FE+rm;nk^dw5ɮ\a͜-F5Dq/$I(ۅY$6Ί&ShM#n 5FXb#Y*C_s^%گq\Q0pǰ:`vwy2"io$cN'@l'j&{Y C̳O8K:ϟd,%#Hb#YߒI#n{B0ǿ6Z׶/d&Oc<GHbd榏csr]ͪN Q*/{]PF5k2xb1(uAR.eS":iZTJlj7MIН_!R:O=%׸-3 _ E&ˋ)8ۮ<8նl8# eK'Ap+'/l4HzN`tmBCr;f\4)4yDg}e.{xoA=LۓIw_N4Æ>5>%ed8_yzRl"#YG~^=TPͫ_8y[v-D2yC8=@csT㪃E?=Uͮ#X 5=$-qI{aϣK w gBE.P<$YĔbYfg#YJ"vfu_qM1[N3P=dž/Ԑ<@Eב?Lj Նb鬠IpG/ѧ;GPMpq5@ Rh8̠+P)qRy#Xh8wpﳯB*i>>Ww vOȽ#AcS0՝#Y;e^1{4iH<DǣS6NG^f`X4PRQ1UPI>EwᛋKM˜6½=TȪ*-%eyr,YnaAb{1ӣ}Ky,D`0a#oOv ;fi (^W;:֯wXr'#&lF}׵v\=^iBu]:8v$#nBi{R#Y鳢P8-eKFnF8gd.;)jGZphǼgGݶ1oGg `j0#0r'{h xqϳ+#YArnqwdW"NTv;'@aޒM7}ɻ3Ok2rCjėF/%}/AĈeE#nykZ#YE8o䏘1!#7/7bH^~_O92B#YyECy!굅w']hDe4:FDߴ=)j|S !lJ59*!`:a!|uBܜV_#XDX~OӡU<(+Yr@ A<42p2p,*,/݅M-b2|{֌]vY;Lk&=kA x$lyKHLa9y&OU4Z g"16SˆY@`LT@?/j|vM9r(Sz8#YGӄ<ĉG/nF:we{T=#Y=sosO鎳c}-tk[^kJB(?_OOc^}Uڳ#X-/2Lyf:I,iOx@$#{Nu@/t\8Bp=4kߌ̃h*&#YY.16?oehU؃އ!C A_Fdo Y܆xEn'S1όx܌`v){e@|N#n< buд_ϬݮD"wsDۗO/Jj7*cQEg:+$ y*cx1xcmIc녌)!r-;#YJ!*I8G\冎Aa zRy)c7F0cHԌ,ŀԩMFqlCr+'R r?֯4+(qsJ/R]A`Hp7eGI:k`ٟX0&lbqFY#n§B^>ϛއ{#np~ļu4w%N9}a j<ˈtN{9?DDLE%P$ʖI)#X&)eB!eO%6EKd#XtćF7iɑSe,H(**ߵ#X#n@UoA%PWDU{5ڣ@TqP-I5$tWWo*}Z)ة9o,E чDG??WLD,T E @0k'b?#X#Xs͚5#YVx䮲z1u|ɶČ?L+<5: {!(_\A?ZL#Y[Olh4"$q&9M|7e$2f`JQlM vT:H ||F)`w*RBNqF*~2r$kN!#Y5#YJXPeE&2J>@!gAW{GO!x"?`vXsց˿,Lصj n|gNř,Q9&tݭwTh5#YIPG 2ۧ2CQDV{|DoӧVZ2#DDۏP#XH#XPJBPߞA`!D)A;#n#Xh@F4%*BL2*߬#Y>NTW%**#XJ1DD &tPDvPРЊ#X"C#n/ IP}pch.;*= JֆVa>V[{>dEt#n1]Q0ĩ#XUb<{2$R ڂQ֬`?)'i,S1ҙL6M2^` Bd3Isą "$B"t8&qѹMAОR-0L)#nZy4I0P4iLHb$t#XQ%(fRh(HsWj#Yn!0 bqNZ7"$!OÈz(8=]}>4D;Fx߬b@(b=ćb`kc%d>!/<"W-^ (#YF7<Sˎ&IIК#n|RtEwm;R J4Q9IP#Xg].r, <{Λ0EЈwq17bb#n-A$Bt`b#X(U:z=uW̾8O7"&i@QѥsyS?OupZm\4QY9M8Z¡Izfjv6 N:qU7pU=~zQ&#ne^&LqacҘ(M:k&LB:naFJ$׮eW2z32ux#X8!@?C8}j~Y#ni( H Iy!#XP0#X W/كloXs&IqpI:MkMrgwbq*" tzs$A5>od 'X/mB9#@0 j4{5Q,Z69N@,Lu "~l+AfTpUA$ߞu^,i1J6IzDecf%1$#Yxf?vZb4]T8CLtEQ7䭹#D2& -4#Y֙Rd|']%\.(X[2W8ۗP" 5ZC͓l{ʡxxB#XBcU]眺`Q=p!l#nH#;^'9̐JP*d{y@=s܉7IJQXࠞ0 Ǘjg|DB%$Q"])#YTP5B@#UK[b$ڣQ ?\#Xn=-&LMLT44PIU!H4Rn@hǕNH>CqpQ5(@ki@>">0Rvd p~#Xzd(h҈Pw35uaHwy<DDShJ/U#7M~/%ij'._Ou|{ʩ*X.gqDzky2E)/tX+;ᮅ@ļ!>`6#Y #X@U)uڅGm2L^ CG LI$ m9:(݉XX iL N d?/}ZF&ԅw=Op=\slĜANـ[CUnwOb}l!֕/cyQ:J>*u8#YG[AO.{~CGUΩPgRe43EbR#n&؋v@-L`4E?0%5NC|+;S>GO ;!; Cx!&i#n4 $Qg^' I C=tXO& pӋϩz\O\Jb-3G=),ρ2aӤ;.e csfcDI3u6>I #YlU:y̕AϏf0fAAaMy؆Gg[C#*#X 0/#n5ʄ7 J]/lP-(_}!ʙi,E>< s#nL#Xޤ.ĨmDEC] |>p^-'H(\UeL";+DP3!#n;/XF # T݄}z_ݭ BlW N^ԋ"v狡aD;!ϣ9=_Lw݈Psz8ϋ`l:#c뮝0F UDio.-S-#c)h=z$'ow;džzU T0Ԇcm,^=|yi[iª*R,UݤΛz5U#9% c~UU֕&́Y/t0T( IV&Ou'YG9$'rOnP#Y}3rP"DO*ϦZY孇)#;s H9dt}ٹ6ŋ":ҧY˝GgN/{tqipFY|7:edTq2AŘ۵F)?˚,`-jkG\yOA SlM8)xxz-}"ƫ+2r6Yay}GpA@{v@2#n0H65v;0g<9|1@)otFʆhQt]OhcV8l<9Au7M6*Q"T}v=>z[E3ŠkCc|+j,|CZ$H0}?sYHܵ%E2"*]ԉpM5ܥ`Q%Mq/n.8w3A<802ˋxZ>Ғ##C^.Z9#nX@Cwm{'ɥ<<`@uX%8dLj)5R\nQωpk×RÚʈu7Dn#nUTC!ǿM۶mɶ{D`]D1 Q7s$#n˔0U(﫹^=6z88(5=xk&= Tn:|or@8`v<.FG0SH;g=ECi#^%P$*Rto鸾z.X'GG˰}yI@ vUM2#C?^vѺZgS^l}vTѓ kHj#Y#YSuTRDn8q'kb#q24Ziڅ)h&#n&B%%SVWB!Vr}XS5v0ݍ @p?D5u='Ԟ|tG#nx ӘYQcV*-*¶O5)F̎UbyHr]#b#X@$2Fy@bTCc7='8=7#h>ǒ}bIzNʳ#Xl)BF~3C5ZH<1#Yف#X(Tal:T*[J5[u42i(#YkTtC::q+}P 5 :Nôf:2y@?=WN^[E$a0G0zϟh|PmE!~-]ynuz2u3* *Z1QA#+1 Lэ:$09ejd"B#n0"Ex q!AF6T~sMrmg-WHѴsp \$(R׷nnaZd;-'0@t I8W%6HHs!:J(QVC#7x8 qO@}wfR{jKL81G郔=hgOnehd$>D0L:'m, ' {".f ax$v#nWo8櫧}GJRi)b@)F"@6t 1PpҖ2tTTE*iQ.k˓f 4fX#=G4Ө~[1ֆ@&gjE+(|>o'}܉3ɠ}¨>ҏ4J Yhn$>5̷KM,X dwHCn--"& T]0l>X #Xa4ԙC:VS؅ 0w*6iKĨC0kv;a֬E!RE/V|8Pr= ´+#Y4 [QF* l~qy")Ɂ;؏[Sbl?Q@FUXOX{1'#}.Cj(l&(1-Dó!) kU#n#Y1lQj hc].C0>׬1c6 n!!CP FLt L0 gj 7@ۙS uav`=Ω?)6!F#`81#& dxi8s_#Y۸DdHl4/"D's8<4f#n4B3(.L=D]=y9@]g!Z,J_Xc=9A(UApn=3|_'׳Wi#Y&Q$sJ\Zyp< pXX1 !%\$T|ȩY0`iP!ihD?V7h`NsAs;ױ`XiL%KjoaJ; 6n8聊p{9yUH/{f,܆9s> 磓 ዏd`hvk#Y["HuD?[):.TBW`H׃JyB-_$P%MMgEth CۄSM6B`OCOSD~M,'p ?2Jz[[u:xM RJCyXtprkR3_Pzt4n6֌>ߨL F!'80IQLRvF=7D'T" #XxD{Lݬ/ICZgpUIOMa#.%4!=@Hؙ#Y#XpQ|@it'ڵ)mj1;_8wJS?CݒBhTQ#nGA-#XLs=ɹHٲyѢil;MAu$G" Y Q#Xs"(O#Y*YBQhsdT䈈h*Bh1R )UJC|5!"i $;)#nD s*( & N_}E)NE9J/g11A]D(P1:z7r_]tĘ^!MQ%.ㄍ)CDgB: "d ֆt7N' d9{MG}|#X<͐:;֘Mjp32C3Io0>CIG\Ǿ2^:rLK&wNX3JAsZ5A#naY$u2Parbi1W {-(HP<ޭ$m<"l'L7Xc\Ӛ24 IN700r#nx #Y&44<,5%c_Ec ܕa5+]ZEzj#nmH8]ܦF#Y4Mi3pn5LK(h< si<Ǽ #(MVƨC]3Q̓VQ,ڥAO,uhAGdXi!+5UG:HVpYCVw{=L$#XP,S4P˪jB*`ԺgMa] >y;Y[>\:u3V܆%{PDm6!*CN|>*qVH+|DViXFqauF [l}69aƗDX<79nAN ԆL5GcqG/6_ N`]#bχlD! '#X&g"j3 8/46L#Yͺ`kPIڢY^B~ ڂ#nTV74uçf1W>C)P1n:`WO˿~މ$דz׻j@]l~@#X4@#X!_5.Z D^ՙ2z-QTl{[zIzYUa&hy4Dj;Y{#vVMo:-NL=eWD7^}Gpv[dæHs1TDuzh8 BN杧@uw$αt9Η|[/f@D )@ Jg@~z7xG#X0?O_?(_;`gj6Լ\cEg>!$7c1mN'J1EK=3GIԋ8"M;u,.881X8#=`3/eP4OAT-+fEJ. a>'Rҥ>nWV|V/&x%.N#--Oljl;opSfv(19}L#YӦeQ)Z\T8~0f<:8E>ɼщnOOu֔jtJ(tf=D<\vF3xw:{1] t݆@ڧEKM/9`#bx3tIEa[fv⸚èC#nLr=ѻr(៧t"QY:u]m&kl^#Yb2$9+4;τǜ9xf+&gkM" %EͲ=U5D[Y_UT\#@H굢NCoҘf2;væf;aMM\0ݧ42}:/3~ciY3ЗA;wh#nY@lIQӶ+ӨQotqAYe(%Ă(g}.^_)w<)Gavž<1}PoakҒtcz㞎Y5:۷sHM4{10֣\3?9xz3A l"/PRrpĮc0$x9Î/9za%<{OtԆBri0gX@ g !bM  R*i<0 *Xv02mqVq?n:<3ƞNR h7#nXʤwn6ա%ByeJCf-ZêٽB0fE?q$IX^^VX.Z-xDhRPOYj! w./)E*];)JPʪ "ޭ۲TPN%ijQR&NA#! Ǡ4 I9l5|B306#Ydiяa6"5(gh`Dc1X5`1\C¿H0#n"Z8{)r*fwb *\-9 E5MF+vkz3oX/P &<%E~2+Yt#+Thumu- BbJʽLSOx xG/adgn7unpS[$g%[-JS&rs#Y# rX9)E6Ef3 U$Rfa dF>sU}U#*8'Ih%ţ6Zvf؇^-2tЅx;iƔh19DL)u­*d"ICQZtmH:a["8;*r923&rfh/@b Ž~p%EŽ<.8f,ΰ8wFDn)Q&0VLW j(;.^ָ=IH[;h[2ptxVNYs\BpuZ;a#nC%; f])6s6HC,ڊ"TB͔9t4ZXmepgXwxyO&m#s/PrO3@sQD%[kqYBI6?t1w`b;:|kȍFTc#YNZ^&&߉#]&*gZ|,c,Tfb$H™֎mnMd3p6#8;!wlVwЌhQFr/w7pET'_bZ}лkMYy^J^ u@#Yd,t$ Nt7D/ro:SG*, ؛2rN ,Hqd:O$<1pWeBd&1̹m0l dL+\uF")g83̱S!MMMyktxda";QHj_XHI6jBPO3Mb$+)"MoF:&tۿJԍ4\ː+,h#Y ږU&L7*#ΚN#nWas=͜Ga3q! v9vV$H8(848̊k]=F}Nm>T8BKMO:fUNf@E;0)3Y 6=l(&#Igol89#Y(t0SWvYÇT{%L#Y``1sյbTMΜ^PZAXN:dvx85>E84 PF)ph.3d( Neƒm66 ##n*)#X}D7G`dY|KԝR"E8M6 p!$⼒9tE$DxG0h4C:K#Y2QX4d 3ث@ѡɆBL01L53#YarXa̘3I,n39DpKp#YYAБa.R 3#YtX6LPqZ6y㐩f֮iwίGMVoa3,*yHYİHJPID*JQfȝAjSh5s䰡#4094 " 0潯2#nNC>fN#X&^ϣ}_I ČI {qȪ`tC&%#n#Xe,WOrG{D!GN0s:OZNݣP$.GM3#Y$y#ny<;c=u<8v iN#Y ^9!;~]#YXdx2#YgxKIBB{#XA#Xy.P#XH6L#X0~.v~6Oqflfni\ӗGGrxhx:م#n4}f ;'wl%-SΥ%K PCZI8r򇓵YB "8rDf3Y]r:Coqרr0 XLs3>2xwlpO?YI B0:vZ4*J]PnL tU΋[\֪BU38{ƢYZ(6rznq)p$Lэ ӳM.X4Kı^NlnOe!Sc+I)qU(q99)9;:|&$Chw̘xj55G;oz:>ap= ,rqJ0gt&}(Ms҈I:EBeuҠ X|O%,5T.tqv߳U֣dΩPjĦK=䧗bi#Y9l;"J0x(v4†)s㬔 |$,ú %ߺ.(/fL(R!_^JżxRZxv$̃ œRIL1#YsT@t9҉d:$4U9unpOY@zZ(j lӚL Gϻ+o8оziѣ-\#Ylr+$N<#X;DoMsT0Q!-IH~80'@D))fiĤO#Xv B@C/W (T71v<@5ub#XI׉z6`SƂCM*nh.i4?j0ERn;M҄QzѲ%}#f8VbuI"qu::R^.I AT@:HugI0V ƜbpHV iՊ# 5`}0z|#XЯ{G©zׁNP}#X<ݲ4usO8>ф+Ϯ%)ǎBiQd)8C̈́6/ 0ߠ pLbC dHțN|(VMOVd}Rd͑(@b,?C#XT @Þs!Se`s]ŷ}7`c[]6st5(‘}E26&mpe}RKumE5:&Z˥Gȧ3v!Տ*("'\pfNR#׳]#4'B$#X&Ƒu/X>#n9e93d7LNF%&#nqr[h:bdሼN!VЛh19p-BEGXza]~uS #n-1O!M~#XL1x N ><"JQH%#nDM!CDKTLLP--\JC: (/u>e5ސ~`b7G x] p#Y5HZvf+\G=oa#Y 2l/ܻb,}çN~k1̘{w):"'Jm2#.ɍ'C,1zrceT성|2 x2sny!Hs֟XD=N< \lC$˶39+u]yis߯Cp`>ÅТ"x%tC T@J VR nzZ-g(PMXIGBhuYV]( oI3!ZYE@p(.crp_JBU#YQNtf0J4;~r GG[*f(fyS.b#J0D E#t#Xب˒ H_2t+1#nA#Y [lPPRݢcF9hJGCwc_-LLhb9R?! ?˳htUkhwgaA {Zg䦔4tn"͎U1qꡱe99wISܞ_f"4w~v3\T0Kb7YŤYYڸ4$nbrxG#XX0m sVXk5g ;+o7G)2c$]Fu-*֏+al#nrѶA#5$¤ca:![uXc ÙÓ xr2 ew2=:KtZ$#Xu(2f}:| @U'W@xBʑ!mC$t4ddX%0fay4 XAM*)T :h6E{|$rj xЬB!w$Гbr,M#XX-E<+f9%wWBPqf؄U`r xsf?ԨRqp#X~\!o]9TLc }F+#YL1jT$:CFcM3##Y}ԴOAV}FCq27S:Ot-} 6HXaéBs@#XBZ$bS#YpL%) KB4n!@MY>F+s,Yg"x#<ib"x]C;ԝӻ Zoxw/FfyÌخ1fRi%Lz[Sܿ#YSi89gIAҤ6O8?n&Պ7t܌OXz֓5E#nHة]nOʬHô7i"|L:>g1u&&jUICd'qm#Crf1zHvFPP^Z0}甥5I'4RoVV1)LKq^]XݽgӉo1Q]D+sE#YUF˷If"$18̏MHg#}:Dm @`BoX;?P*꫙nf- Ϫp_کm %6TLIݥΩSh+䗴BncA8k %ڱ16KshIz9 6.&/.s÷s~` ka`7vÌR`;]7|g>RiлQ\=QC11T Q47jzwװqdӘMQv2`=m,!4s*drX҆u孓#nFĄj#Yw$pL6ա:9M5t" !CG&07Bp!I#n"i|;CrT=J:J,$<#Xiu9ʬ( ̳#X?АURi~ewaXG :UEɳm=UΈ$,&{]Sҗ@0l9#X:%ۄ.R@`\[>d9h#^b י>( "HR v;`Hz8:3!JRDqd,-\멽 :k.)*c!Ď'FoUpy;b!ۛf,m||~zaQKJEA#nA(S%F9 V;Zݷ';8ֈZCYIbbXĹIhI#)WF4f %T"VtxHxx0cξ#XNPJDEHAD$#XХR ˂@# @J ,==Fr#n#n)(P#n$B-=ݙSTU0SH3)C*hsB)cE/2L@P>>Ԛ]`Mm1{"RMYTt?=wM߁H*H"B#XE%-DM4U)"Gk#X|PQ~t:gGSBl Dh<3@pwICBIԉ] EK- {rQAN,Cvq¬P2)U3F%A"L{\kLĸ5Y%u~&$άu!Iv,Io MRsr}@:P=C^_Bv6ybͤ+514TڂO6"b>&)ik;?wƕ"/V2N@p2PBT':%b%d&HdfBy&v@БQE""@LB-#XyfFHdtCX! g"SKHТRo_UN>#n"; #nDЏiT`&hhH#nG T$#n+ 4)#YCK亨)$b" b#nh#n()&""#nZba#n H"&X*b $ab*H&$ hj$h(i#n`#n$b*"B" "ZA#n f&"I"e#nI@!X#nP"b"TR&!) $`dNȩ-4$AEMDB L"3(#nX#nA40 !*$h"Q$!DE\Nz#Yq雑7^c/x5ЙƓwrJ'#Y1(.'<;a6&| ϴ^#Y/+!/2zȁİ(>{#)'Ѱoւٰ:&:O(ō,q&}1y.`se(ݧa9RtZK<׿8 S~.ι#Xb? 'li6R&]ͲRĔ8󺁱 ɀ 2&`ɩ5q## $xI'y:rf(vT&qC!>Ą#Yow#Yԫ  4DDCRSxb#XzBh1& WA w$#Xi5f$ p#^s~ZLv|Ja\u`:X dz& Su˯#X2'`$b%dF̓/<3KMY0x.,YX#Q1IS|**~2#Yehbx8G=qUЦGGɮ§DW׀i"kc|i #C*~ۮT[( *$DKkF2hflAQk GENQ}@@ǣ%"mUUTUT#HRQvC@leW|ɋ=j +lh )Y sr ׁ#YNuTXl*zߙ2'^s!щfe~`&8cژ悜PF'rjw}SLIsoOp91wU>MW}zz;t_(G0d`q_!E'!?Ay †I:'#Y(#Y*k#X{\'ѩPʤGpQG_0#n~$*AQ=v:İKE@M{rRXe.2G#ngYͰb$$f)d.gۣ$ N8s45ytT&b gK2I#ARH^Rxh;e*& 6Væ H"cM#,쇦%,t*%‘Ӽ&UU'mݶKQA鞩Aч#J!pCG0$t"X*h#n@L`;@$E?4'@#Xxp};nMP#YRa``O÷e/E.[̓ƅ0##X(w(xvccX,a#Y,+6XLoG<jQ%W Q#By(kA3n$&#QQc(KX#Yl6:Gm#n"ŒcA!T4EO&3Tu? ^*y9W +텶Zɮ.]\sߜIpuoqzpмbKh\*1O`3A e;1O)FJ0_nU(^oD/@UcbA!LdJ  nY+Ks,|3̽9;ki4AZ2kBŜհybFS6NObd4Sd@j ?RSs-(DChv#YH؄d7B`1Y#XP$>Zk]EǿEMUQml{,A5ЉnzlIn %CQ$ y]], z,ri#XJUbSm0zb!̾Yۭ0iw;Sp8xxq%DLFpjLBii#AIH!o_eOHxPiLf$b!yȫ戲䷪Cv^(e C3I2D #X˯FEMN#YS]{Bb#nT#niG/#nJOi腓@T#YRĴ1w`bU!BSO:ʢMB44#M0T$TEyNf$`/ڠ6X(y۬t28DSU UAT%дG`|E2 w$MiBUL)i0h|%8QDPkLz#X"]/lSڃB4M#Y=|+ m294@ʓӂImzͧ{Ls;_< 4i#n;3p=#X!郱d)ٹ7gMbe2ӚLS2ՃJʃh7+{N!P2 iR~с*wD<7(`}1E#Y պχO 9K`rm6QFeWKզ3fuDz=HaϦ"MSV\ <:BZ(`a{|КjfGA]R,Ovv5bL8xGu_7FʚqE& pM}RP4ҩxsdO%]vz{~|LT?BN؝ P(-)I@WJP4%% R4HB+IT DP S2)B,N44B#Xi#nB4#Y!HIRP5K@,E(#Db:#X,B4PSD4AET@4-#XD4%H(ZB#XJJJi)F()b"B)) N#nD$"m#X©7gOxhJ#YC !﯒0vB^] +֙+E* $UJFh)H ״#n3 >Μs*>~K6Abœ!@<Ay&EQ3YD#YMv H8#XH1gnE\Cp˲ ;1UQ#Y#Y'xtOЩ9T=P[[bb)l6PE ?&LUE& >RSMPS4QMPTAID@LLBJ4AMRp8$Аzչ?Ё?P4#Xΰ5@T64RZva PhLi7&8$8"%#Y2Â#nXF!Gz'?;@6#n#XMQ7mBf)l?{"=F𨠕A/QC=ضuq _^tԐM ()1X799es;7;#X+Q!@wcm롡%z'FIEa 8˲"L)''Q4lDQ449m Oi ^D EZPZhĠM(CH NJ*ҁ }Up6j NB)HJ#nGOp(|ِ&VUooSZ#Ygj47[Dk(-&Y36Зg t0ӗJ2fi(<7O#+dFQOϫ$,;#X^sn|^| ^P#X?׎RN"D#Y|;aU,J4J4#XbJ,0 ДjHx?ÎQI5ZjN5B4]_uU$;O$2(DT::]\Rn#n~E"ZJHi7͆}Iy`p@r %5iuW>/{([_E4TOH`4]Y"zhRɝ֤ƹ D=d8&Clc2^MŒ`ܹLK%^Y*(%()+Fŗ*)98'#nf@$NΨ,?SfSLR̵~\]Qόtvt%+),; QJi#Y#qm!(i+8chk0vNG}IF4{K$"rGr֢#{%yDvy+x@mdLh 5vX7 IM21;v)]9Ay<܁`(i#nD";&zuRWsӆ'"!b(#YDmtEQ6栊VHHr ãP}kFr1ƴ3@Em#,K^l.V5rc}dd"b3TLG#X_y%0TEd| P#n Tu~eG+rA!Im_ x:KIDډQe2Δe(@"82Ƀ8 -.4P?"TkB᪈k 6XT5'C9;HGU0 {NlEdC#XAD*$@Ҕ#nz xR#nh#ntRb46A/B<^Y"4p K D&C %UĀP.@i ($)#n6&bW,JfI@H YMPR Rs}BN $() M#XBL 6 ϢS@ss"RJ_e3D@Hf\(ai]A?.Õy}%@pY3%9@=~uQ#n {\0>WtΑ0C ?!vA'5=D? DNJbhvMgo2gPpz^z!g``0a18 !!0hc!WRQGB4Q2lvv%.$##XH& k&i.)B4ٺG]@qE8t@ZMY o7Gdp\0IZq_npN]jp}-VOHWne N 1pLoXCUcq oc^^>{(lЯc!R.f("\9 x44#YXTuÑS{Wnhs_JhRp 5Ѣ,|A8!}ҿuupre}$TǢ0Ao6$[P9<@E8*j& R_2xI$IRCpA44׽1@PDPS5P RN=fd폯V&e8B}Meʖ*1{o(9.O)]:I&#C K#Yd&t g&S[K,[]Gf]䤿ųXڧgPcWr'TmL>4aQ}vSLG.lgRi=&yn&L;v3S#Xd w'r4ftG~G\~o*{v9$x_41A(!Ec,f =(ēk9 "#Y.ŃƼE!nuM3#5*?5p-+ANWPG3d6pT뻾;۝E+ gݒU:*egюv,:cjL$gz$RtNbcVr*z˜f$.>rQ(]nX1aGrҷuvkXЈ+Z1 =0̠c2"N"Q) !LiPQFx-U#g˧[SL2#Yvc2wx'ICO{7q#XUI'`Oա֡ &T#n)-̀/oX xpPa U/#nk<_#X;VXpz:yyG?ǺqLo~^ۻG#YjgB?auJ{Bۀ{ 1@Z΃1ƑlqQ/bPtʼn7Fwe;ODgDS^ !CNPS:M:po1 ́ڃY+ԋE#XmS'}k77okbkf*9`i<\ Uta!`HlC*jn#nHxr4w:d#%ThΠ?y˒tFfu#X>N)F^3*'b>MIDԅ4R -E% !HR#LC@LTSB5TD(1%4@Е2Q1BRM4̔#nДDTPRAM,KIE$Q#(@ԅ13I h*BB)!! $* *U T  DMA "D2EIU$HIEB%CH)!(,"%E$LQ-PC,%DT5TS#4LI$PHLMU )EJLQ$JP%5PpzrI&!c`ٍb1"F3D2̝NaP7N@;?UtCr#X#XJt"PY;@hE0Z@ J2]N3TDqD)#X" DB` [!M"%":{#X(td96Z"w2| "`AT~?ߚ~x P"z׌J4ܿ8ЍP'RQ04@DSHD1BR#XD4>J^ #XA8@8`:@/8oYL@6ؠѤ0@`;Ԛ'[N+7Χj̛3 ]#YѓP9:ğ$'(g#X$kI!˽֊Թ$#a%DmbApbK:W}0uϖ8Gl#XB'D6#XaDy]*xWJX|.{Q#X>H4(oʘ$yioȓ J3LA (X}i1=>2Y9}f@59 (Rb(F@9#X.V?=Ny"O#$f"@w`;dRR*L`m4Dd9q^B "$KeQda(q1i4)JT`ZhljY,9O/{?z}Ip-`H1 ;0#cͫ?Fi⮠qݠ& >ϳ͵$#nBŕp5 Owg,1(/\HoHi{z#n(\9EEJQ0ITRPĥRB4UQEAQP Ü*(dB*#YyOQߝۦeMnHHpa'$3h%*N)',<9}@V1"t/Lh# Ԇ˼´^Ӎc|IZԴ- όbLu1 w.#Yi>1q#g2ΝL$ٚHMmf>(wSE=}'|g#$gpA-4W)?\hB&,J>E(*ė {GRA/} ^ƛ;u C~.$ʣd PwqDB-RK$jBw#3BXƌYR/DŽzn(#n]'֠]j-B#X(Sۀx-U]qG\1boNM#Y,(Ր=lB!!u0#E-@/`<')r}BרFsR; SJD$,޾*^9An+ͧMsI?\ C0䎃#nHb"&!&eN)!H(8|1CX#YMwQ#YŦPAE ;jMqѵOrvy#nTq@Ys`1L6ƷZihiIi@h59H&#nv!Wk?)C`8H_x!P {˦_(A# $'y#Y 2`Ϸ^r2}gTL1vzQ; ݇ GIS!QÞGjjRR!͈i 'Ѵ3N?<,F+"!E!J`#XDL 4F@i(n3^v]Am([?I ;>V9S6':bۥQF/©.F*8A ;X^*pS,\ ٥!R<=#Ys+jYKV{&0RvcN9##YXGōq&bUA#n# hT.5#ۉq-45dEvX ؒDH5jCf=#YVRU2΃qq82yx 2/? $$Ew\҃!#nj.)46F58Z1hYm;i^=:Kg'h;GC^$[J'J oߍ#F#Yx=URxLꂦ^,MdH`Ox^wcς"I4P$mf(8XYn#X4Bv]"A#X?7#|$ wi\ZPy!QWjH"*D ah((Ah )-r"AJP-$H @P1$46#n@4_⢪YH^~QAkh4)'}11L Wu(Kr5enH/+ Q4ɲTc1duT%lミ0䨝308.FԊ( 8O0dnפ-1͖B,kiVA/}G6CAx)HR#!~#] 1>8cm۱=$.A 7@Ł-1`(R=8896qc҄1`Vy!QZ.nѻLe%ݘR'O(TVT١,̈́q "`4E@=)N2*q;{I'%|l!{#8)HN#XOm֕/6Kd~9TSac4dY//?,>#Y`vY8N-1C,0IY׻ҁ#XY7 }#X]xBXC3^Br"Q(+KӍhնk>(1o\̴RçTds4Rϯؚˀ$ yzpI'_Ç0-OWr8=#Y$_ #Y#n3~9U#YHsr=pǧL!Q(e&Őy7ϸӗN)ɾx 8}r@ fS5v^E9Lv´ܦſʰW[fE%:^ocMK9u4rjThcSdbq'VE #K#lmuyVkq+Jq,RZˆ?ThxHϖ\N]#Y_#Y&S5LT82s ܡTq2 ʴ"gV썱R KgRp8,w$m:.*['#XP$3w!Ģ#X9C˴4$(6DIS% SSP4%$O.BS j#nm{ 206#n#XǴ.€O_UFڙBvGD;~&kLS}lvzbCvCZ/:sǓ4E3f#n倨#ns xBEAh[sIνN $\n˃qr(jDpD#n*,rhP#n(iM@= p#Y@| ~fJWq}Ǟdzl=_k}+| agj'XB) #n#XgǤ82Gå:U{i18/ݷ{Llw{X7\l,Ӥ}$w\ʤ!f/V,LgF0\1FѸnzkt`V4%Py I}<n0xǸ۴$Bi;zTۼВ\bƆSiv ÄĤ 8`@$"J# #Xp#nR$HDQ%(,BP#n$8&ǐyAD^X\0ulsAHjhHf,PVsQrR4aGJB%Vgv0&cuRq#n#c*.A6AN"ArC=MB8ASM#@D@$Y:_tyFN{BX"Vk#YE}:N|7:31B; q ˉ=y95$$fcծn-#鑶~^cJSs58g0llŪJ)]9p k10ӫW&:x^SJ(XHL#Y><'Ğ!pfc$pK#n(-`_i$|h^q,4\g .EE1)`B DA#XiJN;h˽0~DBZ xd 'N3kckyӞ|/Bzw@R3)^Q)$˔3ynln 9ʃC#04#Xxb<%U'R`NsH ĤHhp+C 2E.>>N)qʪ#X#n@?_` 1GjƳJz? hHy||_&C$@s-N0tg̺Ia6ʰ}ffhowhZ 4MHk&`&رW"h3E1c7t@58W 6@$ak֊>%-B ?:^NNLi1^bMk`-DP:'{ 8#n:'#4^Vhŏj.#XX `šx.F(sP={^ypB8Kj]sy1 D=ݧo1@gvOoc'#Y2BJ##!J!c85U4#X @.ln u$=}Dvkq);."*BwDXEͲ?Iy#YL60700+8 SENw:ݽrSO{3+ tϺơ:'9UmyC==σĞ=6#Ci-Y"Ho՜Ih<]+*Tè!ېū)d8aHz(C@@u>B~M"bP<1g"q;∻|K`񖄠#nZ(RF#XiA[~<9RiqbAQ9i#Y#&"JaTB wK jه׸sV"8v2i#c*IL7#X۾th,>|)C>(9j*6G22^N䈝!Hj `)TJ#X#bMU Ga8$4C89GFI;#)c/J}f59Jxð1~T0,);0/4}|z~>U^3@ m#Y%8)!IEc#Y(|XX#X% P>Gۛ&Kf#SFc]!Bp2D CO0?#XyHcAP DOs90#nH{nslHl2p2ʓX@tC0?:<͂*>LM4 B\2@?úQ8 4*ȴ&FRhn#Y^ Go~f\hQ@hC Z,NVN,E.'#YmzF~-Ym ""G>;!̅6zPpAIz| ~7v]02ƛ]BO_;yvI(`I’Eݗƶ[)BWu\ #nM?Ţض&_?:4fl3Ҟ|̹w}dFHlg4 @0@5\E陇)99:nQ`pY4-I>#n|om/ iˣC_yf>j}nE:'Ը(y@wss\#n9ց&}ΟVC875?(f3Y q#YŻl44>TH횽MPьE1B[\"4H<NZņ#nF)%,AACM,cfssTҰSALյQ1r5OC> ŗ|c`xG#DT#Y4X)-9Q8\ AA@1jj`w<C.$!#!~$pyͮУVS)] 6SQ d"V*PbfhގfѾ7o|~ѭf"Kn$"\#Ln k6˲G#YXsp/qBban5%Iс#Yj>\j DœyҌy8KZ>\3kT4{0瓂q2$7SCɪbPI.#Yai-(H1kA# JBHaLi7pnkkq;9yDt"7#X14z=& LLRf#Y1X*4(cyBw;ewhE"@ ojޱ̼-1iY:nݍʍ=2Dqڄ-,0v_ϛW` >F6-B#nHއL`{DƵ㕙5}50Ed gQ2QRx#Y]u,cbfJ 6#r I$@hO<}xGN+<%Q׬,x@qJ2A3/FV2#ce1&7a Je#n*[o~)=IFg՘#m``#YjXh 66LԸe8$b1L@xB^ $xY )*bBUBJ%)H%%a!^BCnjffHM#Y49#91uY!"`E`{\8)d?t6,zR ,tpU4$pqv c=4iuF91UI7m]wcKec! Mi#,, Ì&-Syr*48^Z0c@p]׭[] K|iPm6 Lm#YZ 5+#n9Њ ct\QaO1+Mclxeec5,S5ce4#CSk(ֱ61ŭi6be5@E#a1 Ó0LXaD1#nEJ6w.sˇSɖ+xn)|O<bO8'0L( bk6pRD0r)ՠNv;ÎmAT,#YGҹ6**nF:YM54\pFICۑ0nrkݏ,t~#nm{m.ftqM̝!M;' uçm eY*6Hu#yoJeb'Nna:Zi;NSA4sDib{FK\"ʩ±cD( #n(vXBzHm[f2Ŷ2~+9f;eLXzAtWSڞrѾ~8nŘіL!IWgf[fBAeHڪkX6g6fFE-ԄٺѦQ P\s<%pq#nĀK'=NPQXH˸84`3c7QYJˏ0x;iq#XK`Y͊V(`U,|tE 8#n!$A:&7ՂЪP+!o)#XA#X]9#Yj1ŴFk!ssmwuoz;eItMMHKdG@r @)#nܯ`+vh"W=c#X҃4 I3E1gAPM0*mt\Ar)4f;b*(Z#nTaNB NM!!sDM#XRrTŊY@llPR2#]<ո}tUg fDu\%HaGhHXL #n}$/#Xw9Fd#\H(Uio R&)qR@¬#X'.9ʡ3-b2Ch(yP:(#nOw$:M C5(RA#JS4$UAdUT4DEQDxXDEnFA$ECLD4-#XRAKTT4%H4(K@LKQD0E L @PHy11 HRʨ9v#w$ |ȦH8 1y`bop>8G E"A3kF݌E4#Y4!MB"0 @X_V*'Y|Ӆs4Ak >#Y4:E*{!d)E(p pCJ"Izw{#Yd{BJh@L2Д4A#nbQ3O D4+LLBA0U I'A@:i#Xq0A(#XrRbQF(6 ȃ(IR !#nHPJRT(k 0]*8In~U@{o tF?J21HU'rac@4*ГpFS#n&k#Xӷp&"Pfb"Pً@x,[0 &ma:0 H3"wI%^J`J`-P᷁[!^rA@"|T.}6ϿQ}IH*|?Qd"L&Zro\5&haVYxRɆ.t(E#nS!&:'=]R-l{JjFH۾k[wwp#Y]Ir#YmFưN(2pj:\r.ren??O: 7aF"tZ+U4jYH&TQGzP24/Y YTd+d2Պ7We$t?lDTTY'HP't,#'^6 MP"#Y_+s:=#FOtE^@݁(#n5=j\7qy>X:br6Sb¹4W3'p$! ϲ#X&pGp#X(xH$QM U(DRPM#XPy~WLH^INʽ1zEB#n/ $R(}.ah _>]#Xzqؘ#n>ԑd~N#n&_"c$a䆴C;R_[F#X#nPa$/fG^y|h+eVۜ5QMQ1E`qTr8rhg~^ J T!,(WߕOH~8iWER\}! r$?'pɢkXYshHJ(+4C _܀ b9zEMAGd~Fq¸t|a "M?ٽ6qKT5uC~|4^S3q64n7c?$QT%AiE UD+PhF? ^?Anh͔JcჍ|@x\S^e3:OX96f"3YIwq2{+b:}lH(4ddR;վ6*H Vѐt:tD 0DC^TT-Ed4Rc35#XY1t JwD5ҩ`v`I5(s=(qA&6ƿ"/qrxz՚r`F=d@{46}rV:M=@w==thF#X!S\<1#X - +4@dKI(n}~}7j¾ #n\ŠR׽)Ep~{r*4Su(O_v0P66ybu*k*=ԀjripϟLL_0DC6Kq}]u6[c#XIyc_^Fr0]kUҰKl4"iF(VH:u¡qOCYx*MP!vE*aX**#YMU)N 74<4LD,I:8Pd!Ԍ')e>ykz=H:Kg&.j#YUu_b#Xf*t>N(o*cאS&Z:fM$5NX&ODry RCvZH&_# J@z p$<]{RɽnbJy? D2<)()(NL#X!B{E A]>(#Y{\-lb2AP;#X'i}+F 0,bA݉Ȥ#YE5ݦr_N z)N $0y"CbV%ϣ@,T?!\n1cd#n^BRvX ]7>_m8R@#YTQ;A"fh)I)#n$(VB!I#X(!j%۰PӫӈJ|ǬAQBZTRD;#Yî%YI@B$ Z Zb(EfF!DlR,__'@:+0aػ$.TPG *b)LJD|(xՁ#X 0*LDE0nEOItӷepyGd(ӉA{:`Iq`'F(e^_9؊/4gi@~[;"-0?Q0#nHaGyL@8lFtYllȞ6$#Xv9ytq~e<9BrEO& M##Y-G8 H> =dͿ'o":+#tF92v#XHT0wBrQ^w`4BZdPQ?dddHRJ ")}7GP6r050 e"B ʓY^A$AH';{8sv H l9B\+޲m1똖1A#XR1`HJKȨ̚\sw.#n aM(39ДO}-)êYH7ݰRA)$vC#IB-&z>aД% v#YW"#Y"d^Ө!E WH !.NA?i *3>HhAQPG] eU4dfbѠE6$e mT%#X%0L#Xp'dSGrD`c.pOWN#Y #XinU<*BSG2To(!ABWFID>4(UOPJD #Y쓠@ H_`/#YQ俉Z#{bU4zɦCTETR #YW##XX0u#X;#|ь"#n5Xj J7>xIM!8P/LH9@$OAI-`HC!'P2mu x"sLϫcXI[ND/IJM<6ӓU>:JWTEYd0oZh=#nd0 Ad(T}EP=E }x.P!qգi9P!1vb2A/ytQ<@ ''XMZ$XuF#Y󮁵(j~8 )q2VMy2Fm Oiٍ?œ\+/ӌ,Q0,qO}1rI=̨klfsbeN b9JLg,ȸzg-?҃ G;36X83SZSV@%A4JhUW[ƮTIaMbj\g'65h-wDaaÆN_N&g642fA3l?1B2gJ8R#n9d5}_ MB ';6aQ:yqDso괐5 #Y͆ǭb1ǢM)5#Y/셉uLљu,G#Y~"$r11N}Ă߲ai,$@ #=x1jqw0xcN/i~ ɓ{*nH@m6pwpA{Lڢp-yhhK6#YCM^>l!."Qc[FSwۑ#nH<1۷'<"_0!Z1toGT~苑Y.6#XdrB %T#Yȓ ~>r0#XB#XsJ."cXcap ;=PcqO7"#X›&H9A^;׿i?/JR"[.ݚ+cX>X n^+{С[#X(QT>'d$yi읺sj,!+8H}WI3kQ_j#YRNݑrYSw܌yOّg/H1äyЩv^sOה4^F5^J$޵Yypv%q#6Npݣ<(Қy+o9_c26#Y[޸ٍ{{6ɨdS)T"rͭ=,`dAj[c\1W[*dc\`t:z@S$zYaMaBu&#[҈-68I$*N( 2E$D Ill𕡝/S7.bȍ_g\=f/4#fdBu+BrE4:%#YT$LjJaNS&=9 4sQ0x}FIT68%u#~nEAzyYulVs8{*IF@7ܨyB썠5(*:f(B/!P dp4b76A}ҋ%_yh^R?98+PE U"C(AB ?! 漓6-5]5=<[O\^uO=&ÙMg䚀=Ϧ\4<'9Ӣ7~8'OFC{CKA#Y1y5אz<8Pw'z7KOy#nIEJ#n'H[@( c(21 z<r{C*#XWȔ&9)1"!67NVhF!:@[-hǡ_y c ӅfT4o_9+:ԍ3\z@~x?g8qb`]B=d <}:?%@qTDn+#XW-KV Mų9Ov(%e2}#Yh4q|q Ќ v60'ϳ)ld#nƊ!}߽WF7|^ Aغidh`i5C',dP5 l0Vߗcn433q 2zQY.SE|`6rc4#Xu4 +0qSٷݦ l{^^cȖ1ڤ#X(1ɒHOv(ZݖF3a6sʏL>n~fa8zUKjMQWIpMA*$h#Ysd#nUIXReIe He@P" ݈ͪý9/^k?FB@ʂ)`22$#nf& HIPsobLdL`0O|lbk"#Yyvj{~ZT`Zo0rey18!}!t Oq "ArOS휌;15 `q*dؒCHvT=Y1<,&93(Și ֘cJjEƴig Dr2e6M˜.G:ctM6̍/yhh$۷l%JH $#nZ`c$+M9X` dέawIe*VX3Dt~vHypz&mad"x"<eybtScMC 0n3 jecpkt >"|&urr}JgLLKj[lGEFBi:Hhi$#n^"@#I -3yA Q6oe¶)B{:AҚ'etG8=(6s0\+6 j P;pֹkDHY4V6]cޗc^iim 1#X|Z'7q{3.^Or#n`5AR7:8dGHRͪ:J(} )(P!ÎAk6uWǼzr|t Fϋ=P?1yRI>lN75BA$)A' G&qB``#n"()Q@ECBi#n!I"(6q5L_h% Ć;n|uLDlD.pήF#YAWxCXy;pp:Mߓ"0 fo8qMaĜ$/6oiGlcc t0+jchZg3FM36CX.4!9UJ ŴrHvZ ! u4PD5,SXBj(A55QrlpN;թ;o\tDH2zh!QHpwzU4n4tƞ^!r4aN79s ')x|1SA#n^ybV5maQ# 0kxEsV@& n|H7i(nZڱD&i$%8426ߜ~&*c(*[y2ׁ|f󂦈?!C"$J*G*V`(h;t$bPв0 $Wn_",}CTuf8uxixWD|v(BLI#NF>bdHxxRssS%չ#Y 4#Y<q^wW@U#Y$t;m##.$_O3#XYbсiνӧ&{ډ}7{j[h tK컁|M xWSN pT/51FX#>@`w04XEM%#Y(P-@ĸI?qNRvkvnAMArT9*@ɜa9oyi)B&Jy01Q(,~nm&Pdya}#Xm^ }+#Wԉ7pw.KO#*5Dvv{y4{xxHubs#Y(j"i~Ge =._V_{==;+(ZAK #Y4SHA\Ftq [TEW6E.b2rXivҺ#X:@p N4KJNTehZZiLJiPИbit@:V!ѭBSsE_Pw_Y#HE %ՀO().Ixc|6f #X>yRC_&.#XPN2V'H1)"!A@]\DXQx#XcJN )"LD7`nm9GBI/V !p9h 8rS_Ard()F {kIJ@5[XGuKBnҧ L447v#XuΛapVԿo/ @IE̐4< mX5u8CVHY`Δ&D3p:<EhV*h#X)@PL+#X$I E$*؝t5J$)"c:\:(hiWhڀ1-%31@!zzin~&ZZ-dPsZu)PhpR#Y/5$Nv@3#Y,?Gu-_Sc $\ö֊BHH}O`F+L ȩjH"6T[#YG +$X;; CtX#Yk%ERvus&5ΎhL= P5L\,E1LQ3DBRRS$F(BcxK;!^ObT}?GrY2n~O!<ˑŔp#cMf)ʲ!% 2 _YxA:8[|&j^ab$0$ uO{CJɓǘ̠~p_e֔9t&s+_I?$Mc̽&VGN{ABҘf;pPŨ;^^X“Ѝ`v23%3hm.CF1'#nqfVi ICxóՇ;T:Wǿo$_M&)́&`XVN)PZJ:LRXeg.͹YAWP?cR#Yj4%)#Yg+$d7C|us&e ~nM{\b26YI#6iFLwա+/Ŧ]x7>rc8;]|r MJUÎ9(#nXB{I(N 1Vv7=N'^K܏~˿-+.%CFw-8 0=)ו$cYeWF" 3pB};fS]|t{;}Cӱ¢YVze'YO›wk/LY}t>S6Ue8EQc*Ppb)LJТ3;Db5Dr9jL,s{'MWT6NZgSH%JQN̄1I7+At@Χ׊.ýIwtt-7dh:û[N>UReamd3t:hpYڡUĴ׌kL\կvq>l߂;&ڮ׷沲\4zP)91#Yg'f2\--!BK$h:뾷j=_o7#nc.4e~61y}H-^`VٜQ#nb)H{ԫx}Uphkyi-_ewGNviŅ=`+~B4ϧWå_Pqb3-qcrR0#%69qiJF]#nTָŨsk;?"~GCHep8}3cUCXrE1/ΐ|Ś︦^ݜΞä>>ރlY&4#X`QHidU'3j|@%D6EmsK{Fr//gv|0|uiiAbHvD`#YT7 R=y%2FdpB¿.7w |3"# ӵI`}q#m:2w]:&9p~f33BuJ;b#YWI4#Yn&#Y9ۣҟ)_LFo'{b͡ UĄNM*)ʚY@ l(KV)xш}3$B҉ڔt]*k\Hm ^Sb㚀`^d6O=m/Ry|7Aq"egW :yyi#n$4$q#Ν^)ƖX`rmӳvIHZ!0‹ф CU ٟjk#X`PT |'Н@ D9#Xg#Y`~=c4q&oS%q#X^eG0@́$`A2v8MxÔ=!C|% $DX8n#npW4%cTy2PC/":4b<*cm$'_]{0R`0q֠&#nf@]R),!d N\wȨ+7Q,ƹ>eAqY! =Ee7(RZ-V|74[Cw^qq&J.h#nd>$5.w``h\`?~C3wT"kֹ9 BdoM0zyrf:˾=qLa0N38J#YWge ıt|&~&S͐8vdh8#Pui*9*p_x-h^hs d#X64GZLṵJ<;!#X-XdB6:UTQՎX":f@bΞ;5pz'=)+;/mB(IS^NgzQ\Ŗ\B8=qQ-Q m+=MV<^?s9Q:膫dn/mX3=6ܹ&`1PЙX9vVMüQR|я=9?it8#g!MS^6kݯg5!,<#YC000ߜ!d"&.YC=LN3d7#Ydrz1F@`!SQ5ErB(eO)(TM rt6I}c1ѭ:5syECb6twbJF0cbw+6&ٌ}q=%1E"qFA0sd䕠5my`AhNAn$Dǎ=sTkD--qr(`w۹"#nR6>N#2ZB̆&3+=Bgig$Iʂ)x >-B[j% ⎑]AmD)86Mڧüx F( x)<ƪrg@5z<7hBaQ7!i.!B&9b %X8s`h"%w8QSH]%.!T:NVWxdKA[Ca m=kys#XPP(v Fcj+1j,r3i38>y3^#Y:w3A 2K#nElm #Yj$H7pf.d9'C9D6;_8!FBAqsi> EINpj"$F z\?#YWkQbR>pO91BvxcI<ؠ5,#YEAdD] hmmQE TU}<S g#F 5u٦#IKUܲss76%/W+w!,>Ʉ=#X'$(NMP6!`PQ:2PUUD>uzmИ2&] )04}ꂅW-kJ~O.#Y=Ԇzө@]>:M2oPužcߦ0SZ`l#XK-'d%^@JI$cNy^T ;Pˋ^ j:P4AztésSیh`6a`J 9qΫCY< >YT'tJt0%0TL6I)`]o>KF6}5"bϹrT:mRaQ7`0Π#n)R`&"HGXɵ4S KL 1D$%1#Y+lM j!"T*F(#nh(h&h%$bBm%-@S~8R8`T҉`#nPP5G1P)$*Oگ^*jdJ*Il<%(ہliA3r=H4ثrW i϶Lo5Kw}GSH22#nBRg}Hhg0h|{$.$*"cNyC1merҙkѡޤY-86a-c^8oLL$ĝBJ]A[le5aU\`lxQJƶoch+W Y,Ey(#Y1&n]ňܝkWSTt$&QraUH6ˁ;#Xr!9EUl89IA-0IĴ걳wLΜt@Nr=WyIx@=DF21ȄJǣQըm'gIH|ɥnϛp  >DP:2nS⮷} @,)r~#Y?G.r;eת{F+z)T!wI,0 >31(i AIBP;#0H:& 5V&CH9#n%|HЗ ,$-.r/A" !n4%14_B֟O/ɯ5K:HP!"l驖#XxqJrبo}8Z޿>EwU4#X0~oCB0F#n+HPK'ɤ>c>uSծIom\k t4!NP:'uG1@W5ATM'#n-3o<%OoJho_f^U3mz^I)Nq>f2nꪹf+9ƀrS,˙~kkswɹ6lÏNfN )l!8#nwJekXK<-v#iJ^<1ṶSG1w.6()E.Q#Y.$\bafxĖxlE)B> 8)ߑ6xG/k|6 yыr΢V5ΦyU#Y! VNMbA4p8Դqp#n`9}mITrC_s/amRiH8Тr瞛w-8Q-:Z_Ϫ >MϗCEBt&23 !bT#"#÷j`0VG.& '4ŗt{p|6a#hmK; g#Y&Ì9irvaƸqq!#Gbƹz3YF* t9A0\&}o;s1jslzw#X$V5xJv6ZlQcFI˼=Bx)Cpcnc%aAkL7I:Y}TG!2]Tt0McB!QM-3,Z\#ň; N3>r׹#X.#n!sq3s˙-k|٭,v/zR14Mi;]4s#X_t^) _CfW۱YJ4j}GtUVvv޸p;j fmގ8\K}L\ f@4 p3.iPjJE1ڱ́ Zӯ~t"Pu\$b^{;JNGBH5 c{'BD#XQp,cb4mѪeZO2He$x%4ӫwX{w:QU;cq.WT#Y,p pH+8阋DbGi#nq`ʂ]gK hѨoMi-Gﴶ\"|UwcHKZA:,jd| &/(bk*Ydf=b e͹<`=rO{SPbdp&噞d$t #YvsC,'˚3(`CĨ0Awhp#JCf#Y2al&.KvQϖptd#A"I,2ewT{ ^[[_-xRd%Mμu2ːvQFjv4qaM*%55DO4,XW[d{3a#X-"V8N±RD#HMyLU4&GKw \qs_Bb(L#nNP!9 )`3(HyE"Y%Am.G)pָ rWUDj?%Gy$.kz[~nfE!nl>D1MI'k#XA>/yP\4&'1ɧB~ΥMGUѐtCeጣ0A"R|q5by#XA'˹: 0%0]伇F8=[U8pc6(*4fD#nqbXJY9Cf80f#n-A) Ez $ted)81FR_x5\tC;$hRȃb*i6uð>CrF*{3&ѐ|پ>v9)DG#`h9ELWQS;#Y+h:EG` K`g{Х$01wJC0#nhB#ps~{I80M 8]Dtc t2W+}I/8xc= #n \1oF+#V_vLN{hײ\h'" $)bT<.\4\h9lvX:`ޡycQ#XįAC:$Nϯ*0>T19d! :#Yy#YsLQX)H #XIp~TH8B3yd_ڦ&tZ8Hؗ|A4113!4?$RP*)#YȹLGx0m@#BBpH3Z`a/#Xu:G2h(p9Ж0ic&NG8#nн dNNHO8S#nf \y02#n)Bz70ÅC/=JFqdo,mtnv=*Rk,+m[Gd0 vtq>YNyX  +5Zz˱"4 I:ID#Yҵ~\Gߊd$]T))QchZC}IDP .~t~54p3;sxC9iˣ鄢srneYIEkn 9}.>effKT#nҌkJfNxWW%P[DiL#6&(ZM#Yb8f-/DY|kW b;& }$WSEzKB>,M{yυBsm4394W)iѺ9k)1i61#Yn1S9jh5#Y20pCg ^57z+#Xu3|=&`C,"E9SP}'vH((64bmI62ʙhL=l)ϊ4רRFقNc˲("ΔgMS;f ʏ5~gyr*|nFp5!oX}o >#ظHRO_ #)Ű074]L""ag Lzb.*#nIP<;.ف!AchU4d.0)r\+&YƤ#Y cwwy%#Y#vA-qDq_gHT{^QɯQ?& ;֢?vffV1Vr}TZqڦƈ [#(D:䰅ٮ\LhhX)X"'4d.b뿨S#XPA3P?\PHP ]3wn'&`6! #X2DAHMD"}ҙ@4DBN hC NBWaB$IXy#XPC$ )31A)#n@iE5V]#XihD9#XiG#n#n:F B#nY&XOGjt%e%U4=rtaxH?XnA0̰6Dy%#X4%tO@@#nD#STP/]?Ҽw1m {-xҡHR!D#Y#Y3 #_ǰ;?*H{"wCۇDNwh ۱ŎP*#X )9U2<`j3u;F^mrrNo$: :fkz;x{w^iнFD4OiE9x-Ri2b>DȓFrE>h̋L4Ɲ5uִMkurd  VАɴ#YL&#o:\拠$ipibp-DrKJUxCn#Y'#X* LzLTz8%:X-g*krmb7-G6 +RVfLv[s(-#&mٌF!#Y0U+l1!X$mǁG`I`Hnj y!poCTpD<#n:\81/s&hK#nJj%)Okj9lT):pgZŋni6#n)My#Y#X#XZD(2L("K@恩+U?ٯY;gx4_.vnl\`MjcAQw1Y |q i-@3K"(Moa6rT27aHPz<<ܑAYPāK,ibJHjje̡Q/e!SB(R|2@h%N@4*R-H֑)Rh8dz}P_al#X?x%p+#X 01!BC0M4 U#X+HD)HCA@@#nQxpp᠃2ʑSw] MߡD/^X}R4z}^oGeP!.{ 0tGV T P#Y)J4B&W.>YLv{<5@$ #~P"P|#YS׷/5O85>O9.kxTe|*z@f[ZJ[2F?rkw+U)hES;PeΔ1:ZLatW!С1-8#Y%J,C#ngIf"%LG9AmKA(|\V(Z*%s9 T%2K!"8h^p56VmꩪD\+unur^*r^#nYɖ"NZ1x7Y;&r쎡ShyQnb"tۉ7H6ݔɼLP%A4I(‘J̎#na|,X'02d^iwQה@$AQwnic A}KL@#`iRiIP;۔j‚t^ZptH3hPb9tn qC|MdĔK7'MW9BGI<C؂h`eb'gC9 䝊Xy?'A1vsJ&21bo4FOW{f#neSRHI"^O]*-mZRuq72AKZ5pJa%R`mՠX(\s#Xߓ~ '=a ~1W7Q<H4#X>qhJ=APԐN;e:(%B(5uX#Xwj"-o4AITm[&"eby.\5 \^n.d]ifB#nX"Y D&(PIE(ZUTs҃4TQ_#8ǵQzk@RQARR 1P4LR H%4QTPCLKA !LPSB@rEUOWNȠ*I0?ND.p8ᓘ8 ضRsiD *HaR, ÁI-¸;>9%+c늪#Xy#XpbA;:D(DV]d$#Yt$) #X( (:1GP8!B=p#nǫsښa# F& cx$؇OњWs}iSIDB#HU-c$5Tɂ2 9*M"ala[DF6Ӫjm嬑%+I$A--%CIHmVc1"B#nx_^sHI#nQ.DǩWWM#X~:Ԡ qcr(?K J;)2,bd`xBDRx=*4fRv(x`y,z`|N)'v9a(4~Q)LnQ}&4H@ %3傉P*ϱ rd1h2"Tan̈>qk8g9{k(t6~v*gJAu=o>cZǵb6#Y2C_SXqˤ}7囻wE*t|/K."$~ƚ^u`n˶h!B*XPrn#n#n&oNUQжkXA 9\ŸsEgV,tg[{¿{UrN#Yd,4'dD l j-"l[Dc#XnY8s I;M"v馲J(C6E/P#Xut2} H$1ßKQ]nP0rIaI\⥦&#X8P<\9:/\|DDh10q,NNwr!`D(krAPA.FS9ra)ȝv9c?^ha,kJ} đ~R1f1Å.pĝj 9=m#Yq_pH8#Y4*҃SS:br4Nxtϖ*#<H4Q{ՊPʓUR*m(9\!>a$=;5_@$S7sG@#X5K'98 ]t"Y=,Un'"Ib*jI* -ATDM41' ' \ogf+cdMhA<_#Xׂ-G|gPb=@H(>ǭy}о`CCT?=\Jm#Xؐ 7N~AyF щHҋaLK_יfbQ51TM2tV#XY#X5H68'ci1~B㍫w{iӠ E(ꂛ!#E[:vvB#M8zv;:μC;K#X0Q ph?c;iMQWFս-֤pO&yu(X~DNrųJE+?Dzlq_eVcS$wl'(#n!dx^o|:4O"' gJxE,k@ta7R3qTG_6 :욓lE4&HY'>}z>.jGt75/3=#Y!C[*:P%#X@'|ttP#Y@$JF&"d(eYtxC+;g(Z#Ylb$&1O,^!ch:|t?f#?R.UaBV|$5@Q_V?2~>>PUB20 A!ޘyAʧ:u]:.b{i#nhb#n (SB$#n#XH!*)@d&@iBTO~t?l#XiA8~Y0 CC_Gc1%A8T>~Up6L1#W^60RE%RE#n㷎/qk0+35clv#nU-e~`̔bj$K@JeH+TxȚD>#׼k7nlx!;XCxSٸf`N44BJPwSkppAc#X|zL|9Y<2p9UjcnWS8h ! !P;*#n)kst3Ѐ7^jq2}HI][+)xz?dz BP:GTsǷ+6ej#2 2IsHwx#ȥ1$āi 7_*)ELQ2TR@PHEEK̥:?#Yez@L@=b[J4Y56]Ӵthxؑd8XﵟGIGIwFu (~?m2 胄ufҏe)*qP;̀"'|DQ~Go}.63*(?>L?ş}!ǭxy|)ΟpO'Y0=8)w)[ (<΋}'F<|6Խ%p#n1i)[V AN뀖$7#YЗ]{#XRgq^ï>NLb #e1HV/7 K/j)pzD$?"(H5R) #<== diff --git a/wscript b/wscript index c74cd960..5b23d9e2 100644 --- a/wscript +++ b/wscript @@ -3,12 +3,14 @@ # a1batross, mittorn, 2018 from waflib import Build, Context, Logs +from waflib.Tools import waf_unit_test import sys import os VERSION = '0.99' APPNAME = 'xash3d-fwgs' top = '.' +default_prefix = '/' # Waf uses it to set default prefix Context.Context.line_just = 55 # should fit for everything on 80x26 @@ -29,6 +31,35 @@ class Subproject: return True +class RefDll: + def __init__(self, name, default, key = None): + self.name = name + self.default = default + self.dest = key if key else name.upper() + + def register_option(self, opt): + kw = dict() + if self.default: + act = 'disable' + kw['action'] = 'store_false' + else: + act = 'enable' + kw['action'] = 'store_true' + + key = '--%s-%s' % (act, self.name) + + kw['dest'] = self.dest + kw['default'] = self.default + kw['help'] = '%s %s renderer [default: %%default]' % (act, self.name) + + opt.add_option(key, **kw) + + def register_env(self, env, opts, force): + env[self.dest] = force or opts.__dict__[self.dest] + + def register_define(self, conf): + conf.define_cond('XASH_REF_%s_ENABLED' % self.dest, conf.env[self.dest]) + SUBDIRS = [ # always configured and built Subproject('public'), @@ -56,9 +87,23 @@ SUBDIRS = [ # enabled optionally Subproject('utils/mdldec', lambda x: x.env.ENABLE_UTILS), Subproject('utils/run-fuzzer', lambda x: x.env.ENABLE_FUZZER), + + # enabled on PSVita only + Subproject('ref/gl/vgl_shim', lambda x: x.env.DEST_OS == 'psvita'), +] + +REFDLLS = [ + RefDll('soft', True), + RefDll('gl', True), + RefDll('vulkan', True), + RefDll('gles1', False, 'NANOGL'), + RefDll('gles2', False, 'GLWES'), + RefDll('gl4es', False), ] def options(opt): + opt.load('reconfigure compiler_optimizations xshlib xcompile compiler_cxx compiler_c sdl2 clang_compilation_database strip_on_install waf_unit_test msdev msvs msvc subproject') + grp = opt.add_option_group('Common options') grp.add_option('-d', '--dedicated', action = 'store_true', dest = 'DEDICATED', default = False, @@ -88,28 +133,16 @@ def options(opt): grp.add_option('--disable-werror', action = 'store_true', dest = 'DISABLE_WERROR', default = False, help = 'disable compilation abort on warning') + grp.add_option('--enable-tests', action = 'store_true', dest = 'TESTS', default = False, + help = 'enable building standalone tests (does not enable engine tests!) [default: %default]') + grp = opt.add_option_group('Renderers options') grp.add_option('--enable-all-renderers', action='store_true', dest='ALL_RENDERERS', default=False, help = 'enable all renderers supported by Xash3D FWGS [default: %default]') - grp.add_option('--enable-gles1', action='store_true', dest='NANOGL', default=False, - help = 'enable gles1 renderer [default: %default]') - - grp.add_option('--enable-gles2', action='store_true', dest='GLWES', default=False, - help = 'enable gles2 renderer [default: %default]') - - grp.add_option('--enable-gl4es', action='store_true', dest='GL4ES', default=False, - help = 'enable gles2 renderer [default: %default]') - - grp.add_option('--disable-gl', action='store_false', dest='GL', default=True, - help = 'disable opengl renderer [default: %default]') - - grp.add_option('--disable-vulkan', action='store_false', dest='VULKAN', default=True, - help = 'disable vulkan renderer [default: %default]') - - grp.add_option('--disable-soft', action='store_false', dest='SOFT', default=True, - help = 'disable soft renderer [default: %default]') + for dll in REFDLLS: + dll.register_option(grp) grp = opt.add_option_group('Utilities options') @@ -119,16 +152,12 @@ def options(opt): 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: if not i.is_exists(opt): continue opt.add_subproject(i.name) - opt.load('xshlib xcompile compiler_cxx compiler_c sdl2 clang_compilation_database strip_on_install waf_unit_test msdev msvs msvc reconfigure') - def configure(conf): conf.load('fwgslib reconfigure compiler_optimizations') conf.env.MSVC_TARGETS = ['x86' if not conf.options.ALLOW64 else 'x64'] @@ -139,6 +168,9 @@ def configure(conf): if conf.options.NSWITCH: conf.load('nswitch') + if conf.options.PSVITA: + conf.load('psvita') + # HACKHACK: override msvc DEST_CPU value by something that we understand if conf.env.DEST_CPU == 'amd64': conf.env.DEST_CPU = 'x86_64' @@ -185,6 +217,15 @@ def configure(conf): conf.options.NO_ASYNC_RESOLVE = True conf.options.USE_STBTT = True conf.options.VULKAN = False # https://github.com/w23/xash3d-fwgs/issues/429 + elif conf.env.DEST_OS == 'psvita': + conf.options.NO_VGUI = True + conf.options.GL = True + conf.options.SINGLE_BINARY = True + conf.options.NO_ASYNC_RESOLVE = True + conf.options.USE_STBTT = True + conf.options.VULKAN = False + # we'll specify -fPIC by hand for shared libraries only + enforce_pic = False if conf.env.STATIC_LINKING: enforce_pic = False # PIC may break full static builds @@ -202,54 +243,8 @@ def configure(conf): conf.load('force_32bit') - compiler_optional_flags = [ -# '-Wall', '-Wextra', '-Wpedantic', - '-fdiagnostics-color=always', - '-Werror=return-type', - '-Werror=parentheses', - '-Werror=vla', - '-Werror=tautological-compare', - '-Werror=duplicated-cond', - '-Werror=bool-compare', - '-Werror=bool-operation', - '-Werror=cast-align=strict', # =strict is for GCC >=8 - '-Werror=packed', - '-Werror=packed-not-aligned', - '-Wuninitialized', # older GCC versions have -Wmaybe-uninitialized enabled by this switch, which is not accurate - # so just warn, not error - '-Winit-self', - '-Werror=implicit-fallthrough=2', # clang incompatible without "=2" - '-Werror=logical-op', - '-Werror=write-strings', - '-Werror=sizeof-pointer-memaccess', - '-Werror=sizeof-array-div', - '-Werror=sizeof-pointer-div', - '-Werror=string-compare', - '-Werror=use-after-free=3', - '-Werror=sequence-point', -# '-Werror=format=2', -# '-Wdouble-promotion', # disable warning flood - '-Wstrict-aliasing', - '-Wmisleading-indentation', - ] - - c_compiler_optional_flags = [ - '-Werror=incompatible-pointer-types', - '-Werror=implicit-function-declaration', - '-Werror=int-conversion', - '-Werror=implicit-int', - '-Werror=strict-prototypes', - '-Werror=old-style-declaration', - '-Werror=old-style-definition', - '-Werror=declaration-after-statement', - '-Werror=enum-conversion', - '-Werror=jump-misses-init', - '-Werror=strict-prototypes', -# '-Werror=nested-externs', - '-fnonconst-initializers' # owcc - ] - cflags, linkflags = conf.get_optimization_flags() + cxxflags = list(cflags) # optimization flags are common between C and C++ but we need a copy # on the Switch, allow undefined symbols by default, which is needed for libsolder to work # we'll specifically disallow them for the engine executable @@ -258,43 +253,96 @@ def configure(conf): linkflags.remove('-Wl,--no-undefined') conf.env.append_unique('LINKFLAGS_cshlib', ['-nostdlib', '-nostartfiles']) conf.env.append_unique('LINKFLAGS_cxxshlib', ['-nostdlib', '-nostartfiles']) + # same on the vita + elif conf.env.DEST_OS == 'psvita': + conf.env.append_unique('CFLAGS_cshlib', ['-fPIC']) + conf.env.append_unique('CXXFLAGS_cxxshlib', ['-fPIC', '-fno-use-cxa-atexit']) + conf.env.append_unique('LINKFLAGS_cshlib', ['-nostdlib', '-Wl,--unresolved-symbols=ignore-all']) + conf.env.append_unique('LINKFLAGS_cxxshlib', ['-nostdlib', '-Wl,--unresolved-symbols=ignore-all']) + # check if we need to use irix linkflags + elif conf.env.DEST_OS == 'irix' and conf.env.COMPILER_CC == 'gcc': + linkflags.remove('-Wl,--no-undefined') + linkflags.append('-Wl,--unresolved-symbols=ignore-all') + # check if we're in a sgug environment + if 'sgug' in os.environ['LD_LIBRARYN32_PATH']: + linkflags.append('-lc') - # And here C++ flags starts to be treated separately - cxxflags = list(cflags) - if conf.env.COMPILER_CC != 'msvc' and not conf.options.DISABLE_WERROR: - conf.check_cc(cflags=cflags, linkflags=linkflags, msg='Checking for required C flags') - conf.check_cxx(cxxflags=cflags, linkflags=linkflags, msg='Checking for required C++ flags') - - conf.env.append_unique('CFLAGS', cflags) - conf.env.append_unique('CXXFLAGS', cxxflags) - conf.env.append_unique('LINKFLAGS', linkflags) - - cxxflags += conf.filter_cxxflags(compiler_optional_flags, cflags) - cflags += conf.filter_cflags(compiler_optional_flags + c_compiler_optional_flags, cflags) - - # check if we need to use irix linkflags - if conf.env.DEST_OS == 'irix' and conf.env.COMPILER_CC == 'gcc': - linkflags.remove('-Wl,--no-undefined') - linkflags.append('-Wl,--unresolved-symbols=ignore-all') - # check if we're in a sgug environment - if 'sgug' in os.environ['LD_LIBRARYN32_PATH']: - linkflags.append('-lc') + conf.check_cc(cflags=cflags, linkflags=linkflags, msg='Checking for required C flags') + conf.check_cxx(cxxflags=cxxflags, linkflags=linkflags, msg='Checking for required C++ flags') conf.env.append_unique('CFLAGS', cflags) conf.env.append_unique('CXXFLAGS', cxxflags) conf.env.append_unique('LINKFLAGS', linkflags) + if conf.env.COMPILER_CC != 'msvc' and not conf.options.DISABLE_WERROR: + opt_flags = [ + # '-Wall', '-Wextra', '-Wpedantic', + '-fdiagnostics-color=always', + + # stable diagnostics, forced to error, sorted + '-Werror=bool-compare', + '-Werror=bool-operation', + '-Werror=cast-align=strict', + '-Werror=duplicated-cond', + # '-Werror=format=2', + '-Werror=implicit-fallthrough=2', + '-Werror=logical-op', + '-Werror=packed', + '-Werror=packed-not-aligned', + '-Werror=parentheses', + '-Werror=return-type', + '-Werror=sequence-point', + '-Werror=sizeof-pointer-memaccess', + '-Werror=sizeof-array-div', + '-Werror=sizeof-pointer-div', + '-Werror=strict-aliasing', + '-Werror=string-compare', + '-Werror=tautological-compare', + '-Werror=use-after-free=3', + '-Werror=vla', + '-Werror=write-strings', + + # unstable diagnostics, may cause false positives + '-Winit-self', + '-Wmisleading-indentation', + '-Wunintialized', + + # disabled, flood + # '-Wdouble-promotion', + ] + + opt_cflags = [ + '-Werror=declaration-after-statement', + '-Werror=enum-conversion', + '-Werror=implicit-int', + '-Werror=implicit-function-declaration', + '-Werror=incompatible-pointer-types', + '-Werror=int-conversion', + '-Werror=jump-misses-init', + '-Werror=old-style-declaration', + '-Werror=old-style-definition', + '-Werror=strict-prototypes', + '-fnonconst-initializers' # owcc + ] + + opt_cxxflags = [] # TODO: + + cflags = conf.filter_cflags(opt_flags + opt_cflags, cflags) + cxxflags = conf.filter_cxxflags(opt_flags + opt_cxxflags, cxxflags) + + conf.env.append_unique('CFLAGS', cflags) + conf.env.append_unique('CXXFLAGS', cxxflags) + + conf.env.TESTS = conf.options.TESTS 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 - conf.env.NANOGL = conf.options.NANOGL or conf.options.ALL_RENDERERS - conf.env.GLWES = conf.options.GLWES or conf.options.ALL_RENDERERS - conf.env.GL4ES = conf.options.GL4ES or conf.options.ALL_RENDERERS - conf.env.GL = conf.options.GL or conf.options.ALL_RENDERERS - conf.env.SOFT = conf.options.SOFT or conf.options.ALL_RENDERERS - conf.env.VULKAN = conf.options.VULKAN or conf.options.ALL_RENDERERS + setattr(conf, 'refdlls', REFDLLS) + + for refdll in REFDLLS: + refdll.register_env(conf.env, conf.options, conf.options.ALL_RENDERERS) conf.env.GAMEDIR = conf.options.GAMEDIR conf.define('XASH_GAMEDIR', conf.options.GAMEDIR) @@ -308,20 +356,19 @@ def configure(conf): elif conf.check_cc(header_name='malloc.h', mandatory=False): conf.define('ALLOCA_H', 'malloc.h') - if conf.env.DEST_OS != 'win32': - if conf.env.DEST_OS == 'nswitch': - conf.check_cfg(package='solder', args='--cflags --libs', uselib_store='SOLDER', mandatory=True) - if conf.env.HAVE_SOLDER and conf.env.LIB_SOLDER and conf.options.BUILD_TYPE == 'debug': - conf.env.LIB_SOLDER[0] += 'd' # load libsolderd in debug mode - else: - conf.check_cc(lib='dl', mandatory=False) - - if not conf.env.LIB_M: # HACK: already added in xcompile! - conf.check_cc(lib='m') - - if conf.env.DEST_OS == 'android': - conf.check_cc(lib='log') - else: + if conf.env.DEST_OS == 'nswitch': + conf.check_cfg(package='solder', args='--cflags --libs', uselib_store='SOLDER') + if conf.env.HAVE_SOLDER and conf.env.LIB_SOLDER and conf.options.BUILD_TYPE == 'debug': + conf.env.LIB_SOLDER[0] += 'd' # load libsolderd in debug mode + conf.check_cc(lib='m') + elif conf.env.DEST_OS == 'psvita': + conf.check_cc(lib='vrtld') + conf.check_cc(lib='m') + elif conf.env.DEST_OS == 'android': + conf.check_cc(lib='dl') + conf.check_cc(lib='log') + # LIB_M added in xcompile! + elif conf.env.DEST_OS == 'win32': # Common Win32 libraries # Don't check them more than once, to save time # Usually, they are always available @@ -335,6 +382,10 @@ def configure(conf): else: for i in a: conf.check_cc(lib = i) + else: + conf.check_cc(lib='dl') + conf.check_cc(lib='m') + # check if we can use C99 tgmath if conf.check_cc(header_name='tgmath.h', mandatory=False): @@ -382,13 +433,12 @@ int main(int argc, char **argv) { strchrnul(argv[1], 'x'); return 0; }''' check_gnu_function(strchrnul_frag, 'Checking for strchrnul', 'HAVE_STRCHRNUL') # indicate if we are packaging for Linux/BSD + conf.env.PACKAGING = conf.options.PACKAGING if conf.options.PACKAGING: + conf.env.PREFIX = conf.options.prefix conf.env.LIBDIR = conf.env.BINDIR = conf.env.LIBDIR + '/xash3d' conf.env.SHAREDIR = '${PREFIX}/share/xash3d' else: - if sys.platform != 'win32' and conf.env.DEST_OS != 'android': - conf.env.PREFIX = '/' - conf.env.SHAREDIR = conf.env.LIBDIR = conf.env.BINDIR = conf.env.PREFIX if not conf.options.BUILD_BUNDLED_DEPS: @@ -412,6 +462,10 @@ int main(void){ return !opus_custom_encoder_init(0, 0, 0); }''', fatal = False): conf.add_subproject(i.name) def build(bld): + # guard rails to not let install to root + if bld.is_install and not bld.options.PACKAGING and not bld.options.destdir: + bld.fatal('Set the install destination directory using --destdir option') + # don't clean QtCreator files and reconfigure saved options bld.clean_files = bld.bldnode.ant_glob('**', excl='*.user configuration.py .lock* *conf_check_*/** config.log %s/*' % Build.CACHE_DIR, @@ -424,3 +478,7 @@ def build(bld): continue bld.add_subproject(i.name) + + if bld.env.TESTS: + bld.add_post_fun(waf_unit_test.summary) + bld.add_post_fun(waf_unit_test.set_exit_code)