diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml deleted file mode 100644 index 6578f9e1..00000000 --- a/.github/workflows/linux.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Testbuild for Linux -run-name: testbuild_linux -on: - push: - branches: - - 'master' - pull_request: - types: - - edited - - opened - - synchronize -concurrency: - # Cancel concurrent workflows for the same PR or commit hash. - group: ${{github.workflow}}-${{github.event_name == 'pull_request' && github.head_ref || github.sha}} - cancel-in-progress: true -jobs: - build_ubuntu_x86_64: - runs-on: ubuntu-22.04 - strategy: - fail-fast: false - matrix: - include: - - env: ubuntu - steps: - - name: Install build dependencies - run: | - sudo apt update - sudo apt install libgl1-mesa-dev libsdl2-dev libopenal-dev libcurl4-openssl-dev \ - libavformat-dev libswscale-dev libvulkan-dev build-essential - - name: Check out repository code - uses: actions/checkout@v4 - - name: Build - run: | - sed -i 's|WITH_AVCODEC:=yes|WITH_AVCODEC:=no|g' Makefile - # Public runners come with 2 CPUs. - make -j2 - make -j2 ref_gles1 - - name: Create testbuild package - run: | - # Create release directory tree - mkdir -p publish/quake2-linux-${{github.sha}}/misc/docs - # Copy release assets - cp -r release/* publish/quake2-linux-${{github.sha}}/ - # Copy misc assets - cp -r stuff/yq2.cfg publish/quake2-linux-${{github.sha}}/misc/yq2.cfg - cp -r stuff/mapfixes publish/quake2-linux-${{github.sha}}/misc - cp LICENSE publish/quake2-linux-${{github.sha}}/misc/docs/LICENSE.txt - cp README.md publish/quake2-linux-${{github.sha}}/misc/docs/README.txt - cp doc/010_index.md publish/quake2-linux-${{github.sha}}/misc/docs/010_index.txt - cp doc/020_installation.md publish/quake2-linux-${{github.sha}}/misc/docs/020_installation.txt - cp doc/030_configuration.md publish/quake2-linux-${{github.sha}}/misc/docs/030_configuration.txt - cp doc/040_cvarlist.md publish/quake2-linux-${{github.sha}}/misc/docs/040_cvarlist.txt - cp doc/050_commands.md publish/quake2-linux-${{github.sha}}/misc/docs/050_commands.txt - cp doc/060_multiplayer.md publish/quake2-linux-${{github.sha}}/misc/docs/060_multiplayer.txt - cp doc/070_packaging.md publish/quake2-linux-${{github.sha}}/misc/docs/070_packaging.txt - cp doc/080_contributing.md publish/quake2-linux-${{github.sha}}/misc/docs/080_contributing.txt - cp doc/090_filelists.md publish/quake2-linux-${{github.sha}}/misc/docs/090_filelists.md - - name: Upload testbuild package - uses: actions/upload-artifact@v4 - with: - name: quake2-linux-${{github.sha}} - path: publish/ - if-no-files-found: error diff --git a/.github/workflows/linux_aarch64.yml b/.github/workflows/linux_aarch64.yml new file mode 100644 index 00000000..4891facf --- /dev/null +++ b/.github/workflows/linux_aarch64.yml @@ -0,0 +1,63 @@ +name: Testbuild for Linux (aarch64) +run-name: testbuild_linux_aarch64 +on: + push: + branches: + - 'master' + pull_request: + types: + - edited + - opened + - synchronize +concurrency: + # Cancel concurrent workflows for the same PR or commit hash. + group: ${{github.workflow}}-${{github.event_name == 'pull_request' && github.head_ref || github.sha}} + cancel-in-progress: true +jobs: + build_ubuntu_aarch64: + runs-on: ubuntu-22.04-arm + strategy: + fail-fast: false + matrix: + include: + - env: ubuntu + steps: + - name: Install build dependencies + run: | + sudo apt update + sudo apt install libgl1-mesa-dev libsdl2-dev libopenal-dev libcurl4-openssl-dev \ + libavformat-dev libswscale-dev libvulkan-dev build-essential + - name: Check out repository code + uses: actions/checkout@v4 + - name: Build + run: | + sed -i 's|WITH_AVCODEC:=yes|WITH_AVCODEC:=no|g' Makefile + # Public runners come with 4 CPUs. + make -j4 + make -j4 ref_gles1 + - name: Create testbuild package + run: | + # Create release directory tree + mkdir -p publish/quake2-linux_aarch64-${{github.sha}}/misc/docs + # Copy release assets + cp -r release/* publish/quake2-linux_aarch64-${{github.sha}}/ + # Copy misc assets + cp -r stuff/yq2.cfg publish/quake2-linux_aarch64-${{github.sha}}/misc/yq2.cfg + cp -r stuff/mapfixes publish/quake2-linux_aarch64-${{github.sha}}/misc + cp LICENSE publish/quake2-linux_aarch64-${{github.sha}}/misc/docs/LICENSE.txt + cp README.md publish/quake2-linux_aarch64-${{github.sha}}/misc/docs/README.txt + cp doc/010_index.md publish/quake2-linux_aarch64-${{github.sha}}/misc/docs/010_index.txt + cp doc/020_installation.md publish/quake2-linux_aarch64-${{github.sha}}/misc/docs/020_installation.txt + cp doc/030_configuration.md publish/quake2-linux_aarch64-${{github.sha}}/misc/docs/030_configuration.txt + cp doc/040_cvarlist.md publish/quake2-linux_aarch64-${{github.sha}}/misc/docs/040_cvarlist.txt + cp doc/050_commands.md publish/quake2-linux_aarch64-${{github.sha}}/misc/docs/050_commands.txt + cp doc/060_multiplayer.md publish/quake2-linux_aarch64-${{github.sha}}/misc/docs/060_multiplayer.txt + cp doc/070_packaging.md publish/quake2-linux_aarch64-${{github.sha}}/misc/docs/070_packaging.txt + cp doc/080_contributing.md publish/quake2-linux_aarch64-${{github.sha}}/misc/docs/080_contributing.txt + cp doc/090_filelists.md publish/quake2-linux_aarch64-${{github.sha}}/misc/docs/090_filelists.md + - name: Upload testbuild package + uses: actions/upload-artifact@v4 + with: + name: quake2-linux_aarch64-${{github.sha}} + path: publish/ + if-no-files-found: error diff --git a/.github/workflows/linux_x86_64.yml b/.github/workflows/linux_x86_64.yml new file mode 100644 index 00000000..3722a1e0 --- /dev/null +++ b/.github/workflows/linux_x86_64.yml @@ -0,0 +1,63 @@ +name: Testbuild for Linux (x86_64) +run-name: testbuild_linux_x86_64 +on: + push: + branches: + - 'master' + pull_request: + types: + - edited + - opened + - synchronize +concurrency: + # Cancel concurrent workflows for the same PR or commit hash. + group: ${{github.workflow}}-${{github.event_name == 'pull_request' && github.head_ref || github.sha}} + cancel-in-progress: true +jobs: + build_ubuntu_x86_64: + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + include: + - env: ubuntu + steps: + - name: Install build dependencies + run: | + sudo apt update + sudo apt install libgl1-mesa-dev libsdl2-dev libopenal-dev libcurl4-openssl-dev \ + libavformat-dev libswscale-dev libvulkan-dev build-essential + - name: Check out repository code + uses: actions/checkout@v4 + - name: Build + run: | + sed -i 's|WITH_AVCODEC:=yes|WITH_AVCODEC:=no|g' Makefile + # Public runners come with 4 CPUs. + make -j4 + make -j4 ref_gles1 + - name: Create testbuild package + run: | + # Create release directory tree + mkdir -p publish/quake2-linux_x86_64-${{github.sha}}/misc/docs + # Copy release assets + cp -r release/* publish/quake2-linux_x86_64-${{github.sha}}/ + # Copy misc assets + cp -r stuff/yq2.cfg publish/quake2-linux_x86_64-${{github.sha}}/misc/yq2.cfg + cp -r stuff/mapfixes publish/quake2-linux_x86_64-${{github.sha}}/misc + cp LICENSE publish/quake2-linux_x86_64-${{github.sha}}/misc/docs/LICENSE.txt + cp README.md publish/quake2-linux_x86_64-${{github.sha}}/misc/docs/README.txt + cp doc/010_index.md publish/quake2-linux_x86_64-${{github.sha}}/misc/docs/010_index.txt + cp doc/020_installation.md publish/quake2-linux_x86_64-${{github.sha}}/misc/docs/020_installation.txt + cp doc/030_configuration.md publish/quake2-linux_x86_64-${{github.sha}}/misc/docs/030_configuration.txt + cp doc/040_cvarlist.md publish/quake2-linux_x86_64-${{github.sha}}/misc/docs/040_cvarlist.txt + cp doc/050_commands.md publish/quake2-linux_x86_64-${{github.sha}}/misc/docs/050_commands.txt + cp doc/060_multiplayer.md publish/quake2-linux_x86_64-${{github.sha}}/misc/docs/060_multiplayer.txt + cp doc/070_packaging.md publish/quake2-linux_x86_64-${{github.sha}}/misc/docs/070_packaging.txt + cp doc/080_contributing.md publish/quake2-linux_x86_64-${{github.sha}}/misc/docs/080_contributing.txt + cp doc/090_filelists.md publish/quake2-linux_x86_64-${{github.sha}}/misc/docs/090_filelists.md + - name: Upload testbuild package + uses: actions/upload-artifact@v4 + with: + name: quake2-linux_x86_64-${{github.sha}} + path: publish/ + if-no-files-found: error diff --git a/.github/workflows/win32.yml b/.github/workflows/win32.yml index 4bdc46d3..3c856664 100644 --- a/.github/workflows/win32.yml +++ b/.github/workflows/win32.yml @@ -46,8 +46,8 @@ jobs: shell: msys2 {0} run: | sed -i 's|WITH_AVCODEC:=yes|WITH_AVCODEC:=no|g' Makefile - # Public runners come with 2 CPUs. - make -j2 + # Public runners come with 4 CPUs. + make -j4 - name: Create testbuild package shell: msys2 {0} run: | @@ -70,8 +70,8 @@ jobs: cp doc/080_contributing.md publish/quake2-win32-${{github.sha}}/misc/docs/080_contributing.txt cp doc/090_filelists.md publish/quake2-win32-${{github.sha}}/misc/docs/090_filelists.md # SDL2 - wget -c https://github.com/libsdl-org/SDL/releases/download/release-2.30.11/SDL2-2.30.11-win32-x86.zip - unzip -o SDL2-2.30.11-win32-x86.zip + wget -c https://github.com/libsdl-org/SDL/releases/download/release-2.32.0/SDL2-2.32.0-win32-x86.zip + unzip -o SDL2-2.32.0-win32-x86.zip cp SDL2.dll publish/quake2-win32-${{github.sha}}/ # openal-soft wget -c https://github.com/kcat/openal-soft/releases/download/1.23.1/openal-soft-1.23.1-bin.zip diff --git a/.github/workflows/win64.yml b/.github/workflows/win64.yml index 2ede8563..2f945aee 100644 --- a/.github/workflows/win64.yml +++ b/.github/workflows/win64.yml @@ -1,4 +1,4 @@ -name: Testbuild for Win64 +name: Testbuild for Win64 (SDL3) run-name: testbuild_win64 on: push: @@ -8,8 +8,9 @@ on: - "*" pull_request: types: - - opened - edited + - opened + - synchronize concurrency: # Cancel concurrent workflows for the same PR or commit hash. group: ${{github.workflow}}-${{github.event_name == 'pull_request' && github.head_ref || github.sha}} @@ -37,17 +38,19 @@ jobs: mingw-w64-${{matrix.env}}-gcc mingw-w64-${{matrix.env}}-make mingw-w64-${{matrix.env}}-openal - mingw-w64-${{matrix.env}}-SDL2 + mingw-w64-${{matrix.env}}-sdl3 mingw-w64-${{matrix.env}}-vulkan-headers mingw-w64-${{matrix.env}}-wget + mingw-w64-${{matrix.env}}-pkgconf - name: Check out repository code uses: actions/checkout@v4 - name: Build shell: msys2 {0} run: | - # Public runners come with 2 CPUs. - make -j2 - make -j2 ref_gles1 + sed -i 's|WITH_SDL3:=no|WITH_SDL3:=yes|g' Makefile + # Public runners come with 4 CPUs. + make -j4 + make -j4 ref_gles1 - name: Create testbuild package shell: msys2 {0} run: | @@ -69,10 +72,10 @@ jobs: cp doc/070_packaging.md publish/quake2-win64-${{github.sha}}/misc/docs/070_packaging.txt cp doc/080_contributing.md publish/quake2-win64-${{github.sha}}/misc/docs/080_contributing.txt cp doc/090_filelists.md publish/quake2-win64-${{github.sha}}/misc/docs/090_filelists.md - # SDL2 - wget -c https://github.com/libsdl-org/SDL/releases/download/release-2.30.11/SDL2-2.30.11-win32-x64.zip - unzip -o SDL2-2.30.11-win32-x64.zip - cp SDL2.dll publish/quake2-win64-${{github.sha}}/ + # SDL3 + wget -c https://github.com/libsdl-org/SDL/releases/download/release-3.2.4/SDL3-3.2.4-win32-x64.zip + unzip -o SDL3-3.2.4-win32-x64.zip + cp SDL3.dll publish/quake2-win64-${{github.sha}}/ # static ffmpeg wget -c https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2024-08-31-12-50/ffmpeg-n7.0.2-6-g7e69129d2f-win64-lgpl-shared-7.0.zip unzip -o ffmpeg-n7.0.2-6-g7e69129d2f-win64-lgpl-shared-7.0.zip @@ -84,9 +87,9 @@ jobs: unzip -o openal-soft-1.23.1-bin.zip cp openal-soft-1.23.1-bin/bin/Win64/soft_oal.dll publish/quake2-win64-${{github.sha}}/openal32.dll # curl (releases use a custom build curl.dll) - wget -c https://curl.se/windows/dl-8.9.1_1/curl-8.9.1_1-win64-mingw.zip - unzip -o curl-8.9.1_1-win64-mingw.zip - cp curl-8.9.1_1-win64-mingw/bin/libcurl-x64.dll publish/quake2-win64-${{github.sha}}/curl.dll + wget -c -O curl-mingw-latest.zip "https://curl.se/windows/latest.cgi?p=win64-mingw.zip" + unzip -o curl-mingw-latest.zip + cp curl-*-win64-mingw/bin/libcurl-x64.dll publish/quake2-win64-${{github.sha}}/curl.dll - name: Upload testbuild package uses: actions/upload-artifact@v4 if: ${{ ! startsWith(github.ref, 'refs/tags/') }} diff --git a/doc/040_cvarlist.md b/doc/040_cvarlist.md index 0d706f1b..e7da221d 100644 --- a/doc/040_cvarlist.md +++ b/doc/040_cvarlist.md @@ -617,18 +617,33 @@ it's `+set busywait 0` (setting the `busywait` cvar) and `-portable` * **sw_colorlight**: enable experimental color lighting. -## Game Controller +## Gamepad * **in_initjoy**: Toggles initialization of game controller. Default is `1`, which enables gamepad usage; `0` disables its detection at startup. Can only be set from command line. -* **in_sdlbackbutton**: Defines which button is used in the gamepad as +* **joy_escbutton**: Defines which button is used in the gamepad as the `Esc` key, to pull the main menu and 'cancel' / 'go back' on its - options. Valid values are `0` = Back / Select / Minus, `1` = Start / - Menu / Plus (default), or `2` = Guide / Home / PS. Requires a game + options. Valid values are `0` = Start / Menu / Plus (default), `1` = + Back / Select / Minus, or `2` = Guide / Home / PS. Requires a game restart, or gamepad replug, when changed. +* **joy_labels**: Defines style of button labels in binding menus. Note + that binding through console only uses the SDL nomenclature (`0`). + Default is `-1`, which requires at least SDL 2.0.12 to work. + - `-1`: *Autodetect*, sets to `0` if gamepad type isn't detected + - `0`: *SDL*, face buttons appear as cardinal points + - `1`: *Xbox*, with One / Series X / S labels + - `2`: *Playstation*, 4 & 5 format + - `3`: *Switch*, traditional Nintendo button format + +* **joy_confirm**: Style of *confirm* and *cancel* buttons in menus. As + with the previous one, SDL 2.0.12 is required for `-1` to work. + - `-1`: *Autodetect*, sets to `1` if Nintendo, `0` otherwise + - `0`: SOUTH to confirm, EAST to cancel (standard style) + - `1`: EAST to confirm, SOUTH to cancel (Japanese style) + * **joy_layout**: Allows to select the stick layout of the gamepad. - `0`: *Default*, left stick moves, right aims - `1`: *Southpaw*, same as previous one with inverted sticks diff --git a/src/client/cl_keyboard.c b/src/client/cl_keyboard.c index 1ee5a826..7f5decfb 100644 --- a/src/client/cl_keyboard.c +++ b/src/client/cl_keyboard.c @@ -139,62 +139,6 @@ keyname_t keynames[] = { {"MWHEELUP", K_MWHEELUP}, {"MWHEELDOWN", K_MWHEELDOWN}, - {"BTN_A", K_BTN_A}, - {"BTN_B", K_BTN_B}, - {"BTN_X", K_BTN_X}, - {"BTN_Y", K_BTN_Y}, - {"STICK_LEFT", K_STICK_LEFT}, - {"STICK_RIGHT", K_STICK_RIGHT}, - {"SHOULDR_LEFT", K_SHOULDER_LEFT}, - {"SHOULDR_RIGHT", K_SHOULDER_RIGHT}, - {"TRIG_LEFT", K_TRIG_LEFT}, - {"TRIG_RIGHT", K_TRIG_RIGHT}, - - {"DP_UP", K_DPAD_UP}, - {"DP_DOWN", K_DPAD_DOWN}, - {"DP_LEFT", K_DPAD_LEFT}, - {"DP_RIGHT", K_DPAD_RIGHT}, - - {"PADDLE_1", K_PADDLE_1}, - {"PADDLE_2", K_PADDLE_2}, - {"PADDLE_3", K_PADDLE_3}, - {"PADDLE_4", K_PADDLE_4}, - {"BTN_MISC1", K_BTN_MISC1}, - {"TOUCHPAD", K_TOUCHPAD}, - {"BTN_BACK", K_BTN_BACK}, - {"BTN_GUIDE", K_BTN_GUIDE}, - {"BTN_START", K_BTN_START}, - - // virtual keys you get by pressing the corresponding normal joy key - // and the altselector key - {"BTN_A_ALT", K_BTN_A_ALT}, - {"BTN_B_ALT", K_BTN_B_ALT}, - {"BTN_X_ALT", K_BTN_X_ALT}, - {"BTN_Y_ALT", K_BTN_Y_ALT}, - {"STICK_LEFT_ALT", K_STICK_LEFT_ALT}, - {"STICK_RIGHT_ALT", K_STICK_RIGHT_ALT}, - {"SHOULDR_LEFT_ALT", K_SHOULDER_LEFT_ALT}, - {"SHOULDR_RIGHT_ALT", K_SHOULDER_RIGHT_ALT}, - {"TRIG_LEFT_ALT", K_TRIG_LEFT_ALT}, - {"TRIG_RIGHT_ALT", K_TRIG_RIGHT_ALT}, - - {"DP_UP_ALT", K_DPAD_UP_ALT}, - {"DP_DOWN_ALT", K_DPAD_DOWN_ALT}, - {"DP_LEFT_ALT", K_DPAD_LEFT_ALT}, - {"DP_RIGHT_ALT", K_DPAD_RIGHT_ALT}, - - {"PADDLE_1_ALT", K_PADDLE_1_ALT}, - {"PADDLE_2_ALT", K_PADDLE_2_ALT}, - {"PADDLE_3_ALT", K_PADDLE_3_ALT}, - {"PADDLE_4_ALT", K_PADDLE_4_ALT}, - {"BTN_MISC1_ALT", K_BTN_MISC1_ALT}, - {"TOUCHPAD_ALT", K_TOUCHPAD_ALT}, - {"BTN_BACK_ALT", K_BTN_BACK_ALT}, - {"BTN_GUIDE_ALT", K_BTN_GUIDE_ALT}, - {"BTN_START_ALT", K_BTN_START_ALT}, - - {"JOY_BACK", K_JOY_BACK}, - {"SUPER", K_SUPER}, {"COMPOSE", K_COMPOSE}, {"MODE", K_MODE}, @@ -267,6 +211,147 @@ keyname_t keynames[] = { {NULL, 0} }; +static char *gamepadbtns[] = +{ + // It is imperative that this list of buttons follow EXACTLY the order they + // appear in QKEYS enum in keyboard.h, which in turn is the same order as + // they appear in SDL_GamepadButton / SDL_GameControllerButton enum. + "BTN_SOUTH", + "BTN_EAST", + "BTN_WEST", + "BTN_NORTH", + "BTN_BACK", + "BTN_GUIDE", + "BTN_START", + "STICK_LEFT", + "STICK_RIGHT", + "SHOULDR_LEFT", + "SHOULDR_RIGHT", + "DP_UP", + "DP_DOWN", + "DP_LEFT", + "DP_RIGHT", + "BTN_MISC1", + "PADDL_RIGHT1", + "PADDL_LEFT1", + "PADDL_RIGHT2", + "PADDL_LEFT2", + "TOUCHPAD", + "BTN_MISC2", + "BTN_MISC3", + "BTN_MISC4", + "BTN_MISC5", + "BTN_MISC6", + "TRIG_LEFT", + "TRIG_RIGHT", + // Same with _ALT buttons ( button + 'alt modifier' pressed ) + "BTN_SOUTH_ALT", + "BTN_EAST_ALT", + "BTN_WEST_ALT", + "BTN_NORTH_ALT", + "BTN_BACK_ALT", + "BTN_GUIDE_ALT", + "BTN_START_ALT", + "STICK_LEFT_ALT", + "STICK_RIGHT_ALT", + "SHOULDR_LEFT_ALT", + "SHOULDR_RIGHT_ALT", + "DP_UP_ALT", + "DP_DOWN_ALT", + "DP_LEFT_ALT", + "DP_RIGHT_ALT", + "BTN_MISC1_ALT", + "PADDL_RIGHT1_ALT", + "PADDL_LEFT1_ALT", + "PADDL_RIGHT2_ALT", + "PADDL_LEFT2_ALT", + "TOUCHPAD_ALT", + "BTN_MISC2_ALT", + "BTN_MISC3_ALT", + "BTN_MISC4_ALT", + "BTN_MISC5_ALT", + "BTN_MISC6_ALT", + "TRIG_LEFT_ALT", + "TRIG_RIGHT_ALT" +}; + +#define NUM_GAMEPAD_BTNS (sizeof gamepadbtns / sizeof gamepadbtns[0]) + +static char *gpbtns_face[] = +{ + // Xbox + "A", + "B", + "X", + "Y", + "VIEW", + "XBOX", + "MENU", + "LS", + "RS", + "LB", + "RB", + // Playstation + "CROSS", + "CIRCLE", + "SQUARE", + "TRIANGLE", + "CREATE", + "PS", + "OPTIONS", + "L3", + "R3", + "L1", + "R1", + // Nintendo Switch + "B", + "A", + "Y", + "X", + "-", + "HOME", + "+", + "L stick", + "R stick", + "L btn", + "R btn", +}; + +static char *gpbtns_paddles[] = +{ + // Xbox + "SHARE", + "P1", + "P3", + "P2", + "P4", + // Playstation + "MIC", + "RB", + "LB", + "Right Fn", + "Left Fn", + // Switch + "CAPTURE", + "Right SR", + "Left SL", + "Right SL", + "Left SR" // JoyCon btn positions suck +}; + +static char *gpbtns_triggers[] = +{ + // Xbox + "LT", + "RT", + // Playstation + "L2", + "R2", + // Switch + "ZL", + "ZR" +}; + /* ------------------------------------------------------------------ */ static void @@ -738,6 +823,7 @@ static int Key_StringToKeynum(char *str) { keyname_t *kn; + int i; if (!str || !str[0]) { @@ -757,6 +843,14 @@ Key_StringToKeynum(char *str) } } + for (i = 0; i < NUM_GAMEPAD_BTNS; i++) + { + if (!Q_stricmp(str, gamepadbtns[i])) + { + return K_JOY_FIRST_BTN + i; + } + } + return -1; } @@ -784,6 +878,11 @@ Key_KeynumToString(int keynum) return tinystr; } + if (keynum >= K_JOY_FIRST_BTN) // gamepad button + { + return gamepadbtns[keynum - K_JOY_FIRST_BTN]; + } + for (kn = keynames; kn->name; kn++) { if (keynum == kn->keynum) @@ -795,6 +894,49 @@ Key_KeynumToString(int keynum) return ""; } +/* + * Same as Key_KeynumToString(), but for joystick/gamepad buttons. + */ +char * +Key_KeynumToString_Joy(int key) +{ + extern gamepad_labels_t joy_current_lbls; + const int lbl_style = (int)joy_current_lbls - 1; + + if (key < K_JOY_FIRST_BTN) + { + return Key_KeynumToString(key); + } + + // Don't print the _ALT buttons (buttons with the alt modifier pressed) + if (key >= K_JOY_FIRST_BTN_ALT) + { + key -= K_JOY_FIRST_BTN_ALT - K_JOY_FIRST_BTN; + } + + if (lbl_style < 0) // was SDL + { + goto exit_sdl; + } + + // Alter this logic if new gamepad buttons are added in SDL + if (key < K_DPAD_UP) // face & shoulder buttons + { + return gpbtns_face[lbl_style * (K_DPAD_UP - K_BTN_SOUTH) + key - K_BTN_SOUTH]; + } + else if (key >= K_TRIG_LEFT) // triggers + { + return gpbtns_triggers[lbl_style * (K_JOY_FIRST_BTN_ALT - K_TRIG_LEFT) + key - K_TRIG_LEFT]; + } + else if (key > K_DPAD_RIGHT && key < K_TOUCHPAD) // paddles & misc1 + { + return gpbtns_paddles[lbl_style * (K_TOUCHPAD - K_BTN_MISC1) + key - K_BTN_MISC1]; + } + +exit_sdl: + return gamepadbtns[key - K_JOY_FIRST_BTN]; +} + void Key_SetBinding(int keynum, char *binding) { @@ -884,7 +1026,7 @@ Key_Bind_f(void) } /* don't allow binding escape or the special console keys */ - if(b == K_ESCAPE || b == '^' || b == '`' || b == '~' || b == K_JOY_BACK) + if(b == K_ESCAPE || b == '^' || b == '`' || b == '~') { if(doneWithDefaultCfg) { @@ -1202,12 +1344,12 @@ Key_Event(int key, qboolean down, qboolean special) unsigned int time = Sys_Milliseconds(); // evil hack for the joystick key altselector, which turns K_BTN_x into K_BTN_x_ALT - if(joy_altselector_pressed && key >= K_JOY_FIRST_REGULAR && key <= K_JOY_LAST_REGULAR) + if(joy_altselector_pressed && key >= K_JOY_FIRST_BTN && key < K_JOY_FIRST_BTN_ALT) { // make sure key is not the altselector itself (which we won't turn into *_ALT) if(keybindings[key] == NULL || strcmp(keybindings[key], "+joyaltselector") != 0) { - int altkey = key + (K_JOY_FIRST_REGULAR_ALT - K_JOY_FIRST_REGULAR); + int altkey = key + (K_JOY_FIRST_BTN_ALT - K_JOY_FIRST_BTN); // allow fallback to binding with non-alt key if(keybindings[altkey] != NULL || keybindings[key] == NULL) key = altkey; @@ -1274,7 +1416,7 @@ Key_Event(int key, qboolean down, qboolean special) } /* Key is unbound */ - if ((key >= K_MOUSE1 && key != K_JOY_BACK) && !keybindings[key] && (cls.key_dest != key_console) && + if ((key >= K_MOUSE1) && !keybindings[key] && (cls.key_dest != key_console) && (cls.state == ca_active)) { Com_Printf("%s (%d) is unbound, hit F4 to set.\n", Key_KeynumToString(key), key); @@ -1295,47 +1437,43 @@ Key_Event(int key, qboolean down, qboolean special) - moves one menu level up - closes the menu - closes the help computer - - closes the chat window - Fully same logic for K_JOY_BACK */ - if (!cls.disable_screen) + - closes the chat window */ + if (key == K_ESCAPE && !cls.disable_screen) { - if (key == K_ESCAPE || key == K_JOY_BACK) + if (!down) { - if (!down) - { - return; - } - - /* Close the help computer */ - if (cl.frame.playerstate.stats[STAT_LAYOUTS] && - (cls.key_dest == key_game)) - { - Cbuf_AddText("cmd putaway\n"); - return; - } - - switch (cls.key_dest) - { - /* Close chat window */ - case key_message: - Key_Message(key); - break; - - /* Close menu or one layer up */ - case key_menu: - M_Keydown(key); - break; - - /* Pause game and / or leave console, - break into the menu. */ - case key_game: - case key_console: - M_Menu_Main_f(); - break; - } - return; } + + /* Close the help computer */ + if (cl.frame.playerstate.stats[STAT_LAYOUTS] && + (cls.key_dest == key_game)) + { + Cbuf_AddText("cmd putaway\n"); + return; + } + + switch (cls.key_dest) + { + /* Close chat window */ + case key_message: + Key_Message(key); + break; + + /* Close menu or one layer up */ + case key_menu: + M_Keydown(key); + break; + + /* Pause game and / or leave console, + break into the menu. */ + case key_game: + case key_console: + M_Menu_Main_f(); + break; + } + + return; } /* This is one of the most ugly constructs I've diff --git a/src/client/header/client.h b/src/client/header/client.h index 7ee7182a..98f68146 100644 --- a/src/client/header/client.h +++ b/src/client/header/client.h @@ -491,8 +491,18 @@ void CL_BaseMove (usercmd_t *cmd); void IN_CenterView (void); +typedef enum +{ + LBL_SDL = 0, + LBL_XBOX, + LBL_PLAYSTATION, + LBL_SWITCH, + LBL_MAX_COUNT +} gamepad_labels_t; + float CL_KeyState (kbutton_t *key); char *Key_KeynumToString (int keynum); +char *Key_KeynumToString_Joy (int key); void CL_WriteDemoMessage (void); void CL_Stop_f (void); diff --git a/src/client/header/keyboard.h b/src/client/header/keyboard.h index f91f7375..cd1358ab 100644 --- a/src/client/header/keyboard.h +++ b/src/client/header/keyboard.h @@ -208,11 +208,11 @@ enum QKEYS { // From here on, only gamepad controls must be allowed. // Otherwise, separate bindings (keyboard / controller) menu options will not work. - K_BTN_A, - K_JOY_FIRST_REGULAR = K_BTN_A, - K_BTN_B, - K_BTN_X, - K_BTN_Y, + K_BTN_SOUTH, + K_JOY_FIRST_BTN = K_BTN_SOUTH, + K_BTN_EAST, + K_BTN_WEST, + K_BTN_NORTH, K_BTN_BACK, K_BTN_GUIDE, K_BTN_START, @@ -225,26 +225,28 @@ enum QKEYS { K_DPAD_LEFT, K_DPAD_RIGHT, K_BTN_MISC1, - K_PADDLE_1, - K_PADDLE_2, - K_PADDLE_3, - K_PADDLE_4, - K_TOUCHPAD, // SDL_CONTROLLER_BUTTON_MAX - 1 + K_PADDLE_RIGHT_1, + K_PADDLE_LEFT_1, + K_PADDLE_RIGHT_2, + K_PADDLE_LEFT_2, + K_TOUCHPAD, // SDL_CONTROLLER_BUTTON_MAX - 1, SDL2 limit + K_BTN_MISC2, + K_BTN_MISC3, + K_BTN_MISC4, + K_BTN_MISC5, + K_BTN_MISC6, // SDL_GAMEPAD_BUTTON_COUNT - 1, current SDL3 count K_TRIG_LEFT, // buttons for triggers (axes) K_TRIG_RIGHT, - // add other joystick/controller keys before this one - // and adjust it accordingly, also remember to add corresponding _ALT key below! - K_JOY_LAST_REGULAR = K_TRIG_RIGHT, + // Add other gamepad keys before this one, adjust from SDL 2/3 definitions, and + // add the corresponding _ALT key below! Respect the order, must be the same as above. + // Also, verify if cl_keyboard.c needs a refactor on its arrays. - /* Can't be mapped to any action (=> not regular) */ - K_JOY_BACK, - - K_BTN_A_ALT, - K_JOY_FIRST_REGULAR_ALT = K_BTN_A_ALT, - K_BTN_B_ALT, - K_BTN_X_ALT, - K_BTN_Y_ALT, + K_BTN_SOUTH_ALT, + K_JOY_FIRST_BTN_ALT = K_BTN_SOUTH_ALT, + K_BTN_EAST_ALT, + K_BTN_WEST_ALT, + K_BTN_NORTH_ALT, K_BTN_BACK_ALT, K_BTN_GUIDE_ALT, K_BTN_START_ALT, @@ -257,11 +259,16 @@ enum QKEYS { K_DPAD_LEFT_ALT, K_DPAD_RIGHT_ALT, K_BTN_MISC1_ALT, - K_PADDLE_1_ALT, - K_PADDLE_2_ALT, - K_PADDLE_3_ALT, - K_PADDLE_4_ALT, + K_PADDLE_RIGHT_1_ALT, + K_PADDLE_LEFT_1_ALT, + K_PADDLE_RIGHT_2_ALT, + K_PADDLE_LEFT_2_ALT, K_TOUCHPAD_ALT, + K_BTN_MISC2_ALT, + K_BTN_MISC3_ALT, + K_BTN_MISC4_ALT, + K_BTN_MISC5_ALT, + K_BTN_MISC6_ALT, K_TRIG_LEFT_ALT, K_TRIG_RIGHT_ALT, diff --git a/src/client/input/sdl2.c b/src/client/input/sdl2.c index 06d4d595..fecfca0b 100644 --- a/src/client/input/sdl2.c +++ b/src/client/input/sdl2.c @@ -78,7 +78,7 @@ typedef enum // IN_Update() called at the beginning of a frame to the // actual movement functions called at a later time. static float mouse_x, mouse_y; -static unsigned char sdl_back_button = SDL_CONTROLLER_BUTTON_START; +static unsigned char joy_escbutton = SDL_CONTROLLER_BUTTON_START; static int joystick_left_x, joystick_left_y, joystick_right_x, joystick_right_y; static float gyro_yaw, gyro_pitch; static qboolean mlooking; @@ -91,6 +91,12 @@ int sys_frame_time; // is pressed qboolean joy_altselector_pressed = false; +// Gamepad labels' style (Xbox, Playstation, etc.) in use, normally set after detection +gamepad_labels_t joy_current_lbls = LBL_SDL; + +// Using japanese style for confirm & cancel buttons on gamepad +qboolean japanese_confirm = false; + // Console Variables cvar_t *freelook; cvar_t *lookstrafe; @@ -134,6 +140,12 @@ static int last_haptic_effect_size = HAPTIC_EFFECT_LIST_SIZE; static int last_haptic_effect_pos = 0; static haptic_effects_cache_t last_haptic_effect[HAPTIC_EFFECT_LIST_SIZE]; +// Gamepad labels' style (Xbox, Playstation, etc.) requested by user +static cvar_t *joy_labels; + +// Gamepad style for confirm and cancel buttons (traditional or japanese) +static cvar_t *joy_confirm; + // Joystick sensitivity static cvar_t *joy_yawsensitivity; static cvar_t *joy_pitchsensitivity; @@ -506,6 +518,110 @@ IN_TranslateScancodeToQ2Key(SDL_Scancode sc) static void IN_Controller_Init(qboolean notify_user); static void IN_Controller_Shutdown(qboolean notify_user); +/* + * Sets the gamepad buttons' style of labels (SDL, Xbox, PS, Switch). + * They are only visible in the gamepad binding options. + * Traditional binding uses SDL style, no matter the gamepad. + */ +static void +IN_GamepadLabels_Changed(void) +{ + const int requested = (int)joy_labels->value; + joy_labels->modified = false; + joy_current_lbls = LBL_SDL; + +#if SDL_VERSION_ATLEAST(2, 0, 12) + if (requested < 0 && controller) // try to autodetect... + { + switch (SDL_GameControllerGetType(controller)) + { + case SDL_CONTROLLER_TYPE_XBOX360: + case SDL_CONTROLLER_TYPE_XBOXONE: + joy_current_lbls = LBL_XBOX; + return; + + case SDL_CONTROLLER_TYPE_PS3: + case SDL_CONTROLLER_TYPE_PS4: +#if SDL_VERSION_ATLEAST(2, 0, 14) + case SDL_CONTROLLER_TYPE_PS5: +#endif // SDL_VERSION_ATLEAST(2, 0, 14) + joy_current_lbls = LBL_PLAYSTATION; + return; + + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO: +#if SDL_VERSION_ATLEAST(2, 24, 0) + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: +#endif // SDL_VERSION_ATLEAST(2, 24, 0) + joy_current_lbls = LBL_SWITCH; + default: + return; + } + } + else +#endif // SDL_VERSION_ATLEAST(2, 0, 12) + if (requested >= LBL_SDL && requested < LBL_MAX_COUNT) + { + joy_current_lbls = (gamepad_labels_t)requested; + } +} + +/* + * Sets which gamepad button works as "confirm", and which + * works as "cancel", in menus. + */ +static void +IN_GamepadConfirm_Changed(void) +{ + const int requested = (int)joy_confirm->value; + japanese_confirm = false; + joy_confirm->modified = false; + +#if SDL_VERSION_ATLEAST(2, 0, 12) + if (requested < 0 && controller) // try to autodetect... + { + switch (SDL_GameControllerGetType(controller)) + { + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO: +#if SDL_VERSION_ATLEAST(2, 24, 0) + case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: +#endif // SDL_VERSION_ATLEAST(2, 24, 0) + japanese_confirm = true; + default: + return; + } + } + else +#endif // SDL_VERSION_ATLEAST(2, 0, 12) + if (requested == 1) + { + japanese_confirm = true; + } +} + +static void +IN_GyroMode_Changed(void) +{ + if (gyro_mode->value < 2) + { + gyro_active = false; + } + else + { + gyro_active = true; + } + gyro_mode->modified = false; +} + +static void +IN_VirtualKeyEvent(int keynum, qboolean *state_store, qboolean new_state) +{ + if (new_state != *state_store) + { + *state_store = new_state; + Key_Event(keynum, *state_store, true); + } +} + qboolean IN_NumpadIsOn() { SDL_Keymod mod = SDL_GetModState(); @@ -534,6 +650,7 @@ IN_Update(void) static qboolean left_trigger = false; static qboolean right_trigger = false; + static qboolean left_stick[4] = {false, false, false, false}; // left, right, up, down virtual keys static int consoleKeyCode = 0; @@ -734,8 +851,8 @@ IN_Update(void) qboolean down = (event.type == SDL_CONTROLLERBUTTONDOWN); unsigned char btn = event.cbutton.button; - // Handle Back Button, to override its original key - Key_Event( (btn == sdl_back_button)? K_JOY_BACK : K_BTN_A + btn, + // Handle Esc button first, to override its original key + Key_Event( (btn == joy_escbutton)? K_ESCAPE : K_JOY_FIRST_BTN + btn, down, true ); break; } @@ -747,26 +864,12 @@ IN_Update(void) switch (event.caxis.axis) { case SDL_CONTROLLER_AXIS_TRIGGERLEFT: - { - qboolean new_left_trigger = axis_value > 8192; - if (new_left_trigger != left_trigger) - { - left_trigger = new_left_trigger; - Key_Event(K_TRIG_LEFT, left_trigger, true); - } + IN_VirtualKeyEvent(K_TRIG_LEFT, &left_trigger, axis_value > 8192); break; - } case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: - { - qboolean new_right_trigger = axis_value > 8192; - if (new_right_trigger != right_trigger) - { - right_trigger = new_right_trigger; - Key_Event(K_TRIG_RIGHT, right_trigger, true); - } + IN_VirtualKeyEvent(K_TRIG_RIGHT, &right_trigger, axis_value > 8192); break; - } } if (!cl_paused->value && cls.key_dest == key_game) @@ -786,6 +889,24 @@ IN_Update(void) joystick_right_y = axis_value; break; } + break; + } + + // Virtual keys to navigate menus with left stick + if (cls.key_dest == key_menu) + { + switch (event.caxis.axis) + { + case SDL_CONTROLLER_AXIS_LEFTX: + IN_VirtualKeyEvent(K_LEFTARROW, &left_stick[0], axis_value < -16896); + IN_VirtualKeyEvent(K_RIGHTARROW, &left_stick[1], axis_value > 16896); + break; + + case SDL_CONTROLLER_AXIS_LEFTY: + IN_VirtualKeyEvent(K_UPARROW, &left_stick[2], axis_value < -16896); + IN_VirtualKeyEvent(K_DOWNARROW, &left_stick[3], axis_value > 16896); + break; + } } break; } @@ -834,8 +955,7 @@ IN_Update(void) #endif // !NO_SDL_GYRO - if (gyro_active && gyro_mode->value && - !cl_paused->value && cls.key_dest == key_game) + if (gyro_active && !cl_paused->value && cls.key_dest == key_game) { #ifndef NO_SDL_GYRO if (!gyro_turning_axis->value) @@ -986,6 +1106,19 @@ IN_Update(void) countdown_reason = REASON_NONE; } } + + if (joy_labels->modified) + { + IN_GamepadLabels_Changed(); + } + if (joy_confirm->modified) + { + IN_GamepadConfirm_Changed(); + } + if (gyro_mode->modified) + { + IN_GyroMode_Changed(); + } } /* @@ -1960,19 +2093,19 @@ IN_Controller_Init(qboolean notify_user) SDL_Joystick *joystick = NULL; SDL_bool is_controller = SDL_FALSE; - cvar = Cvar_Get("in_sdlbackbutton", "1", CVAR_ARCHIVE); + cvar = Cvar_Get("joy_escbutton", "0", CVAR_ARCHIVE); if (cvar) { switch ((int)cvar->value) { - case 0: - sdl_back_button = SDL_CONTROLLER_BUTTON_BACK; + case 1: + joy_escbutton = SDL_CONTROLLER_BUTTON_BACK; break; case 2: - sdl_back_button = SDL_CONTROLLER_BUTTON_GUIDE; + joy_escbutton = SDL_CONTROLLER_BUTTON_GUIDE; break; default: - sdl_back_button = SDL_CONTROLLER_BUTTON_START; + joy_escbutton = SDL_CONTROLLER_BUTTON_START; } } @@ -1996,6 +2129,9 @@ IN_Controller_Init(qboolean notify_user) #ifdef SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE SDL_SetHint( SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1" ); #endif +#ifdef SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS // use button positions instead of labels, like SDL3 + SDL_SetHint( SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0" ); +#endif if (SDL_Init(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) == -1) { @@ -2181,6 +2317,10 @@ IN_Controller_Init(qboolean notify_user) #endif } } + + IN_GamepadLabels_Changed(); + IN_GamepadConfirm_Changed(); + IN_GyroMode_Changed(); } /* @@ -2216,6 +2356,8 @@ IN_Init(void) joy_forwardsensitivity = Cvar_Get("joy_forwardsensitivity", "1.0", CVAR_ARCHIVE); joy_sidesensitivity = Cvar_Get("joy_sidesensitivity", "1.0", CVAR_ARCHIVE); + joy_labels = Cvar_Get("joy_labels", "-1", CVAR_ARCHIVE); + joy_confirm = Cvar_Get("joy_confirm", "-1", CVAR_ARCHIVE); joy_layout = Cvar_Get("joy_layout", "0", CVAR_ARCHIVE); joy_left_expo = Cvar_Get("joy_left_expo", "2.0", CVAR_ARCHIVE); joy_left_snapaxis = Cvar_Get("joy_left_snapaxis", "0.15", CVAR_ARCHIVE); @@ -2230,16 +2372,11 @@ IN_Init(void) gyro_calibration_y = Cvar_Get("gyro_calibration_y", "0.0", CVAR_ARCHIVE); gyro_calibration_z = Cvar_Get("gyro_calibration_z", "0.0", CVAR_ARCHIVE); - gyro_yawsensitivity = Cvar_Get("gyro_yawsensitivity", "1.0", CVAR_ARCHIVE); - gyro_pitchsensitivity = Cvar_Get("gyro_pitchsensitivity", "1.0", CVAR_ARCHIVE); + gyro_yawsensitivity = Cvar_Get("gyro_yawsensitivity", "2.5", CVAR_ARCHIVE); + gyro_pitchsensitivity = Cvar_Get("gyro_pitchsensitivity", "2.5", CVAR_ARCHIVE); gyro_tightening = Cvar_Get("gyro_tightening", "3.5", CVAR_ARCHIVE); gyro_turning_axis = Cvar_Get("gyro_turning_axis", "0", CVAR_ARCHIVE); - gyro_mode = Cvar_Get("gyro_mode", "2", CVAR_ARCHIVE); - if ((int)gyro_mode->value == 2) - { - gyro_active = true; - } windowed_pauseonfocuslost = Cvar_Get("vid_pauseonfocuslost", "0", CVAR_USERINFO | CVAR_ARCHIVE); windowed_mouse = Cvar_Get("windowed_mouse", "1", CVAR_USERINFO | CVAR_ARCHIVE); diff --git a/src/client/input/sdl3.c b/src/client/input/sdl3.c index 1ab17341..ae60b14b 100644 --- a/src/client/input/sdl3.c +++ b/src/client/input/sdl3.c @@ -81,7 +81,7 @@ typedef enum // IN_Update() called at the beginning of a frame to the // actual movement functions called at a later time. static float mouse_x, mouse_y; -static unsigned char sdl_back_button = SDL_GAMEPAD_BUTTON_START; +static unsigned char joy_escbutton = SDL_GAMEPAD_BUTTON_START; static int joystick_left_x, joystick_left_y, joystick_right_x, joystick_right_y; static float gyro_yaw, gyro_pitch; static qboolean mlooking; @@ -94,6 +94,12 @@ int sys_frame_time; // is pressed qboolean joy_altselector_pressed = false; +// Gamepad labels' style (Xbox, Playstation, etc.) in use, normally set after detection +gamepad_labels_t joy_current_lbls = LBL_SDL; + +// Using japanese style for confirm & cancel buttons on gamepad +qboolean japanese_confirm = false; + // Console Variables cvar_t *freelook; cvar_t *lookstrafe; @@ -137,6 +143,12 @@ static int last_haptic_effect_size = HAPTIC_EFFECT_LIST_SIZE; static int last_haptic_effect_pos = 0; static haptic_effects_cache_t last_haptic_effect[HAPTIC_EFFECT_LIST_SIZE]; +// Gamepad labels' style (Xbox, Playstation, etc.) requested by user +static cvar_t *joy_labels; + +// Gamepad style for confirm and cancel buttons (traditional or japanese) +static cvar_t *joy_confirm; + // Joystick sensitivity static cvar_t *joy_yawsensitivity; static cvar_t *joy_pitchsensitivity; @@ -504,6 +516,98 @@ IN_TranslateScancodeToQ2Key(SDL_Scancode sc) static void IN_Controller_Init(qboolean notify_user); static void IN_Controller_Shutdown(qboolean notify_user); +/* + * Sets the gamepad buttons' style of labels (SDL, Xbox, PS, Switch). + * They are only visible in the gamepad binding options. + * Traditional binding uses SDL style, no matter the gamepad. + */ +static void +IN_GamepadLabels_Changed(void) +{ + const int requested = (int)joy_labels->value; + joy_labels->modified = false; + joy_current_lbls = LBL_SDL; + + if (requested < 0 && controller) // try to autodetect... + { + switch (SDL_GetGamepadType(controller)) + { + case SDL_GAMEPAD_TYPE_XBOX360: + case SDL_GAMEPAD_TYPE_XBOXONE: + joy_current_lbls = LBL_XBOX; + return; + + case SDL_GAMEPAD_TYPE_PS3: + case SDL_GAMEPAD_TYPE_PS4: + case SDL_GAMEPAD_TYPE_PS5: + joy_current_lbls = LBL_PLAYSTATION; + return; + + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: + joy_current_lbls = LBL_SWITCH; + default: + return; + } + } + else if (requested >= LBL_SDL && requested < LBL_MAX_COUNT) + { + joy_current_lbls = (gamepad_labels_t)requested; + } +} + +/* + * Sets which gamepad button works as "confirm", and which + * works as "cancel", in menus. + */ +static void +IN_GamepadConfirm_Changed(void) +{ + const int requested = (int)joy_confirm->value; + japanese_confirm = false; + joy_confirm->modified = false; + + if (requested < 0 && controller) // try to autodetect... + { + switch (SDL_GetGamepadType(controller)) + { + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: + japanese_confirm = true; + default: + return; + } + } + else if (requested == 1) + { + japanese_confirm = true; + } +} + +static void +IN_GyroMode_Changed(void) +{ + if (gyro_mode->value < 2) + { + gyro_active = false; + } + else + { + gyro_active = true; + } + gyro_mode->modified = false; +} + +static void +IN_VirtualKeyEvent(int keynum, qboolean *state_store, qboolean new_state) +{ + if (new_state != *state_store) + { + *state_store = new_state; + Key_Event(keynum, *state_store, true); + } +} + qboolean IN_NumpadIsOn() { SDL_Keymod mod = SDL_GetModState(); @@ -532,6 +636,7 @@ IN_Update(void) static qboolean left_trigger = false; static qboolean right_trigger = false; + static qboolean left_stick[4] = {false, false, false, false}; // left, right, up, down virtual keys static int consoleKeyCode = 0; @@ -733,8 +838,8 @@ IN_Update(void) qboolean down = (event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN); unsigned char btn = event.gbutton.button; - // Handle Back Button, to override its original key - Key_Event( (btn == sdl_back_button)? K_JOY_BACK : K_BTN_A + btn, + // Handle Esc button first, to override its original key + Key_Event( (btn == joy_escbutton)? K_ESCAPE : K_JOY_FIRST_BTN + btn, down, true ); break; } @@ -746,26 +851,12 @@ IN_Update(void) switch (event.gaxis.axis) { case SDL_GAMEPAD_AXIS_LEFT_TRIGGER : - { - qboolean new_left_trigger = axis_value > 8192; - if (new_left_trigger != left_trigger) - { - left_trigger = new_left_trigger; - Key_Event(K_TRIG_LEFT, left_trigger, true); - } + IN_VirtualKeyEvent(K_TRIG_LEFT, &left_trigger, axis_value > 8192); break; - } case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER : - { - qboolean new_right_trigger = axis_value > 8192; - if (new_right_trigger != right_trigger) - { - right_trigger = new_right_trigger; - Key_Event(K_TRIG_RIGHT, right_trigger, true); - } + IN_VirtualKeyEvent(K_TRIG_RIGHT, &right_trigger, axis_value > 8192); break; - } } if (!cl_paused->value && cls.key_dest == key_game) @@ -785,6 +876,24 @@ IN_Update(void) joystick_right_y = axis_value; break; } + break; + } + + // Virtual keys to navigate menus with left stick + if (cls.key_dest == key_menu) + { + switch (event.gaxis.axis) + { + case SDL_GAMEPAD_AXIS_LEFTX : + IN_VirtualKeyEvent(K_LEFTARROW, &left_stick[0], axis_value < -16896); + IN_VirtualKeyEvent(K_RIGHTARROW, &left_stick[1], axis_value > 16896); + break; + + case SDL_GAMEPAD_AXIS_LEFTY : + IN_VirtualKeyEvent(K_UPARROW, &left_stick[2], axis_value < -16896); + IN_VirtualKeyEvent(K_DOWNARROW, &left_stick[3], axis_value > 16896); + break; + } } break; } @@ -833,8 +942,7 @@ IN_Update(void) #endif // !NO_SDL_GYRO - if (gyro_active && gyro_mode->value && - !cl_paused->value && cls.key_dest == key_game) + if (gyro_active && !cl_paused->value && cls.key_dest == key_game) { #ifndef NO_SDL_GYRO if (!gyro_turning_axis->value) @@ -983,6 +1091,20 @@ IN_Update(void) countdown_reason = REASON_NONE; } } + + // Gamepad labels' type and "confirm & cancel style" change handling + if (joy_labels->modified) + { + IN_GamepadLabels_Changed(); + } + if (joy_confirm->modified) + { + IN_GamepadConfirm_Changed(); + } + if (gyro_mode->modified) + { + IN_GyroMode_Changed(); + } } /* @@ -2068,19 +2190,19 @@ IN_Controller_Init(qboolean notify_user) SDL_Joystick *joystick = NULL; bool is_controller = false; - cvar = Cvar_Get("in_sdlbackbutton", "1", CVAR_ARCHIVE); + cvar = Cvar_Get("joy_escbutton", "0", CVAR_ARCHIVE); if (cvar) { switch ((int)cvar->value) { - case 0: - sdl_back_button = SDL_GAMEPAD_BUTTON_BACK; + case 1: + joy_escbutton = SDL_GAMEPAD_BUTTON_BACK; break; case 2: - sdl_back_button = SDL_GAMEPAD_BUTTON_GUIDE; + joy_escbutton = SDL_GAMEPAD_BUTTON_GUIDE; break; default: - sdl_back_button = SDL_GAMEPAD_BUTTON_START; + joy_escbutton = SDL_GAMEPAD_BUTTON_START; } } @@ -2131,6 +2253,8 @@ IN_Controller_Init(qboolean notify_user) show_haptic = true; } + SDL_free((void *)joysticks); + return; } @@ -2280,6 +2404,9 @@ IN_Controller_Init(qboolean notify_user) } SDL_free((void *)joysticks); + IN_GamepadLabels_Changed(); + IN_GamepadConfirm_Changed(); + IN_GyroMode_Changed(); } /* @@ -2315,6 +2442,8 @@ IN_Init(void) joy_forwardsensitivity = Cvar_Get("joy_forwardsensitivity", "1.0", CVAR_ARCHIVE); joy_sidesensitivity = Cvar_Get("joy_sidesensitivity", "1.0", CVAR_ARCHIVE); + joy_labels = Cvar_Get("joy_labels", "-1", CVAR_ARCHIVE); + joy_confirm = Cvar_Get("joy_confirm", "-1", CVAR_ARCHIVE); joy_layout = Cvar_Get("joy_layout", "0", CVAR_ARCHIVE); joy_left_expo = Cvar_Get("joy_left_expo", "2.0", CVAR_ARCHIVE); joy_left_snapaxis = Cvar_Get("joy_left_snapaxis", "0.15", CVAR_ARCHIVE); @@ -2329,16 +2458,11 @@ IN_Init(void) gyro_calibration_y = Cvar_Get("gyro_calibration_y", "0.0", CVAR_ARCHIVE); gyro_calibration_z = Cvar_Get("gyro_calibration_z", "0.0", CVAR_ARCHIVE); - gyro_yawsensitivity = Cvar_Get("gyro_yawsensitivity", "1.0", CVAR_ARCHIVE); - gyro_pitchsensitivity = Cvar_Get("gyro_pitchsensitivity", "1.0", CVAR_ARCHIVE); + gyro_yawsensitivity = Cvar_Get("gyro_yawsensitivity", "2.5", CVAR_ARCHIVE); + gyro_pitchsensitivity = Cvar_Get("gyro_pitchsensitivity", "2.5", CVAR_ARCHIVE); gyro_tightening = Cvar_Get("gyro_tightening", "3.5", CVAR_ARCHIVE); gyro_turning_axis = Cvar_Get("gyro_turning_axis", "0", CVAR_ARCHIVE); - gyro_mode = Cvar_Get("gyro_mode", "2", CVAR_ARCHIVE); - if ((int)gyro_mode->value == 2) - { - gyro_active = true; - } windowed_pauseonfocuslost = Cvar_Get("vid_pauseonfocuslost", "0", CVAR_USERINFO | CVAR_ARCHIVE); windowed_mouse = Cvar_Get("windowed_mouse", "1", CVAR_USERINFO | CVAR_ARCHIVE); diff --git a/src/client/menu/menu.c b/src/client/menu/menu.c index da33cc00..2c897d74 100644 --- a/src/client/menu/menu.c +++ b/src/client/menu/menu.c @@ -257,6 +257,8 @@ M_PushMenu(menuframework_s* menu) cls.key_dest = key_menu; } +extern qboolean japanese_confirm; + int Key_GetMenuKey(int key) { @@ -297,24 +299,27 @@ Key_GetMenuKey(int key) case K_KP_ENTER: case K_ENTER: - case K_BTN_A: return K_ENTER; - case K_ESCAPE: - case K_JOY_BACK: - case K_BTN_B: - return K_ESCAPE; - - case K_BACKSPACE: - case K_DEL: case K_KP_DEL: if (IN_NumpadIsOn() == true) { break; } - case K_BTN_Y: + case K_BACKSPACE: + case K_DEL: + case K_BTN_NORTH: return K_BACKSPACE; + case K_KP_INS: if (IN_NumpadIsOn() == true) { break; } case K_INS: return K_INS; + + case K_BTN_SOUTH: + if (japanese_confirm) return K_ESCAPE; + else return K_ENTER; + + case K_BTN_EAST: + if (japanese_confirm) return K_ENTER; + else return K_ESCAPE; } return key; @@ -954,14 +959,14 @@ M_UnbindCommand(char *command, int scope) switch (scope) { case KEYS_KEYBOARD_MOUSE: - end = K_JOY_FIRST_REGULAR; + end = K_JOY_FIRST_BTN; break; case KEYS_CONTROLLER: - begin = K_JOY_FIRST_REGULAR; - end = K_JOY_LAST_REGULAR + 1; + begin = K_JOY_FIRST_BTN; + end = K_JOY_FIRST_BTN_ALT; break; case KEYS_CONTROLLER_ALT: - begin = K_JOY_FIRST_REGULAR_ALT; + begin = K_JOY_FIRST_BTN_ALT; } for (j = begin; j < end; j++) @@ -990,14 +995,14 @@ M_FindKeysForCommand(char *command, int *twokeys, int scope) switch (scope) { case KEYS_KEYBOARD_MOUSE: - end = K_JOY_FIRST_REGULAR; + end = K_JOY_FIRST_BTN; break; case KEYS_CONTROLLER: - begin = K_JOY_FIRST_REGULAR; - end = K_JOY_LAST_REGULAR + 1; + begin = K_JOY_FIRST_BTN; + end = K_JOY_FIRST_BTN_ALT; break; case KEYS_CONTROLLER_ALT: - begin = K_JOY_FIRST_REGULAR_ALT; + begin = K_JOY_FIRST_BTN_ALT; } twokeys[0] = twokeys[1] = -1; @@ -1138,7 +1143,7 @@ Keys_MenuKey(int key) if (menukeyitem_bind) { // Any key/button except from the game controller and escape keys - if ((key != K_ESCAPE) && (key != '`') && (key < K_JOY_FIRST_REGULAR)) + if ((key != K_ESCAPE) && (key != '`') && (key < K_JOY_FIRST_BTN)) { char cmd[1024]; @@ -1291,7 +1296,7 @@ MultiplayerKeys_MenuKey(int key) if (menukeyitem_bind) { // Any key/button but the escape ones - if ((key != K_ESCAPE) && (key != '`') && (key != K_JOY_BACK)) + if ((key != K_ESCAPE) && (key != '`')) { char cmd[1024]; @@ -1333,6 +1338,30 @@ M_Menu_Multiplayer_Keys_f(void) * GAME CONTROLLER ( GAMEPAD / JOYSTICK ) BUTTONS MENU */ +static void +GamepadMenu_StatusPrompt(menuframework_s *m) +{ + static char m_gamepadbind_statusbar[64]; + int btn_confirm, btn_cancel; + + if (japanese_confirm) + { + btn_confirm = K_BTN_EAST; + btn_cancel = K_BTN_SOUTH; + } + else + { + btn_confirm = K_BTN_SOUTH; + btn_cancel = K_BTN_EAST; + } + + snprintf(m_gamepadbind_statusbar, 64, "%s assigns, %s clears, %s exits", + Key_KeynumToString_Joy(btn_confirm), Key_KeynumToString_Joy(K_BTN_NORTH), + Key_KeynumToString_Joy(btn_cancel)); + + Menu_SetStatusBar(m, m_gamepadbind_statusbar); +} + char *controller_bindnames[][2] = { {"+attack", "attack"}, @@ -1388,7 +1417,7 @@ DrawControllerButtonBindingFunc(void *self) int x; const char *name; - name = Key_KeynumToString(keys[0]); + name = Key_KeynumToString_Joy(keys[0]); Menu_DrawString(a->generic.x + a->generic.parent->x + RCOLUMN_OFFSET * scale, a->generic.y + a->generic.parent->y, name); @@ -1401,7 +1430,7 @@ DrawControllerButtonBindingFunc(void *self) a->generic.y + a->generic.parent->y, "or"); Menu_DrawString(a->generic.x + a->generic.parent->x + 48 * scale + (x * scale), a->generic.y + a->generic.parent->y, - Key_KeynumToString(keys[1])); + Key_KeynumToString_Joy(keys[1])); } } } @@ -1446,7 +1475,7 @@ ControllerButtons_MenuInit(void) Menu_AddItem(&s_controller_buttons_menu, (void *)&s_controller_buttons_actions[i]); } - Menu_SetStatusBar(&s_controller_buttons_menu, "BTN_A assigns, BTN_Y clears, BTN_B exits"); + GamepadMenu_StatusPrompt(&s_controller_buttons_menu); Menu_Center(&s_controller_buttons_menu); } @@ -1465,7 +1494,7 @@ ControllerButtons_MenuKey(int key) if (menukeyitem_bind) { // Only controller buttons allowed - if (key >= K_JOY_FIRST_REGULAR && key != K_JOY_BACK) + if (key >= K_JOY_FIRST_BTN) { char cmd[1024]; @@ -1474,7 +1503,7 @@ ControllerButtons_MenuKey(int key) Cbuf_InsertText(cmd); } - Menu_SetStatusBar(&s_controller_buttons_menu, "BTN_A assigns, BTN_Y clears, BTN_B exits"); + GamepadMenu_StatusPrompt(&s_controller_buttons_menu); menukeyitem_bind = false; return menu_out_sound; } @@ -1562,7 +1591,7 @@ DrawControllerAltButtonBindingFunc(void *self) size_t x; const char *name; - name = Key_KeynumToString(keys[0]); + name = Key_KeynumToString_Joy(keys[0]); Menu_DrawString(a->generic.x + a->generic.parent->x + RCOLUMN_OFFSET * scale, a->generic.y + a->generic.parent->y, name); @@ -1575,7 +1604,7 @@ DrawControllerAltButtonBindingFunc(void *self) a->generic.y + a->generic.parent->y, "or"); Menu_DrawString(a->generic.x + a->generic.parent->x + 48 * scale + (x * scale), a->generic.y + a->generic.parent->y, - Key_KeynumToString(keys[1])); + Key_KeynumToString_Joy(keys[1])); } } } @@ -1620,7 +1649,7 @@ ControllerAltButtons_MenuInit(void) Menu_AddItem(&s_controller_alt_buttons_menu, (void *)&s_controller_alt_buttons_actions[i]); } - Menu_SetStatusBar(&s_controller_alt_buttons_menu, "BTN_A assigns, BTN_Y clears, BTN_B exits"); + GamepadMenu_StatusPrompt(&s_controller_alt_buttons_menu); Menu_Center(&s_controller_alt_buttons_menu); } @@ -1639,17 +1668,17 @@ ControllerAltButtons_MenuKey(int key) if (menukeyitem_bind) { // Only controller buttons allowed, different from the alt buttons modifier - if (key >= K_JOY_FIRST_REGULAR && key != K_JOY_BACK && (keybindings[key] == NULL || strcmp(keybindings[key], "+joyaltselector") != 0)) + if (key >= K_JOY_FIRST_BTN && (keybindings[key] == NULL || strcmp(keybindings[key], "+joyaltselector") != 0)) { char cmd[1024]; - key = key + (K_JOY_FIRST_REGULAR_ALT - K_JOY_FIRST_REGULAR); // change input to its ALT mode + key = key + (K_JOY_FIRST_BTN_ALT - K_JOY_FIRST_BTN); // change input to its ALT mode Com_sprintf(cmd, sizeof(cmd), "bind \"%s\" \"%s\"\n", Key_KeynumToString(key), controller_alt_bindnames[item->generic.localdata[0]][0]); Cbuf_InsertText(cmd); } - Menu_SetStatusBar(&s_controller_alt_buttons_menu, "BTN_A assigns, BTN_Y clears, BTN_B exits"); + GamepadMenu_StatusPrompt(&s_controller_alt_buttons_menu); menukeyitem_bind = false; return menu_out_sound; } diff --git a/src/client/refresh/files/stb_image.h b/src/client/refresh/files/stb_image.h index 9eedabed..17ffbc3d 100644 --- a/src/client/refresh/files/stb_image.h +++ b/src/client/refresh/files/stb_image.h @@ -100,7 +100,7 @@ RECENT REVISION HISTORY: Bug & warning fixes Marc LeBlanc David Woo Guillaume George Martins Mozeiko Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski - Phil Jordan Dave Moore Roy Eltham + Phil Jordan Henner Zeller Dave Moore Roy Eltham Hayaki Saito Nathan Reed Won Chun Luke Graham Johan Duparc Nick Verigakis the Horde3D community Thomas Ruf Ronny Chevalier github:rlyeh @@ -111,7 +111,7 @@ RECENT REVISION HISTORY: Cass Everitt Ryamond Barbiero github:grim210 Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus - Josh Tobin Neil Bickford Matthew Gregan github:poppolopoppo + Josh Tobin Nia Bickford Matthew Gregan github:poppolopoppo Julian Raschke Gregory Mullen Christian Floisand github:darealshinji Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 Brad Weinberger Matvey Cherevko github:mosra @@ -373,6 +373,15 @@ RECENT REVISION HISTORY: #define STBI_VERSION 1 +#if defined(__has_attribute) +# if __has_attribute(fallthrough) +# define STBI_FALLTHROUGH __attribute__((fallthrough)); +# endif +#endif +#if !defined(STBI_FALLTHROUGH) +# define STBI_FALLTHROUGH +#endif + enum { STBI_default = 0, // only used for desired_channels @@ -1209,7 +1218,7 @@ static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int chan int img_len = w * h * channels; stbi__uint16 *enlarged; - enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + enlarged = (stbi__uint16 *) stbi__malloc(((size_t)img_len)*2); if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); for (i = 0; i < img_len; ++i) @@ -1269,7 +1278,12 @@ static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); if (ri.bits_per_channel != 8) { - result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + stbi_uc *converted = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + if (converted == NULL) { + STBI_FREE(result); + return NULL; + } + result = converted; ri.bits_per_channel = 8; } @@ -1295,7 +1309,12 @@ static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); if (ri.bits_per_channel != 16) { - result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + stbi__uint16 * converted = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + if (converted == NULL) { + STBI_FREE(result); + return NULL; + } + result = converted; ri.bits_per_channel = 16; } @@ -1381,7 +1400,12 @@ STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); if (result) { // need to 'unget' all the characters in the IO buffer - fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + if (fseek(f, -(int)(s.img_buffer_end - s.img_buffer), SEEK_CUR) != 0) { + // fseek() failed; we can no longer maintain the file cursor position + // guarantee of this function, so return null. + STBI_FREE(result); + return stbi__errpuc("bad file", "fseek() failed; seek position unreliable"); + } } return result; } @@ -1394,7 +1418,12 @@ STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); if (result) { // need to 'unget' all the characters in the IO buffer - fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + if (fseek(f, -(int)(s.img_buffer_end - s.img_buffer), SEEK_CUR) != 0) { + // fseek() failed; we can no longer maintain the file cursor position + // guarantee of this function, so return null. + STBI_FREE(result); + return (stbi__uint16 *) stbi__errpuc("bad file", "fseek() failed; seek position unreliable"); + } } return result; } @@ -1448,8 +1477,9 @@ STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int * stbi__start_mem(&s,buffer,len); result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); - if (stbi__vertically_flip_on_load) { - stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); + if (stbi__vertically_flip_on_load && result) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip_slices( result, *x, *y, *z, channels ); } return result; @@ -1542,12 +1572,13 @@ STBIDEF int stbi_is_hdr (char const *filename) STBIDEF int stbi_is_hdr_from_file(FILE *f) { #ifndef STBI_NO_HDR - long pos = ftell(f); int res; + long pos = ftell(f); + if (pos < 0) return stbi__err("bad file", "ftell() failed"); stbi__context s; stbi__start_file(&s,f); res = stbi__hdr_test(&s); - fseek(f, pos, SEEK_SET); + if (fseek(f, pos, SEEK_SET) != 0) return stbi__err("bad file", "fseek() failed"); return res; #else STBI_NOTUSED(f); @@ -1757,6 +1788,7 @@ static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int r int i,j; unsigned char *good; + if (data == NULL) return data; if (req_comp == img_n) return data; STBI_ASSERT(req_comp >= 1 && req_comp <= 4); @@ -1797,7 +1829,7 @@ static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int r } #endif -#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PNM) // nothing #else static stbi__uint16 stbi__compute_y_16(int r, int g, int b) @@ -1806,7 +1838,7 @@ static stbi__uint16 stbi__compute_y_16(int r, int g, int b) } #endif -#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PNM) // nothing #else static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) @@ -2223,8 +2255,8 @@ static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta","Corrupt JPEG"); dc = j->img_comp[b].dc_pred + diff; j->img_comp[b].dc_pred = dc; - if (!stbi__mul2shorts_valid(dc, dequant[0])) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); - data[0] = (short) (dc * dequant[0]); + if ((dc > SHRT_MAX) || (dequant[0] > SHRT_MAX) || !stbi__mul2shorts_valid((short) dc, (short) dequant[0])) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + data[0] = (short) ((size_t)dc * dequant[0]); // decode AC components, see JPEG spec k = 1; @@ -2280,7 +2312,7 @@ static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__ if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta", "Corrupt JPEG"); dc = j->img_comp[b].dc_pred + diff; j->img_comp[b].dc_pred = dc; - if (!stbi__mul2shorts_valid(dc, 1 << j->succ_low)) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + if ((dc > SHRT_MAX) || !stbi__mul2shorts_valid((short) dc, 1 << j->succ_low)) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); data[0] = (short) (dc * (1 << j->succ_low)); } else { // refinement scan for DC coefficient @@ -5556,7 +5588,7 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req if (info.hsz == 12) { if (info.bpp < 24) - psize = (info.offset - info.extra_read - 24) / 3; + psize = (info.offset - info.extra_read - info.hsz) / 3; } else { if (info.bpp < 16) psize = (info.offset - info.extra_read - info.hsz) >> 2; @@ -5743,7 +5775,7 @@ static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) switch(bits_per_pixel) { case 8: return STBI_grey; case 16: if(is_grey) return STBI_grey_alpha; - // fallthrough + STBI_FALLTHROUGH; case 15: if(is_rgb16) *is_rgb16 = 1; return STBI_rgb; case 24: // fallthrough @@ -5934,7 +5966,10 @@ static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req for (i=0; i < tga_height; ++i) { int row = tga_inverted ? tga_height -i - 1 : i; stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; - stbi__getn(s, tga_row, tga_width * tga_comp); + if(!stbi__getn(s, tga_row, tga_width * tga_comp)) { + STBI_FREE(tga_data); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } } } else { // do I need to load a palette? @@ -6528,7 +6563,7 @@ static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_c if (!stbi__pic_load_core(s,x,y,comp, result)) { STBI_FREE(result); - result=0; + return 0; } *px = x; *py = y; @@ -6991,9 +7026,20 @@ static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, stride = g.w * g.h * 4; if (out) { + if (stride == 0) { + void *ret = stbi__load_gif_main_outofmem(&g, out, delays); + return ret; + } + if (!stbi__mul2sizes_valid(layers, stride)) { + void *ret = stbi__load_gif_main_outofmem(&g, out, delays); + return ret; + } void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride ); - if (!tmp) - return stbi__load_gif_main_outofmem(&g, out, delays); + if (!tmp) { + void *ret = stbi__load_gif_main_outofmem(&g, out, delays); + if (delays && *delays) *delays = 0; + return ret; + } else { out = (stbi_uc*) tmp; out_size = layers * stride; @@ -7007,9 +7053,16 @@ static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, delays_size = layers * sizeof(int); } } else { + if (!stbi__mul2sizes_valid(layers, stride)) { + void *ret = stbi__load_gif_main_outofmem(&g, out, delays); + return ret; + } out = (stbi_uc*)stbi__malloc( layers * stride ); - if (!out) - return stbi__load_gif_main_outofmem(&g, out, delays); + if (!out) { + void *ret = stbi__load_gif_main_outofmem(&g, out, delays); + if (delays && *delays) *delays = 0; + return ret; + } out_size = layers * stride; if (delays) { *delays = (int*) stbi__malloc( layers * sizeof(int) ); @@ -7020,7 +7073,7 @@ static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, } memcpy( out + ((layers - 1) * stride), u, stride ); if (layers >= 2) { - two_back = out - 2 * stride; + two_back = out + (layers - 2) * stride; } if (delays) { @@ -7145,10 +7198,10 @@ static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) if (req_comp == 4) output[3] = 1; } else { switch (req_comp) { - case 4: output[3] = 1; /* fallthrough */ + case 4: output[3] = 1; STBI_FALLTHROUGH; case 3: output[0] = output[1] = output[2] = 0; break; - case 2: output[1] = 1; /* fallthrough */ + case 2: output[1] = 1; STBI_FALLTHROUGH; case 1: output[0] = 0; break; } @@ -7219,7 +7272,10 @@ static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int re for (i=0; i < width; ++i) { stbi_uc rgbe[4]; main_decode_loop: - stbi__getn(s, rgbe, 4); + if (!stbi__getn(s, rgbe, 4)) { + STBI_FREE(hdr_data); + return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); + } stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); } } @@ -7703,9 +7759,10 @@ STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) int r; stbi__context s; long pos = ftell(f); + if (pos < 0) return stbi__err("bad file", "ftell() failed"); stbi__start_file(&s, f); r = stbi__info_main(&s,x,y,comp); - fseek(f,pos,SEEK_SET); + if (fseek(f, pos, SEEK_SET) != 0) return stbi__err("bad file", "fseek() failed"); return r; } @@ -7724,9 +7781,10 @@ STBIDEF int stbi_is_16_bit_from_file(FILE *f) int r; stbi__context s; long pos = ftell(f); + if (pos < 0) return stbi__err("bad file", "ftell() failed"); stbi__start_file(&s, f); r = stbi__is_16_main(&s); - fseek(f,pos,SEEK_SET); + if (fseek(f, pos, SEEK_SET) != 0) return stbi__err("bad file", "fseek() failed"); return r; } #endif // !STBI_NO_STDIO diff --git a/src/client/refresh/gl1/gl1_sdl.c b/src/client/refresh/gl1/gl1_sdl.c index 00f585eb..010e6bf7 100644 --- a/src/client/refresh/gl1/gl1_sdl.c +++ b/src/client/refresh/gl1/gl1_sdl.c @@ -270,7 +270,7 @@ int RI_InitContext(void* win) #ifdef YQ2_GL1_GLES // Load GL pointers through GLAD and check context. - if( !gladLoadGLES1Loader(SDL_GL_GetProcAddress)) + if( !gladLoadGLES1Loader( (void * (*)(const char *)) SDL_GL_GetProcAddress ) ) { R_Printf(PRINT_ALL, "RI_InitContext(): ERROR: loading OpenGL ES function pointers failed!\n"); return false; diff --git a/src/client/sound/header/stb_vorbis.h b/src/client/sound/header/stb_vorbis.h index 3e5c2504..a4e043ff 100644 --- a/src/client/sound/header/stb_vorbis.h +++ b/src/client/sound/header/stb_vorbis.h @@ -21,6 +21,7 @@ // // Feature contributors: // Dougall Johnson (sample-exact seeking) +// Vitaly Novichkov (sample-accurate tell) // // Bugfix/warning contributors: // Terje Mathisen Niklas Frykholm Andy Hill @@ -34,6 +35,7 @@ // github:audinowho Dougall Johnson David Reid // github:Clownacy Pedro J. Estebanez Remi Verschelde // AnthoFoxo github:morlat Gabriel Ravier +// Seb de Graffenried Alice Rowan // // Partial history: // 1.22 - 2021-07-11 - various small fixes @@ -648,6 +650,17 @@ typedef signed short int16; typedef unsigned int uint32; typedef signed int int32; +#ifdef __has_feature +#if __has_feature(undefined_behavior_sanitizer) +#define HAS_UBSAN +#endif +#endif +#ifdef HAS_UBSAN +#define STB_NO_SANITIZE(s) __attribute__((no_sanitize(s))) +#else +#define STB_NO_SANITIZE(s) +#endif + #ifndef TRUE #define TRUE 1 #define FALSE 0 @@ -752,11 +765,11 @@ typedef struct typedef struct { - uint16 coupling_steps; MappingChannel *chan; + uint16 coupling_steps; uint8 submaps; - uint8 submap_floor[15]; // varies - uint8 submap_residue[15]; // varies + uint8 submap_floor[16]; // varies + uint8 submap_residue[16]; // varies } Mapping; typedef struct @@ -898,6 +911,12 @@ struct stb_vorbis // sample-access int channel_buffer_start; int channel_buffer_end; + + // temporary buffers + void *temp_lengths; + void *temp_codewords; + void *temp_values; + void *temp_mults; }; #if defined(STB_VORBIS_NO_PUSHDATA_API) @@ -949,9 +968,12 @@ static void *make_block_array(void *mem, int count, int size) static void *setup_malloc(vorb *f, int sz) { + if (INT_MAX - 7 < sz) return NULL; + if (sz < 0) return NULL; sz = (sz+7) & ~7; // round up to nearest 8 for alignment of future allocs. f->setup_memory_required += sz; if (f->alloc.alloc_buffer) { + if (sz == 0) return NULL; void *p = (char *) f->alloc.alloc_buffer + f->setup_offset; if (f->setup_offset + sz > f->temp_offset) return NULL; f->setup_offset += sz; @@ -977,8 +999,10 @@ static void *setup_temp_malloc(vorb *f, int sz) return malloc(sz); } -static void setup_temp_free(vorb *f, void *p, int sz) +static void setup_temp_free(vorb *f, void **_p, int sz) { + void *p = *_p; + *_p = NULL; if (f->alloc.alloc_buffer) { f->temp_offset += (sz+7)&~7; return; @@ -1238,6 +1262,10 @@ static int vorbis_validate(uint8 *data) // called from setup only, once per code book // (formula implied by specification) +// +// suppress an UBSan error caused by invalid input data. +// upstream: https://github.com/nothings/stb/issues/1168. +STB_NO_SANITIZE("float-cast-overflow") static int lookup1_values(int entries, int dim) { int r = (int) floor(exp((float) log((float) entries) / dim)); @@ -1583,8 +1611,8 @@ static int get32_packet(vorb *f) { uint32 x; x = get8_packet(f); - x += get8_packet(f) << 8; - x += get8_packet(f) << 16; + x += (uint32) get8_packet(f) << 8; + x += (uint32) get8_packet(f) << 16; x += (uint32) get8_packet(f) << 24; return x; } @@ -1696,7 +1724,7 @@ static int codebook_decode_scalar_raw(vorb *f, Codebook *c) assert(!c->sparse); for (i=0; i < c->entries; ++i) { if (c->codeword_lengths[i] == NO_CODE) continue; - if (c->codewords[i] == (f->acc & ((1 << c->codeword_lengths[i])-1))) { + if (c->codewords[i] == (f->acc & ((1U << c->codeword_lengths[i])-1))) { if (f->valid_bits >= c->codeword_lengths[i]) { f->acc >>= c->codeword_lengths[i]; f->valid_bits -= c->codeword_lengths[i]; @@ -1753,7 +1781,7 @@ static int codebook_decode_scalar(vorb *f, Codebook *c) #define DECODE(var,f,c) \ DECODE_RAW(var,f,c) \ - if (c->sparse) var = c->sorted_values[var]; + if (c->sparse && var >= 0) var = c->sorted_values[var]; #ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK #define DECODE_VQ(var,f,c) DECODE_RAW(var,f,c) @@ -1888,7 +1916,7 @@ static int codebook_decode_deinterleave_repeat(vorb *f, Codebook *c, float **out // buffer (len*ch), our current offset within it (p_inter*ch)+(c_inter), // and the length we'll be using (effective) if (c_inter + p_inter*ch + effective > len * ch) { - effective = len*ch - (p_inter*ch - c_inter); + effective = len*ch - (p_inter*ch + c_inter); } #ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK @@ -2067,7 +2095,7 @@ static __forceinline void draw_line(float *output, int x0, int y0, int x1, int y ady -= abs(base) * adx; if (x1 > n) x1 = n; if (x < x1) { - LINE_OP(output[x], inverse_db_table[y&255]); + LINE_OP(output[x], inverse_db_table[(uint32)y&255]); for (++x; x < x1; ++x) { err += ady; if (err >= adx) { @@ -2075,7 +2103,7 @@ static __forceinline void draw_line(float *output, int x0, int y0, int x1, int y y += sy; } else y += base; - LINE_OP(output[x], inverse_db_table[y&255]); + LINE_OP(output[x], inverse_db_table[(uint32)y&255]); } } } @@ -3058,7 +3086,7 @@ void inverse_mdct_naive(float *buffer, int n) static float *get_window(vorb *f, int len) { - len <<= 1; + len = (unsigned int)len << 1; if (len == f->blocksize_0) return f->window[0]; if (len == f->blocksize_1) return f->window[1]; return NULL; @@ -3650,7 +3678,7 @@ static int start_decoder(vorb *f) if (!vorbis_validate(header)) return error(f, VORBIS_invalid_setup); //file vendor len = get32_packet(f); - f->vendor = (char*)setup_malloc(f, sizeof(char) * (len+1)); + f->vendor = (INT_MAX == len) ? NULL : (char*)setup_malloc(f, sizeof(char) * (len+1)); if (f->vendor == NULL) return error(f, VORBIS_outofmem); for(i=0; i < len; ++i) { f->vendor[i] = get8_packet(f); @@ -3661,14 +3689,25 @@ static int start_decoder(vorb *f) f->comment_list = NULL; if (f->comment_list_length > 0) { + if (INT_MAX / sizeof(char*) < f->comment_list_length) { + f->comment_list_length = 0; + return error(f, VORBIS_outofmem); + } f->comment_list = (char**) setup_malloc(f, sizeof(char*) * (f->comment_list_length)); - if (f->comment_list == NULL) return error(f, VORBIS_outofmem); + if (f->comment_list == NULL) + { + f->comment_list_length = 0; + return error(f, VORBIS_outofmem); + } + memset(f->comment_list, 0, sizeof(char*) * f->comment_list_length); } for(i=0; i < f->comment_list_length; ++i) { len = get32_packet(f); - f->comment_list[i] = (char*)setup_malloc(f, sizeof(char) * (len+1)); - if (f->comment_list[i] == NULL) return error(f, VORBIS_outofmem); + f->comment_list[i] = (INT_MAX == len) ? NULL : (char*)setup_malloc(f, sizeof(char) * (len+1)); + if (f->comment_list[i] == NULL) { + return error(f, VORBIS_outofmem); + } for(j=0; j < len; ++j) { f->comment_list[i][j] = get8_packet(f); @@ -3713,6 +3752,7 @@ static int start_decoder(vorb *f) // codebooks f->codebook_count = get_bits(f,8) + 1; + if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); f->codebooks = (Codebook *) setup_malloc(f, sizeof(*f->codebooks) * f->codebook_count); if (f->codebooks == NULL) return error(f, VORBIS_outofmem); memset(f->codebooks, 0, sizeof(*f->codebooks) * f->codebook_count); @@ -3735,10 +3775,12 @@ static int start_decoder(vorb *f) c->sparse = ordered ? 0 : get_bits(f,1); if (c->dimensions == 0 && c->entries != 0) return error(f, VORBIS_invalid_setup); + if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); - if (c->sparse) + if (c->sparse) { lengths = (uint8 *) setup_temp_malloc(f, c->entries); - else + f->temp_lengths = lengths; + } else lengths = c->codeword_lengths = (uint8 *) setup_malloc(f, c->entries); if (!lengths) return error(f, VORBIS_outofmem); @@ -3749,8 +3791,9 @@ static int start_decoder(vorb *f) while (current_entry < c->entries) { int limit = c->entries - current_entry; int n = get_bits(f, ilog(limit)); + if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); if (current_length >= 32) return error(f, VORBIS_invalid_setup); - if (current_entry + n > (int) c->entries) { return error(f, VORBIS_invalid_setup); } + if (current_entry + n > (int) c->entries) return error(f, VORBIS_invalid_setup); memset(lengths + current_entry, current_length, n); current_entry += n; ++current_length; @@ -3758,11 +3801,14 @@ static int start_decoder(vorb *f) } else { for (j=0; j < c->entries; ++j) { int present = c->sparse ? get_bits(f,1) : 1; + if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); if (present) { lengths[j] = get_bits(f, 5) + 1; ++total; - if (lengths[j] == 32) - return error(f, VORBIS_invalid_setup); + if (lengths[j] == 32) { + if (c->sparse) setup_temp_free(f, &f->temp_lengths, c->entries); + return error(f, VORBIS_invalid_setup); + } } else { lengths[j] = NO_CODE; } @@ -3775,9 +3821,12 @@ static int start_decoder(vorb *f) f->setup_temp_memory_required = c->entries; c->codeword_lengths = (uint8 *) setup_malloc(f, c->entries); - if (c->codeword_lengths == NULL) return error(f, VORBIS_outofmem); + if (c->codeword_lengths == NULL) { + setup_temp_free(f, &f->temp_lengths, c->entries); + return error(f, VORBIS_outofmem); + } memcpy(c->codeword_lengths, lengths, c->entries); - setup_temp_free(f, lengths, c->entries); // note this is only safe if there have been no intervening temp mallocs! + setup_temp_free(f, &f->temp_lengths, c->entries); // note this is only safe if there have been no intervening temp mallocs! lengths = c->codeword_lengths; c->sparse = 0; } @@ -3805,11 +3854,22 @@ static int start_decoder(vorb *f) unsigned int size; if (c->sorted_entries) { c->codeword_lengths = (uint8 *) setup_malloc(f, c->sorted_entries); - if (!c->codeword_lengths) return error(f, VORBIS_outofmem); + if (!c->codeword_lengths) { + setup_temp_free(f, &f->temp_lengths, c->entries); + return error(f, VORBIS_outofmem); + } c->codewords = (uint32 *) setup_temp_malloc(f, sizeof(*c->codewords) * c->sorted_entries); - if (!c->codewords) return error(f, VORBIS_outofmem); + f->temp_codewords = c->codewords; + if (!c->codewords) { + setup_temp_free(f, &f->temp_lengths, c->entries); + return error(f, VORBIS_outofmem); + } values = (uint32 *) setup_temp_malloc(f, sizeof(*values) * c->sorted_entries); - if (!values) return error(f, VORBIS_outofmem); + f->temp_values = values; + if (!values) { + setup_temp_free(f, &f->temp_lengths, c->entries); + return error(f, VORBIS_outofmem); + } } size = c->entries + (sizeof(*c->codewords) + sizeof(*values)) * c->sorted_entries; if (size > f->setup_temp_memory_required) @@ -3817,27 +3877,32 @@ static int start_decoder(vorb *f) } if (!compute_codewords(c, lengths, c->entries, values)) { - if (c->sparse) setup_temp_free(f, values, 0); return error(f, VORBIS_invalid_setup); } if (c->sorted_entries) { // allocate an extra slot for sentinels c->sorted_codewords = (uint32 *) setup_malloc(f, sizeof(*c->sorted_codewords) * (c->sorted_entries+1)); - if (c->sorted_codewords == NULL) return error(f, VORBIS_outofmem); + if (c->sorted_codewords == NULL) { + if (c->sparse) setup_temp_free(f, &f->temp_lengths, c->entries); + return error(f, VORBIS_outofmem); + } // allocate an extra slot at the front so that c->sorted_values[-1] is defined // so that we can catch that case without an extra if c->sorted_values = ( int *) setup_malloc(f, sizeof(*c->sorted_values ) * (c->sorted_entries+1)); - if (c->sorted_values == NULL) return error(f, VORBIS_outofmem); + if (c->sorted_values == NULL) { + if (c->sparse) setup_temp_free(f, &f->temp_lengths, c->entries); + return error(f, VORBIS_outofmem); + } ++c->sorted_values; c->sorted_values[-1] = -1; compute_sorted_huffman(c, lengths, values); } if (c->sparse) { - setup_temp_free(f, values, sizeof(*values)*c->sorted_entries); - setup_temp_free(f, c->codewords, sizeof(*c->codewords)*c->sorted_entries); - setup_temp_free(f, lengths, c->entries); + setup_temp_free(f, &f->temp_values, sizeof(*values)*c->sorted_entries); + setup_temp_free(f, &f->temp_codewords, sizeof(*c->codewords)*c->sorted_entries); + setup_temp_free(f, &f->temp_lengths, c->entries); c->codewords = NULL; } @@ -3857,14 +3922,22 @@ static int start_decoder(vorb *f) if (values < 0) return error(f, VORBIS_invalid_setup); c->lookup_values = (uint32) values; } else { + /* If this overflows, the file's not valid. + * See https://github.com/nothings/stb/issues/1168. */ + if (c->entries != 0 && c->dimensions > INT_MAX / c->entries) return error(f, VORBIS_invalid_setup); c->lookup_values = c->entries * c->dimensions; } if (c->lookup_values == 0) return error(f, VORBIS_invalid_setup); + // Before we allocate the lookup table, verify that it can be read + // from the file. Otherwise, c->lookup_values can be arbitrarily + // large, and we run out of memory. + if (c->lookup_values > (8 * f->stream_len) / c->value_bits) return error(f, VORBIS_invalid_setup); mults = (uint16 *) setup_temp_malloc(f, sizeof(mults[0]) * c->lookup_values); + f->temp_mults = mults; if (mults == NULL) return error(f, VORBIS_outofmem); for (j=0; j < (int) c->lookup_values; ++j) { int q = get_bits(f, c->value_bits); - if (q == EOP) { setup_temp_free(f,mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_invalid_setup); } + if (f->valid_bits < 0) return error(f, VORBIS_invalid_setup); mults[j] = q; } @@ -3878,7 +3951,7 @@ static int start_decoder(vorb *f) c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->sorted_entries * c->dimensions); } else c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->entries * c->dimensions); - if (c->multiplicands == NULL) { setup_temp_free(f,mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_outofmem); } + if (c->multiplicands == NULL) return error(f, VORBIS_outofmem); len = sparse ? c->sorted_entries : c->entries; for (j=0; j < len; ++j) { unsigned int z = sparse ? c->sorted_values[j] : j; @@ -3891,7 +3964,6 @@ static int start_decoder(vorb *f) last = val; if (k+1 < c->dimensions) { if (div > UINT_MAX / (unsigned int) c->lookup_values) { - setup_temp_free(f, mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_invalid_setup); } div *= c->lookup_values; @@ -3906,7 +3978,7 @@ static int start_decoder(vorb *f) float last=0; CHECK(f); c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->lookup_values); - if (c->multiplicands == NULL) { setup_temp_free(f, mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_outofmem); } + if (c->multiplicands == NULL) return error(f, VORBIS_outofmem); for (j=0; j < (int) c->lookup_values; ++j) { float val = mults[j] * c->delta_value + c->minimum_value + last; c->multiplicands[j] = val; @@ -3917,7 +3989,7 @@ static int start_decoder(vorb *f) #ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK skip:; #endif - setup_temp_free(f, mults, sizeof(mults[0])*c->lookup_values); + setup_temp_free(f, &f->temp_mults, sizeof(mults[0])*c->lookup_values); CHECK(f); } @@ -3934,6 +4006,7 @@ static int start_decoder(vorb *f) // Floors f->floor_count = get_bits(f, 6)+1; + if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); f->floor_config = (Floor *) setup_malloc(f, f->floor_count * sizeof(*f->floor_config)); if (f->floor_config == NULL) return error(f, VORBIS_outofmem); for (i=0; i < f->floor_count; ++i) { @@ -3963,6 +4036,7 @@ static int start_decoder(vorb *f) for (j=0; j <= max_class; ++j) { g->class_dimensions[j] = get_bits(f, 3)+1; g->class_subclasses[j] = get_bits(f, 2); + if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); if (g->class_subclasses[j]) { g->class_masterbooks[j] = get_bits(f, 8); if (g->class_masterbooks[j] >= f->codebook_count) return error(f, VORBIS_invalid_setup); @@ -4010,6 +4084,7 @@ static int start_decoder(vorb *f) // Residue f->residue_count = get_bits(f, 6)+1; + if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); f->residue_config = (Residue *) setup_malloc(f, f->residue_count * sizeof(f->residue_config[0])); if (f->residue_config == NULL) return error(f, VORBIS_outofmem); memset(f->residue_config, 0, f->residue_count * sizeof(f->residue_config[0])); @@ -4024,6 +4099,7 @@ static int start_decoder(vorb *f) r->part_size = get_bits(f,24)+1; r->classifications = get_bits(f,6)+1; r->classbook = get_bits(f,8); + if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); if (r->classbook >= f->codebook_count) return error(f, VORBIS_invalid_setup); for (j=0; j < r->classifications; ++j) { uint8 high_bits=0; @@ -4032,12 +4108,14 @@ static int start_decoder(vorb *f) high_bits = get_bits(f,5); residue_cascade[j] = high_bits*8 + low_bits; } + if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); r->residue_books = (short (*)[8]) setup_malloc(f, sizeof(r->residue_books[0]) * r->classifications); if (r->residue_books == NULL) return error(f, VORBIS_outofmem); for (j=0; j < r->classifications; ++j) { for (k=0; k < 8; ++k) { if (residue_cascade[j] & (1 << k)) { r->residue_books[j][k] = get_bits(f, 8); + if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); if (r->residue_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup); } else { r->residue_books[j][k] = -1; @@ -4062,6 +4140,7 @@ static int start_decoder(vorb *f) } f->mapping_count = get_bits(f,6)+1; + if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); f->mapping = (Mapping *) setup_malloc(f, f->mapping_count * sizeof(*f->mapping)); if (f->mapping == NULL) return error(f, VORBIS_outofmem); memset(f->mapping, 0, f->mapping_count * sizeof(*f->mapping)); @@ -4083,6 +4162,7 @@ static int start_decoder(vorb *f) for (k=0; k < m->coupling_steps; ++k) { m->chan[k].magnitude = get_bits(f, ilog(f->channels-1)); m->chan[k].angle = get_bits(f, ilog(f->channels-1)); + if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); if (m->chan[k].magnitude >= f->channels) return error(f, VORBIS_invalid_setup); if (m->chan[k].angle >= f->channels) return error(f, VORBIS_invalid_setup); if (m->chan[k].magnitude == m->chan[k].angle) return error(f, VORBIS_invalid_setup); @@ -4119,6 +4199,7 @@ static int start_decoder(vorb *f) m->windowtype = get_bits(f,16); m->transformtype = get_bits(f,16); m->mapping = get_bits(f,8); + if (f->valid_bits < 0) return error(f, VORBIS_unexpected_eof); if (m->windowtype != 0) return error(f, VORBIS_invalid_setup); if (m->transformtype != 0) return error(f, VORBIS_invalid_setup); if (m->mapping >= f->mapping_count) return error(f, VORBIS_invalid_setup); @@ -4161,7 +4242,8 @@ static int start_decoder(vorb *f) int i,max_part_read=0; for (i=0; i < f->residue_count; ++i) { Residue *r = f->residue_config + i; - unsigned int actual_size = f->blocksize_1 / 2; + unsigned int rtype = f->residue_types[i]; + unsigned int actual_size = rtype == 2 ? f->blocksize_1 : f->blocksize_1 / 2; unsigned int limit_r_begin = r->begin < actual_size ? r->begin : actual_size; unsigned int limit_r_end = r->end < actual_size ? r->end : actual_size; int n_read = limit_r_end - limit_r_begin; @@ -4233,7 +4315,8 @@ static void vorbis_deinit(stb_vorbis *p) Codebook *c = p->codebooks + i; setup_free(p, c->codeword_lengths); setup_free(p, c->multiplicands); - setup_free(p, c->codewords); + if (c->codewords != p->temp_codewords) // Might be the temporary buffer-allocated array. + setup_free(p, c->codewords); setup_free(p, c->sorted_codewords); // c->sorted_values[-1] is the first entry in the array setup_free(p, c->sorted_values ? c->sorted_values-1 : NULL); @@ -4263,6 +4346,12 @@ static void vorbis_deinit(stb_vorbis *p) setup_free(p, p->window[i]); setup_free(p, p->bit_reverse[i]); } + if (!p->alloc.alloc_buffer) { + setup_temp_free(p, &p->temp_lengths, 0); + setup_temp_free(p, &p->temp_codewords, 0); + setup_temp_free(p, &p->temp_values, 0); + setup_temp_free(p, &p->temp_mults, 0); + } #ifndef STB_VORBIS_NO_STDIO if (p->close_on_free) fclose(p->f); #endif @@ -4649,10 +4738,12 @@ static int get_seek_page_info(stb_vorbis *f, ProbedPage *z) z->page_start = stb_vorbis_get_file_offset(f); // parse the header - getn(f, header, 27); + if (!getn(f, header, 27)) + return 0; if (header[0] != 'O' || header[1] != 'g' || header[2] != 'g' || header[3] != 'S') return 0; - getn(f, lacing, header[26]); + if (!getn(f, lacing, header[26])) + return 0; // determine the length of the payload len = 0; @@ -5147,7 +5238,9 @@ static int8 channel_position[7][6] = #ifndef STB_VORBIS_NO_FAST_SCALED_FLOAT typedef union { float f; - int i; + // changed this to unsigned to suppress an UBSan error. + // upstream: https://github.com/nothings/stb/issues/1168. + unsigned int i; } float_conv; typedef char stb_vorbis_float_size_test[sizeof(float)==4 && sizeof(int) == 4]; #define FASTDEF(x) float_conv x @@ -5169,7 +5262,7 @@ static void copy_samples(short *dest, float *src, int len) for (i=0; i < len; ++i) { FASTDEF(temp); int v = FAST_SCALED_FLOAT_TO_INT(temp, src[i],15); - if ((unsigned int) (v + 32768) > 65535) + if (((unsigned int)v + 32768) > 65535) v = v < 0 ? -32768 : 32767; dest[i] = v; } @@ -5193,7 +5286,7 @@ static void compute_samples(int mask, short *output, int num_c, float **data, in for (i=0; i < n; ++i) { FASTDEF(temp); int v = FAST_SCALED_FLOAT_TO_INT(temp,buffer[i],15); - if ((unsigned int) (v + 32768) > 65535) + if (((unsigned int)v + 32768) > 65535) v = v < 0 ? -32768 : 32767; output[o+i] = v; } @@ -5233,7 +5326,7 @@ static void compute_stereo_samples(short *output, int num_c, float **data, int d for (i=0; i < (n<<1); ++i) { FASTDEF(temp); int v = FAST_SCALED_FLOAT_TO_INT(temp,buffer[i],15); - if ((unsigned int) (v + 32768) > 65535) + if (((unsigned int)v + 32768) > 65535) v = v < 0 ? -32768 : 32767; output[o2+i] = v; } @@ -5283,7 +5376,7 @@ static void convert_channels_short_interleaved(int buf_c, short *buffer, int dat FASTDEF(temp); float f = data[i][d_offset+j]; int v = FAST_SCALED_FLOAT_TO_INT(temp, f,15);//data[i][d_offset+j],15); - if ((unsigned int) (v + 32768) > 65535) + if (((unsigned int)v + 32768) > 65535) v = v < 0 ? -32768 : 32767; *buffer++ = v; } diff --git a/src/client/vid/header/stb_image_write.h b/src/client/vid/header/stb_image_write.h index e4b32ed1..a5dcc12e 100644 --- a/src/client/vid/header/stb_image_write.h +++ b/src/client/vid/header/stb_image_write.h @@ -765,6 +765,8 @@ static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, f else { // Each component is stored separately. Allocate scratch space for full output scanline. unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); + if (NULL == scratch) + return 0; int i, len; char buffer[128]; char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; @@ -772,6 +774,8 @@ static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, f #ifdef __STDC_LIB_EXT1__ len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + len = snprintf(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); #else len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); #endif