Compare commits

..

47 Commits

Author SHA1 Message Date
JailDesigner f3371c33b0 changelog: entrada 2026-05-17 2026-05-17 13:23:43 +02:00
JailDesigner 441a2122bc bump version a 2026.05.17 2026-05-17 13:22:33 +02:00
JailDesigner 502127283b ajusta posició inicial de la bala cap amunt (-1px x, -2px y) i pinta-la sobre el jugador 2026-05-17 13:20:42 +02:00
JailDesigner 0fb9be931f defaults: zoom de finestra a 3 (era 2) 2026-05-17 13:11:13 +02:00
JailDesigner 1d46c4f3bd vsync: separa preferència persistent de l'estat aplicat al hardware 2026-05-17 13:09:28 +02:00
JailDesigner dbef9c558d guarda anti-crash al lector de la demo + fade trigger >= 2026-05-17 12:47:25 +02:00
JailDesigner 64586f4a86 merge: migració PostFX i chroma min/max 2026-05-17 12:32:33 +02:00
JailDesigner ab20e98663 ajusta defaults preset CRT Live (chroma_max + curvature) 2026-05-17 12:30:21 +02:00
JailDesigner ae6e72e0d9 chroma min/max amb mostreig bilinear subpíxel 2026-05-17 12:24:14 +02:00
JailDesigner bd5683d498 migra postfx a aee_arcade i elimina supersampling/downsampling 2026-05-17 11:05:06 +02:00
JailDesigner 11eec8f222 reinici real (execv) des del service menu
El Reiniciar fins ara recarregava recursos però no rellegia el preset
ni recreava la finestra (idioma/dificultat/preset es quedaven pendents
fins al pròxim arrencada manual). Ara Director rep argv al constructor
i, quan Section::name passa a RESET, fa execv del propi binari
(_execv a Windows). El procés es reemplaça → init complet amb tots
els canvis aplicats.

Refactor: extret shutdownSubsystems() de close() i compartit amb
relaunch(). Si execv falla els subsistemes ja s'han destruït, no
podem tornar al bucle: exit amb error.

A Emscripten s'amaga l'opció Reiniciar al service menu (execv no
existeix; el cheat code per teclat encara cau al reset clàssic com
fallback).
2026-05-17 10:12:22 +02:00
JailDesigner 2c1673d2dd arregla presets red i classic
red.txt: vel/grav dels globus passats al sistema deltaTime
(165/320, 222/360, 282/360, 327/360 pixels/s i pixels/s²) i
title_duration de 800 frames a 14 segons; eliminats item_size,
hit_stop i hit_stop_ms (paràmetres ja no parsejats que generaven
warnings a l'arrencada).

classic.txt: scoreboard.rect.y de 216 a 200 per encaixar amb
game.height=240 (el marcador sortia 16px fora de pantalla);
eliminat el duplicat de resource.color.
2026-05-17 09:46:03 +02:00
JailDesigner e58b7d36fb opció de preset al service menu (params_file → params_preset)
Defaults::Settings::PARAMS_FILE passa a PARAMS_PRESET (id intern:
classic/arcade/red); director concatena .txt al carregar. Nou
ListOption "GAME_PRESET" al service menu amb les tres opcions
localitzades (cal reinici per aplicar). Validació al parser del YAML:
si el preset desat no existeix, cau al default.
2026-05-17 09:43:30 +02:00
JailDesigner 0d14e10de5 config/ dins del pack: assets, params, stages, pools, formations 2026-05-17 08:38:46 +02:00
JailDesigner a39cd45bf1 neteja tidy: ranges::fill, default member init, NOLINT _USE_MATH_DEFINES 2026-05-17 08:05:30 +02:00
JailDesigner ab858aacb8 Merge branch 'tidy-cleanup' 2026-05-16 23:49:40 +02:00
JailDesigner 0647eceab7 neteja clang-tidy: enums uint8_t, includes, naming i altres 2026-05-16 23:48:07 +02:00
JailDesigner a903343385 neteja clang-tidy 2026-05-16 22:47:41 +02:00
JailDesigner 17341f923d merge: neteja cppcheck CCAE (105→0) 2026-05-16 19:35:27 +02:00
JailDesigner fcd2718794 neteja cppcheck (105 → 0) 2026-05-16 19:35:23 +02:00
JailDesigner c9d16959d0 afegir git hooks per format, tidy i cppcheck 2026-05-14 17:48:13 +02:00
JailDesigner ce662609f3 unifica shader compile script com a compile_spirv.cmake i regenera headers 2026-05-14 17:40:01 +02:00
JailDesigner 583d901162 binari i recursos a build/, targets en kebab 2026-05-14 17:26:06 +02:00
JailDesigner 61d35374e0 estandaritzat .clang-tidy amb el d'AEEA 2026-05-14 16:35:35 +02:00
JailDesigner 194c8187f9 afegit suppress de cppcheck per a external/ i spv/ 2026-05-14 16:27:53 +02:00
JailDesigner 490d5c923b detecta Ninja com a generador i passa GIT_HASH a totes les invocacions de CMake 2026-05-14 16:23:39 +02:00
JailDesigner 0e49f35d3b correccions en makefile de macos i en el numero de versio (de guionets a punts) 2026-05-03 17:55:54 +02:00
JailDesigner b9cee1bc70 afegit c_cpp_properties.json 2026-05-03 17:48:43 +02:00
JailDesigner 3390d01ef6 normalitzat Audio 2026-04-18 11:43:13 +02:00
JailDesigner 561028ff04 build: unifica .clang-format/.clang-tidy i exclou external/ i spv/ amb dummies 2026-04-17 16:21:56 +02:00
JailDesigner 671583ebbe arreglos en make i cmake per estandaritzar amb la resta de projectes
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 12:59:57 +02:00
JailDesigner e941502615 fix: input android amb sdl_joystick 2026-04-15 23:53:47 +02:00
JailDesigner 8bab2da2ed filtre nearest o linear per al pipeline sdl 2026-04-15 11:10:00 +02:00
JailDesigner 5ef278ce70 make controllerdb
trim del nom del mando
2026-04-15 09:50:42 +02:00
JailDesigner 14103175a9 arreglos en screen 2026-04-15 06:31:23 +02:00
JailDesigner 10a3e2fedd service menu vitaminat: cliping, swapping animation i versió 2026-04-14 19:41:17 +02:00
JailDesigner 25a36d5064 arreglos en service menu per a emscripten 2026-04-14 19:08:45 +02:00
JailDesigner 8706b2c7fb càrrega de recursos no bloquejant 2026-04-14 18:10:28 +02:00
JailDesigner d493ebf4f0 afegida versió controlada, no automatica 2026-04-14 17:38:27 +02:00
JailDesigner 3e795998d1 arreglos cosmetics en la pantalla de carrega de recursos 2026-04-14 17:31:24 +02:00
JailDesigner c694781f38 correccions en la detecció de mandos 2026-04-14 17:09:09 +02:00
JailDesigner 1a2298963d afegit mode autoplay en debug 2026-04-14 16:11:33 +02:00
JailDesigner 4c1ed1cf9b path emscripten 2026-04-14 13:54:50 +02:00
JailDesigner f80d0a656e idem 2026-04-14 13:26:30 +02:00
JailDesigner 4429cd92c1 reestructuració 2026-04-14 13:26:22 +02:00
JailDesigner 4ac34b8583 eliminat soport per a arguments 2026-04-14 13:09:54 +02:00
JailDesigner cf7ea6cc9c eliminat molt de ruido de la consola de log 2026-04-14 13:04:24 +02:00
193 changed files with 21465 additions and 23657 deletions
+71 -44
View File
@@ -8,73 +8,100 @@ Checks:
- -bugprone-integer-division
- -bugprone-easily-swappable-parameters
- -bugprone-narrowing-conversions
- -modernize-avoid-c-arrays,-warnings-as-errors
- -modernize-avoid-c-arrays
WarningsAsErrors: '*'
# Solo incluir archivos de tu código fuente (external tiene su propio .clang-tidy)
# Excluye los headers SPIR-V generados en rendering/sdl3gpu/
HeaderFilterRegex: 'source/(?!external/|rendering/sdl3gpu/.*_spv\.h).*'
# Headers nostres (excloem source/external/ que conté dependències de tercers no editables)
HeaderFilterRegex: 'source/(core|game|utils)/'
FormatStyle: file
CheckOptions:
# bugprone-empty-catch: aceptar catches vacíos marcados con @INTENTIONAL en un comentario
- { key: bugprone-empty-catch.IgnoreCatchWithKeywords, value: '@INTENTIONAL' }
# Variables locales en snake_case
# =====================================================================
# CONSTANTES → UPPER_CASE (compile-time y runtime, en cualquier scope)
# =====================================================================
# Todo lo que sea const o constexpr se identifica visualmente en UPPER_CASE,
# sin importar si es global, local, miembro o static.
# constexpr en cualquier scope (globales y locales)
- { key: readability-identifier-naming.ConstexprVariableCase, value: UPPER_CASE }
# Constantes globales (const no-constexpr)
- { key: readability-identifier-naming.GlobalConstantCase, value: UPPER_CASE }
# Constantes locales (const en función)
- { key: readability-identifier-naming.LocalConstantCase, value: UPPER_CASE }
# Static const a nivel de archivo/namespace
- { key: readability-identifier-naming.StaticConstantCase, value: UPPER_CASE }
# Miembros static const/constexpr de clase (p.ej. static constexpr int MAX = 100;)
- { key: readability-identifier-naming.ClassConstantCase, value: UPPER_CASE }
# Miembros const no-static de clase (p.ej. const int limit;)
- { key: readability-identifier-naming.ConstantMemberCase, value: UPPER_CASE }
# Valores de enums
- { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE }
# NOTA: Los parámetros const NO se tratan como constantes aquí.
# Un parámetro sigue siendo un parámetro aunque sea const → hereda ParameterCase.
# =====================================================================
# VARIABLES NO-CONST
# =====================================================================
# Variables locales
- { key: readability-identifier-naming.VariableCase, value: lower_case }
- { key: readability-identifier-naming.LocalVariableCase, value: lower_case }
# Miembros privados en snake_case con sufijo _
- { key: readability-identifier-naming.PrivateMemberCase, value: lower_case }
- { key: readability-identifier-naming.PrivateMemberSuffix, value: _ }
# Parámetros de función
- { key: readability-identifier-naming.ParameterCase, value: lower_case }
# Miembros protegidos en snake_case con sufijo _
- { key: readability-identifier-naming.ProtectedMemberCase, value: lower_case }
- { key: readability-identifier-naming.ProtectedMemberSuffix, value: _ }
# Miembros públicos en snake_case (sin sufijo)
- { key: readability-identifier-naming.PublicMemberCase, value: lower_case }
# Namespaces en CamelCase
- { key: readability-identifier-naming.NamespaceCase, value: CamelCase }
# Variables estáticas privadas como miembros privados
# Variables estáticas no-const (static locales, static file-scope,
# y static members no-const de clase como el instance_ de un Singleton).
# Sufijo _ para marcar que tienen storage estático.
- { key: readability-identifier-naming.StaticVariableCase, value: lower_case }
- { key: readability-identifier-naming.StaticVariableSuffix, value: _ }
# Constantes estáticas sin sufijo
- { key: readability-identifier-naming.StaticConstantCase, value: UPPER_CASE }
# =====================================================================
# MIEMBROS DE CLASE NO-CONST
# =====================================================================
# Privados: snake_case con sufijo _
- { key: readability-identifier-naming.PrivateMemberCase, value: lower_case }
- { key: readability-identifier-naming.PrivateMemberSuffix, value: _ }
# Constantes globales en UPPER_CASE
- { key: readability-identifier-naming.GlobalConstantCase, value: UPPER_CASE }
# Protegidos: snake_case con sufijo _
- { key: readability-identifier-naming.ProtectedMemberCase, value: lower_case }
- { key: readability-identifier-naming.ProtectedMemberSuffix, value: _ }
# Variables constexpr globales en UPPER_CASE
- { key: readability-identifier-naming.ConstexprVariableCase, value: UPPER_CASE }
# Públicos: snake_case sin sufijo
- { key: readability-identifier-naming.PublicMemberCase, value: lower_case }
# Constantes locales en UPPER_CASE
- { key: readability-identifier-naming.LocalConstantCase, value: UPPER_CASE }
# Constexpr miembros en UPPER_CASE (sin sufijo)
- { key: readability-identifier-naming.ConstexprMemberCase, value: UPPER_CASE }
# Constexpr miembros privados/protegidos con sufijo _
- { key: readability-identifier-naming.ConstexprMethodCase, value: UPPER_CASE }
# Clases, structs y enums en CamelCase
# =====================================================================
# TIPOS
# =====================================================================
- { key: readability-identifier-naming.ClassCase, value: CamelCase }
- { key: readability-identifier-naming.StructCase, value: CamelCase }
- { key: readability-identifier-naming.EnumCase, value: CamelCase }
- { key: readability-identifier-naming.UnionCase, value: CamelCase }
- { key: readability-identifier-naming.TypeAliasCase, value: CamelCase }
- { key: readability-identifier-naming.TypedefCase, value: CamelCase }
- { key: readability-identifier-naming.TemplateParameterCase, value: CamelCase }
# Valores de enums en UPPER_CASE
- { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE }
# Namespaces
- { key: readability-identifier-naming.NamespaceCase, value: CamelCase }
# Métodos en camelBack (sin sufijos)
# =====================================================================
# FUNCIONES Y MÉTODOS (incluyendo constexpr)
# =====================================================================
# Un método/función constexpr es un invocable, no una constante → camelBack.
- { key: readability-identifier-naming.FunctionCase, value: camelBack }
- { key: readability-identifier-naming.ConstexprFunctionCase, value: camelBack }
- { key: readability-identifier-naming.MethodCase, value: camelBack }
- { key: readability-identifier-naming.PrivateMethodCase, value: camelBack }
- { key: readability-identifier-naming.ProtectedMethodCase, value: camelBack }
- { key: readability-identifier-naming.PublicMethodCase, value: camelBack }
# Funciones en camelBack
- { key: readability-identifier-naming.FunctionCase, value: camelBack }
# Parámetros en lower_case
- { key: readability-identifier-naming.ParameterCase, value: lower_case }
- { key: readability-identifier-naming.ConstexprMethodCase, value: camelBack }
+92
View File
@@ -0,0 +1,92 @@
#!/usr/bin/env bash
# Pre-commit hook: aplica clang-format als fitxers C++ staged abans del commit.
# - Només toca fitxers staged dins source/ (exclou source/external/).
# - Avorta el commit si hi ha canvis NO staged en aquests fitxers (per no incloure'ls sense voler).
set -euo pipefail
if ! command -v clang-format >/dev/null 2>&1; then
echo "pre-commit: clang-format no trobat — saltant format check" >&2
exit 0
fi
mapfile -t STAGED < <(git diff --cached --name-only --diff-filter=ACMR \
| grep -E '^source/.*\.(cpp|hpp|h)$' \
| grep -vE '^source/external/' || true)
if [ ${#STAGED[@]} -eq 0 ]; then
exit 0
fi
UNSTAGED_DIRTY=()
for f in "${STAGED[@]}"; do
if ! git diff --quiet -- "$f"; then
UNSTAGED_DIRTY+=("$f")
fi
done
if [ ${#UNSTAGED_DIRTY[@]} -gt 0 ]; then
echo "pre-commit: aquests fitxers tenen canvis NO staged i estan al commit." >&2
echo " Fes 'git add' o 'git stash' abans de continuar:" >&2
printf ' %s\n' "${UNSTAGED_DIRTY[@]}" >&2
exit 1
fi
clang-format -i "${STAGED[@]}"
git add -- "${STAGED[@]}"
# --- clang-tidy només sobre els fitxers staged ---
if ! command -v clang-tidy >/dev/null 2>&1; then
echo "pre-commit: clang-tidy no trobat — saltant tidy" >&2
exit 0
fi
REPO_ROOT="$(git rev-parse --show-toplevel)"
BUILD_DIR="$REPO_ROOT/build"
if [ ! -f "$BUILD_DIR/compile_commands.json" ]; then
echo "pre-commit: generant compile_commands.json (build dir buit)..." >&2
cmake -S "$REPO_ROOT" -B "$BUILD_DIR" >/dev/null
fi
echo "pre-commit: clang-tidy sobre ${#STAGED[@]} fitxer(s)..." >&2
if ! clang-tidy -p "$BUILD_DIR" --quiet "${STAGED[@]}"; then
echo "pre-commit: clang-tidy ha trobat errors — commit avortat" >&2
exit 1
fi
# --- cppcheck només sobre els .cpp staged ---
if ! command -v cppcheck >/dev/null 2>&1; then
echo "pre-commit: cppcheck no trobat — saltant cppcheck" >&2
exit 0
fi
CPP_STAGED=()
for f in "${STAGED[@]}"; do
[[ "$f" == *.cpp ]] && CPP_STAGED+=("$f")
done
if [ ${#CPP_STAGED[@]} -eq 0 ]; then
exit 0
fi
echo "pre-commit: cppcheck sobre ${#CPP_STAGED[@]} fitxer(s)..." >&2
if ! cppcheck \
--enable=warning,style,performance,portability \
--std=c++20 \
--language=c++ \
--inline-suppr \
--suppress=missingIncludeSystem \
--suppress=toomanyconfigs \
--suppress='*:*source/external/*' \
--suppress='*:*source/core/rendering/sdl3gpu/spv/*' \
--suppress=normalCheckLevelMaxBranches \
-D_DEBUG \
-DLINUX_BUILD \
--quiet \
--error-exitcode=1 \
-I "$REPO_ROOT/source" \
"${CPP_STAGED[@]}"; then
echo "pre-commit: cppcheck ha trobat errors — commit avortat" >&2
exit 1
fi
+51
View File
@@ -0,0 +1,51 @@
{
"configurations": [
{
"name": "Mac",
"includePath": [
"${workspaceFolder}/source/**",
"${workspaceFolder}/build"
],
"defines": [
"_DEBUG",
"_NO_AUDIO",
"MACOS_BUILD"
],
"cStandard": "c17",
"cppStandard": "c++20",
"intelliSenseMode": "macos-clang-arm64"
},
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/source/**",
"${workspaceFolder}/build"
],
"defines": [
"_DEBUG",
"_NO_AUDIO",
"LINUX_BUILD"
],
"cStandard": "c17",
"cppStandard": "c++20",
"intelliSenseMode": "linux-gcc-x64"
},
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/source/**",
"${workspaceFolder}/build"
],
"defines": [
"_DEBUG",
"_NO_AUDIO",
"WINDOWS_BUILD",
"_WIN32"
],
"cStandard": "c17",
"cppStandard": "c++20",
"intelliSenseMode": "windows-msvc-x64"
}
],
"version": 4
}
+16
View File
@@ -4,6 +4,22 @@ Historial de canvis i novetats de Coffee Crisis Arcade Edition.
---
## 2026-05-17
- **Migració del shader PostFX a la versió d'aee_arcade**: filtratge analític de scanlines amb `smoothstep` (paràmetres `scan_dark_ratio`, `scan_dark_floor`, `scan_edge_soft` exposats per preset). Eliminat tot el pipeline de supersampling 3× + Lanczos downscale (shaders, render targets, opcions de menu, claus de YAML, keybinding F0, llengües). 1 sol render pass vs 3 anteriors.
- **Chroma `min`/`max` amb mostreig bilinear subpíxel**: substitueix l'antic `chroma_strength`. Amb min==max queda estàtic; amb min!=max pulsa sinusoidalment entre els dos valors. Adéu al *tic-tac* del NEAREST amb offsets fraccionals.
- **MSL extret a headers separats** sota `source/core/rendering/sdl3gpu/msl/` (postfx_vert, postfx_frag, crtpi_frag).
- **Service Menu: VSync ara funciona de veritat**. `Screen::applySettings()` propaga al backend GPU (abans només tocava `SDL_SetRenderVSync` i amb backend GPU el present real el fa el swapchain — el canvi era invisible). Separat el setter aplicat al hardware del setter de preferència persistent: `Resource::beginLoad` ja no clobera `Options::video.vsync` durant el preload, així la preferència de l'usuari es preserva entre llançaments.
- **Demo player anti-crash**: guarda amb modul al lector (`demo_.data.at(index).at(demo_.index % size)`) i trigger del fade `>=` (era `==`, fragil a frame skips per canvis de preset des del service menu) — evita `vector::_M_range_check` quan el frame de trigger 1800 saltava i `demo_.index` arribava a 2000.
- **Reinici real des del service menu** via `execv`.
- **Opció de preset de paràmetres al service menu** (`params_file``params_preset`: classic / arcade / red). Presets `red` i `classic` arreglats.
- **Pack inclou ara la carpeta `config/`** (assets, params, stages, pools, formations).
- **Bullet "fire up" reposicionada** (-1 px x, -2 px y) i pintada sobre el jugador en lloc de sota.
- **Defaults**: zoom de finestra a 3 (era 2).
- Neteja de codi: passades exhaustives de clang-tidy i cppcheck (de 105 warnings a 0).
---
## 2026-04-03
- **Nova intro cinematogràfica**: les tarjetes s'llancen des dels costats de la pantalla amb zoom, rotació i rebot, simulant tirar cartes sobre una mesa. Les anteriors ixen despedides girant quan arriba la següent. Sombra amb efecte de perspectiva 2D→3D.
+27 -11
View File
@@ -44,24 +44,38 @@ make tidy-fix # run clang-tidy with auto-fix
## Architecture
### Singletons (core systems)
- **Director** (`source/director.hpp`) — Application state machine, orchestrates scene transitions (Logo → Intro → Title → Game → Credits/HiScore → Title)
- **Screen** (`source/screen.hpp`) — Window management, SDL3 GPU rendering pipeline, post-processing effects
- **Resource** (`source/resource.hpp`) — Asset loading/caching with PRELOAD and LAZY_LOAD modes, reads from `resources.pack`
- **Audio** (`source/audio.hpp`) — Music and SFX management
- **Input** (`source/input.hpp`) — Keyboard and gamepad input handling
### Source layout
The `source/` tree is organised in the same style as the sibling projects `projecte_2026` and `jaildoctors_dilemma`:
### Scenes (source/sections/)
```
source/
├── core/ # engine: audio, input, locale, rendering (+ sdl3gpu, sprite), resources, system
├── game/ # gameplay: entities, gameplay, scenes, ui, options
├── utils/ # color, param, utils
├── external/ # vendored third-party headers (json, fkyaml, stb_*)
└── main.cpp
```
`#include` paths are absolute relative to `source/` (e.g. `#include "core/audio/audio.hpp"`, `#include "game/scenes/logo.hpp"`). The CMake build adds a single `-I${CMAKE_SOURCE_DIR}/source`.
### Singletons (core systems)
- **Director** (`source/core/system/director.hpp`) — Application state machine, orchestrates scene transitions (Logo → Intro → Title → Game → Credits/HiScore → Title)
- **Screen** (`source/core/rendering/screen.hpp`) — Window management, SDL3 GPU rendering pipeline, post-processing effects
- **Resource** (`source/core/resources/resource.hpp`) — Asset loading/caching with PRELOAD and LAZY_LOAD modes, reads from `resources.pack`
- **Audio** (`source/core/audio/audio.hpp`) — Music and SFX management
- **Input** (`source/core/input/input.hpp`) — Keyboard and gamepad input handling
### Scenes (source/game/scenes/)
Each scene is a self-contained class with update/render lifecycle. Scene flow is managed by Director.
### Entity Managers
- `BalloonManager` / `BulletManager` — Object pool-based entity management
- `Player` — Two-player support (player 1: keyboard, player 2: gamepad)
- `BalloonManager` / `BulletManager` — Object pool-based entity management (`source/game/gameplay/`)
- `Player` — Two-player support (player 1: keyboard, player 2: gamepad) (`source/game/entities/`)
### Rendering Pipeline
- SDL3 GPU API (Vulkan/Metal/D3D12 backends)
- SPIR-V shaders compiled offline from GLSL (`data/shaders/`) via `glslc`
- Compiled shader headers embedded in `source/rendering/sdl3gpu/postfx_*_spv.h`
- Compiled shader headers embedded in `source/core/rendering/sdl3gpu/postfx_*_spv.h`
- macOS uses Metal (no SPIR-V compilation needed)
### Configuration
@@ -72,7 +86,9 @@ Each scene is a self-contained class with update/render lifecycle. Scene flow is
- Gamepad mappings: `config/gamecontrollerdb.txt`
### External Libraries (header-only/vendored in source/external/)
- nlohmann/json, fkyaml (YAML), stb_image, stb_vorbis, jail_audio
- nlohmann/json, fkyaml (YAML), stb_image, stb_vorbis
`jail_audio` lives in `source/core/audio/` and `gif.{hpp,cpp}` in `source/core/rendering/` — these are first-party, not third-party.
## Code Style
+244 -152
View File
@@ -3,6 +3,11 @@
cmake_minimum_required(VERSION 3.10)
project(coffee_crisis_arcade_edition VERSION 2.00)
# Tipus de build per defecte (Debug) si no se n'ha especificat cap
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE)
endif()
# Establecer estándar de C++
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)
@@ -10,17 +15,24 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# --- GENERACIÓN DE VERSIÓN AUTOMÁTICA ---
find_package(Git QUIET)
if(GIT_FOUND)
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-parse --short=7 HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
else()
set(GIT_HASH "unknown")
# Si GIT_HASH se ha pasado desde fuera (p.ej. desde el Makefile via -DGIT_HASH=xxx),
# lo usamos tal cual. Esto evita problemas con Docker/emscripten, donde git aborta por
# "dubious ownership" en el volumen montado. En builds locales sin -DGIT_HASH, se
# resuelve aquí ejecutando git directamente.
if(NOT DEFINED GIT_HASH OR GIT_HASH STREQUAL "")
find_package(Git QUIET)
if(GIT_FOUND)
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-parse --short=7 HEAD
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE GIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
endif()
if(NOT DEFINED GIT_HASH OR GIT_HASH STREQUAL "")
set(GIT_HASH "unknown")
endif()
endif()
# Configurar archivo de versión
@@ -28,173 +40,189 @@ configure_file(${CMAKE_SOURCE_DIR}/source/version.h.in ${CMAKE_BINARY_DIR}/versi
# --- 1. LISTA EXPLÍCITA DE FUENTES ---
set(APP_SOURCES
# --- Archivos Principales del Sistema ---
source/asset.cpp
source/audio.cpp
source/director.cpp
source/global_events.cpp
source/global_inputs.cpp
source/input.cpp
source/lang.cpp
source/main.cpp
source/param.cpp
source/resource.cpp
source/resource_helper.cpp
source/resource_loader.cpp
source/resource_pack.cpp
source/screen.cpp
source/text.cpp
source/writer.cpp
# --- UI (User Interface) ---
source/ui/menu_option.cpp
source/ui/menu_renderer.cpp
source/ui/notifier.cpp
source/ui/service_menu.cpp
source/ui/ui_message.cpp
source/ui/window_message.cpp
# --- Lógica del Juego ---
source/balloon_formations.cpp
source/balloon_manager.cpp
source/balloon.cpp
source/bullet.cpp
source/bullet_manager.cpp
source/enter_name.cpp
source/explosions.cpp
source/game_logo.cpp
source/item.cpp
source/manage_hiscore_table.cpp
source/player.cpp
source/scoreboard.cpp
source/tabe.cpp
# --- Escenas ---
source/sections/credits.cpp
source/sections/game.cpp
source/sections/hiscore_table.cpp
source/sections/instructions.cpp
source/sections/intro.cpp
source/sections/logo.cpp
source/sections/title.cpp
# --- Sprites y Gráficos ---
source/animated_sprite.cpp
source/background.cpp
source/card_sprite.cpp
source/fade.cpp
source/moving_sprite.cpp
source/path_sprite.cpp
source/smart_sprite.cpp
source/sprite.cpp
source/texture.cpp
source/tiled_bg.cpp
# --- Otros ---
source/color.cpp
source/demo.cpp
source/define_buttons.cpp
source/difficulty.cpp
source/input_types.cpp
source/mouse.cpp
source/options.cpp
source/shutdown.cpp
source/stage.cpp
source/system_utils.cpp
source/utils.cpp
)
# Fuentes de librerías de terceros
set(EXTERNAL_SOURCES
source/external/json.hpp
source/external/gif.cpp
)
# --- core/audio ---
source/core/audio/audio.cpp
source/core/audio/audio_adapter.cpp
# Fuentes del sistema de renderizado
set(RENDERING_SOURCES
source/rendering/sdl3gpu/sdl3gpu_shader.cpp
# --- core/input ---
source/core/input/define_buttons.cpp
source/core/input/global_inputs.cpp
source/core/input/input.cpp
source/core/input/input_types.cpp
source/core/input/mouse.cpp
# --- core/locale ---
source/core/locale/lang.cpp
# --- core/rendering ---
source/core/rendering/background.cpp
source/core/rendering/fade.cpp
source/core/rendering/gif.cpp
source/core/rendering/screen.cpp
source/core/rendering/text.cpp
source/core/rendering/texture.cpp
source/core/rendering/tiled_bg.cpp
source/core/rendering/writer.cpp
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp
source/core/rendering/sprite/animated_sprite.cpp
source/core/rendering/sprite/card_sprite.cpp
source/core/rendering/sprite/moving_sprite.cpp
source/core/rendering/sprite/path_sprite.cpp
source/core/rendering/sprite/smart_sprite.cpp
source/core/rendering/sprite/sprite.cpp
# --- core/resources ---
source/core/resources/asset.cpp
source/core/resources/asset_integrated.cpp
source/core/resources/resource.cpp
source/core/resources/resource_helper.cpp
source/core/resources/resource_loader.cpp
source/core/resources/resource_pack.cpp
# --- core/system ---
source/core/system/demo.cpp
source/core/system/director.cpp
source/core/system/global_events.cpp
source/core/system/shutdown.cpp
source/core/system/system_utils.cpp
# --- game ---
source/game/options.cpp
# --- game/entities ---
source/game/entities/balloon.cpp
source/game/entities/bullet.cpp
source/game/entities/explosions.cpp
source/game/entities/item.cpp
source/game/entities/player.cpp
source/game/entities/tabe.cpp
# --- game/gameplay ---
source/game/gameplay/balloon_formations.cpp
source/game/gameplay/balloon_manager.cpp
source/game/gameplay/bullet_manager.cpp
source/game/gameplay/difficulty.cpp
source/game/gameplay/enter_name.cpp
source/game/gameplay/game_logo.cpp
source/game/gameplay/manage_hiscore_table.cpp
source/game/gameplay/scoreboard.cpp
source/game/gameplay/stage.cpp
# --- game/scenes ---
source/game/scenes/credits.cpp
source/game/scenes/game.cpp
source/game/scenes/hiscore_table.cpp
source/game/scenes/instructions.cpp
source/game/scenes/intro.cpp
source/game/scenes/logo.cpp
source/game/scenes/preload.cpp
source/game/scenes/title.cpp
# --- game/ui ---
source/game/ui/menu_option.cpp
source/game/ui/menu_renderer.cpp
source/game/ui/notifier.cpp
source/game/ui/service_menu.cpp
source/game/ui/ui_message.cpp
source/game/ui/window_message.cpp
# --- utils ---
source/utils/color.cpp
source/utils/param.cpp
source/utils/utils.cpp
)
# Configuración de SDL3
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}")
if(EMSCRIPTEN)
# En Emscripten, SDL3 se compila desde source con FetchContent
include(FetchContent)
FetchContent_Declare(
SDL3
GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
GIT_TAG release-3.4.4
GIT_SHALLOW TRUE
)
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
set(SDL_STATIC ON CACHE BOOL "" FORCE)
set(SDL_TEST_LIBRARY OFF CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(SDL3)
message(STATUS "SDL3 compilado desde source para Emscripten")
else()
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3)
message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}")
endif()
# --- SHADER COMPILATION (Linux/Windows only - macOS uses Metal) ---
if(NOT APPLE)
# --- SHADER COMPILATION (Linux/Windows only - macOS uses Metal, Emscripten no soporta SDL3 GPU) ---
if(NOT APPLE AND NOT EMSCRIPTEN)
find_program(GLSLC_EXE NAMES glslc)
set(SHADER_VERT_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.vert")
set(SHADER_FRAG_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.frag")
set(SHADER_CRTPI_SRC "${CMAKE_SOURCE_DIR}/data/shaders/crtpi_frag.glsl")
set(SHADER_UPSCALE_SRC "${CMAKE_SOURCE_DIR}/data/shaders/upscale.frag")
set(SHADER_DOWNSCALE_SRC "${CMAKE_SOURCE_DIR}/data/shaders/downscale.frag")
set(SHADERS_DIR "${CMAKE_SOURCE_DIR}/data/shaders")
set(HEADERS_DIR "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/spv")
set(SHADER_VERT_H "${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu/postfx_vert_spv.h")
set(SHADER_FRAG_H "${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu/postfx_frag_spv.h")
set(SHADER_CRTPI_H "${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu/crtpi_frag_spv.h")
set(SHADER_UPSCALE_H "${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu/upscale_frag_spv.h")
set(SHADER_DOWNSCALE_H "${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu/downscale_frag_spv.h")
set(ALL_SHADER_SOURCES "${SHADER_VERT_SRC}" "${SHADER_FRAG_SRC}" "${SHADER_CRTPI_SRC}" "${SHADER_UPSCALE_SRC}" "${SHADER_DOWNSCALE_SRC}")
set(ALL_SHADER_HEADERS "${SHADER_VERT_H}" "${SHADER_FRAG_H}" "${SHADER_CRTPI_H}" "${SHADER_UPSCALE_H}" "${SHADER_DOWNSCALE_H}")
set(ALL_SHADER_HEADERS
"${HEADERS_DIR}/postfx_vert_spv.h"
"${HEADERS_DIR}/postfx_frag_spv.h"
"${HEADERS_DIR}/crtpi_frag_spv.h"
)
set(ALL_SHADER_SOURCES
"${SHADERS_DIR}/postfx.vert"
"${SHADERS_DIR}/postfx.frag"
"${SHADERS_DIR}/crtpi_frag.glsl"
)
if(GLSLC_EXE)
set(COMPILE_SHADER_SCRIPT "${CMAKE_SOURCE_DIR}/tools/shaders/compile_shader.cmake")
macro(add_shader SRC_FILE OUT_H VAR_NAME)
cmake_parse_arguments(S "" "STAGE" "" ${ARGN})
add_custom_command(
OUTPUT "${OUT_H}"
COMMAND ${CMAKE_COMMAND}
"-DGLSLC=${GLSLC_EXE}"
"-DSRC=${SRC_FILE}"
"-DOUT_H=${OUT_H}"
"-DVAR=${VAR_NAME}"
"-DSTAGE=${S_STAGE}"
-P "${COMPILE_SHADER_SCRIPT}"
DEPENDS "${SRC_FILE}" "${COMPILE_SHADER_SCRIPT}"
COMMENT "Compilando shader: ${VAR_NAME}"
)
endmacro()
add_shader("${SHADER_VERT_SRC}" "${SHADER_VERT_H}" "postfx_vert_spv")
add_shader("${SHADER_FRAG_SRC}" "${SHADER_FRAG_H}" "postfx_frag_spv")
add_shader("${SHADER_CRTPI_SRC}" "${SHADER_CRTPI_H}" "crtpi_frag_spv" STAGE fragment)
add_shader("${SHADER_UPSCALE_SRC}" "${SHADER_UPSCALE_H}" "upscale_frag_spv")
add_shader("${SHADER_DOWNSCALE_SRC}" "${SHADER_DOWNSCALE_H}" "downscale_frag_spv")
add_custom_command(
OUTPUT ${ALL_SHADER_HEADERS}
COMMAND ${CMAKE_COMMAND}
-D GLSLC=${GLSLC_EXE}
-D SHADERS_DIR=${SHADERS_DIR}
-D HEADERS_DIR=${HEADERS_DIR}
-P ${CMAKE_SOURCE_DIR}/tools/shaders/compile_spirv.cmake
DEPENDS ${ALL_SHADER_SOURCES}
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
COMMENT "Compilando shaders SPIR-V..."
)
add_custom_target(shaders DEPENDS ${ALL_SHADER_HEADERS})
message(STATUS "glslc encontrado: shaders se compilarán automáticamente")
else()
foreach(_h IN LISTS ALL_SHADER_HEADERS)
if(NOT EXISTS "${_h}")
foreach(HDR ${ALL_SHADER_HEADERS})
if(NOT EXISTS "${HDR}")
message(FATAL_ERROR
"glslc no encontrado y header SPIR-V no existe: ${_h}\n"
"glslc no encontrado y header SPIR-V no existe: ${HDR}\n"
" Instala glslc: sudo apt install glslang-tools (Linux)\n"
" choco install vulkan-sdk (Windows)\n"
" O genera los headers manualmente: tools/shaders/compile_spirv.sh"
" choco install vulkan-sdk (Windows)"
)
endif()
endforeach()
message(STATUS "glslc no encontrado - usando headers SPIR-V precompilados")
endif()
else()
message(STATUS "macOS: shaders SPIR-V omitidos (usa Metal)")
if(EMSCRIPTEN)
message(STATUS "Emscripten: shaders SPIR-V omitidos (SDL3 GPU no soportado en WebGL2)")
else()
message(STATUS "macOS: shaders SPIR-V omitidos (usa Metal)")
endif()
endif()
# --- 2. AÑADIR EJECUTABLE ---
add_executable(${PROJECT_NAME} ${APP_SOURCES} ${EXTERNAL_SOURCES} ${RENDERING_SOURCES})
if(EMSCRIPTEN)
# En Emscripten no compilamos sdl3gpu_shader (SDL3 GPU no está soportado en WebGL2)
set(APP_SOURCES_WASM ${APP_SOURCES})
list(REMOVE_ITEM APP_SOURCES_WASM source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp)
add_executable(${PROJECT_NAME} ${APP_SOURCES_WASM})
else()
add_executable(${PROJECT_NAME} ${APP_SOURCES})
endif()
if(NOT APPLE AND GLSLC_EXE)
if(NOT APPLE AND NOT EMSCRIPTEN AND GLSLC_EXE)
add_dependencies(${PROJECT_NAME} shaders)
endif()
# --- 3. DIRECTORIOS DE INCLUSIÓN ---
target_include_directories(${PROJECT_NAME} PUBLIC
"${CMAKE_SOURCE_DIR}/source"
"${CMAKE_SOURCE_DIR}/source/external"
"${CMAKE_SOURCE_DIR}/source/rendering"
"${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu"
"${CMAKE_BINARY_DIR}"
)
@@ -231,13 +259,27 @@ elseif(APPLE)
-rpath @executable_path/../Frameworks/
)
endif()
elseif(EMSCRIPTEN)
target_compile_definitions(${PROJECT_NAME} PRIVATE EMSCRIPTEN_BUILD NO_SHADERS)
# -fexceptions: habilita excepciones C++ (fkyaml, std::runtime_error...) — sin esto cualquier throw llama a abort()
target_compile_options(${PROJECT_NAME} PRIVATE -fexceptions)
target_link_options(${PROJECT_NAME} PRIVATE
"SHELL:--preload-file ${CMAKE_SOURCE_DIR}/data@/data"
"SHELL:--preload-file ${CMAKE_SOURCE_DIR}/config@/config"
-fexceptions
-sALLOW_MEMORY_GROWTH=1
-sMAX_WEBGL_VERSION=2
-sINITIAL_MEMORY=67108864
-sASSERTIONS=1
# ASYNCIFY solo para permitir emscripten_sleep(0) durante la precarga de recursos
# (el bucle principal del juego ya usa SDL3 Callback API, no depende de ASYNCIFY).
-sASYNCIFY=1
)
set_target_properties(${PROJECT_NAME} PROPERTIES SUFFIX ".html")
elseif(UNIX AND NOT APPLE)
target_compile_definitions(${PROJECT_NAME} PRIVATE LINUX_BUILD)
endif()
# Especificar la ubicación del ejecutable
set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR})
# --- 5. STATIC ANALYSIS TARGETS ---
find_program(CLANG_TIDY_EXE NAMES clang-tidy)
@@ -258,7 +300,6 @@ list(FILTER ALL_SOURCE_FILES EXCLUDE REGEX ".*/external/.*")
# cuyo uso vive en un .cpp distinto.
set(CPPCHECK_SOURCES ${ALL_SOURCE_FILES})
list(FILTER CPPCHECK_SOURCES INCLUDE REGEX ".*\\.cpp$")
list(FILTER CPPCHECK_SOURCES EXCLUDE REGEX ".*_spv\\.h$")
# Targets de clang-tidy
if(CLANG_TIDY_EXE)
@@ -327,13 +368,12 @@ if(CPPCHECK_EXE)
--inline-suppr
--suppress=missingIncludeSystem
--suppress=toomanyconfigs
--suppress=*:*/source/external/*
--suppress=*:*/source/core/rendering/sdl3gpu/spv/*
-D_DEBUG
-DLINUX_BUILD
--quiet
-I ${CMAKE_SOURCE_DIR}/source
-I ${CMAKE_SOURCE_DIR}/source/external
-I ${CMAKE_SOURCE_DIR}/source/rendering
-I ${CMAKE_SOURCE_DIR}/source/rendering/sdl3gpu
${CPPCHECK_SOURCES}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Running cppcheck..."
@@ -341,3 +381,55 @@ if(CPPCHECK_EXE)
else()
message(STATUS "cppcheck no encontrado - target 'cppcheck' no disponible")
endif()
# --- EINA STANDALONE: pack_resources ---
# Executable auxiliar que empaqueta `data/` a `resources.pack`.
# No es compila per defecte (EXCLUDE_FROM_ALL). Build explícit:
# cmake --build build --target pack_resources
# Després executar: ./build/pack_resources data resources.pack
if(NOT EMSCRIPTEN)
add_executable(pack_resources EXCLUDE_FROM_ALL
tools/pack_resources/pack_resources.cpp
source/core/resources/resource_pack.cpp
)
target_include_directories(pack_resources PRIVATE "${CMAKE_SOURCE_DIR}/source")
target_compile_options(pack_resources PRIVATE -Wall)
# Regeneració automàtica de resources.pack en cada build si canvia data/.
file(GLOB_RECURSE DATA_FILES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/data/*")
set(RESOURCE_PACK "${CMAKE_BINARY_DIR}/resources.pack")
add_custom_command(
OUTPUT ${RESOURCE_PACK}
COMMAND $<TARGET_FILE:pack_resources>
"${CMAKE_SOURCE_DIR}/data"
"${RESOURCE_PACK}"
DEPENDS pack_resources ${DATA_FILES}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Empaquetant data/ → resources.pack"
VERBATIM
)
add_custom_target(resource_pack ALL DEPENDS ${RESOURCE_PACK})
add_dependencies(${PROJECT_NAME} resource_pack)
# --- CÒPIA DE gamecontrollerdb.txt AL COSTAT DEL BINARI ---
# SDL_AddGamepadMappingsFromFile només llegeix del filesystem real (no del
# pack), així que el fitxer ha de viure al directori del binari. Es copia
# només si existeix per no fallar la build d'algú que encara no ha fet
# `make controllerdb`.
if(EXISTS "${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt")
set(CONTROLLER_DB "${CMAKE_BINARY_DIR}/gamecontrollerdb.txt")
add_custom_command(
OUTPUT ${CONTROLLER_DB}
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt"
"${CONTROLLER_DB}"
DEPENDS "${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt"
COMMENT "Copiant gamecontrollerdb.txt → build/"
VERBATIM
)
add_custom_target(controller_db ALL DEPENDS ${CONTROLLER_DB})
add_dependencies(${PROJECT_NAME} controller_db)
endif()
endif()
+240 -214
View File
@@ -2,17 +2,13 @@
# DIRECTORIES
# ==============================================================================
DIR_ROOT := $(dir $(abspath $(MAKEFILE_LIST)))
DIR_TOOLS := $(addsuffix /, $(DIR_ROOT)tools)
BUILDDIR := build
# ==============================================================================
# TARGET NAMES
# ==============================================================================
TARGET_NAME := coffee_crisis_arcade_edition
ifeq ($(OS),Windows_NT)
TARGET_FILE := $(DIR_ROOT)$(TARGET_NAME).exe
else
TARGET_FILE := $(DIR_ROOT)$(TARGET_NAME)
endif
TARGET_FILE := $(BUILDDIR)/$(TARGET_NAME)
APP_NAME := Coffee Crisis Arcade Edition
DIST_DIR := dist
RELEASE_FOLDER := dist/_tmp
@@ -22,16 +18,33 @@ RESOURCE_FILE := release/windows/coffee.res
# ==============================================================================
# TOOLS
# ==============================================================================
DIR_PACK_TOOL := $(DIR_TOOLS)pack_resources
SHADER_SCRIPT := $(DIR_ROOT)tools/shaders/compile_spirv.sh
SHADER_CMAKE := $(DIR_ROOT)tools/shaders/compile_spirv.cmake
SHADERS_DIR := $(DIR_ROOT)data/shaders
HEADERS_DIR := $(DIR_ROOT)source/core/rendering/sdl3gpu/spv
ifeq ($(OS),Windows_NT)
GLSLC := $(shell where glslc 2>NUL)
else
GLSLC := $(shell command -v glslc 2>/dev/null)
endif
# ==============================================================================
# VERSION (fecha actual)
# VERSION (extraída de defines.hpp)
# ==============================================================================
ifeq ($(OS),Windows_NT)
VERSION := $(shell powershell -Command "Get-Date -Format 'yyyy-MM-dd'")
VERSION := $(shell powershell -Command "(Select-String -Path 'source/utils/defines.hpp' -Pattern 'constexpr const char\* VERSION = \"(.+?)\"').Matches.Groups[1].Value")
else
VERSION := $(shell date +%Y-%m-%d)
VERSION := $(shell grep 'constexpr const char\* VERSION' source/utils/defines.hpp | sed -E 's/.*VERSION = "([^"]+)".*/\1/')
endif
# Hash del commit actual, computado en el host. Se pasa a CMake via -DGIT_HASH
# para que el build en docker/emscripten no falle por "dubious ownership" de Git.
ifeq ($(OS),Windows_NT)
GIT_HASH := $(shell git rev-parse --short=7 HEAD 2>NUL)
else
GIT_HASH := $(shell git rev-parse --short=7 HEAD 2>/dev/null)
endif
ifeq ($(GIT_HASH),)
GIT_HASH := unknown
endif
# ==============================================================================
@@ -45,11 +58,15 @@ endif
# WINDOWS-SPECIFIC VARIABLES
# ==============================================================================
ifeq ($(OS),Windows_NT)
WIN_TARGET_FILE := $(DIR_ROOT)$(APP_NAME)
WIN_TARGET_FILE := $(BUILDDIR)/$(APP_NAME)
WIN_RELEASE_FILE := $(RELEASE_FOLDER)/$(APP_NAME)
# Escapa apòstrofs per a PowerShell (duplica ' → ''). Sense això, APP_NAMEs
# com "JailDoctor's Dilemma" trencarien el parsing de -Destination '...'.
WIN_RELEASE_FILE_PS := $(subst ','',$(WIN_RELEASE_FILE))
else
WIN_TARGET_FILE := $(TARGET_FILE)
WIN_RELEASE_FILE := $(RELEASE_FILE)
WIN_RELEASE_FILE_PS := $(WIN_RELEASE_FILE)
endif
# ==============================================================================
@@ -59,7 +76,6 @@ WINDOWS_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-win32-x64.z
MACOS_INTEL_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-macos-intel.dmg
MACOS_APPLE_SILICON_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-macos-apple-silicon.dmg
LINUX_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-linux.tar.gz
RASPI_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-raspberry.tar.gz
# ==============================================================================
# PLATAFORMA
@@ -76,57 +92,98 @@ else
UNAME_S := $(shell uname -s)
endif
# ==============================================================================
# CMAKE GENERATOR (usa Ninja si está disponible; si no, MinGW Makefiles en
# Windows / generador por defecto en Linux/macOS). Ninja paraleliza mejor.
# ==============================================================================
ifeq ($(OS),Windows_NT)
# Dins MSYS2/Git Bash/MinGW, $(shell ...) usa sh.exe i "NUL" NO és
# dispositiu — un redirect "2>NUL" crearia un fitxer literal anomenat
# NUL al cwd. Detectem MSYSTEM per usar /dev/null en aquests entorns.
ifneq ($(MSYSTEM),)
NULDEV := /dev/null
else
NULDEV := NUL
endif
HAS_NINJA := $(shell ninja --version 2>$(NULDEV))
ifneq ($(HAS_NINJA),)
CMAKE_GEN := -G "Ninja"
else
CMAKE_GEN := -G "MinGW Makefiles"
endif
else
HAS_NINJA := $(shell ninja --version 2>/dev/null)
ifneq ($(HAS_NINJA),)
CMAKE_GEN := -G "Ninja"
else
CMAKE_GEN :=
endif
endif
# ==============================================================================
# COMPILACIÓN CON CMAKE
# ==============================================================================
all:
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
@cmake --build build
debug:
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Debug -DGIT_HASH=$(GIT_HASH)
@cmake --build build
run: all
@./$(TARGET_FILE)
run-debug: debug
@./$(TARGET_FILE)
clean:
@rm -rf $(BUILDDIR)
rebuild: clean all
# ==============================================================================
# RELEASE AUTOMÁTICO (detecta SO)
# ==============================================================================
release:
ifeq ($(OS),Windows_NT)
@"$(MAKE)" windows_release
@"$(MAKE)" _windows-release
else
ifeq ($(UNAME_S),Darwin)
@$(MAKE) macos_release
@$(MAKE) _macos-release
else
@$(MAKE) linux_release
@$(MAKE) _linux-release
endif
endif
# ==============================================================================
# REGLAS PARA HERRAMIENTA DE EMPAQUETADO Y RESOURCES.PACK
# EMPAQUETADO DE RECURSOS (build previ de l'eina + execució)
# ==============================================================================
pack_tool:
@$(MAKE) -C $(DIR_PACK_TOOL)
resources.pack:
@$(MAKE) -C $(DIR_PACK_TOOL) pack
pack:
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
@cmake --build build --target pack_resources
@./build/pack_resources data build/resources.pack
# ==============================================================================
# COMPILACIÓN DE SHADERS
# REGLAS PARA COMPILACIÓN DE SHADERS (multiplataforma via cmake)
# ==============================================================================
spirv:
@echo "Compilando shaders SPIR-V..."
$(SHADER_SCRIPT)
compile-shaders:
ifdef GLSLC
@cmake -D GLSLC=$(GLSLC) -D SHADERS_DIR=$(SHADERS_DIR) -D HEADERS_DIR=$(HEADERS_DIR) -P $(SHADER_CMAKE)
else
@echo "glslc no encontrado - asegurate de que los headers SPIR-V precompilados existen"
endif
# ==============================================================================
# COMPILACIÓN PARA WINDOWS (RELEASE)
# ==============================================================================
windows_release:
@$(MAKE) resources.pack
_windows-release:
@$(MAKE) pack
@echo off
@echo Creando release para Windows - Version: $(VERSION)
# Compila con cmake
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
@cmake --build build
# Crea carpeta de distribución y carpeta temporal 'RELEASE_FOLDER'
@@ -136,13 +193,13 @@ windows_release:
# Copia la carpeta 'config' y el archivo 'resources.pack'
@powershell -Command "Copy-Item -Path 'config' -Destination '$(RELEASE_FOLDER)' -recurse -Force"
@powershell -Command "Copy-Item -Path 'resources.pack' -Destination '$(RELEASE_FOLDER)'"
@powershell -Command "Copy-Item -Path 'build/resources.pack' -Destination '$(RELEASE_FOLDER)'"
# Copia los ficheros que estan en la raíz del proyecto
@powershell -Command "Copy-Item 'LICENSE' -Destination '$(RELEASE_FOLDER)'"
@powershell -Command "Copy-Item 'README.md' -Destination '$(RELEASE_FOLDER)'"
@powershell -Command "Copy-Item 'release\windows\dll\*.dll' -Destination '$(RELEASE_FOLDER)'"
@powershell -Command "Copy-Item -Path '$(TARGET_FILE)' -Destination '$(WIN_RELEASE_FILE).exe'"
@powershell -Command "Copy-Item -Path '$(TARGET_FILE).exe' -Destination '$(WIN_RELEASE_FILE_PS).exe'"
strip -s -R .comment -R .gnu.version "$(WIN_RELEASE_FILE).exe" --strip-unneeded
# Crea el fichero .zip
@@ -156,16 +213,32 @@ windows_release:
# ==============================================================================
# COMPILACIÓN PARA MACOS (RELEASE)
# ==============================================================================
macos_release:
@$(MAKE) resources.pack
_macos-release:
@$(MAKE) pack
@echo "Creando release para macOS - Version: $(VERSION)"
# Verificar e instalar create-dmg si es necesario
@which create-dmg > /dev/null || (echo "Instalando create-dmg..." && brew install create-dmg)
# Compila la versión para procesadores Intel con cmake
@cmake -S . -B build/intel -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DMACOS_BUNDLE=ON
@cmake --build build/intel
# Verifica dependencias necesarias (create-dmg). Si falta, intenta instalarla
# con brew; si brew tampoco está, indica el comando exacto al usuario.
@command -v create-dmg >/dev/null 2>&1 || { \
echo ""; \
echo "============================================"; \
echo " Falta la dependencia: create-dmg"; \
echo "============================================"; \
if command -v brew >/dev/null 2>&1; then \
echo " Instalando con: brew install create-dmg"; \
brew install create-dmg || { \
echo ""; \
echo " ERROR: 'brew install create-dmg' ha fallado."; \
echo " Ejecuta el comando manualmente y vuelve a probar."; \
exit 1; \
}; \
else \
echo " Homebrew no está instalado."; \
echo " Instálalo desde https://brew.sh y luego ejecuta:"; \
echo " brew install create-dmg"; \
exit 1; \
fi; \
}
# Elimina datos de compilaciones anteriores
$(RMDIR) "$(RELEASE_FOLDER)"
@@ -181,39 +254,64 @@ macos_release:
$(MKDIR) "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
# Copia carpetas y ficheros
cp -R config "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
cp resources.pack "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
cp build/resources.pack "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
cp -R release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64/SDL3.framework "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks"
cp release/icons/*.icns "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Resources"
cp release/macos/Info.plist "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents"
cp LICENSE "$(RELEASE_FOLDER)"
cp README.md "$(RELEASE_FOLDER)"
# Copia el ejecutable Intel al bundle
cp "$(TARGET_FILE)" "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)"
# Actualiza versión en Info.plist
@echo "Actualizando Info.plist con versión $(VERSION)..."
@RAW_VERSION=$$(echo "$(VERSION)" | sed 's/^v//'); \
sed -i '' '/<key>CFBundleShortVersionString<\/key>/{n;s|<string>.*</string>|<string>'"$$RAW_VERSION"'</string>|;}' "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Info.plist"; \
sed -i '' '/<key>CFBundleVersion<\/key>/{n;s|<string>.*</string>|<string>'"$$RAW_VERSION"'</string>|;}' "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Info.plist"
# Firma la aplicación
codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app"
# Empaqueta el .dmg de la versión Intel con create-dmg
@echo "Creando DMG Intel con iconos de 96x96..."
create-dmg \
--volname "$(APP_NAME)" \
--window-pos 200 120 \
--window-size 720 300 \
--icon-size 96 \
--text-size 12 \
--icon "$(APP_NAME).app" 278 102 \
--icon "LICENSE" 441 102 \
--icon "README.md" 604 102 \
--app-drop-link 115 102 \
--hide-extension "$(APP_NAME).app" \
"$(MACOS_INTEL_RELEASE)" \
"$(RELEASE_FOLDER)" || true
@echo "Release Intel creado: $(MACOS_INTEL_RELEASE)"
# Compila y empaqueta la versión Intel (best-effort: si falla, se omite el
# DMG Intel y continúa con la build de Apple Silicon).
@echo ""
@echo "============================================"
@echo " Compilando version Intel (x86_64)"
@echo "============================================"
@if cmake -S . -B build/intel -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_OSX_ARCHITECTURES=x86_64 \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 \
-DMACOS_BUNDLE=ON \
-DGIT_HASH=$(GIT_HASH) \
&& cmake --build build/intel; then \
cp "$(TARGET_FILE)" "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)"; \
codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app"; \
echo "Creando DMG Intel con iconos de 96x96..."; \
create-dmg \
--volname "$(APP_NAME)" \
--window-pos 200 120 \
--window-size 720 300 \
--icon-size 96 \
--text-size 12 \
--icon "$(APP_NAME).app" 278 102 \
--icon "LICENSE" 441 102 \
--icon "README.md" 604 102 \
--app-drop-link 115 102 \
--hide-extension "$(APP_NAME).app" \
"$(MACOS_INTEL_RELEASE)" \
"$(RELEASE_FOLDER)" || true; \
echo "Release Intel creado: $(MACOS_INTEL_RELEASE)"; \
else \
echo ""; \
echo "============================================"; \
echo " WARNING: la build Intel ha fallado."; \
echo " Se omite el DMG Intel y se continúa con"; \
echo " la build de Apple Silicon."; \
echo "============================================"; \
echo ""; \
fi
# Compila la versión para procesadores Apple Silicon con cmake
@cmake -S . -B build/arm -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 -DMACOS_BUNDLE=ON
@echo ""
@echo "============================================"
@echo " Compilando version Apple Silicon (arm64)"
@echo "============================================"
@cmake -S . -B build/arm -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 -DMACOS_BUNDLE=ON -DGIT_HASH=$(GIT_HASH)
@cmake --build build/arm
cp "$(TARGET_FILE)" "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)"
@@ -246,12 +344,12 @@ macos_release:
# ==============================================================================
# COMPILACIÓN PARA LINUX (RELEASE)
# ==============================================================================
linux_release:
@$(MAKE) resources.pack
_linux-release:
@$(MAKE) pack
@echo "Creando release para Linux - Version: $(VERSION)"
# Compila con cmake
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
@cmake --build build
# Elimina carpeta temporal previa y la recrea (crea dist/ si no existe)
@@ -259,8 +357,7 @@ linux_release:
$(MKDIR) "$(RELEASE_FOLDER)"
# Copia ficheros
cp -R config "$(RELEASE_FOLDER)"
cp resources.pack "$(RELEASE_FOLDER)"
cp build/resources.pack "$(RELEASE_FOLDER)"
cp LICENSE "$(RELEASE_FOLDER)"
cp README.md "$(RELEASE_FOLDER)"
cp "$(TARGET_FILE)" "$(RELEASE_FILE)"
@@ -275,161 +372,86 @@ linux_release:
$(RMDIR) "$(RELEASE_FOLDER)"
# ==============================================================================
# COMPILACIÓN PARA LINUX (RELEASE CON INTEGRACIÓN DESKTOP)
# COMPILACIÓN PARA WEBASSEMBLY (requiere Docker)
# ==============================================================================
linux_release_desktop:
@$(MAKE) resources.pack
@echo "Creando release con integracion desktop para Linux - Version: $(VERSION)"
wasm:
@echo "Compilando para WebAssembly - Version: $(VERSION) ($(GIT_HASH))"
docker run --rm \
--user $(shell id -u):$(shell id -g) \
-v $(DIR_ROOT):/src \
-w /src \
emscripten/emsdk:latest \
bash -c "emcmake cmake -S . -B build/wasm -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH) && cmake --build build/wasm"
$(MKDIR) "$(DIST_DIR)/wasm"
cp build/wasm/$(TARGET_NAME).html $(DIST_DIR)/wasm/
cp build/wasm/$(TARGET_NAME).js $(DIST_DIR)/wasm/
cp build/wasm/$(TARGET_NAME).wasm $(DIST_DIR)/wasm/
cp build/wasm/$(TARGET_NAME).data $(DIST_DIR)/wasm/
@echo "Output: $(DIST_DIR)/wasm/"
scp $(DIST_DIR)/wasm/$(TARGET_NAME).js $(DIST_DIR)/wasm/$(TARGET_NAME).wasm $(DIST_DIR)/wasm/$(TARGET_NAME).data \
maverick:/home/sergio/gitea/web_jailgames/static/games/coffee-crisis-arcade-edition/wasm/
ssh maverick 'cd /home/sergio/gitea/web_jailgames && ./deploy.sh'
@echo "Deployed to maverick"
# Compila con cmake
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
@cmake --build build
# Elimina carpetas previas y recrea (crea dist/ si no existe)
$(RMDIR) "$(RELEASE_FOLDER)"
# Crea la estructura de directorios estándar para Linux
$(MKDIR) "$(RELEASE_FOLDER)/$(TARGET_NAME)"
$(MKDIR) "$(RELEASE_FOLDER)/$(TARGET_NAME)/bin"
$(MKDIR) "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications"
$(MKDIR) "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/icons/hicolor/256x256/apps"
$(MKDIR) "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/$(TARGET_NAME)"
# Copia ficheros del juego
cp -R config "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/$(TARGET_NAME)/"
cp resources.pack "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/$(TARGET_NAME)/"
cp LICENSE "$(RELEASE_FOLDER)/$(TARGET_NAME)/"
cp README.md "$(RELEASE_FOLDER)/$(TARGET_NAME)/"
# Copia el ejecutable
cp "$(TARGET_FILE)" "$(RELEASE_FOLDER)/$(TARGET_NAME)/bin/$(TARGET_NAME)"
strip -s -R .comment -R .gnu.version "$(RELEASE_FOLDER)/$(TARGET_NAME)/bin/$(TARGET_NAME)" --strip-unneeded
# Crea el archivo .desktop
@echo '[Desktop Entry]' > "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
@echo 'Version=1.0' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
@echo 'Type=Application' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
@echo 'Name=$(APP_NAME)' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
@echo 'Comment=Arcade action game - defend Earth from alien invasion!' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
@echo 'Exec=/opt/$(TARGET_NAME)/bin/$(TARGET_NAME)' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
@echo 'Icon=$(TARGET_NAME)' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
@echo 'Path=/opt/$(TARGET_NAME)/share/$(TARGET_NAME)' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
@echo 'Terminal=false' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
@echo 'StartupNotify=true' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
@echo 'Categories=Game;ArcadeGame;' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
@echo 'Keywords=arcade;action;shooter;retro;' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop"
# Copia el icono (si existe) y lo redimensiona si es necesario
@if [ -f "release/icons/icon.png" ]; then \
if command -v magick >/dev/null 2>&1; then \
magick "release/icons/icon.png" -resize 256x256 "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/icons/hicolor/256x256/apps/$(TARGET_NAME).png"; \
echo "Icono redimensionado de release/icons/icon.png (usando ImageMagick)"; \
elif command -v convert >/dev/null 2>&1; then \
convert "release/icons/icon.png" -resize 256x256 "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/icons/hicolor/256x256/apps/$(TARGET_NAME).png"; \
echo "Icono redimensionado de release/icons/icon.png (usando ImageMagick legacy)"; \
elif command -v ffmpeg >/dev/null 2>&1; then \
ffmpeg -i "release/icons/icon.png" -vf scale=256:256 "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/icons/hicolor/256x256/apps/$(TARGET_NAME).png" -y -loglevel quiet; \
echo "Icono redimensionado de release/icons/icon.png (usando ffmpeg)"; \
else \
cp "release/icons/icon.png" "$(RELEASE_FOLDER)/$(TARGET_NAME)/share/icons/hicolor/256x256/apps/$(TARGET_NAME).png"; \
echo "Icono copiado sin redimensionar (instalar ImageMagick o ffmpeg para redimensionado automatico)"; \
fi; \
else \
echo "Advertencia: No se encontró release/icons/icon.png - crear icono manualmente"; \
fi
# Crea script de instalación
@echo '#!/bin/bash' > "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
@echo 'echo "Instalando $(APP_NAME)..."' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
@echo 'sudo mkdir -p /opt/$(TARGET_NAME)' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
@echo 'sudo cp -R bin /opt/$(TARGET_NAME)/' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
@echo 'sudo cp -R share /opt/$(TARGET_NAME)/' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
@echo 'sudo cp LICENSE /opt/$(TARGET_NAME)/' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
@echo 'sudo cp README.md /opt/$(TARGET_NAME)/' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
@echo 'sudo mkdir -p /usr/share/applications' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
@echo 'sudo mkdir -p /usr/share/icons/hicolor/256x256/apps' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
@echo 'sudo cp /opt/$(TARGET_NAME)/share/applications/$(TARGET_NAME).desktop /usr/share/applications/' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
@echo 'sudo cp /opt/$(TARGET_NAME)/share/icons/hicolor/256x256/apps/$(TARGET_NAME).png /usr/share/icons/hicolor/256x256/apps/' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
@echo 'sudo update-desktop-database /usr/share/applications 2>/dev/null || true' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
@echo 'sudo gtk-update-icon-cache /usr/share/icons/hicolor 2>/dev/null || true' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
@echo 'echo "$(APP_NAME) instalado correctamente!"' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
@echo 'echo "Ya puedes encontrarlo en el menu de aplicaciones en la categoria Juegos."' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
chmod +x "$(RELEASE_FOLDER)/$(TARGET_NAME)/install.sh"
# Crea script de desinstalación
@echo '#!/bin/bash' > "$(RELEASE_FOLDER)/$(TARGET_NAME)/uninstall.sh"
@echo 'echo "Desinstalando $(APP_NAME)..."' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/uninstall.sh"
@echo 'sudo rm -rf /opt/$(TARGET_NAME)' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/uninstall.sh"
@echo 'sudo rm -f /usr/share/applications/$(TARGET_NAME).desktop' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/uninstall.sh"
@echo 'sudo rm -f /usr/share/icons/hicolor/256x256/apps/$(TARGET_NAME).png' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/uninstall.sh"
@echo 'sudo update-desktop-database /usr/share/applications 2>/dev/null || true' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/uninstall.sh"
@echo 'sudo gtk-update-icon-cache /usr/share/icons/hicolor 2>/dev/null || true' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/uninstall.sh"
@echo 'echo "$(APP_NAME) desinstalado correctamente."' >> "$(RELEASE_FOLDER)/$(TARGET_NAME)/uninstall.sh"
chmod +x "$(RELEASE_FOLDER)/$(TARGET_NAME)/uninstall.sh"
# Empaqueta ficheros
$(RMFILE) "$(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-linux-desktop.tar.gz"
tar -czvf "$(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-linux-desktop.tar.gz" -C "$(RELEASE_FOLDER)" .
@echo "Release con integracion desktop creado: $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-linux-desktop.tar.gz"
@echo "Para instalar: extraer y ejecutar ./$(TARGET_NAME)/install.sh"
# Elimina la carpeta temporal
$(RMDIR) "$(RELEASE_FOLDER)"
# ==============================================================================
# COMPILACIÓN PARA RASPBERRY PI (RELEASE)
# ==============================================================================
raspi_release:
@$(MAKE) resources.pack
@echo "Creando release para Raspberry Pi - Version: $(VERSION)"
# Compila con cmake
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
@cmake --build build
# Elimina carpetas previas y recrea (crea dist/ si no existe)
$(RMDIR) "$(RELEASE_FOLDER)"
$(MKDIR) "$(RELEASE_FOLDER)"
# Copia ficheros
cp -R config "$(RELEASE_FOLDER)"
cp resources.pack "$(RELEASE_FOLDER)"
cp LICENSE "$(RELEASE_FOLDER)"
cp README.md "$(RELEASE_FOLDER)"
cp "$(TARGET_FILE)" "$(RELEASE_FILE)"
strip -s -R .comment -R .gnu.version "$(RELEASE_FILE)" --strip-unneeded
# Empaqueta ficheros
$(RMFILE) "$(RASPI_RELEASE)"
tar -czvf "$(RASPI_RELEASE)" -C "$(RELEASE_FOLDER)" .
@echo "Release creado: $(RASPI_RELEASE)"
# Elimina la carpeta temporal
$(RMDIR) "$(RELEASE_FOLDER)"
# Versió Debug del build wasm: build local sense deploy. Sortida a dist/wasm-debug/.
wasm-debug:
@echo "Compilando WebAssembly Debug - Version: $(VERSION) ($(GIT_HASH))"
docker run --rm \
--user $(shell id -u):$(shell id -g) \
-v $(DIR_ROOT):/src \
-w /src \
emscripten/emsdk:latest \
bash -c "emcmake cmake -S . -B build/wasm-debug -DCMAKE_BUILD_TYPE=Debug -DGIT_HASH=$(GIT_HASH) && cmake --build build/wasm-debug"
$(MKDIR) "$(DIST_DIR)/wasm-debug"
cp build/wasm-debug/$(TARGET_NAME).html $(DIST_DIR)/wasm-debug/
cp build/wasm-debug/$(TARGET_NAME).js $(DIST_DIR)/wasm-debug/
cp build/wasm-debug/$(TARGET_NAME).wasm $(DIST_DIR)/wasm-debug/
cp build/wasm-debug/$(TARGET_NAME).data $(DIST_DIR)/wasm-debug/
@echo "Output: $(DIST_DIR)/wasm-debug/"
# ==============================================================================
# CODE QUALITY (delegados a cmake)
# ==============================================================================
format:
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
@cmake --build build --target format
format-check:
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
@cmake --build build --target format-check
tidy:
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
@cmake --build build --target tidy
tidy-fix:
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
@cmake --build build --target tidy-fix
cppcheck:
@cmake $(CMAKE_GEN) -S . -B build -DCMAKE_BUILD_TYPE=Release -DGIT_HASH=$(GIT_HASH)
@cmake --build build --target cppcheck
# ==============================================================================
# GIT HOOKS
# ==============================================================================
hooks-install:
@git config core.hooksPath .githooks
@echo "Git hooks activats: $(shell pwd)/.githooks"
# ==============================================================================
# DESCARGA DE GAMECONTROLLERDB
# ==============================================================================
controllerdb:
@echo "Descargando gamecontrollerdb.txt..."
curl -fsSL https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/master/gamecontrollerdb.txt \
-o gamecontrollerdb.txt
@echo "gamecontrollerdb.txt actualizado"
# ==============================================================================
# REGLAS ESPECIALES
# ==============================================================================
show_version:
show-version:
@echo "Version actual: $(VERSION)"
help:
@@ -440,27 +462,31 @@ help:
@echo " make - Compilar con cmake (Release)"
@echo " make debug - Compilar con cmake (Debug)"
@echo ""
@echo " Ejecucion:"
@echo " make run - Compilar (Release) y ejecutar"
@echo " make run-debug - Compilar (Debug) y ejecutar"
@echo ""
@echo " Release:"
@echo " make release - Crear release (detecta SO automaticamente)"
@echo " make windows_release - Crear release para Windows"
@echo " make linux_release - Crear release basico para Linux"
@echo " make linux_release_desktop - Crear release con integracion desktop para Linux"
@echo " make macos_release - Crear release para macOS"
@echo " make raspi_release - Crear release para Raspberry Pi"
@echo " make wasm - Crear build WebAssembly (requiere Docker) en dist/wasm"
@echo " make wasm-debug - Build WebAssembly Debug local (sin deploy)"
@echo ""
@echo " Herramientas:"
@echo " make spirv - Compilar shaders SPIR-V"
@echo " make pack_tool - Compilar herramienta de empaquetado"
@echo " make resources.pack - Generar pack de recursos desde data/"
@echo " make compile-shaders - Compilar shaders SPIR-V"
@echo " make pack - Empaquetar recursos a $(BUILDDIR)/resources.pack"
@echo ""
@echo " Calidad de codigo:"
@echo " make format - Formatear codigo con clang-format"
@echo " make format-check - Verificar formato sin modificar"
@echo " make tidy - Analisis estatico con clang-tidy"
@echo " make tidy-fix - Analisis estatico con auto-fix"
@echo " make cppcheck - Analisis estatico con cppcheck"
@echo ""
@echo " Otros:"
@echo " make show_version - Mostrar version actual ($(VERSION))"
@echo " make clean - Borrar carpeta $(BUILDDIR)/"
@echo " make rebuild - clean + all"
@echo " make show-version - Mostrar version actual ($(VERSION))"
@echo " make hooks-install - Activar git hooks del proyecto"
@echo " make help - Mostrar esta ayuda"
.PHONY: all debug release windows_release macos_release linux_release linux_release_desktop raspi_release pack_tool resources.pack spirv format format-check tidy tidy-fix show_version help
.PHONY: all debug run run-debug clean rebuild release _windows-release _macos-release _linux-release wasm wasm-debug pack compile-shaders format format-check tidy tidy-fix cppcheck controllerdb hooks-install show-version help
+9 -8
View File
@@ -10,14 +10,15 @@ DATA|${SYSTEM_FOLDER}/postfx.yaml|optional,absolute
DATA|${SYSTEM_FOLDER}/crtpi.yaml|optional,absolute
DATA|${SYSTEM_FOLDER}/score.bin|optional,absolute
# Archivos de configuración del juego
DATA|${PREFIX}/config/formations.txt
DATA|${PREFIX}/config/gamecontrollerdb.txt
DATA|${PREFIX}/config/param_320x240.txt
DATA|${PREFIX}/config/param_320x256.txt
DATA|${PREFIX}/config/param_red.txt
DATA|${PREFIX}/config/pools.txt
DATA|${PREFIX}/config/stages.txt
# Dades de gameplay (viatgen dins el pack)
DATA|/data/config/gameplay/formations.txt
DATA|/data/config/gameplay/pools.txt
DATA|/data/config/gameplay/stages.txt
# Presets de paràmetres (viatgen dins el pack)
DATA|/data/config/presets/classic.txt
DATA|/data/config/presets/arcade.txt
DATA|/data/config/presets/red.txt
# Archivos con los datos de la demo
DEMODATA|/data/demo/demo1.bin
@@ -22,7 +22,7 @@ fade.venetian_size 12 # Tamaño de las bandas para el efecto v
# --- SCOREBOARD ---
scoreboard.rect.x 0 # Posición X del marcador
scoreboard.rect.y 216 # Posición Y del marcador
scoreboard.rect.y 200 # Posición Y del marcador
scoreboard.rect.w 320 # Ancho del marcador
scoreboard.rect.h 40 # Alto del marcador
scoreboard.separator_autocolor true # ¿El separador usa color automático?
@@ -99,8 +99,7 @@ intro.text_distance_from_bottom 48 # Posicion del texto
debug.color 00FFFF # Color para elementos de depuración
# --- RESOURCE ---
resource.color FFFFFF # Color de recurso 1
resource.color FFFFFF # Color de recurso 2
resource.color FFFFFF # Color de recursos
# --- TABE ---
tabe.min_spawn_time 2.0f # Tiempo mínimo en minutos para que aparezca el Tabe
@@ -2,7 +2,6 @@
# Formato: PARAMETRO VALOR
# --- GAME ---
game.item_size 20 # Tamaño de los items del juego (en píxeles)
game.item_text_outline_color FFB8B8F0 # Color del outline del texto de los items (RGBA hex) - Rojo claro
game.width 320 # Ancho de la resolución nativa del juego (en píxeles)
game.height 256 # Alto de la resolución nativa del juego (en píxeles)
@@ -12,8 +11,6 @@ game.play_area.rect.w 320 # Ancho de la zona jugable
game.play_area.rect.h 216 # Alto de la zona jugable
game.name_entry_idle_time 10 # Segundos para introducir el nombre al finalizar la partida si no se pulsa nada
game.name_entry_total_time 60 # Segundos totales para introducir el nombre al finalizar la partida
game.hit_stop false # Indica si debe haber un paro cuando el jugador es golpeado por un globo
game.hit_stop_ms 500 # Cantidad de milisegundos que dura el hit_stop
# --- FADE ---
fade.color 5C1F1F # Color hexadecimal para el efecto de fundido - Rojo oscuro
@@ -39,24 +36,24 @@ scoreboard.text_color2 FFE6E6 # Color secundario del texto del marca
scoreboard.skip_countdown_value 8 # Valor para saltar la cuenta atrás (segundos)
# --- TITLE ---
title.press_start_position 180 # Posición Y del texto "Press Start"
title.title_duration 800 # Duración de la pantalla de título (frames)
title.arcade_edition_position 123 # Posición Y del subtítulo "Arcade Edition"
title.title_c_c_position 80 # Posición Y del título principal
title.bg_color 8B4A3A # Color de fondo en la sección titulo - Marrón rojizo
title.press_start_position 180 # Posición Y del texto "Press Start"
title.title_duration 14 # Duración de la pantalla de título (segundos)
title.arcade_edition_position 123 # Posición Y del subtítulo "Arcade Edition"
title.title_c_c_position 80 # Posición Y del título principal
title.bg_color 8B4A3A # Color de fondo en la sección titulo - Marrón rojizo
# --- BACKGROUND ---
background.attenuate_color FF4A3A40 # Color de atenuación del fondo (RGBA hexadecimal) - Blanco rosado
# --- BALLOONS ---
balloon.settings[0].vel 2.75f # Velocidad inicial del globo 1
balloon.settings[0].grav 0.09f # Gravedad aplicada al globo 1
balloon.settings[1].vel 3.70f # Velocidad inicial del globo 2
balloon.settings[1].grav 0.10f # Gravedad aplicada al globo 2
balloon.settings[2].vel 4.70f # Velocidad inicial del globo 3
balloon.settings[2].grav 0.10f # Gravedad aplicada al globo 3
balloon.settings[3].vel 5.45f # Velocidad inicial del globo 4
balloon.settings[3].grav 0.10f # Gravedad aplicada al globo 4
# --- BALLOONS --- (deltaTime en segundos: vel en pixels/s, grav en pixels/s²)
balloon.settings[0].vel 165.0f # Velocidad inicial del globo 1 (pixels/s)
balloon.settings[0].grav 320.0f # Gravedad aplicada al globo 1 (pixels/s²)
balloon.settings[1].vel 222.0f # Velocidad inicial del globo 2 (pixels/s)
balloon.settings[1].grav 360.0f # Gravedad aplicada al globo 2 (pixels/s²)
balloon.settings[2].vel 282.0f # Velocidad inicial del globo 3 (pixels/s)
balloon.settings[2].grav 360.0f # Gravedad aplicada al globo 3 (pixels/s²)
balloon.settings[3].vel 327.0f # Velocidad inicial del globo 4 (pixels/s)
balloon.settings[3].grav 360.0f # Gravedad aplicada al globo 4 (pixels/s²)
balloon.color[0] orange # Color de creación del globo normal
balloon.color[1] red # Color del globo normal
+6 -1
View File
@@ -72,6 +72,7 @@
"[NOTIFICATIONS] DISCONNECTED": "desconectat",
"[RESOURCE] LOADING": "Carregant",
"[RESOURCE] PRESS_TO_CONTINUE": "Prem una tecla per continuar",
"[SERVICE_MENU] TITLE": "Menu de servei",
"[SERVICE_MENU] RESET": "Reiniciar",
@@ -82,9 +83,9 @@
"[SERVICE_MENU] SHADER": "Shader",
"[SERVICE_MENU] SHADER_DISABLED": "Desactivat",
"[SERVICE_MENU] SHADER_PRESET": "Preset",
"[SERVICE_MENU] SUPERSAMPLING": "Supermostreig",
"[SERVICE_MENU] VSYNC": "Sincronisme vertical",
"[SERVICE_MENU] INTEGER_SCALE": "Escalat sencer",
"[SERVICE_MENU] FILTER": "Filtre",
"[SERVICE_MENU] MAIN_VOLUME": "Volumen general",
"[SERVICE_MENU] MUSIC_VOLUME": "Volumen de la musica",
"[SERVICE_MENU] SFX_VOLUME": "Volumen dels sons",
@@ -103,6 +104,10 @@
"[SERVICE_MENU] EASY": "Facil",
"[SERVICE_MENU] NORMAL": "Normal",
"[SERVICE_MENU] HARD": "Dificil",
"[SERVICE_MENU] GAME_PRESET": "Perfil",
"[SERVICE_MENU] PRESET_CLASSIC": "Classic",
"[SERVICE_MENU] PRESET_ARCADE": "Arcade",
"[SERVICE_MENU] PRESET_RED": "Roig",
"[SERVICE_MENU] NEED_RESTART_MESSAGE": "Reiniciar per aplicar canvis",
"[SERVICE_MENU] ENABLE_SHUTDOWN": "Permetre apagar el sistema",
"[SERVICE_MENU] CONTROLS": "Controls",
+6 -1
View File
@@ -71,6 +71,7 @@
"[NOTIFICATIONS] DISCONNECTED": "disconnected",
"[RESOURCE] LOADING": "Loading",
"[RESOURCE] PRESS_TO_CONTINUE": "Press any key to continue",
"[SERVICE_MENU] TITLE": "Service Menu",
"[SERVICE_MENU] RESET": "Reset",
@@ -81,9 +82,9 @@
"[SERVICE_MENU] SHADER": "Shader",
"[SERVICE_MENU] SHADER_DISABLED": "Disabled",
"[SERVICE_MENU] SHADER_PRESET": "Preset",
"[SERVICE_MENU] SUPERSAMPLING": "Supersampling",
"[SERVICE_MENU] VSYNC": "V-Sync",
"[SERVICE_MENU] INTEGER_SCALE": "Integer Scale",
"[SERVICE_MENU] FILTER": "Filter",
"[SERVICE_MENU] MAIN_VOLUME": "Main Volume",
"[SERVICE_MENU] MUSIC_VOLUME": "Music Volume",
"[SERVICE_MENU] SFX_VOLUME": "Sound Volume",
@@ -102,6 +103,10 @@
"[SERVICE_MENU] EASY": "Easy",
"[SERVICE_MENU] NORMAL": "Normal",
"[SERVICE_MENU] HARD": "Hard",
"[SERVICE_MENU] GAME_PRESET": "Preset",
"[SERVICE_MENU] PRESET_CLASSIC": "Classic",
"[SERVICE_MENU] PRESET_ARCADE": "Arcade",
"[SERVICE_MENU] PRESET_RED": "Red",
"[SERVICE_MENU] NEED_RESTART_MESSAGE": "Restart to apply changes",
"[SERVICE_MENU] ENABLE_SHUTDOWN": "Allow system shutdown",
"[SERVICE_MENU] CONTROLS": "Controls",
+6 -1
View File
@@ -71,6 +71,7 @@
"[NOTIFICATIONS] DISCONNECTED": "desconectado",
"[RESOURCE] LOADING": "Cargando",
"[RESOURCE] PRESS_TO_CONTINUE": "Pulsa una tecla para continuar",
"[SERVICE_MENU] TITLE": "Menu de servicio",
"[SERVICE_MENU] RESET": "Reiniciar",
@@ -81,9 +82,9 @@
"[SERVICE_MENU] SHADER": "Shader",
"[SERVICE_MENU] SHADER_DISABLED": "Desactivado",
"[SERVICE_MENU] SHADER_PRESET": "Preset",
"[SERVICE_MENU] SUPERSAMPLING": "Supersampling",
"[SERVICE_MENU] VSYNC": "Sincronismo vertical",
"[SERVICE_MENU] INTEGER_SCALE": "Escalado proporcional",
"[SERVICE_MENU] FILTER": "Filtro",
"[SERVICE_MENU] MAIN_VOLUME": "Volumen general",
"[SERVICE_MENU] MUSIC_VOLUME": "Volumen de la musica",
"[SERVICE_MENU] SFX_VOLUME": "Volumen de los efectos",
@@ -102,6 +103,10 @@
"[SERVICE_MENU] EASY": "Facil",
"[SERVICE_MENU] NORMAL": "Normal",
"[SERVICE_MENU] HARD": "Dificil",
"[SERVICE_MENU] GAME_PRESET": "Perfil",
"[SERVICE_MENU] PRESET_CLASSIC": "Clasico",
"[SERVICE_MENU] PRESET_ARCADE": "Arcade",
"[SERVICE_MENU] PRESET_RED": "Rojo",
"[SERVICE_MENU] NEED_RESTART_MESSAGE": "Reiniciar para aplicar cambios",
"[SERVICE_MENU] ENABLE_SHUTDOWN": "Permitir apagar el sistema",
"[SERVICE_MENU] CONTROLS": "Controles",
-48
View File
@@ -1,48 +0,0 @@
#version 450
layout(location = 0) in vec2 v_uv;
layout(location = 0) out vec4 out_color;
layout(set = 2, binding = 0) uniform sampler2D source;
layout(set = 3, binding = 0) uniform DownscaleUniforms {
int algorithm; // 0 = Lanczos2 (ventana 2, ±2 taps), 1 = Lanczos3 (ventana 3, ±3 taps)
float pad0;
float pad1;
float pad2;
} u;
// Kernel Lanczos normalizado: sinc(t) * sinc(t/a) para |t| < a, 0 fuera.
float lanczos(float t, float a) {
t = abs(t);
if (t < 0.0001) { return 1.0; }
if (t >= a) { return 0.0; }
const float PI = 3.14159265358979;
float pt = PI * t;
return (a * sin(pt) * sin(pt / a)) / (pt * pt);
}
void main() {
vec2 src_size = vec2(textureSize(source, 0));
// Posición en coordenadas de texel (centros de texel en N+0.5)
vec2 p = v_uv * src_size;
vec2 p_floor = floor(p);
float a = (u.algorithm == 0) ? 2.0 : 3.0;
int win = int(a);
vec4 color = vec4(0.0);
float weight_sum = 0.0;
for (int j = -win; j <= win; j++) {
for (int i = -win; i <= win; i++) {
// Centro del texel (i,j) relativo a p_floor
vec2 tap_center = p_floor + vec2(float(i), float(j)) + 0.5;
vec2 offset = tap_center - p;
float w = lanczos(offset.x, a) * lanczos(offset.y, a);
color += texture(source, tap_center / src_size) * w;
weight_sum += w;
}
}
out_color = (weight_sum > 0.0) ? (color / weight_sum) : vec4(0.0, 0.0, 0.0, 1.0);
}
+47 -18
View File
@@ -6,7 +6,9 @@
// xxd -i postfx.frag.spv > ../../source/core/rendering/sdl3gpu/postfx_frag_spv.h
//
// PostFXUniforms must match exactly the C++ struct in sdl3gpu_shader.hpp
// (8 floats, 32 bytes, std140/scalar layout).
// (16 floats = 4 × vec4 = 64 bytes, std140/scalar layout).
// IMPORTANT: Qualsevol canvi ací cal replicar-lo a mà a
// source/core/rendering/sdl3gpu/msl/postfx_frag.msl.h (no hi ha generador).
layout(location = 0) in vec2 v_uv;
layout(location = 0) out vec4 out_color;
@@ -15,7 +17,7 @@ layout(set = 2, binding = 0) uniform sampler2D scene;
layout(set = 3, binding = 0) uniform PostFXUniforms {
float vignette_strength;
float chroma_strength;
float chroma_min; // intensitat mínima de l'aberració cromàtica
float scanline_strength;
float screen_height;
float mask_strength;
@@ -24,10 +26,28 @@ layout(set = 3, binding = 0) uniform PostFXUniforms {
float bleeding;
float pixel_scale; // physical pixels per logical pixel (vh / tex_height_)
float time; // seconds since SDL init
float oversample; // supersampling factor (1.0 = off, 3.0 = 3×SS)
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz — 48 bytes total (3 × 16)
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz
float chroma_max; // intensitat màxima; si == chroma_min → chroma estàtic
// vec4 #3 — paràmetres de scanlines (exposats per preset YAML)
float scan_dark_ratio; // fracció de subfila fosca per fila lògica (1/3 ≈ 0.333)
float scan_dark_floor; // multiplicador de brillantor de la subfila fosca
float scan_edge_soft; // 0 = step dur; 1 = suavitzat d'1 píxel físic (estil crtpi)
float pad3; // padding per tancar a 64 bytes (4 × vec4)
} u;
// Mostreig bilinear horitzontal d'un canal RGB. Evita el "tic-tac" del sampler
// NEAREST quan l'offset de chroma és subpíxel: sense interpolar, l'offset
// arrodonia entre 1 i 2 píxels i el drift temporal feia un parpelleig discret.
float sampleBilinearX(vec2 uv_target, int channel) {
vec2 tex_size = vec2(textureSize(scene, 0));
float px = uv_target.x * tex_size.x - 0.5;
float p_floor = floor(px);
float f = px - p_floor;
vec4 c0 = texture(scene, vec2((p_floor + 0.5) / tex_size.x, uv_target.y));
vec4 c1 = texture(scene, vec2((p_floor + 1.5) / tex_size.x, uv_target.y));
return mix(c0[channel], c1[channel], f);
}
// YCbCr helpers for NTSC bleeding
vec3 rgb_to_ycc(vec3 rgb) {
return vec3(
@@ -69,11 +89,11 @@ void main() {
vec3 base = texture(scene, uv).rgb;
// Sangrado NTSC — difuminado horizontal de crominancia.
// step = 1 pixel lógico de juego en UV (corrige SS: textureSize.x = game_w * oversample).
// step = 1 pixel lógico de juego en UV.
vec3 colour;
if (u.bleeding > 0.0) {
float tw = float(textureSize(scene, 0).x);
float step = u.oversample / tw; // 1 pixel lógico en UV
float step = 1.0 / tw; // 1 pixel lógico en UV
vec3 ycc = rgb_to_ycc(base);
vec3 ycc_l2 = rgb_to_ycc(texture(scene, uv - vec2(2.0*step, 0.0)).rgb);
vec3 ycc_l1 = rgb_to_ycc(texture(scene, uv - vec2(1.0*step, 0.0)).rgb);
@@ -85,10 +105,14 @@ void main() {
colour = base;
}
// Aberración cromática (drift animado con time para efecto NTSC real)
float ca = u.chroma_strength * 0.005 * (1.0 + 0.15 * sin(u.time * 7.3));
colour.r = texture(scene, uv + vec2(ca, 0.0)).r;
colour.b = texture(scene, uv - vec2(ca, 0.0)).b;
// Aberración cromática — intensitat varia entre chroma_min i chroma_max amb
// una sinusoidal (si min == max, queda estàtica). Mostreig bilinear horitzontal
// per evitar el "tic-tac" del NEAREST sampler quan l'offset és subpíxel.
if (u.chroma_min > 0.0 || u.chroma_max > 0.0) {
float ca = mix(u.chroma_min, u.chroma_max, 0.5 + 0.5 * sin(u.time * 7.3)) * 0.005;
colour.r = sampleBilinearX(uv + vec2(ca, 0.0), 0);
colour.b = sampleBilinearX(uv - vec2(ca, 0.0), 2);
}
// Corrección gamma (linealizar antes de scanlines, codificar después)
if (u.gamma_strength > 0.0) {
@@ -96,15 +120,20 @@ void main() {
colour = mix(colour, lin, u.gamma_strength);
}
// Scanlines — 1 pixel físico oscuro por fila lógica.
// Modelo sustractivo: las filas de scanline se oscurecen, las demás no cambian.
// Esto evita el efecto de sobrebrillo en contenido con colores vivos.
// Scanlines — tècnica dels 3 subpíxels verticals per píxel lògic (aee/projecte_2026):
// franja fosca ocupant `scan_dark_ratio` al final de cada fila lògica. La transició es
// suavitza amb smoothstep d'ample ≈ 1 píxel físic (estil crtpi: filtratge analític
// continu), controlat per `scan_edge_soft`. A 0 és equivalent al step dur antic.
if (u.scanline_strength > 0.0) {
float ps = max(1.0, round(u.pixel_scale));
float frac_in_row = fract(uv.y * u.screen_height);
float row_pos = floor(frac_in_row * ps);
float is_dark = step(ps - 1.0, row_pos);
float scan = mix(1.0, 0.0, is_dark);
float ps = max(u.pixel_scale, 1.0);
float sub = fract(uv.y * u.screen_height); // [0,1) dins la fila lògica
float dark_center = 1.0 - u.scan_dark_ratio * 0.5; // centre de la franja fosca
float d = abs(sub - dark_center);
d = min(d, 1.0 - d); // wrap a la fila següent
float half_width = u.scan_dark_ratio * 0.5;
float softness = u.scan_edge_soft * 0.5 / ps; // mig píxel físic a cada costat
float band = 1.0 - smoothstep(half_width - softness, half_width + softness, d);
float scan = mix(1.0, u.scan_dark_floor, band);
colour *= mix(1.0, scan, u.scanline_strength);
}
-15
View File
@@ -1,15 +0,0 @@
#version 450
// Vulkan GLSL fragment shader — Nearest-neighbour upscale pass
// Used as the first render pass when supersampling is active.
// Compile: glslc upscale.frag -o upscale.frag.spv
// xxd -i upscale.frag.spv > ../../source/core/rendering/sdl3gpu/upscale_frag_spv.h
layout(location = 0) in vec2 v_uv;
layout(location = 0) out vec4 out_color;
layout(set = 2, binding = 0) uniform sampler2D scene;
void main() {
out_color = texture(scene, v_uv);
}
@@ -1,19 +1,5 @@
### JailDesigner - Custom mappings
# Mandos recreativa CCAE
0300457e790000000600000010010000,DragonRise Inc. Generic USB Joystick,platform:Linux,a:b0,b:b1,x:b2,y:b6,back:b5,leftx:a0,lefty:a1,
# RAP4
030086650d0f00008c000000140100003853140,Generic X-Box pad,platform:Linux,a:b0,b:b1,x:b2,y:b3,back:b6,start:b7,guide:b8,leftshoulder:b4,rightshoulder:b5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,
# Mad Catz FightStick Alpha PS4
03007fcb380700008081000011010000184833152,Mad Catz FightStick Alpha PS4,platform:Linux,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,guide:b12,leftshoulder:b4,rightshoulder:b5,leftstick:b13,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b6,righttrigger:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,
# Mando custom de Dani en madera + Agetec
030020617900000006000000100100001216,DragonRise Inc. Generic USB Joystick,platform:Linux,a:b2,b:b1,x:b0,y:b6,start:b5,leftx:a0,lefty:a1,
# Game Controller DB for SDL in 2.0.16 format
# Source: https://github.com/gabomdq/SDL_GameControllerDB
# Source: https://github.com/mdqinc/SDL_GameControllerDB
# Windows
03000000300f00000a01000000000000,3 In 1 Conversion Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b8,x:b3,y:b0,platform:Windows,
@@ -24,6 +10,7 @@
03000000d0160000060d000000000000,4Play Adapter,a:b1,b:b3,back:b4,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b6,leftstick:b14,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b15,righttrigger:b9,rightx:a3,righty:a4,start:b5,x:b0,y:b2,platform:Windows,
03000000d0160000070d000000000000,4Play Adapter,a:b1,b:b3,back:b4,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b6,leftstick:b14,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b15,righttrigger:b9,rightx:a3,righty:a4,start:b5,x:b0,y:b2,platform:Windows,
03000000d0160000600a000000000000,4Play Adapter,a:b1,b:b3,back:b4,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b6,leftstick:b14,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b15,righttrigger:b9,rightx:a3,righty:a4,start:b5,x:b0,y:b2,platform:Windows,
03000000c82d00001930000000000000,8BitDo 64,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a4,start:b11,platform:Windows,
03000000c82d00000031000000000000,8BitDo Adapter,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00000531000000000000,8BitDo Adapter 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00000951000000000000,8BitDo Dogbone,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a2,rightx:a3,righty:a5,start:b11,platform:Windows,
@@ -61,9 +48,10 @@
03000000c82d00002038000000000000,8BitDo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000751000000000000,8BitDo P30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a5,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00000851000000000000,8BitDo P30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b7,righttrigger:b9,rightx:a3,righty:a5,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00000360000000000000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000360000000000000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000361000000000000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000660000000000000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000960000000000000,8BitDo Pro 3,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b17,paddle2:b16,paddle3:b2,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000131000000000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000231000000000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000331000000000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,
@@ -87,14 +75,16 @@
03000000c82d00000161000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000260000000000000,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00000261000000000000,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows,
03000000c82d00001130000000000000,8BitDo Ultimate,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b26,paddle1:b24,paddle2:b25,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001230000000000000,8BitDo Ultimate,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001330000000000000,8BitDo Ultimate,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b23,paddle2:b19,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001260000000000000,8BitDo Ultimate 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b17,paddle2:b16,paddle3:b2,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001b30000000000000,8BitDo Ultimate 2C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001c30000000000000,8BitDo Ultimate 2C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001d30000000000000,8BitDo Ultimate 2C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001530000000000000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001630000000000000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001730000000000000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001130000000000000,8BitDo Ultimate Wired,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b26,paddle1:b24,paddle2:b25,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001330000000000000,8BitDo Ultimate Wireless,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b23,paddle2:b19,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00000121000000000000,8BitDo Xbox One SN30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000a00500003232000000000000,8BitDo Zero,a:b0,b:b1,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001890000000000000,8BitDo Zero 2,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,
@@ -135,6 +125,7 @@
03000000a30c00002800000000000000,Astro City Mini,a:b2,b:b1,back:b8,leftx:a3,lefty:a4,rightshoulder:b4,righttrigger:b5,start:b9,x:b3,y:b0,platform:Windows,
03000000050b00000579000000000000,ASUS ROG Kunai 3,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000050b00000679000000000000,ASUS ROG Kunai 3,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
030000006a0e00001140000000000000,Atari CX Controller,a:b0,leftx:a0,lefty:a1,platform:Windows,
03000000503200000110000000000000,Atari VCS Classic Controller,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,start:b3,platform:Windows,
03000000503200000210000000000000,Atari VCS Modern Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:Windows,
03000000380800001889000000000000,AtGames Legends Gamer Pro,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b13,lefttrigger:b14,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
@@ -150,6 +141,7 @@
03000000d62000002a79000000000000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,
03000000bc2000005250000000000000,Beitong G3,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b5,righttrigger:b9,rightx:a3,righty:a4,start:b15,x:b3,y:b4,platform:Windows,
030000000d0500000208000000000000,Belkin Nostromo N40,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Windows,
030000000d0500000308000000000000,Belkin Nostromo N45,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Windows,
03000000bc2000006012000000000000,Betop 2126F,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
03000000bc2000000055000000000000,Betop BFM,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000790000000700000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows,
@@ -186,7 +178,7 @@
030000001a1c00000001000000000000,Datel Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
03000000451300000830000000000000,Defender Game Racer X7,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
03000000791d00000103000000000000,Dual Box Wii,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
03000000c0160000e105000000000000,Dual Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,
03000000151a00006222000000000000,Dual Plus PlayStation Adapter,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,
030000004f040000070f000000000000,Dual Power,a:b8,b:b9,back:b4,dpdown:b1,dpleft:b2,dpright:b3,dpup:b0,leftshoulder:b13,leftstick:b6,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b12,rightstick:b7,righttrigger:b15,start:b5,x:b10,y:b11,platform:Windows,
030000004f04000012b3000000000000,Dual Power 3,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,
030000004f04000020b3000000000000,Dual Trigger,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,
@@ -201,7 +193,8 @@
03000095090000010000000000000000,Elecom JC-U609,a:b0,b:b1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,start:b8,x:b3,y:b4,platform:Windows,
0300004112000000e500000000000000,Elecom JC-U909Z,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b7,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,start:b8,x:b3,y:b4,platform:Windows,
03000041120000001050000000000000,Elecom JC-U911,a:b1,b:b2,back:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,start:b0,x:b4,y:b5,platform:Windows,
030000006e0500000520000000000000,Elecom P301U PlayStation Controller Adapter,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Windows,
030000006e0500000520000000000000,Elecom P301U PlayStation Adapter,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Windows,
03000000250900000218000000000000,Elecom PlayStation Adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,
03000000411200004450000000000000,Elecom U1012,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Windows,
030000006e0500000320000000000000,Elecom U3613M,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Windows,
030000006e0500000e20000000000000,Elecom U3912T,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Windows,
@@ -236,17 +229,19 @@
03000000b40400001224000000000000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b12,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b2,paddle1:b16,paddle2:b17,paddle3:b14,paddle4:b15,rightshoulder:b7,rightstick:b13,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
030000008305000000a0000000000000,G08XU,a:b0,b:b1,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b5,x:b2,y:b3,platform:Windows,
0300000066f700000100000000000000,Game VIB Joystick,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Windows,
03000000790000004618000000000000,GameCube Adapter,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Windows,
03000000260900002625000000000000,GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,lefttrigger:a4,leftx:a0,lefty:a1,righttrigger:a5,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Windows,
03000000341a000005f7000000000000,GameCube Controller,a:b2,b:b3,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b1,y:b0,platform:Windows,
03000000430b00000500000000000000,GameCube Controller,a:b0,b:b2,dpdown:b10,dpleft:b8,dpright:b9,dpup:b11,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a3,rightx:a5,righty:a2,start:b7,x:b1,y:b3,platform:Windows,
03000000790000004718000000000000,GameCube Controller,a:b1,b:b0,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Windows,
03000000790000004618000000000000,GameCube Controller Adapter,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Windows,
030000008f0e00000d31000000000000,Gamepad 3 Turbo,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
03000000ac0500003d03000000000000,GameSir G3,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000ac0500005b05000000000000,GameSir G3w,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
03000000ac0500002d02000000000000,GameSir G4,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,
03000000ac0500004d04000000000000,GameSir G4,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000373500002210000000000000,GameSir G7 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000ac0500001a06000000000000,GameSir T3 2.02,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000373500000410000000000000,GameSir T4 Kaleid,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
03000000373500009410000000000000,GameSir Tegenaria Lite,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
030000004c0e00001035000000000000,Gamester,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows,
030000000d0f00001110000000000000,GameStick Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,
@@ -255,7 +250,6 @@
03000000b62500000100000000000000,Gametel GT004 01,a:b3,b:b0,dpdown:b10,dpleft:b9,dpright:b8,dpup:b11,leftshoulder:b4,rightshoulder:b5,start:b7,x:b1,y:b2,platform:Windows,
030000008f0e00001411000000000000,Gamo2 Divaller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
03000000120c0000a857000000000000,Gator Claw,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
03000000c9110000f055000000000000,GC100XF,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
030000008305000009a0000000000000,Genius,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
030000008305000031b0000000000000,Genius Maxfire Blaze 3,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
03000000451300000010000000000000,Genius Maxfire Grandias 12,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
@@ -353,7 +347,7 @@
030000000d0f0000dc00000000000000,Horipad Switch,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
03000000242e00000b20000000000000,Hyperkin Admiral N64 Controller,+rightx:b11,+righty:b13,-rightx:b8,-righty:b12,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,platform:Windows,
03000000242e0000ff0b000000000000,Hyperkin N64 Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a3,start:b9,platform:Windows,
03000000790000004e95000000000000,Hyperkin N64 Controller Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b7,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a5,righty:a2,start:b9,platform:Windows,
03000000790000004e95000000000000,Hyperkin N64 Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b7,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a5,righty:a2,start:b9,platform:Windows,
03000000242e00006a48000000000000,Hyperkin RetroN Sq,a:b3,b:b7,back:b5,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,leftshoulder:b0,rightshoulder:b1,start:b4,x:b2,y:b6,platform:Windows,
03000000242f00000a20000000000000,Hyperkin Scout,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows,
03000000242e00000a20000000000000,Hyperkin Scout Premium SNES Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows,
@@ -375,18 +369,20 @@
03000000fd0500002a26000000000000,InterAct Hammerhead FX,a:b3,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b0,y:b1,platform:Windows,
03000000fd0500002f26000000000000,InterAct Hammerhead FX,a:b4,b:b5,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b1,y:b2,platform:Windows,
03000000fd0500005302000000000000,InterAct ProPad,a:b3,b:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,x:b0,y:b1,platform:Windows,
03000000ac0500002c02000000000000,Ipega Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b13,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b14,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000ac0500002c02000000000000,Ipega Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b8,leftstick:b13,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b14,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000491900000204000000000000,Ipega PG9023,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000491900000304000000000000,Ipega PG9087,+righty:+a5,-righty:-a4,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,start:b11,x:b3,y:b4,platform:Windows,
030000007e0500000620000000000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Windows,
030000007e0500000720000000000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows,
03000000250900000017000000000000,Joypad Adapter,a:b2,b:b1,back:b9,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b8,x:b3,y:b0,platform:Windows,
03000000bd12000003c0000000000000,Joypad Alpha Shock,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
030000008b0100002301000000000000,Joytech JS-112,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a2,lefty:a3,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a0,righty:a1,start:b9,x:b0,y:b1,platform:Windows,
03000000ff1100004033000000000000,JPD FFB,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a2,start:b15,x:b3,y:b0,platform:Windows,
03000000242f00002d00000000000000,JYS Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
03000000242f00008a00000000000000,JYS Adapter,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows,
03000000c4100000c082000000000000,KADE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
03000000828200000180000000000000,Keio,a:b4,b:b5,back:b8,leftshoulder:b2,lefttrigger:b3,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b9,x:b0,y:b1,platform:Windows,
03000000c4ac00001c80000000000000,KidzPlay Adventure PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
03000000790000000200000000000000,King PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows,
03000000bd12000001e0000000000000,Leadership,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,
030000006f0e00000103000000000000,Logic3,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
@@ -394,7 +390,6 @@
030000008f0e00001300000000000000,Logic3,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
030000006d040000d1ca000000000000,Logitech ChillStream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
030000006d040000d2ca000000000000,Logitech Cordless Precision,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
030000006d04000011c2000000000000,Logitech Cordless Wingman,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b5,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b2,righttrigger:b7,rightx:a3,righty:a4,x:b4,platform:Windows,
030000006d04000016c2000000000000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
030000006d0400001dc2000000000000,Logitech F310,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
030000006d04000018c2000000000000,Logitech F510,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
@@ -404,6 +399,7 @@
030000006d0400001ac2000000000000,Logitech Precision,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
030000006d04000009c2000000000000,Logitech WingMan,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Windows,
030000006d0400000bc2000000000000,Logitech WingMan Action Pad,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b8,lefttrigger:a5~,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b5,righttrigger:a2~,start:b8,x:b3,y:b4,platform:Windows,
030000006d04000011c2000000000000,Logitech WingMan Cordless,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b5,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b2,righttrigger:b7,rightx:a3,righty:a4,x:b4,platform:Windows,
030000006d0400000ac2000000000000,Logitech WingMan RumblePad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,rightx:a3,righty:a4,x:b3,y:b4,platform:Windows,
03000000380700005645000000000000,Lynx,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
03000000222200006000000000000000,Macally,a:b1,b:b2,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b33,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
@@ -430,19 +426,19 @@
03000000380700008084000000000000,Mad Catz TE2 PS4 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,
030000002a0600001024000000000000,Matricom,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:Windows,
030000009f000000adbb000000000000,MaxJoypad Virtual Controller,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,
030000008f0e00001330000000000000,Mayflash Adapter,a:b1,b:b2,back:b8,dpdown:h0.8,dpleft:h0.2,dpright:h0.1,dpup:h0.4,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a3~,righty:a2,start:b9,x:b0,y:b3,platform:Windows,
03000000250900000128000000000000,Mayflash Arcade Stick,a:b1,b:b2,back:b8,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b5,y:b6,platform:Windows,
030000008f0e00001330000000000000,Mayflash Controller Adapter,a:b1,b:b2,back:b8,dpdown:h0.8,dpleft:h0.2,dpright:h0.1,dpup:h0.4,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a3~,righty:a2,start:b9,x:b0,y:b3,platform:Windows,
03000000242f00003700000000000000,Mayflash F101,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
03000000790000003018000000000000,Mayflash F300 Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
03000000242f00003900000000000000,Mayflash F300 Elite Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
03000000790000004318000000000000,Mayflash GameCube Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows,
03000000790000004418000000000000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows,
03000000790000004318000000000000,Mayflash GameCube Controller Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows,
03000000242f00007300000000000000,Mayflash Magic NS,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows,
0300000079000000d218000000000000,Mayflash Magic NS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
03000000d620000010a7000000000000,Mayflash Magic NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
03000000242f0000f400000000000000,Mayflash N64 Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a5,start:b9,platform:Windows,
03000000242f0000f500000000000000,Mayflash N64 Adapter,a:b2,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a5,start:b9,platform:Windows,
03000000242f0000f400000000000000,Mayflash N64 Controller Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a5,start:b9,platform:Windows,
03000000790000007918000000000000,Mayflash N64 Controller Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,righttrigger:b7,rightx:a3,righty:a2,start:b8,platform:Windows,
03000000790000007918000000000000,Mayflash N64 Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,righttrigger:b7,rightx:a3,righty:a2,start:b8,platform:Windows,
030000008f0e00001030000000000000,Mayflash Saturn Adapter,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,lefttrigger:b7,rightshoulder:b6,righttrigger:b2,start:b9,x:b3,y:b4,platform:Windows,
0300000025090000e803000000000000,Mayflash Wii Classic Adapter,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:a4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:a5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows,
03000000790000000318000000000000,Mayflash Wii DolphinBar,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Windows,
@@ -472,7 +468,9 @@
03000000250900006688000000000000,MP-8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,
03000000091200004488000000000000,MUSIA PlayStation 2 Input Display,a:b0,b:b2,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b6,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b7,righttrigger:b11,rightx:a2,righty:a3,start:b5,x:b1,y:b3,platform:Windows,
03000000f70600000100000000000000,N64 Adaptoid,+rightx:b2,+righty:b1,-rightx:b4,-righty:b5,a:b0,b:b3,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,platform:Windows,
030000006b140000010c000000000000,Nacon GC 400ES,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
03000000c9110000f055000000000000,Nacon GC100XF,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
03000000c21100000791000000000000,Nacon GC101 1.03,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
030000006b140000010c000000000000,Nacon GC400ES,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
030000006b1400001106000000000000,Nacon Revolution 3 PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,
0300000085320000170d000000000000,Nacon Revolution 5 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,
0300000085320000190d000000000000,Nacon Revolution 5 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,
@@ -482,25 +480,25 @@
03000000eb0300000000000000000000,NeGcon Adapter,a:a2,b:b13,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,lefttrigger:a4,leftx:a1,righttrigger:b11,start:b3,x:a3,y:b12,platform:Windows,
0300000038070000efbe000000000000,NEO SE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
0300000092120000474e000000000000,NeoGeo X Arcade Stick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,x:b3,y:b2,platform:Windows,
03000000921200004b46000000000000,NES 2 port Adapter,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b11,platform:Windows,
03000000921200004b46000000000000,NES Adapter,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b11,platform:Windows,
03000000000f00000100000000000000,NES Controller,a:b1,b:b0,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b3,platform:Windows,
03000000921200004346000000000000,NES Controller,a:b0,b:b1,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b3,platform:Windows,
03000000790000004518000000000000,NEXILUX GameCube Controller Adapter,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Windows,
03000000790000004518000000000000,NEXiLUX GameCube Adapter,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Windows,
030000001008000001e5000000000000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,righttrigger:b6,start:b9,x:b3,y:b0,platform:Windows,
03000000050b00000045000000000000,Nexus,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,platform:Windows,
03000000152000000182000000000000,NGDS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Windows,
03000000ec110000e1a7000000000000,Nintendo Switch,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
030000007e0500006920000000000000,Nintendo Switch 2 Pro Controller,a:b0,b:b1,back:b14,dpdown:b8,dpleft:b10,dpright:b9,dpup:b11,guide:b16,leftshoulder:b12,leftstick:b15,lefttrigger:b13,leftx:a0,lefty:a1~,misc1:b17,misc2:b20,paddle1:b18,paddle2:b19,rightshoulder:b4,rightstick:b7,righttrigger:b5,rightx:a2,righty:a3~,start:b6,x:b2,y:b3,platform:Windows,
030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
030000000d0500000308000000000000,Nostromo N45,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Windows,
030000007e0500007320000000000000,NSO GameCube Controller,a:b1,b:b3,dpdown:b8,dpleft:b10,dpright:b9,dpup:b11,guide:b16,leftshoulder:b13,lefttrigger:b12,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b4,rightx:a2,righty:a3~,start:b6,x:b0,y:b2,platform:Windows,
030000007e0500001920000000000000,NSO N64 Controller,+rightx:b8,+righty:b2,-rightx:b3,-righty:b7,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,righttrigger:b10,start:b9,platform:Windows,
030000007e0500001720000000000000,NSO SNES Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b15,start:b9,x:b2,y:b3,platform:Windows,
03000000550900001472000000000000,NVIDIA Controller,a:b11,b:b10,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b7,leftstick:b5,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b4,righttrigger:a5,rightx:a3,righty:a6,start:b3,x:b9,y:b8,platform:Windows,
03000000550900001072000000000000,NVIDIA Shield,a:b9,b:b8,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b3,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b2,righttrigger:a4,rightx:a2,righty:a5,start:b0,x:b7,y:b6,platform:Windows,
030000005509000000b4000000000000,NVIDIA Virtual,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
03000000120c00000288000000000000,Nyko Air Flo Xbox Controller,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows,
030000004b120000014d000000000000,NYKO Airflo EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Windows,
030000004b120000014d000000000000,Nyko Airflo EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Windows,
03000000d62000001d57000000000000,Nyko Airflo PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
03000000120c00000288000000000000,Nyko Airflo Xbox Controller,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows,
03000000791d00000900000000000000,Nyko Playpad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,
03000000782300000a10000000000000,Onlive Controller,a:b15,b:b14,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b11,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b13,y:b12,platform:Windows,
030000000d0f00000401000000000000,Onyx,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
@@ -521,8 +519,8 @@
03000000d620000012a7000000000000,PowerA Fusion Nintendo Switch Fight Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
03000000dd62000016a7000000000000,PowerA Fusion Pro Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
03000000d620000013a7000000000000,PowerA Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
03000000d62000003340000000000000,PowerA OPS Pro Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000d62000002640000000000000,PowerA OPS Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000d62000002640000000000000,PowerA OPS Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000d62000003340000000000000,PowerA OPS Pro Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000d62000006dca000000000000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
0300000062060000d570000000000000,PowerA PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
03000000d620000014a7000000000000,PowerA Spectra Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
@@ -540,13 +538,11 @@
03000000250900008888000000000000,PS2 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,
030000006b1400000303000000000000,PS2 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
030000009d0d00001330000000000000,PS2 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
03000000151a00006222000000000000,PS2 Dual Plus Adapter,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,
03000000120a00000100000000000000,PS3 Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,
03000000120c00001307000000000000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
03000000120c00001cf1000000000000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
03000000120c0000f90e000000000000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
03000000250900000118000000000000,PS3 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,
03000000250900000218000000000000,PS3 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,
03000000250900000500000000000000,PS3 Controller,a:b2,b:b1,back:b9,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b0,y:b3,platform:Windows,
030000004c0500006802000000000000,PS3 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b10,lefttrigger:a3~,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:a4~,rightx:a2,righty:a5,start:b8,x:b3,y:b0,platform:Windows,
030000004f1f00000800000000000000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
@@ -609,7 +605,7 @@
030000009b2800006300000000000000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Windows,
030000009b2800006400000000000000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Windows,
030000009b2800000200000000000000,Raphnet NES Adapter,a:b7,b:b6,back:b5,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,start:b4,platform:Windows,
030000009b2800004400000000000000,Raphnet PS1 and PS2 Adapter,a:b1,b:b2,back:b5,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b9,rightx:a3,righty:a4,start:b4,x:b0,y:b3,platform:Windows,
030000009b2800004400000000000000,Raphnet PlayStation Adapter,a:b1,b:b2,back:b5,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b9,rightx:a3,righty:a4,start:b4,x:b0,y:b3,platform:Windows,
030000009b2800004300000000000000,Raphnet Saturn,a:b0,b:b1,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Windows,
030000009b2800000500000000000000,Raphnet Saturn Adapter 2.0,a:b1,b:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows,
030000009b2800000300000000000000,Raphnet SNES Adapter,a:b0,b:b4,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Windows,
@@ -637,7 +633,7 @@
03000000321500000910000000000000,Razer Raiju UE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
03000000321500000011000000000000,Razer Raion PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,
03000000321500000009000000000000,Razer Serval,+lefty:+a2,-lefty:-a1,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,leftx:a0,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
03000000921200004547000000000000,Retro Bit Sega Genesis Controller Adapter,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,lefttrigger:b7,rightshoulder:b5,righttrigger:b2,start:b6,x:b3,y:b4,platform:Windows,
03000000921200004547000000000000,Retro Bit Sega Genesis Adapter,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,lefttrigger:b7,rightshoulder:b5,righttrigger:b2,start:b6,x:b3,y:b4,platform:Windows,
03000000790000001100000000000000,Retro Controller,a:b1,b:b2,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b6,lefttrigger:b7,rightshoulder:b4,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows,
03000000830500006020000000000000,Retro Controller,a:b0,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b8,righttrigger:b9,start:b7,x:b2,y:b3,platform:Windows,
03000000632500007805000000000000,Retro Fighters Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
@@ -658,6 +654,7 @@
030000006f0e00001e01000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
030000006f0e00002801000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
030000006f0e00002f01000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
03000000830500000052000000000000,Rockfire CosmoVoyager,a:b0,b:b1,x:b3,y:b4,back:b8,guide:b10,start:b9,leftshoulder:b6,rightshoulder:b5,dpup:-a1,dpdown:+a1,dpleft:-a0,dpright:+a0,misc1:b11,lefttrigger:b7,righttrigger:b2,platform:Windows,
03000000830500007030000000000000,Rockfire Space Ranger,a:b0,b:b1,back:b5,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b9,righttrigger:b8,start:b2,x:b3,y:b4,platform:Windows,
03000000050b0000e318000000000000,ROG Chakram,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows,
03000000050b0000e518000000000000,ROG Chakram,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows,
@@ -692,7 +689,9 @@
03000000c01100004150000000000000,Sanwa Micro Grip Pro,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,
03000000c01100004450000000000000,Sanwa Online Grip,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b8,rightstick:b11,righttrigger:b9,rightx:a3,righty:a2,start:b14,x:b3,y:b4,platform:Windows,
03000000730700000401000000000000,Sanwa PlayOnline Mobile,a:b0,b:b1,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b3,platform:Windows,
030000009d0d00001130000000000000,Sanwa PlayStation Adapter,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
03000000830500006120000000000000,Sanwa Smart Grip II,a:b0,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,x:b1,y:b3,platform:Windows,
03000000630600000201000000000000,Sanwa Virtua Grip,+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b2,b:b3,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,x:b0,y:b1,platform:Windows,
03000000c01100000051000000000000,Satechi Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,
030000004f04000028b3000000000000,Score A,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
03000000952e00002577000000000000,Scuf PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,
@@ -704,6 +703,7 @@
03000000730700000601000000000000,Sega Saturn Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b9,x:b3,y:b4,platform:Windows,
03000000b40400000a01000000000000,Sega Saturn Controller,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Windows,
030000003b07000004a1000000000000,SFX,a:b0,b:b2,back:b7,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:b5,start:b8,x:b1,y:b3,platform:Windows,
03000000632500002705000000000000,ShanWan Q36,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,rightshoulder:b7,righttrigger:b9,start:b11,x:b3,y:b4,platform:Windows,
03000000f82100001900000000000000,Shogun Bros Chameleon X1,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,
03000000120c00001c1e000000000000,SnakeByte 4S PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,
03000000140300000918000000000000,SNES Controller,a:b0,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Windows,
@@ -714,12 +714,12 @@
030000008f0e00000910000000000000,Sony DualShock 2,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Windows,
03000000317300000100000000000000,Sony DualShock 3,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,
03000000666600006706000000000000,Sony PlayStation Adapter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,platform:Windows,
03000000d9040000160f000000000000,Sony PlayStation Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,
03000000e30500009605000000000000,Sony PlayStation Adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,
03000000fe1400002a23000000000000,Sony PlayStation Adapter,a:b0,b:b1,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,x:b2,y:b3,platform:Windows,
030000004c050000da0c000000000000,Sony PlayStation Classic Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Windows,
03000000632500002306000000000000,Sony PlayStation Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Windows,
03000000f0250000c183000000000000,Sony PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
03000000d9040000160f000000000000,Sony PlayStation Controller Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,
03000000ff000000cb01000000000000,Sony PlayStation Portable,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Windows,
030000004c0500003713000000000000,Sony PlayStation Vita,a:b1,b:b2,back:b8,dpdown:b13,dpleft:b15,dpright:b14,dpup:b12,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,
03000000341a00000208000000000000,Speedlink 6555,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:-a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a3,righty:a2,start:b7,x:b2,y:b3,platform:Windows,
@@ -757,19 +757,21 @@
030000004f04000015b3000000000000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,
030000004f04000023b3000000000000,Thrustmaster Dual Trigger PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
030000004f0400000ed0000000000000,Thrustmaster eSwap Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
030000004f04000008d0000000000000,Thrustmaster Ferrari 150 PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Windows,
030000004f04000004b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,
030000004f04000003d0000000000000,Thrustmaster Run N Drive PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:a3,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:a4,rightstick:b11,righttrigger:b5,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
030000004f04000003d0000000000000,Thrustmaster Run N Drive PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:-a3,leftx:a0,lefty:a1,paddle1:b6,paddle2:b7,rightshoulder:b5,rightstick:b11,righttrigger:-a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
030000004f04000008d0000000000000,Thrustmaster Run N Drive PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3~,leftx:a0,lefty:a1,paddle1:b7,paddle2:b6,rightshoulder:b5,rightstick:b11,righttrigger:a4~,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
030000004f04000009d0000000000000,Thrustmaster Run N Drive PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
03000000011000000200000000000000,ThundeRobot G30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
030000006d04000088ca000000000000,Thunderpad,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows,
03000000666600000288000000000000,TigerGame PlayStation Adapter,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,
03000000666600000488000000000000,TigerGame PlayStation Adapter,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,
030000004f04000007d0000000000000,TMini,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
03000000571d00002100000000000000,Tomee NES Controller Adapter,a:b1,b:b0,back:b2,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,start:b3,platform:Windows,
03000000571d00002000000000000000,Tomee SNES Controller Adapter,a:b0,b:b1,back:b6,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Windows,
03000000571d00002100000000000000,Tomee NES Adapter,a:b1,b:b0,back:b2,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,start:b3,platform:Windows,
03000000571d00002000000000000000,Tomee SNES Adapter,a:b0,b:b1,back:b6,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Windows,
03000000d62000006000000000000000,Tournament PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
03000000c01100000055000000000000,Tronsmart,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
03000000d60600002a00000000000000,Trust 850F,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b2,x:b3,y:b4,platform:Windows,
030000005f140000c501000000000000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
03000000b80500000210000000000000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
030000004f04000087b6000000000000,TWCS Throttle,dpdown:b8,dpleft:b9,dpright:b7,dpup:b6,leftstick:b5,lefttrigger:-a5,leftx:a0,lefty:a1,righttrigger:+a5,platform:Windows,
@@ -816,7 +818,7 @@
03000000c62400000053000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
03000000c6240000fdfa000000000000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
03000000380700002847000000000000,Xbox 360 Fightpad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b11,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
030000005e040000a102000000000000,Xbox 360 Wireless Receiver,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
030000005e040000a102000000000000,Xbox 360 Receiver,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
030000005e0400000a0b000000000000,Xbox Adaptive Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
03000000120c00000a88000000000000,Xbox Controller,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a2,righty:a4,start:b6,x:b2,y:b3,platform:Windows,
03000000120c00001088000000000000,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2~,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5~,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
@@ -848,15 +850,20 @@
03000000172700004431000000000000,Xiaomi Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a7,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,
03000000172700003350000000000000,Xiaomi XMGP01YM,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000bc2000005060000000000000,Xiaomi XMGP01YM,+lefty:+a2,+righty:+a5,-lefty:-a1,-righty:-a4,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,start:b11,x:b3,y:b4,platform:Windows,
03000000c0160000e105000000000000,XinMo Dual Arcade,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
030000007d0400000340000000000000,Xterminator Digital Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:-a4,lefttrigger:+a4,leftx:a0,lefty:a1,paddle1:b7,paddle2:b6,rightshoulder:b5,rightstick:b9,righttrigger:b2,rightx:a3,righty:a5,start:b8,x:b3,y:b4,platform:Windows,
030000002c3600000100000000000000,Yawman Arrow,+rightx:h0.2,+righty:h0.4,-rightx:h0.8,-righty:h0.1,a:b4,b:b5,back:b6,dpdown:b15,dpleft:b14,dpright:b16,dpup:b13,leftshoulder:b10,leftstick:b0,lefttrigger:-a4,leftx:a0,lefty:a1,paddle1:b11,paddle2:b12,rightshoulder:b8,rightstick:b9,righttrigger:+a4,start:b3,x:b1,y:b2,platform:Windows,
03000000790000004f18000000000000,ZDT Android Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000073500000400000000000000,Zenaim Arcade Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
03000000120c00000500000000000000,Zeroplus Adapter,a:b2,b:b1,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b0,righttrigger:b5,rightx:a3,righty:a2,start:b8,x:b3,y:b0,platform:Windows,
03000000120c0000101e000000000000,Zeroplus P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
# Mac OS X
030000008f0e00000300000009010000,2 In 1 Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,
03000000c82d00001930000000000000,8BitDo 64,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,platform:Mac OS X,
03000000c82d00001930000000020000,8BitDo 64,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,platform:Mac OS X,
03000000c82d00001930000001000000,8BitDo 64,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,platform:Mac OS X,
03000000c82d00000031000001000000,8BitDo Adapter,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000c82d00000531000000020000,8BitDo Adapter 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000c82d00000951000000010000,8BitDo Dogbone,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightx:a2,righty:a3,start:b11,platform:Mac OS X,
@@ -889,6 +896,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000c82d00000851000000010000,8BitDo P30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000c82d00000660000000010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000660000000020000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000960000000000000,8BitDo Pro 3,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b17,paddle2:b16,paddle3:b2,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000131000001000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000231000001000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000331000001000000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
@@ -900,7 +908,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000c82d00001290000001000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00004028000000010000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000160000001000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000161000000010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000161000000010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000260000001000000,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000261000000010000,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00001230000000010000,8BitDo Ultimate,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
@@ -925,6 +933,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000050b00000045000031000000,ASUS Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,
03000000050b00000579000000010000,ASUS ROG Kunai 3,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b14,leftshoulder:b6,leftstick:b15,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b42,paddle1:b9,paddle2:b11,rightshoulder:b7,rightstick:b16,righttrigger:a4,rightx:a2,righty:a3,start:b13,x:b3,y:b4,platform:Mac OS X,
03000000050b00000679000000010000,ASUS ROG Kunai 3,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b14,leftshoulder:b6,leftstick:b15,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b23,rightshoulder:b7,rightstick:b16,righttrigger:a4,rightx:a2,righty:a3,start:b13,x:b3,y:b4,platform:Mac OS X,
030000006a0e00001140000001000000,Atari CX Wireless Controller,a:b0,leftx:a0,lefty:a1,platform:Mac OS X,
03000000503200000110000045010000,Atari VCS Classic,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b3,start:b2,platform:Mac OS X,
03000000503200000110000047010000,Atari VCS Classic Controller,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b3,start:b2,platform:Mac OS X,
03000000503200000210000047010000,Atari VCS Modern Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a2,righty:a3,start:b8,x:b2,y:b3,platform:Mac OS X,
@@ -939,6 +948,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000120c0000200e000000010000,Brook Mars PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,
03000000120c0000210e000000010000,Brook Mars PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,
030000008305000031b0000000000000,Cideko AK08b,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
03000000501d00009e61000001000000,CoreOrbits GamerPad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Mac OS X,
03000000d8140000cecf000000000000,Cthulhu,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,
03000000260900008888000088020000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Mac OS X,
03000000a306000022f6000001030000,Cyborg V3 Rumble Pad PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Mac OS X,
@@ -948,7 +958,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000151900004000000001000000,Flydigi Vader 2,a:b14,b:b15,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b12,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,paddle3:b16,paddle4:b17,rightshoulder:b7,rightstick:b13,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Mac OS X,
03000000b40400001124000001040000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b12,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b14,paddle1:b2,paddle2:b5,paddle3:b16,paddle4:b17,rightshoulder:b7,rightstick:b13,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000b40400001224000003030000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b12,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b2,paddle1:b16,paddle2:b17,paddle3:b14,paddle4:b15,rightshoulder:b7,rightstick:b13,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000790000004618000000010000,GameCube Controller Adapter,a:b4,b:b0,dpdown:b56,dpleft:b60,dpright:b52,dpup:b48,lefttrigger:a12,leftx:a0,lefty:a4,rightshoulder:b28,righttrigger:a16,rightx:a20,righty:a8,start:b36,x:b8,y:b12,platform:Mac OS X,
03000000790000004618000000010000,GameCube Adapter,a:b4,b:b0,dpdown:b56,dpleft:b60,dpright:b52,dpup:b48,lefttrigger:a12,leftx:a0,lefty:a4,rightshoulder:b28,righttrigger:a16,rightx:a20,righty:a8,start:b36,x:b8,y:b12,platform:Mac OS X,
03000000ac0500001a06000002020000,GameSir T3 2.02,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000373500000411000023000000,GameSir X4A Xbox Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000ad1b000001f9000000000000,Gamestop BB070 X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
@@ -981,8 +991,9 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000000d0f00006600000000000000,Horipad FPS Plus 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
030000000d0f0000ee00000000010000,Horipad Mini 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
030000000d0f0000c100000072050000,Horipad Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
030000000d0f0000f600000000010000,Horipad Switch Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,
03000000242e0000ff0b000000010000,Hyperkin N64 Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a3,start:b9,platform:Mac OS X,
03000000790000004e95000000010000,Hyperkin N64 Controller Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a5,righty:a2,start:b9,platform:Mac OS X,
03000000790000004e95000000010000,Hyperkin N64 Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a5,righty:a2,start:b9,platform:Mac OS X,
03000000830500006020000000000000,iBuffalo Super Famicom Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Mac OS X,
03000000ef0500000300000000020000,InterAct AxisPad,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Mac OS X,
03000000fd0500000030000010010000,Interact GoPad,a:b3,b:b4,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,x:b0,y:b1,platform:Mac OS X,
@@ -990,6 +1001,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Mac OS X,
03000000242f00002d00000007010000,JYS Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,
030000006d04000019c2000000000000,Logitech Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
030000006d04000019c2000000020000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3~,start:b9,x:b0,y:b3,platform:Mac OS X,
030000006d04000016c2000000020000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
030000006d04000016c2000000030000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
030000006d04000016c2000014040000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
@@ -997,6 +1009,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000006d04000018c2000000000000,Logitech F510,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
030000006d04000019c2000005030000,Logitech F710,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
030000006d0400001fc2000000000000,Logitech F710,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
030000006d0400001ac2000004000000,Logitech Precision,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,
030000006d04000018c2000000010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3~,start:b9,x:b0,y:b3,platform:Mac OS X,
03000000380700005032000000010000,Mad Catz PS3 Fightpad Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
03000000380700008433000000010000,Mad Catz PS3 Fightstick TE S Plus,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
@@ -1004,7 +1017,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000380700008483000000010000,Mad Catz PS4 Fightstick TE S Plus,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,
0300000049190000020400001b010000,Manba One,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b22,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000790000000600000007010000,Marvo GT-004,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,
030000008f0e00001330000011010000,Mayflash Controller Adapter,a:b2,b:b4,back:b16,dpdown:h0.8,dpleft:h0.2,dpright:h0.1,dpup:h0.4,leftshoulder:b12,lefttrigger:b16,leftx:a0,lefty:a2,rightshoulder:b14,rightx:a6~,righty:a4,start:b18,x:b0,y:b6,platform:Mac OS X,
030000008f0e00001330000011010000,Mayflash Adapter,a:b2,b:b4,back:b16,dpdown:h0.8,dpleft:h0.2,dpright:h0.1,dpup:h0.4,leftshoulder:b12,lefttrigger:b16,leftx:a0,lefty:a2,rightshoulder:b14,rightx:a6~,righty:a4,start:b18,x:b0,y:b6,platform:Mac OS X,
03000000790000004318000000010000,Mayflash GameCube Adapter,a:b4,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a12,leftx:a0,lefty:a4,rightshoulder:b28,righttrigger:a16,rightx:a20,righty:a8,start:b36,x:b8,y:b12,platform:Mac OS X,
03000000790000004418000000010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Mac OS X,
03000000242f00007300000000020000,Mayflash Magic NS,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b0,y:b3,platform:Mac OS X,
@@ -1025,8 +1038,9 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000c62400002b89000000010000,MOGA XP5A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000853200008906000000010000,Nacon Revolution X Unlimited,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000632500007505000000020000,NeoGeo mini PAD Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,x:b2,y:b3,platform:Mac OS X,
03000000921200004b46000003020000,NES 2-port Adapter,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b11,platform:Mac OS X,
03000000921200004b46000003020000,NES Adapter,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b11,platform:Mac OS X,
030000001008000001e5000006010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,righttrigger:b6,start:b9,x:b3,y:b0,platform:Mac OS X,
03000000ec110000e1a7000001010000,Nintendo Switch,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,
030000007e0500006920000001010000,Nintendo Switch 2 Pro Controller,a:b0,b:b1,back:b14,dpdown:b8,dpleft:b10,dpright:b9,dpup:b11,guide:b16,leftshoulder:b12,leftstick:b15,lefttrigger:b13,leftx:a0,lefty:a1~,misc1:b17,misc2:b20,paddle1:b18,paddle2:b19,rightshoulder:b4,rightstick:b7,righttrigger:b5,rightx:a2,righty:a3~,start:b6,x:b2,y:b3,platform:Mac OS X,
030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,
030000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,
@@ -1040,10 +1054,10 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
0300000009120000072f000000010000,OrangeFox86 DreamPicoPort,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a2,leftx:a0,lefty:a1,righttrigger:a5,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Mac OS X,
030000006f0e00000901000002010000,PDP PS3 Versus Fighting,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,
030000008f0e00000300000000000000,Piranha Xtreme PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Mac OS X,
03000000100800000300000006010000,PlayStation Adapter,a:b2,b:b1,back:b8,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,
03000000d620000011a7000000020000,PowerA Core Plus Gamecube Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,
03000000d620000011a7000010050000,PowerA Nintendo Switch Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
03000000d62000006dca000000010000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
03000000100800000300000006010000,PS2 Adapter,a:b2,b:b1,back:b8,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,
030000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X,
030000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X,
030000004c0500006802000072050000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X,
@@ -1072,7 +1086,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
0300000032150000030a000000000000,Razer Wildcat,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
03000000632500008005000000010000,Redgear,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,
03000000632500002305000000010000,Redragon Saturn,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,
03000000921200004547000000020000,Retro Bit Sega Genesis Controller Adapter,a:b0,b:b2,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,lefttrigger:b14,rightshoulder:b10,righttrigger:b4,start:b12,x:b6,y:b8,platform:Mac OS X,
03000000921200004547000000020000,Retro Bit Sega Genesis Adapter,a:b0,b:b2,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,lefttrigger:b14,rightshoulder:b10,righttrigger:b4,start:b12,x:b6,y:b8,platform:Mac OS X,
03000000790000001100000000000000,Retro Controller,a:b1,b:b2,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b6,lefttrigger:b7,rightshoulder:b4,righttrigger:b5,start:b9,x:b0,y:b3,platform:Mac OS X,
03000000790000001100000005010000,Retro Controller,a:b1,b:b2,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b4,start:b9,x:b0,y:b3,platform:Mac OS X,
03000000830500006020000000010000,Retro Controller,a:b0,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b8,righttrigger:b9,start:b7,x:b2,y:b3,platform:Mac OS X,
@@ -1106,7 +1120,6 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000381000003114000000000000,SteelSeries Stratus Duo,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
03000000110100001714000000000000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X,
03000000110100001714000020010000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X,
030000000d0f0000f600000000010000,Switch Hori Pad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,
03000000457500002211000000010000,SZMY Power PC Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
03000000e40a00000307000001000000,Taito Egret II Mini Control Panel,a:b4,b:b2,back:b6,guide:b9,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b1,start:b7,x:b8,y:b3,platform:Mac OS X,
03000000e40a00000207000001000000,Taito Egret II Mini Controller,a:b4,b:b2,back:b6,guide:b9,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b1,start:b7,x:b8,y:b3,platform:Mac OS X,
@@ -1117,13 +1130,14 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000004f04000015b3000000000000,Thrustmaster Dual Analog 3.2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Mac OS X,
030000004f0400000ed0000000020000,Thrustmaster eSwap Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Mac OS X,
03000000571d00002100000021000000,Tomee NES Controller Adapter,a:b1,b:b0,back:b2,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,start:b3,platform:Mac OS X,
03000000571d00002100000021000000,Tomee NES Adapter,a:b1,b:b0,back:b2,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,start:b3,platform:Mac OS X,
03000000bd12000015d0000000010000,Tomee Retro Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,
03000000571d00002000000021000000,Tomee SNES Adapter,a:b0,b:b1,back:b6,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Mac OS X,
03000000bd12000015d0000000000000,Tomee SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,
03000000571d00002000000021000000,Tomee SNES Controller Adapter,a:b0,b:b1,back:b6,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Mac OS X,
030000005f140000c501000000020000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,
03000000100800000100000000000000,Twin USB Joystick,a:b4,b:b2,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b12,leftstick:b20,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b14,rightstick:b22,righttrigger:b10,rightx:a6,righty:a4,start:b18,x:b6,y:b0,platform:Mac OS X,
03000000632500002605000000010000,Uberwith Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000c0160000e105000000040000,Ultimate Atari Fight Stick,a:b2,b:b4,back:b18,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,rightshoulder:b8,righttrigger:b10,start:b16,x:b0,y:b6,platform:Mac OS X,
03000000151900005678000010010000,Uniplay U6,a:b3,b:b6,back:b25,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b17,leftstick:b31,lefttrigger:b21,leftx:a1,lefty:a3,rightshoulder:b19,rightstick:b33,righttrigger:b23,rightx:a4,righty:a5,start:b27,x:b11,y:b13,platform:Mac OS X,
030000006f0e00000302000025040000,Victrix PS4 Pro Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,
030000006f0e00000702000003060000,Victrix PS4 Pro Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,
@@ -1157,11 +1171,14 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000005e040000130b000017050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
030000005e040000130b000022050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
030000005e040000220b000017050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
030000005e040000220b000021050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000172700004431000029010000,XiaoMi Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Mac OS X,
03000000120c0000100e000000010000,Zeroplus P4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
03000000120c0000101e000000010000,Zeroplus P4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,
# Linux
03000000c82d00001930000011010000,8BitDo 64,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,platform:Linux,
05000000c82d00001930000001000000,8BitDo 64,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,platform:Linux,
03000000c82d00000031000011010000,8BitDo Adapter,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000c82d00000631000000010000,8BitDo Adapter 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000c82d00000951000000010000,8BitDo Dogbone,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightx:a2,righty:a3,start:b11,platform:Linux,
@@ -1169,7 +1186,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000c82d00000090000011010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
05000000c82d00001038000000010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
05000000c82d00006a28000000010000,8BitDo GameCube,a:b0,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b9,paddle2:b8,rightshoulder:b10,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b1,y:b4,platform:Linux,
03000000c82d00001251000011010000,8BitDo Lite 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
03000000c82d00001251000011010000,8BitDo Lite 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
05000000c82d00001251000000010000,8BitDo Lite 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
03000000c82d00001151000011010000,8BitDo Lite SE,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
05000000c82d00001151000000010000,8BitDo Lite SE,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
@@ -1199,6 +1216,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
05000000c82d00000660000000010000,8BitDo Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
03000000c82d00000020000000000000,8BitDo Pro 2 for Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
06000000c82d00000020000006010000,8BitDo Pro 2 for Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000c82d00000960000011010000,8BitDo Pro 3,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b17,paddle2:b16,paddle3:b2,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
03000000c82d00000131000011010000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
03000000c82d00000231000011010000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
03000000c82d00000331000011010000,8BitDo Receiver,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
@@ -1222,6 +1240,12 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000c82d00000260000011010000,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
05000000c82d00000261000000010000,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
05000000202800000900000000010000,8BitDo SNES30,a:b1,b:b0,back:b10,dpdown:b122,dpleft:b119,dpright:b120,dpup:b117,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,
03000000c82d00000631000010010000,8BitDo Ultimate,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000c82d00000631000014010000,8BitDo Ultimate,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000c82d00000760000011010000,8BitDo Ultimate,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
03000000c82d00001130000011010000,8BitDo Ultimate,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b24,paddle2:b25,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000c82d00001230000011010000,8BitDo Ultimate,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000c82d00001330000011010000,8BitDo Ultimate,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b23,paddle2:b19,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
05000000c82d00001230000000010000,8BitDo Ultimate,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000c82d00000a31000014010000,8BitDo Ultimate 2C,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000c82d00001d30000011010000,8BitDo Ultimate 2C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
@@ -1229,12 +1253,6 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000c82d00001530000011010000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000c82d00001630000011010000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000c82d00001730000011010000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000c82d00001130000011010000,8BitDo Ultimate Wired,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b24,paddle2:b25,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000c82d00000631000010010000,8BitDo Ultimate Wireless,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000c82d00000631000014010000,8BitDo Ultimate Wireless,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000c82d00000760000011010000,8BitDo Ultimate Wireless,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
03000000c82d00001230000011010000,8BitDo Ultimate Wireless,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000c82d00001330000011010000,8BitDo Ultimate Wireless,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b26,paddle1:b23,paddle2:b19,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000c82d00000121000011010000,8BitDo Xbox One SN30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
05000000c82d00000121000000010000,8BitDo Xbox One SN30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
05000000a00500003232000001000000,8BitDo Zero,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux,
@@ -1264,6 +1282,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
05000000050b00000045000040000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,platform:Linux,
03000000050b00000579000011010000,ASUS ROG Kunai 3,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b36,paddle1:b52,paddle2:b53,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
05000000050b00000679000000010000,ASUS ROG Kunai 3,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b21,paddle1:b22,paddle2:b23,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
030000006a0e00001140000010010000,Atari CX Controller,a:b0,leftx:a0,lefty:a1,platform:Linux,
03000000503200000110000000000000,Atari VCS Classic Controller,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b4,start:b3,platform:Linux,
03000000503200000110000011010000,Atari VCS Classic Controller,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b4,start:b3,platform:Linux,
05000000503200000110000000000000,Atari VCS Classic Controller,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b4,start:b3,platform:Linux,
@@ -1285,6 +1304,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000c31100000791000011010000,Be1 GC101 Controller 1.03,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
030000005e0400008e02000003030000,Be1 GC101 Xbox 360,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000bc2000004d50000011010000,Beitong A1T2 BFM,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
030000000d0500000308000010010000,Belkin Nostromo N45,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Linux,
05000000bc2000000055000001000000,Betop AX1 BFM,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000bc2000006412000011010000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b30,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
030000006b1400000209000011010000,Bigben,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
@@ -1297,9 +1317,9 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000e82000006058000001010000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
03000000af1e00002400000010010000,Clockwork Pi DevTerm,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b9,x:b3,y:b0,platform:Linux,
030000000b0400003365000000010000,Competition Pro,a:b0,b:b1,back:b2,leftx:a0,lefty:a1,start:b3,platform:Linux,
03000000632500007a05000001020000,Cosmic Byte Ares Wired Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
03000000260900008888000000010000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Linux,
03000000a306000022f6000011010000,Cyborg V3 Rumble,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux,
030000005e0400008e02000002010000,Data Frog S80,a:b1,b:b0,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,platform:Linux,
03000000791d00000103000010010000,Dual Box Wii Classic Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
030000006f0e00003001000001010000,EA Sports PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
03000000c11100000191000011010000,EasySMX,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
@@ -1316,8 +1336,10 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000b40400001124000011010000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b12,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b14,paddle1:b2,paddle2:b5,paddle3:b16,paddle4:b17,rightshoulder:b7,rightstick:b13,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000b40400001224000011010000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b12,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b2,paddle1:b16,paddle2:b17,paddle3:b14,paddle4:b15,rightshoulder:b7,rightstick:b13,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
05000000151900004000000001000000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b12,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b14,paddle1:b2,paddle2:b5,paddle3:b16,paddle4:b17,rightshoulder:b7,rightstick:b13,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
06000000d73700000124000000000000,Flydigi Vader 5 Pro,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,guide:b8,leftstick:b9,rightstick:b10,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,leftx:a0,lefty:a1,lefttrigger:a2,rightx:a3,righty:a4,righttrigger:a5,misc1:b11,misc2:b12,misc3:b13,misc4:b14,paddle1:b15,paddle2:b17,paddle3:b16,paddle4:b18,misc5:b19,platform:Linux,
030000007e0500003703000000000000,GameCube Adapter,a:b0,b:b1,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,platform:Linux,
19000000030000000300000002030000,GameForce Controller,a:b1,b:b0,back:b8,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b16,leftshoulder:b4,leftstick:b14,lefttrigger:b6,leftx:a1,lefty:a0,rightshoulder:b5,rightstick:b15,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,
03000000373500000b10000019010000,GameSir Cyclone 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000ac0500005b05000010010000,GameSir G3w,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
03000000bc2000000055000011010000,GameSir G3w,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000558500001b06000010010000,GameSir G4 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
@@ -1325,12 +1347,14 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000ac0500007a05000011010000,GameSir G5,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b16,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000373500009710000001020000,GameSir Kaleid Flux,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000ac0500001a06000011010000,GameSir T3 2.02,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000373500000410000044010000,GameSir T4 Kaleid,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000bc2000005656000011010000,GameSir T4w,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
03000000373500009410000010010000,GameSir Tegenaria Lite,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
030000006f0e00000104000000010000,Gamestop Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000008f0e00000800000010010000,Gasia PlayStation Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
03000000451300000010000010010000,Genius Maxfire Grandias 12,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
03000000f025000021c1000010010000,Gioteck PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
03000000f0250000c283000010010000,Gioteck VX2 PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
190000004b4800000010000000010000,GO-Advance Controller,a:b1,b:b0,back:b10,dpdown:b7,dpleft:b8,dpright:b9,dpup:b6,leftshoulder:b4,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b13,start:b15,x:b2,y:b3,platform:Linux,
190000004b4800000010000001010000,GO-Advance Controller,a:b1,b:b0,back:b12,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,leftshoulder:b4,leftstick:b13,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b16,righttrigger:b15,start:b17,x:b2,y:b3,platform:Linux,
@@ -1344,7 +1368,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000280400000140000000010000,Gravis GamePad Pro,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,
030000008f0e00000610000000010000,GreenAsia Electronics Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Linux,
030000008f0e00001200000010010000,GreenAsia Joystick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,
0500000047532067616d657061640000,GS gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
0500000047532067616d657061640000,GS Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
03000000f0250000c383000010010000,GT VX2,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
030000008a2e0000dd10000011010000,Hand Held Legend GC Ultimate,a:b0,b:b2,back:b17,dpdown:b5,dpleft:b6,dpright:b7,dpup:b4,guide:b18,leftshoulder:b10,leftstick:b8,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b19,misc2:b24,paddle1:b13,paddle2:b12,rightshoulder:b11,rightstick:b9,righttrigger:a4,rightx:a2,righty:a5,start:b16,x:b1,y:b3,platform:Linux,
030000008a2e0000df10000011010000,Hand Held Legend ProGCC,a:b1,b:b0,back:b17,dpdown:b5,dpleft:b6,dpright:b7,dpup:b4,guide:b18,leftshoulder:b10,leftstick:b8,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b19,paddle1:b13,paddle2:b12,rightshoulder:b11,rightstick:b9,righttrigger:a4,rightx:a2,righty:a5,start:b16,x:b3,y:b2,platform:Linux,
@@ -1382,14 +1406,19 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000000d0f00006700000001010000,Horipad One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000000d0f0000ab01000011010000,Horipad Steam,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc2:b2,paddle1:b19,paddle2:b18,paddle3:b15,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
050000000d0f00009601000091000000,Horipad Steam,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc2:b2,paddle1:b19,paddle2:b18,paddle3:b15,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
050000000d0f0000f600000001000000,Horipad Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
03000000341a000005f7000010010000,HuiJia GameCube Controller Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux,
050000000d0f0000f600000001000000,Horipad Switch Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
03000000341a000005f7000010010000,HuiJia GameCube Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux,
05000000242e00000b20000001000000,Hyperkin Admiral N64 Controller,+rightx:b11,+righty:b13,-rightx:b8,-righty:b12,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,platform:Linux,
03000000242e0000ff0b000011010000,Hyperkin N64 Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a3,start:b9,platform:Linux,
03000000242e00006a38000010010000,Hyperkin Trooper 2,a:b0,b:b1,back:b4,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b3,start:b5,platform:Linux,
03000000242e00008816000001010000,Hyperkin X91,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000f00300008d03000011010000,HyperX Clutch,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
05000000f00300008d04000000010000,HyperX Clutch,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000830500006020000010010000,iBuffalo Super Famicom Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux,
03000000d80400004aea000011010000,icedragon.io STAC Dance Pad,a:b0,b:b1,back:b4,x:b2,y:b3,platform:Linux,
03000000d80400004bea000011010000,icedragon.io STAC Dance Pad,a:b0,b:b1,back:b4,x:b2,y:b3,platform:Linux,
030000008a2e0000d910000011010000,icedragon.io STAC2 Dance Pad,a:b0,b:b1,back:b4,x:b2,y:b3,platform:Linux,
030000008a2e0000e910000011010000,icedragon.io STAC2 Dance Pad,a:b8,b:b9,back:b12,x:b10,y:b11,platform:Linux,
030000008f0e00001330000001010000,iCode Retro Adapter,b:b3,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b9,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b1,start:b7,x:b2,y:b0,platform:Linux,
050000006964726f69643a636f6e0000,idroidcon Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
03000000b50700001503000010010000,Impact,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,
@@ -1416,7 +1445,6 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000006f0e00000103000000020000,Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000006d040000d1ca000000000000,Logitech Chillstream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
030000006d040000d1ca000011010000,Logitech Chillstream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
030000006d04000016c2000010010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
030000006d04000016c2000011010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
030000006d0400001dc2000014400000,Logitech F310,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
@@ -1425,8 +1453,9 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000006d04000019c2000011010000,Logitech F710,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
030000006d0400001fc2000005030000,Logitech F710,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000006d04000018c2000010010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b10,rightx:a3,righty:a4,start:b8,x:b3,y:b4,platform:Linux,
030000006d04000019c2000010010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
030000006d0400000ac2000010010000,Logitech WingMan RumblePad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,rightx:a3,righty:a4,x:b3,y:b4,platform:Linux,
030000006d04000011c2000010010000,Logitech WingMan RumblePad,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b10,rightx:a3,righty:a4,start:b8,x:b3,y:b4,platform:Linux,
05000000380700006652000025010000,Mad Catz CTRLR,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
03000000380700008532000010010000,Mad Catz Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,
03000000380700005032000011010000,Mad Catz Fightpad Pro PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
@@ -1444,7 +1473,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000380700003847000090040000,Mad Catz Xbox 360 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
03000000ad1b000016f0000090040000,Mad Catz Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000120c00000500000000010000,Manta DualShock 2,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,
030000008f0e00001330000010010000,Mayflash Controller Adapter,a:b1,b:b2,back:b8,dpdown:h0.8,dpleft:h0.2,dpright:h0.1,dpup:h0.4,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a3~,righty:a2,start:b9,x:b0,y:b3,platform:Linux,
030000008f0e00001330000010010000,Mayflash Adapter,a:b1,b:b2,back:b8,dpdown:h0.8,dpleft:h0.2,dpright:h0.1,dpup:h0.4,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a3~,righty:a2,start:b9,x:b0,y:b3,platform:Linux,
03000000790000004318000010010000,Mayflash GameCube Adapter,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Linux,
03000000790000004418000010010000,Mayflash GameCube Controller,a:b1,b:b0,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Linux,
03000000242f00007300000011010000,Mayflash Magic NS,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b0,y:b3,platform:Linux,
@@ -1475,6 +1504,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000005e040000ea02000008040000,Microsoft Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000005e040000ea0200000f050000,Microsoft Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
060000005e040000120b000009050000,Microsoft Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
060000005e040000120b000014050000,Microsoft Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000005e040000e302000003020000,Microsoft Xbox One Elite,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000005e040000000b000007040000,Microsoft Xbox One Elite 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b12,paddle2:b14,paddle3:b13,paddle4:b15,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000005e040000000b000008040000,Microsoft Xbox One Elite 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b12,paddle2:b14,paddle3:b13,paddle4:b15,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
@@ -1498,20 +1528,21 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000250900006688000000010000,MP8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,
030000005e0400008e02000010020000,MSI GC20 V2,a:b0,b:b1,back:b6,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000f70600000100000000010000,N64 Adaptoid,+rightx:b2,+righty:b1,-rightx:b4,-righty:b5,a:b0,b:b3,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,platform:Linux,
030000006b1400000906000014010000,Nacon Asymmetric Wireless PS4 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000006b140000010c000010010000,Nacon GC 400ES,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
03000000853200000706000012010000,Nacon GC-100,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
05000000853200000503000000010000,Nacon MG-X Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
030000006b1400000906000014010000,Nacon Asymmetric PS4 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
0300000085320000030c000011010000,Nacon GC100,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
03000000853200000706000012010000,Nacon GC100,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000006b140000010c000010010000,Nacon GC400ES,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
05000000853200000503000000010000,Nacon MGX Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
0300000085320000170d000011010000,Nacon Revolution 5 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,
0300000085320000190d000011010000,Nacon Revolution 5 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,
030000000d0f00000900000010010000,Natec Genesis P44,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
030000004f1f00000800000011010000,NeoGeo PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,
0300000092120000474e000000010000,NeoGeo X Arcade Stick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,x:b3,y:b2,platform:Linux,
03000000790000004518000010010000,Nexilux GameCube Controller Adapter,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Linux,
03000000790000004518000010010000,NEXiLUX GameCube Adapter,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Linux,
030000001008000001e5000010010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,righttrigger:b6,start:b9,x:b3,y:b0,platform:Linux,
060000007e0500003713000000000000,Nintendo 3DS,a:b0,b:b1,back:b8,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux,
030000007e0500003703000000016800,Nintendo GameCube Controller,a:b0,b:b2,dpdown:b6,dpleft:b4,dpright:b5,dpup:b7,lefttrigger:a4,leftx:a0,lefty:a1~,rightshoulder:b9,righttrigger:a5,rightx:a2,righty:a3~,start:b8,x:b1,y:b3,platform:Linux,
03000000790000004618000010010000,Nintendo GameCube Controller Adapter,a:b1,b:b0,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a5~,righty:a2~,start:b9,x:b2,y:b3,platform:Linux,
03000000790000004618000010010000,Nintendo GameCube Adapter,a:b1,b:b0,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a5~,righty:a2~,start:b9,x:b2,y:b3,platform:Linux,
03000000ec110000e1a7000010010000,Nintendo Switch,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
030000007e0500006920000011010000,Nintendo Switch 2 Pro Controller,a:b0,b:b1,back:b14,dpdown:b8,dpleft:b10,dpright:b9,dpup:b11,guide:b16,leftshoulder:b12,leftstick:b15,lefttrigger:b13,leftx:a0,lefty:a1~,misc1:b17,misc2:b20,paddle1:b18,paddle2:b19,rightshoulder:b4,rightstick:b7,righttrigger:b5,rightx:a2,righty:a3~,start:b6,x:b2,y:b3,platform:Linux,
060000004e696e74656e646f20537700,Nintendo Switch Combined Joy-Cons,a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux,
060000007e0500000620000000000000,Nintendo Switch Combined Joy-Cons,a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,misc1:b4,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux,
@@ -1526,7 +1557,6 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
05000000010000000100000003000000,Nintendo Wii Remote,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
050000007e0500003003000001000000,Nintendo Wii U Pro Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux,
050000005a1d00000218000003000000,Nokia GC 5000,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
030000000d0500000308000010010000,Nostromo n45 Dual Analog,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Linux,
030000007e0500007320000011010000,NSO GameCube Controller,a:b1,b:b3,dpdown:b8,dpleft:b10,dpright:b9,dpup:b11,guide:b16,leftshoulder:b13,lefttrigger:b12,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b4,rightx:a2,righty:a3~,start:b6,x:b0,y:b2,platform:Linux,
030000007e0500001920000011810000,NSO N64 Controller,+rightx:b2,+righty:b3,-rightx:b4,-righty:b10,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b5,rightshoulder:b7,righttrigger:b9,start:b11,platform:Linux,
050000007e0500001920000001000000,NSO N64 Controller,+rightx:b8,+righty:b7,-rightx:b3,-righty:b2,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,righttrigger:b10,start:b9,platform:Linux,
@@ -1538,8 +1568,8 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000550900001072000011010000,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b8,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,
03000000550900001472000011010000,NVIDIA Controller,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b8,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Linux,
05000000550900001472000001000000,NVIDIA Controller,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b8,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Linux,
030000004b120000014d000000010000,NYKO Airflo EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,
03000000451300000830000010010000,NYKO CORE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
030000004b120000014d000000010000,Nyko Airflo EX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,
03000000451300000830000010010000,Nyko CORE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
19000000010000000100000001010000,ODROID Go 2,a:b1,b:b0,dpdown:b7,dpleft:b8,dpright:b9,dpup:b6,guide:b10,leftshoulder:b4,leftstick:b12,lefttrigger:b11,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b13,righttrigger:b14,start:b15,x:b2,y:b3,platform:Linux,
19000000010000000200000011000000,ODROID Go 2,a:b1,b:b0,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b12,leftshoulder:b4,leftstick:b14,lefttrigger:b13,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b15,righttrigger:b16,start:b17,x:b2,y:b3,platform:Linux,
05000000362800000100000002010000,OUYA Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Linux,
@@ -1584,8 +1614,9 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000d62000000b20000001010000,PowerA Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000d62000000f20000001010000,PowerA Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000006d040000d2ca000011010000,Precision Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
03000000250900000017000010010000,PS/SS/N64 Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b5,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2~,righty:a3,start:b8,platform:Linux,
03000000250900000017000010010000,PS SS N64 Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b5,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2~,righty:a3,start:b8,platform:Linux,
03000000ff1100004133000010010000,PS2 Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,
03000000120c0000160e000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
03000000341a00003608000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
030000004c0500006802000010010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,
030000004c0500006802000010810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
@@ -1635,11 +1666,14 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000222c00000023000011010000,Qanba Obsidian Arcade Joystick PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,
030000009b2800000300000001010000,Raphnet 4nes4snes,a:b0,b:b4,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Linux,
030000009b2800004200000001010000,Raphnet Dual NES Adapter,a:b0,b:b1,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b3,platform:Linux,
0300132d9b2800006500000000000000,Raphnet GameCube Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux,
0300132d9b2800006500000001010000,Raphnet GameCube Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux,
030000009b2800006500000000000000,Raphnet GameCube Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux,
030000009b2800006500000001010000,Raphnet GameCube Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux,
03000000401700007f05000001010000,Raphnet GC and N64 Adapter,a:b5,b:b3,back:b8,dpdown:b10,dpleft:b12,dpright:b11,dpup:b9,leftshoulder:b6,lefttrigger:-a4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:-a5,rightx:a2,righty:a3,start:b1,x:b4,y:b2,platform:Linux,
030000009b2800003200000001010000,Raphnet GC and N64 Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux,
030000009b2800006000000001010000,Raphnet GC and N64 Adapter,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux,
030000009b2800003c00000001010000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Linux,
030000009b2800006100000001010000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Linux,
030000009b2800006300000001010000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Linux,
030000009b2800006400000001010000,Raphnet N64 Adapter,+rightx:b9,+righty:b7,-rightx:b8,-righty:b6,a:b0,b:b1,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b4,lefttrigger:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b3,platform:Linux,
030000009b2800008000000020020000,Raphnet Wii Classic Adapter,a:b1,b:b4,back:b2,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b5,platform:Linux,
030000009b2800008000000001010000,Raphnet Wii Classic Adapter V3,a:b1,b:b4,back:b2,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,leftshoulder:b6,rightshoulder:b7,start:b3,x:b0,y:b5,platform:Linux,
@@ -1661,7 +1695,6 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000321500000b10000011010000,Razer Wolverine PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,
0300000032150000140a000001010000,Razer Wolverine Ultimate Xbox,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000000d0f0000c100000010010000,Retro Bit Legacy16,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b12,leftshoulder:b4,lefttrigger:b6,misc1:b13,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,
030000000d0f0000c100000072056800,Retro Bit Legacy16,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b5,leftshoulder:b9,lefttrigger:+a4,misc1:b11,rightshoulder:b10,righttrigger:+a5,start:b6,x:b3,y:b2,platform:Linux,
03000000790000001100000010010000,Retro Controller,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b4,righttrigger:b5,start:b9,x:b0,y:b3,platform:Linux,
0300000003040000c197000011010000,Retrode Adapter,a:b0,b:b4,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Linux,
190000004b4800000111000000010000,RetroGame Joypad,a:b1,b:b0,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
@@ -1695,36 +1728,23 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000952e00004e43000011010000,Scuf Envision,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,platform:Linux,
03000000a30c00002500000011010000,Sega Genesis Mini 3B Controller,a:b2,b:b1,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,righttrigger:b5,start:b9,platform:Linux,
03000000790000001100000011010000,Sega Saturn,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b4,start:b9,x:b0,y:b3,platform:Linux,
03000000790000002201000011010000,Sega Saturn,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,start:b9,x:b2,y:b3,platform:Linux,
03000000b40400000a01000000010000,Sega Saturn,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Linux,
03000000632500002305000010010000,ShanWan Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
03000000632500002605000010010000,Shanwan Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000632500007505000010010000,Shanwan Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
03000000bc2000000055000010010000,Shanwan Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000f025000021c1000010010000,Shanwan Gioteck PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
03000000632500002605000010010000,ShanWan Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000632500007505000010010000,ShanWan Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
03000000bc2000000055000010010000,ShanWan Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000341a00000908000010010000,SL6566,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
03000000250900000500000000010000,SmartJoy PlayStation Adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,
030000004b2900000430000011000000,Snakebyte Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000790000002601000011010000,SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux,
050000004c050000cc09000001000000,Sony DualShock 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,
03000000666600006706000000010000,Sony PlayStation Adapter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,platform:Linux,
03000000d9040000160f000000010000,Sony PlayStation Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,
030000004c050000da0c000011010000,Sony PlayStation Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,
03000000d9040000160f000000010000,Sony PlayStation Controller Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,
03000000ff000000cb01000010010000,Sony PlayStation Portable,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Linux,
030000004c0500003713000011010000,Sony PlayStation Vita,a:b1,b:b2,back:b8,dpdown:b13,dpleft:b15,dpright:b14,dpup:b12,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Linux,
03000000250900000500000000010000,Sony PS2 pad with SmartJoy Adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,
030000005e0400008e02000073050000,Speedlink Torid,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000005e0400008e02000020200000,SpeedLink Xeox Pro Analog,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000de2800000112000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,
03000000de2800000112000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:+a5,dpleft:-a4,dpright:+a4,dpup:-a5,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,paddle1:b15,paddle2:b16,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a3,start:b11,x:b4,y:b5,platform:Linux,
03000000de2800000211000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,
03000000de2800000211000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:b18,dpleft:b19,dpright:b20,dpup:b17,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b16,paddle2:b15,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b5,platform:Linux,
03000000de2800004211000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,
03000000de2800004211000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:b18,dpleft:b19,dpright:b20,dpup:b17,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,paddle1:b16,paddle2:b15,rightshoulder:b7,righttrigger:a6,rightx:a2,righty:a3,start:b11,x:b4,y:b5,platform:Linux,
03000000de280000fc11000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
05000000de2800000212000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,
05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,
05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,
03000000de2800000512000010010000,Steam Deck,a:b3,b:b4,back:b11,dpdown:b17,dpleft:b18,dpright:b19,dpup:b16,guide:b13,leftshoulder:b7,leftstick:b14,lefttrigger:a9,leftx:a0,lefty:a1,rightshoulder:b8,rightstick:b15,righttrigger:a8,rightx:a2,righty:a3,start:b12,x:b5,y:b6,platform:Linux,
03000000de2800000512000011010000,Steam Deck,a:b3,b:b4,back:b11,dpdown:b17,dpleft:b18,dpright:b19,dpup:b16,guide:b13,leftshoulder:b7,leftstick:b14,lefttrigger:a9,leftx:a0,lefty:a1,misc1:b2,paddle1:b21,paddle2:b20,paddle3:b23,paddle4:b22,rightshoulder:b8,rightstick:b15,righttrigger:a8,rightx:a2,righty:a3,start:b12,x:b5,y:b6,platform:Linux,
03000000de280000ff11000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
050000004e696d6275732b0000000000,SteelSeries Nimbus Plus,a:b0,b:b1,back:b10,guide:b11,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b12,x:b2,y:b3,platform:Linux,
03000000381000003014000075010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
@@ -1742,7 +1762,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000008f0e00001431000010010000,SZMY Power PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
03000000e40a00000307000011010000,Taito Egret II Mini Control Panel,a:b4,b:b2,back:b6,guide:b9,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b1,start:b7,x:b8,y:b3,platform:Linux,
03000000e40a00000207000011010000,Taito Egret II Mini Controller,a:b4,b:b2,back:b6,guide:b9,leftx:a0,lefty:a1,rightshoulder:b0,righttrigger:b1,start:b7,x:b8,y:b3,platform:Linux,
03000000ba2200000701000001010000,Technology Innovation PS2 Adapter,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a5,righty:a2,start:b9,x:b3,y:b2,platform:Linux,
03000000ba2200000701000001010000,Technology Innovation PlayStation Adapter,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a5,righty:a2,start:b9,x:b3,y:b2,platform:Linux,
03000000790000001c18000011010000,TGZ Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000591c00002400000010010000,THEC64 Joystick,a:b0,b:b1,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b2,y:b3,platform:Linux,
03000000591c00002600000010010000,THEGamepad,a:b2,b:b1,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b0,platform:Linux,
@@ -1772,11 +1792,24 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000f00600000300000003000000,TRBot Virtual Joypad,a:b11,b:b12,back:b15,dpdown:b6,dpleft:b3,dpright:b4,dpup:b5,leftshoulder:b17,leftstick:b21,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b22,righttrigger:a2,rightx:a3,righty:a4,start:b16,x:b13,y:b14,platform:Linux,
030000005f140000c501000010010000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
06000000f51000000870000003010000,Turtle Beach Recon,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000100800000100000010010000,Twin PS2 Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,
03000000100800000100000010010000,Twin PlayStation Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,
03000000c0160000e105000010010000,Ultimate Atari Fight Stick,a:b1,b:b2,back:b9,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b4,righttrigger:b5,start:b8,x:b0,y:b3,platform:Linux,
03000000151900005678000010010000,Uniplay U6,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000100800000300000010010000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,
03000000790000000600000007010000,USB gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Linux,
03000000790000000600000007010000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Linux,
03000000790000001100000000010000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:a0,dpleft:a1,dpright:a2,dpup:a4,start:b9,platform:Linux,
03000000de2800000112000001000000,Valve Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,
03000000de2800000112000011010000,Valve Steam Controller,a:b2,b:b3,back:b10,dpdown:+a5,dpleft:-a4,dpright:+a4,dpup:-a5,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,paddle1:b15,paddle2:b16,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a3,start:b11,x:b4,y:b5,platform:Linux,
03000000de2800000211000001000000,Valve Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,
03000000de2800000211000011010000,Valve Steam Controller,a:b2,b:b3,back:b10,dpdown:b18,dpleft:b19,dpright:b20,dpup:b17,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b16,paddle2:b15,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b5,platform:Linux,
03000000de2800004211000001000000,Valve Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,
03000000de2800004211000011010000,Valve Steam Controller,a:b2,b:b3,back:b10,dpdown:b18,dpleft:b19,dpright:b20,dpup:b17,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,paddle1:b16,paddle2:b15,rightshoulder:b7,righttrigger:a6,rightx:a2,righty:a3,start:b11,x:b4,y:b5,platform:Linux,
03000000de280000fc11000001000000,Valve Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
05000000de2800000212000001000000,Valve Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,
05000000de2800000511000001000000,Valve Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,
05000000de2800000611000001000000,Valve Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,
03000000de2800000512000010010000,Valve Steam Deck,a:b3,b:b4,back:b11,dpdown:b17,dpleft:b18,dpright:b19,dpup:b16,guide:b13,leftshoulder:b7,leftstick:b14,lefttrigger:a9,leftx:a0,lefty:a1,rightshoulder:b8,rightstick:b15,righttrigger:a8,rightx:a2,righty:a3,start:b12,x:b5,y:b6,platform:Linux,
03000000de2800000512000011010000,Valve Steam Deck,a:b3,b:b4,back:b11,dpdown:b17,dpleft:b18,dpright:b19,dpup:b16,guide:b13,leftshoulder:b7,leftstick:b14,lefttrigger:a9,leftx:a0,lefty:a1,misc1:b2,paddle1:b21,paddle2:b20,paddle3:b23,paddle4:b22,rightshoulder:b8,rightstick:b15,righttrigger:a8,rightx:a2,righty:a3,start:b12,x:b5,y:b6,platform:Linux,
03000000790000001a18000011010000,Venom PS4 Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
03000000790000001b18000011010000,Venom PS4 Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
030000006f0e00000302000011010000,Victrix Pro Fightstick PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,
@@ -1785,24 +1818,30 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
05000000434f4d4d414e440000000000,VX Gaming Command Series,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
0000000058626f782033363020576900,Xbox 360 Controller,a:b0,b:b1,back:b14,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b7,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Linux,
030000005e0400001907000000010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000005e0400008e02000000010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000005e0400008e02000002010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000005e0400008e02000010010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000005e0400008e02000014010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000005e0400008e02000047010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000005e0400008e02000072050000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000005e0400009102000007010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000005e040000a102000000010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000005e040000a102000007010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000005e040000a102000030060000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000006f0e00001503000000020000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000005e0400008e02000000010000,Xbox 360 EasySMX,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000005e040000a102000014010000,Xbox 360 Receiver,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
0000000058626f782047616d65706100,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,
030000005e0400000202000000010000,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,
030000005e0400008e02000072050000,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000006f0e00001304000000010000,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000ffff0000ffff000000010000,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,
030000005e0400000a0b000005040000,Xbox One Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux,
030000005e040000d102000002010000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000005e040000ea02000000000000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
030000005e040000ea02000001030000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
050000005e040000200b000013050000,Xbox One Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
050000005e040000200b000017050000,Xbox One Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
050000005e040000200b000023050000,Xbox One Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
050000005e040000220b000017050000,Xbox One Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
050000005e040000e002000003090000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
050000005e040000fd02000003090000,Xbox One Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
050000005e040000fd02000030110000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
@@ -1838,12 +1877,8 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
060000005e040000120b00000b050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
060000005e040000120b00000d050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
060000005e040000120b00000f050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
060000005e040000120b000011050000,Xbox Series X Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
050000005e040000130b000022050000,Xbox Series X Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
050000005e040000200b000013050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
050000005e040000200b000017050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
050000005e040000200b000023050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
050000005e040000220b000017050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
060000005e040000120b000011050000,Xbox Series X Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000450c00002043000010010000,XEOX SL6556 BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
05000000172700004431000029010000,XiaoMi Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Linux,
03000000c0160000e105000001010000,XinMo Dual Arcade,a:b4,b:b3,back:b6,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b9,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b1,y:b0,platform:Linux,
@@ -1852,6 +1887,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000120c0000100e000011010000,Zeroplus P4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
03000000120c0000101e000011010000,Zeroplus P4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
03000000120c0000182e000011010000,Zeroplus PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
03000000790000002201000011010000,ZhiXu GuliKit D,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
# Android
38653964633230666463343334313533,8BitDo Adapter,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
@@ -1962,7 +1998,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
65656436646661313232656661616130,Hori PC Engine Mini Controller,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,start:b18,platform:Android,
31303433326562636431653534636633,Hori Real Arcade Pro 3,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b0,y:b2,platform:Android,
32656664353964393561366362333636,Hori Switch Split Pad Pro,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Android,
30306539356238653637313730656134,HORIPAD Switch Pro Controller,a:b0,b:b1,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b19,y:b2,platform:Android,
30306539356238653637313730656134,Horipad Switch Pro Controller,a:b0,b:b1,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b19,y:b2,platform:Android,
48797065726b696e2050616400000000,Hyperkin Admiral N64 Controller,+rightx:b6,+righty:b7,-rightx:b17,-righty:b5,a:b1,b:b0,leftshoulder:b3,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b20,start:b18,platform:Android,
62333331353131353034386136626636,Hyperkin Admiral N64 Controller,+rightx:b6,+righty:b7,-rightx:b17,-righty:b5,a:b1,b:b0,leftshoulder:b3,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b20,start:b18,platform:Android,
31306635363562663834633739396333,Hyperkin N64 Adapter,a:b1,b:b19,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightx:a2,righty:a3,start:b18,platform:Android,
@@ -1986,6 +2022,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
4c6f6769746563682047616d65706164,Logitech F710,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
64396331333230326333313330336533,Logitech F710,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
39653365373864633935383236363438,Logitech G Cloud,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
33373336396634316434323337666361,Logitech RumblePad 2,a:b22,b:b23,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b25,lefttrigger:b27,leftx:a0,lefty:a1,rightshoulder:b26,righttrigger:b28,rightx:a2,righty:a3,start:b30,x:b21,y:b24,platform:Android,
416d617a6f6e2047616d6520436f6e74,Luna Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Android,
4c756e612047616d6570616400000000,Luna Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
30363066623539323534363639323363,Magic NS,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b0,y:b2,platform:Android,
@@ -2029,7 +2066,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
4f5559412047616d6520436f6e74726f,OUYA Controller,a:b0,b:b2,dpdown:b18,dpleft:b15,dpright:b6,dpup:b17,leftshoulder:b3,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b19,platform:Android,
506572666f726d616e63652044657369,PDP PS3 Rock Candy Controller,a:b1,b:b17,back:h0.2,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b4,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b16,x:b0,y:b2,platform:Android,
61653962353232366130326530363061,Pokken,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:b9,rightshoulder:b20,righttrigger:b10,start:b18,x:b0,y:b2,platform:Android,
32666633663735353234363064386132,PS2,a:b23,b:b22,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b27,lefttrigger:b25,leftx:a0,lefty:a1,rightshoulder:b28,righttrigger:b26,rightx:a3,righty:a2,start:b30,x:b24,y:b21,platform:Android,
32666633663735353234363064386132,PS2 Controller,a:b23,b:b22,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b27,lefttrigger:b25,leftx:a0,lefty:a1,rightshoulder:b28,righttrigger:b26,rightx:a3,righty:a2,start:b30,x:b24,y:b21,platform:Android,
050000004c05000068020000dfff3f00,PS3 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
536f6e7920504c415953544154494f4e,PS3 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
61363034663839376638653463633865,PS3 Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
@@ -2083,7 +2120,6 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
526574726f5553422e636f6d20534e45,RetroUSB SNES RetroPort,a:b1,b:b20,back:b19,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,rightshoulder:b10,start:b2,x:b0,y:b3,platform:Android,
64643037633038386238303966376137,RetroUSB SNES RetroPort,a:b1,b:b20,back:b19,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b9,rightshoulder:b10,start:b2,x:b0,y:b3,platform:Android,
37656564346533643138636436356230,Rock Candy Switch Controller,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,misc1:b7,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b0,y:b2,platform:Android,
33373336396634316434323337666361,RumblePad 2,a:b22,b:b23,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b25,lefttrigger:b27,leftx:a0,lefty:a1,rightshoulder:b26,righttrigger:b28,rightx:a2,righty:a3,start:b30,x:b21,y:b24,platform:Android,
36363537303435333566386638366333,Samsung EIGP20,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
53616d73756e672047616d6520506164,Samsung EIGP20,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
66386565396238363534313863353065,Sanwa PlayOnline Mobile,a:b21,b:b22,back:b23,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b24,platform:Android,
@@ -2102,8 +2138,6 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
576972656c65737320436f6e74726f6c,Sony PlayStation Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
63303964303462366136616266653561,Sony PSP,a:b21,b:b22,back:b27,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b25,leftx:a0,lefty:a1,rightshoulder:b26,start:b28,x:b23,y:b24,platform:Android,
63376637643462343766333462383235,Sony Vita,a:b1,b:b19,back:b17,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftx:a0,lefty:a1,rightshoulder:b20,rightx:a3,righty:a4,start:b18,x:b0,y:b2,platform:Android,
05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Android,
05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Android,
0500000011010000201400000f7e0f00,SteelSeries Nimbus,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,righttrigger:b10,rightx:a2,righty:a3,x:b19,y:b2,platform:Android,
35306436396437373135383665646464,SteelSeries Nimbus Plus,a:b0,b:b1,leftshoulder:b3,leftstick:b17,lefttrigger:b9,leftx:a0,rightshoulder:b20,rightstick:b18,righttrigger:b10,rightx:a2,x:b19,y:b2,platform:Android,
33313930373536613937326534303931,Taito Egret II Mini Control Panel,a:b25,b:b23,back:b27,guide:b30,leftx:a0,lefty:a1,rightshoulder:b21,righttrigger:b22,start:b28,x:b29,y:b24,platform:Android,
@@ -2113,6 +2147,8 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
38346162326232346533316164363336,THEGamepad,a:b23,b:b22,back:b27,leftshoulder:b25,leftx:a0,lefty:a1,rightshoulder:b26,start:b28,x:b24,y:b21,platform:Android,
050000004f0400000ed00000fffe3f00,Thrustmaster eSwap Pro Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
5477696e20555342204a6f7973746963,Twin Joystick,a:b22,b:b21,back:b28,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b26,leftstick:b30,lefttrigger:b24,leftx:a0,lefty:a1,rightshoulder:b27,rightstick:b31,righttrigger:b25,rightx:a3,righty:a2,start:b29,x:b23,y:b20,platform:Android,
05000000de2800000511000001000000,Valve Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Android,
05000000de2800000611000001000000,Valve Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Android,
30623739343039643830333266346439,Valve Steam Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,paddle1:b24,paddle2:b23,rightshoulder:b10,rightstick:b8,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
31643365666432386133346639383937,Valve Steam Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,paddle1:b24,paddle2:b23,rightshoulder:b10,rightstick:b8,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
30386438313564306161393537333663,Wii Classic Adapter,a:b23,b:b22,back:b29,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b27,lefttrigger:b25,leftx:a0,lefty:a1,rightshoulder:b28,righttrigger:b26,rightx:a2,righty:a3,start:b30,x:b24,y:b21,platform:Android,
@@ -2146,8 +2182,8 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
050000005e040000ea02000000783f00,Xbox One S Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
050000005e040000fd020000ff7f3f00,Xbox One S Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
050000005e040000120b000000783f00,Xbox Series Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Android,
050000005e040000120b000000783f80,Xbox Series Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
050000005e040000130b0000ffff3f00,Xbox Series Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
050000005e040000120b000000783f80,Xbox Series Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
050000005e040000130b0000ffff3f00,Xbox Series Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
65633038363832353634653836396239,Xbox Series Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,
050000001727000044310000ffff3f00,XiaoMi Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a6,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Android,
@@ -2175,6 +2211,8 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
050000007e050000072000004f060000,Nintendo Switch Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b2,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b1,y:b3,platform:iOS,
050000007e05000009200000df870000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b10,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:iOS,
050000007e05000009200000ff870000,Nintendo Switch Pro Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b3,y:b2,platform:iOS,
05000000ac050000040000008e586d04,PlayStation VR2 Sense Controller (L),+leftx:+a3,+lefty:+a1,-leftx:+a2,-lefty:+a4,back:b2,leftstick:b4,lefttrigger:+a7,paddle2:b3,x:b0,y:b1,platform:iOS,
05000000ac050000040000000eb86d04,PlayStation VR2 Sense Controller (R),+rightx:+a3,+righty:+a1,-rightx:+a2,-righty:+a4,a:b0,b:b1,paddle1:b3,rightstick:b4,righttrigger:+a7,start:b2,platform:iOS,
050000004c050000cc090000df070000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS,
050000004c050000cc090000df870001,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS,
050000004c050000cc090000ff070000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS,
@@ -2184,11 +2222,11 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
050000004c050000e60c0000ff870000,PS5 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,touchpad:b11,x:b2,y:b3,platform:iOS,
05000000ac0500000300000000006d03,Remote,a:b0,b:b2,leftx:a0,lefty:a1,platform:iOS,
05000000ac0500000300000043006d03,Remote,a:b0,b:b2,leftx:a0,lefty:a1,platform:iOS,
05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:iOS,
05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:iOS,
050000005e040000050b0000df070001,Xbox Elite Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b10,paddle2:b12,paddle3:b11,paddle4:b13,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS,
050000005e040000050b0000ff070001,Xbox Elite Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS,
05000000de2800000511000001000000,Valve Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:iOS,
05000000de2800000611000001000000,Valve Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:iOS,
050000005e040000050b0000df070001,Xbox Elite Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b10,paddle2:b12,paddle3:b11,paddle4:b13,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS,
050000005e040000050b0000ff070001,Xbox Elite Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS,
050000005e040000e0020000df070000,Xbox One Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS,
050000005e040000e0020000ff070000,Xbox One Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS,
050000005e040000130b0000df870001,Xbox Series X Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b10,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS,
050000005e040000130b0000ff870001,Xbox Series X Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS,
050000005e040000e0020000df070000,Xbox Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS,
050000005e040000e0020000ff070000,Xbox Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS,
-164
View File
@@ -1,164 +0,0 @@
#include "audio.hpp"
#include <SDL3/SDL.h> // Para SDL_LogInfo, SDL_LogCategory, SDL_G...
#include <algorithm> // Para clamp
// Implementación de stb_vorbis (debe estar ANTES de incluir jail_audio.hpp).
// clang-format off
#undef STB_VORBIS_HEADER_ONLY
#include "external/stb_vorbis.h"
// stb_vorbis.h filtra les macros L, C i R (i PLAYBACK_*) al TU. Les netegem
// perquè xocarien amb noms de paràmetres de plantilla en json.hpp i altres.
#undef L
#undef C
#undef R
#undef PLAYBACK_MONO
#undef PLAYBACK_LEFT
#undef PLAYBACK_RIGHT
// clang-format on
#include "external/jail_audio.hpp" // Para JA_FadeOutMusic, JA_Init, JA_PauseM...
#include "options.hpp" // Para AudioOptions, audio, MusicOptions
#include "resource.hpp" // Para Resource
#include "ui/logger.hpp" // Para logger
// Singleton
Audio* Audio::instance = nullptr;
// Inicializa la instancia única del singleton
void Audio::init() { Audio::instance = new Audio(); }
// Libera la instancia
void Audio::destroy() { delete Audio::instance; }
// Obtiene la instancia
auto Audio::get() -> Audio* { return Audio::instance; }
// Constructor
Audio::Audio() { initSDLAudio(); }
// Destructor
Audio::~Audio() {
JA_Quit();
}
// Método principal
void Audio::update() {
JA_Update();
}
// Reproduce la música
void Audio::playMusic(const std::string& name, const int loop) {
music_.name = name;
music_.loop = (loop != 0);
if (music_enabled_ && music_.state != MusicState::PLAYING) {
JA_PlayMusic(Resource::get()->getMusic(name), loop);
music_.state = MusicState::PLAYING;
}
}
// Pausa la música
void Audio::pauseMusic() {
if (music_enabled_ && music_.state == MusicState::PLAYING) {
JA_PauseMusic();
music_.state = MusicState::PAUSED;
}
}
// Continua la música pausada
void Audio::resumeMusic() {
if (music_enabled_ && music_.state == MusicState::PAUSED) {
JA_ResumeMusic();
music_.state = MusicState::PLAYING;
}
}
// Detiene la música
void Audio::stopMusic() {
if (music_enabled_) {
JA_StopMusic();
music_.state = MusicState::STOPPED;
}
}
// Reproduce un sonido
void Audio::playSound(const std::string& name, Group group) const {
if (sound_enabled_) {
JA_PlaySound(Resource::get()->getSound(name), 0, static_cast<int>(group));
}
}
// Detiene todos los sonidos
void Audio::stopAllSounds() const {
if (sound_enabled_) {
JA_StopChannel(-1);
}
}
// Realiza un fundido de salida de la música
void Audio::fadeOutMusic(int milliseconds) const {
if (music_enabled_ && getRealMusicState() == MusicState::PLAYING) {
JA_FadeOutMusic(milliseconds);
}
}
// Consulta directamente el estado real de la música en jailaudio
auto Audio::getRealMusicState() -> MusicState {
JA_Music_state ja_state = JA_GetMusicState();
switch (ja_state) {
case JA_MUSIC_PLAYING:
return MusicState::PLAYING;
case JA_MUSIC_PAUSED:
return MusicState::PAUSED;
case JA_MUSIC_STOPPED:
case JA_MUSIC_INVALID:
case JA_MUSIC_DISABLED:
default:
return MusicState::STOPPED;
}
}
// Establece el volumen de los sonidos
void Audio::setSoundVolume(int sound_volume, Group group) const {
if (sound_enabled_) {
sound_volume = std::clamp(sound_volume, MIN_VOLUME, MAX_VOLUME);
const float CONVERTED_VOLUME = (sound_volume / 100.0F) * (Options::audio.volume / 100.0F);
JA_SetSoundVolume(CONVERTED_VOLUME, static_cast<int>(group));
}
}
// Establece el volumen de la música
void Audio::setMusicVolume(int music_volume) const {
if (music_enabled_) {
music_volume = std::clamp(music_volume, MIN_VOLUME, MAX_VOLUME);
const float CONVERTED_VOLUME = (music_volume / 100.0F) * (Options::audio.volume / 100.0F);
JA_SetMusicVolume(CONVERTED_VOLUME);
}
}
// Aplica la configuración
void Audio::applySettings() {
enable(Options::audio.enabled);
}
// Establecer estado general
void Audio::enable(bool value) {
enabled_ = value;
setSoundVolume(enabled_ ? Options::audio.sound.volume : MIN_VOLUME);
setMusicVolume(enabled_ ? Options::audio.music.volume : MIN_VOLUME);
}
// Inicializa SDL Audio
void Audio::initSDLAudio() {
if (!SDL_Init(SDL_INIT_AUDIO)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_AUDIO could not initialize! SDL Error: %s", SDL_GetError());
} else {
JA_Init(FREQUENCY, SDL_AUDIO_S16LE, 2);
enable(Options::audio.enabled);
Logger::info("Audio system initialized successfully");
}
}
-111
View File
@@ -1,111 +0,0 @@
#pragma once
#include <string> // Para string
#include <utility> // Para move
// --- Clase Audio: gestor de audio (singleton) ---
class Audio {
public:
// --- Enums ---
enum class Group : int {
ALL = -1, // Todos los grupos
GAME = 0, // Sonidos del juego
INTERFACE = 1 // Sonidos de la interfaz
};
enum class MusicState {
PLAYING, // Reproduciendo música
PAUSED, // Música pausada
STOPPED, // Música detenida
};
// --- Constantes ---
static constexpr int MAX_VOLUME = 100; // Volumen máximo
static constexpr int MIN_VOLUME = 0; // Volumen mínimo
static constexpr int FREQUENCY = 48000; // Frecuencia de audio
// --- Métodos de singleton ---
static void init(); // Inicializa el objeto Audio
static void destroy(); // Libera el objeto Audio
static auto get() -> Audio*; // Obtiene el puntero al objeto Audio
Audio(const Audio&) = delete; // Evitar copia
auto operator=(const Audio&) -> Audio& = delete; // Evitar asignación
// --- Método principal ---
static void update();
// --- Control de Música ---
void playMusic(const std::string& name, int loop = -1); // Reproducir música en bucle
void pauseMusic(); // Pausar reproducción de música
void resumeMusic(); // Continua la música pausada
void stopMusic(); // Detener completamente la música
void fadeOutMusic(int milliseconds) const; // Fundido de salida de la música
// --- Control de Sonidos ---
void playSound(const std::string& name, Group group = Group::GAME) const; // Reproducir sonido puntual
void stopAllSounds() const; // Detener todos los sonidos
// --- Configuración General ---
void enable(bool value); // Establecer estado general
void toggleEnabled() { enabled_ = !enabled_; } // Alternar estado general
void applySettings(); // Aplica la configuración
// --- Configuración de Sonidos ---
void enableSound() { sound_enabled_ = true; } // Habilitar sonidos
void disableSound() { sound_enabled_ = false; } // Deshabilitar sonidos
void enableSound(bool value) { sound_enabled_ = value; } // Establecer estado de sonidos
void toggleSound() { sound_enabled_ = !sound_enabled_; } // Alternar estado de sonidos
// --- Configuración de Música ---
void enableMusic() { music_enabled_ = true; } // Habilitar música
void disableMusic() { music_enabled_ = false; } // Deshabilitar música
void enableMusic(bool value) { music_enabled_ = value; } // Establecer estado de música
void toggleMusic() { music_enabled_ = !music_enabled_; } // Alternar estado de música
// --- Control de Volumen ---
void setSoundVolume(int volume, Group group = Group::ALL) const; // Ajustar volumen de efectos
void setMusicVolume(int volume) const; // Ajustar volumen de música
// --- Getters para debug ---
[[nodiscard]] auto isEnabled() const -> bool { return enabled_; }
[[nodiscard]] auto isSoundEnabled() const -> bool { return sound_enabled_; }
[[nodiscard]] auto isMusicEnabled() const -> bool { return music_enabled_; }
[[nodiscard]] auto getMusicState() const -> MusicState { return music_.state; }
[[nodiscard]] static auto getRealMusicState() -> MusicState; // Consulta directamente a jailaudio
[[nodiscard]] auto getCurrentMusicName() const -> const std::string& { return music_.name; }
private:
// --- Estructuras privadas ---
struct Music {
MusicState state; // Estado actual de la música (reproduciendo, detenido, en pausa)
std::string name; // Última pista de música reproducida
bool loop; // Indica si la última pista de música se debe reproducir en bucle
// Constructor para inicializar la música con valores predeterminados
Music()
: state(MusicState::STOPPED),
loop(false) {}
// Constructor para inicializar con valores específicos
Music(MusicState init_state, std::string init_name, bool init_loop)
: state(init_state),
name(std::move(init_name)),
loop(init_loop) {}
};
// --- Variables de estado ---
Music music_; // Estado de la música
bool enabled_ = true; // Estado general del audio
bool sound_enabled_ = true; // Estado de los efectos de sonido
bool music_enabled_ = true; // Estado de la música
// --- Métodos internos ---
void initSDLAudio(); // Inicializa SDL Audio
// --- Constructores y destructor privados (singleton) ---
Audio(); // Constructor privado
~Audio(); // Destructor privado
// --- Instancia singleton ---
static Audio* instance; // Instancia única de Audio
};
+213
View File
@@ -0,0 +1,213 @@
#include "core/audio/audio.hpp"
#include <SDL3/SDL.h> // Para SDL_GetError, SDL_Init
#include <algorithm> // Para clamp
#include <iostream> // Para std::cout
// Implementación de stb_vorbis (debe estar ANTES de incluir jail_audio.hpp).
// clang-format off
#undef STB_VORBIS_HEADER_ONLY
// NOLINTNEXTLINE(bugprone-suspicious-include) — stb_vorbis és single-file: el TU principal inclou el .c per portar la implementació.
#include "external/stb_vorbis.c"
// stb_vorbis.c filtra les macros L, C i R (i PLAYBACK_*) al TU. Les netegem
// perquè xocarien amb noms de paràmetres de plantilla en altres headers.
#undef L
#undef C
#undef R
#undef PLAYBACK_MONO
#undef PLAYBACK_LEFT
#undef PLAYBACK_RIGHT
// clang-format on
#include "core/audio/audio_adapter.hpp" // Para AudioResource::getMusic/getSound
#include "core/audio/jail_audio.hpp" // Para JA_*
#include "game/options.hpp" // Para Options::audio
// Singleton
Audio* Audio::instance = nullptr;
// Inicializa la instancia única del singleton
void Audio::init() { Audio::instance = new Audio(); }
// Libera la instancia
void Audio::destroy() {
delete Audio::instance;
Audio::instance = nullptr;
}
// Obtiene la instancia
auto Audio::get() -> Audio* { return Audio::instance; }
// Constructor
Audio::Audio() { initSDLAudio(); }
// Destructor
Audio::~Audio() {
Ja::quit();
}
// Método principal
void Audio::update() {
Ja::update();
// Sincronizar estado: detectar cuando la música se para (ej. fade-out completado)
if (instance != nullptr && instance->music_.state == MusicState::PLAYING && Ja::getMusicState() != Ja::MusicState::PLAYING) {
instance->music_.state = MusicState::STOPPED;
}
}
// Reproduce la música por nombre (con crossfade opcional)
void Audio::playMusic(const std::string& name, const int loop, const int crossfade_ms) {
bool new_loop = (loop != 0);
// Si ya está sonando exactamente la misma pista y mismo modo loop, no hacemos nada
if (music_.state == MusicState::PLAYING && music_.name == name && music_.loop == new_loop) {
return;
}
if (!music_enabled_) { return; }
auto* resource = AudioResource::getMusic(name);
if (resource == nullptr) { return; }
if (crossfade_ms > 0 && music_.state == MusicState::PLAYING) {
Ja::crossfadeMusic(resource, crossfade_ms, loop);
} else {
if (music_.state == MusicState::PLAYING) {
Ja::stopMusic();
}
Ja::playMusic(resource, loop);
}
music_.name = name;
music_.loop = new_loop;
music_.state = MusicState::PLAYING;
}
// Reproduce la música por puntero (con crossfade opcional)
void Audio::playMusic(Ja::Music* music, const int loop, const int crossfade_ms) {
if (!music_enabled_ || music == nullptr) { return; }
if (crossfade_ms > 0 && music_.state == MusicState::PLAYING) {
Ja::crossfadeMusic(music, crossfade_ms, loop);
} else {
if (music_.state == MusicState::PLAYING) {
Ja::stopMusic();
}
Ja::playMusic(music, loop);
}
music_.name.clear(); // nom desconegut quan es passa per punter
music_.loop = (loop != 0);
music_.state = MusicState::PLAYING;
}
// Pausa la música
void Audio::pauseMusic() {
if (music_enabled_ && music_.state == MusicState::PLAYING) {
Ja::pauseMusic();
music_.state = MusicState::PAUSED;
}
}
// Continua la música pausada
void Audio::resumeMusic() {
if (music_enabled_ && music_.state == MusicState::PAUSED) {
Ja::resumeMusic();
music_.state = MusicState::PLAYING;
}
}
// Detiene la música
void Audio::stopMusic() {
if (music_enabled_) {
Ja::stopMusic();
music_.state = MusicState::STOPPED;
}
}
// Reproduce un sonido por nombre
void Audio::playSound(const std::string& name, Group group) const {
if (sound_enabled_) {
Ja::playSound(AudioResource::getSound(name), 0, static_cast<int>(group));
}
}
// Reproduce un sonido por puntero directo
void Audio::playSound(Ja::Sound* sound, Group group) const {
if (sound_enabled_ && sound != nullptr) {
Ja::playSound(sound, 0, static_cast<int>(group));
}
}
// Detiene todos los sonidos
void Audio::stopAllSounds() const {
if (sound_enabled_) {
Ja::stopChannel(-1);
}
}
// Realiza un fundido de salida de la música
void Audio::fadeOutMusic(int milliseconds) const {
if (music_enabled_ && getRealMusicState() == MusicState::PLAYING) {
Ja::fadeOutMusic(milliseconds);
}
}
// Consulta directamente el estado real de la música en jailaudio
auto Audio::getRealMusicState() -> MusicState {
Ja::MusicState ja_state = Ja::getMusicState();
switch (ja_state) {
case Ja::MusicState::PLAYING:
return MusicState::PLAYING;
case Ja::MusicState::PAUSED:
return MusicState::PAUSED;
case Ja::MusicState::STOPPED:
case Ja::MusicState::INVALID:
case Ja::MusicState::DISABLED:
default:
return MusicState::STOPPED;
}
}
// Establece el volumen de los sonidos (float 0.0..1.0)
void Audio::setSoundVolume(float sound_volume, Group group) const {
if (sound_enabled_) {
sound_volume = std::clamp(sound_volume, MIN_VOLUME, MAX_VOLUME);
const float CONVERTED_VOLUME = sound_volume * Options::audio.volume;
Ja::setSoundVolume(CONVERTED_VOLUME, static_cast<int>(group));
}
}
// Establece el volumen de la música (float 0.0..1.0)
void Audio::setMusicVolume(float music_volume) const {
if (music_enabled_) {
music_volume = std::clamp(music_volume, MIN_VOLUME, MAX_VOLUME);
const float CONVERTED_VOLUME = music_volume * Options::audio.volume;
Ja::setMusicVolume(CONVERTED_VOLUME);
}
}
// Aplica la configuración
void Audio::applySettings() {
enable(Options::audio.enabled);
}
// Establecer estado general
void Audio::enable(bool value) {
enabled_ = value;
setSoundVolume(enabled_ ? Options::audio.sound.volume : MIN_VOLUME);
setMusicVolume(enabled_ ? Options::audio.music.volume : MIN_VOLUME);
}
// Inicializa SDL Audio
void Audio::initSDLAudio() {
if (!SDL_Init(SDL_INIT_AUDIO)) {
std::cout << "SDL_AUDIO could not initialize! SDL Error: " << SDL_GetError() << '\n';
} else {
Ja::init(FREQUENCY, SDL_AUDIO_S16LE, 2);
enable(Options::audio.enabled);
}
}
+119
View File
@@ -0,0 +1,119 @@
#pragma once
#include <cmath> // Para std::lround
#include <cstdint> // Para int8_t, uint8_t
#include <string> // Para string
namespace Ja {
struct Music;
struct Sound;
} // namespace Ja
// --- Clase Audio: gestor de audio (singleton) ---
// Implementació canònica, byte-idèntica entre projectes.
// Els volums es manegen internament com a float 0.01.0; la capa de
// presentació (menús, notificacions) usa les helpers toPercent/fromPercent
// per mostrar 0100 a l'usuari.
class Audio {
public:
// --- Enums ---
enum class Group : std::int8_t {
ALL = -1, // Todos los grupos
GAME = 0, // Sonidos del juego
INTERFACE = 1 // Sonidos de la interfaz
};
enum class MusicState : std::uint8_t {
PLAYING, // Reproduciendo música
PAUSED, // Música pausada
STOPPED, // Música detenida
};
// --- Constantes ---
static constexpr float MAX_VOLUME = 1.0F; // Volumen máximo (float 0..1)
static constexpr float MIN_VOLUME = 0.0F; // Volumen mínimo (float 0..1)
static constexpr float VOLUME_STEP = 0.05F; // Pas estàndard per a UI (5%)
static constexpr int FREQUENCY = 48000; // Frecuencia de audio
static constexpr int DEFAULT_CROSSFADE_MS = 1500; // Duració del crossfade per defecte (ms)
// --- Singleton ---
static void init(); // Inicializa el objeto Audio
static void destroy(); // Libera el objeto Audio
static auto get() -> Audio*; // Obtiene el puntero al objeto Audio
Audio(const Audio&) = delete; // Evitar copia
auto operator=(const Audio&) -> Audio& = delete; // Evitar asignación
static void update(); // Actualización del sistema de audio
// --- Control de música ---
void playMusic(const std::string& name, int loop = -1, int crossfade_ms = 0); // Reproducir música por nombre (con crossfade opcional)
void playMusic(Ja::Music* music, int loop = -1, int crossfade_ms = 0); // Reproducir música por puntero (con crossfade opcional)
void pauseMusic(); // Pausar reproducción de música
void resumeMusic(); // Continua la música pausada
void stopMusic(); // Detener completamente la música
void fadeOutMusic(int milliseconds) const; // Fundido de salida de la música
// --- Control de sonidos ---
void playSound(const std::string& name, Group group = Group::GAME) const; // Reproducir sonido puntual por nombre
void playSound(Ja::Sound* sound, Group group = Group::GAME) const; // Reproducir sonido puntual por puntero
void stopAllSounds() const; // Detener todos los sonidos
// --- Control de volumen (API interna: float 0.0..1.0) ---
void setSoundVolume(float volume, Group group = Group::ALL) const; // Ajustar volumen de efectos
void setMusicVolume(float volume) const; // Ajustar volumen de música
// --- Helpers de conversió per a la capa de presentació ---
// UI (menús, notificacions) manega enters 0..100; internament viu float 0..1.
static auto toPercent(float volume) -> int {
return static_cast<int>(std::lround(volume * 100.0F));
}
static constexpr auto fromPercent(int percent) -> float {
return static_cast<float>(percent) / 100.0F;
}
// --- Configuración general ---
void enable(bool value); // Establecer estado general
void toggleEnabled() { enabled_ = !enabled_; } // Alternar estado general
void applySettings(); // Aplica la configuración
// --- Configuración de sonidos ---
void enableSound() { sound_enabled_ = true; } // Habilitar sonidos
void disableSound() { sound_enabled_ = false; } // Deshabilitar sonidos
void enableSound(bool value) { sound_enabled_ = value; } // Establecer estado de sonidos
void toggleSound() { sound_enabled_ = !sound_enabled_; } // Alternar estado de sonidos
// --- Configuración de música ---
void enableMusic() { music_enabled_ = true; } // Habilitar música
void disableMusic() { music_enabled_ = false; } // Deshabilitar música
void enableMusic(bool value) { music_enabled_ = value; } // Establecer estado de música
void toggleMusic() { music_enabled_ = !music_enabled_; } // Alternar estado de música
// --- Consultas de estado ---
[[nodiscard]] auto isEnabled() const -> bool { return enabled_; }
[[nodiscard]] auto isSoundEnabled() const -> bool { return sound_enabled_; }
[[nodiscard]] auto isMusicEnabled() const -> bool { return music_enabled_; }
[[nodiscard]] auto getMusicState() const -> MusicState { return music_.state; }
[[nodiscard]] static auto getRealMusicState() -> MusicState;
[[nodiscard]] auto getCurrentMusicName() const -> const std::string& { return music_.name; }
private:
// --- Tipos anidados ---
struct Music {
MusicState state{MusicState::STOPPED}; // Estado actual de la música
std::string name; // Última pista de música reproducida
bool loop{false}; // Indica si se reproduce en bucle
};
// --- Métodos ---
Audio(); // Constructor privado
~Audio(); // Destructor privado
void initSDLAudio(); // Inicializa SDL Audio
// --- Variables miembro ---
static Audio* instance; // Instancia única de Audio
Music music_; // Estado de la música
bool enabled_{true}; // Estado general del audio
bool sound_enabled_{true}; // Estado de los efectos de sonido
bool music_enabled_{true}; // Estado de la música
};
+13
View File
@@ -0,0 +1,13 @@
#include "core/audio/audio_adapter.hpp"
#include "core/resources/resource.hpp"
namespace AudioResource {
auto getMusic(const std::string& name) -> Ja::Music* {
return Resource::get()->getMusic(name);
}
auto getSound(const std::string& name) -> Ja::Sound* {
return Resource::get()->getSound(name);
}
} // namespace AudioResource
+19
View File
@@ -0,0 +1,19 @@
#pragma once
// --- Audio Resource Adapter ---
// Aquest fitxer exposa una interfície comuna a Audio per obtenir Ja::Music* /
// Ja::Sound* per nom. Cada projecte la implementa en audio_adapter.cpp
// delegant al seu singleton de recursos (Resource::get(), Resource::Cache::get(),
// etc.). Això permet que audio.hpp/audio.cpp siguin idèntics entre projectes.
#include <string> // Para string
namespace Ja {
struct Music;
struct Sound;
} // namespace Ja
namespace AudioResource {
auto getMusic(const std::string& name) -> Ja::Music*;
auto getSound(const std::string& name) -> Ja::Sound*;
} // namespace AudioResource
+699
View File
@@ -0,0 +1,699 @@
#pragma once
// --- Includes ---
#include <SDL3/SDL.h>
#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#define STB_VORBIS_HEADER_ONLY
// NOLINTNEXTLINE(bugprone-suspicious-include) — stb_vorbis és single-file: la macro de dalt limita a només-declaracions.
#include "external/stb_vorbis.c" // Para stb_vorbis_open_memory i streaming
// Deleter stateless per a buffers reservats amb `SDL_malloc` / `SDL_LoadWAV*`.
// Compatible amb `std::unique_ptr<Uint8[], SdlFreeDeleter>` — zero size
// overhead gràcies a EBO, igual que un unique_ptr amb default_delete.
struct SdlFreeDeleter {
void operator()(Uint8* p) const noexcept {
if (p != nullptr) { SDL_free(p); }
}
};
namespace Ja {
// --- Public Enums ---
enum class ChannelState : std::uint8_t {
INVALID,
FREE,
PLAYING,
PAUSED,
DISABLED,
};
enum class MusicState : std::uint8_t {
INVALID,
PLAYING,
PAUSED,
STOPPED,
DISABLED,
};
// --- Constants ---
inline constexpr int MAX_SIMULTANEOUS_CHANNELS = 20;
inline constexpr int MAX_GROUPS = 2;
inline constexpr SDL_AudioSpec DEFAULT_SPEC{.format = SDL_AUDIO_S16, .channels = 2, .freq = 48000};
// --- Struct Definitions ---
struct Sound {
SDL_AudioSpec spec{DEFAULT_SPEC};
Uint32 length{0};
// Buffer descomprimit (PCM) propietat del sound. Reservat per SDL_LoadWAV
// via SDL_malloc; el deleter `SdlFreeDeleter` allibera amb SDL_free.
std::unique_ptr<Uint8[], SdlFreeDeleter> buffer;
};
// L'ordre (punters primer, ints després, enum de 8 bits al final) minimitza
// el padding a 64-bit (evita avisos de clang-analyzer-optin.performance.Padding).
struct Channel {
Sound* sound{nullptr};
SDL_AudioStream* stream{nullptr};
int pos{0};
int times{0};
int group{0};
ChannelState state{ChannelState::FREE};
};
struct Music {
SDL_AudioSpec spec{DEFAULT_SPEC};
// OGG comprimit en memòria. Propietat nostra; es copia des del buffer
// d'entrada una sola vegada en loadMusic i es descomprimix en chunks
// per streaming. Com que stb_vorbis guarda un punter persistent al
// `.data()` d'aquest vector, no el podem resize'jar un cop establert
// (una reallocation invalidaria el punter que el decoder conserva).
std::vector<Uint8> ogg_data;
stb_vorbis* vorbis{nullptr}; // handle del decoder, viu tot el cicle del Music
std::string filename;
int times{0}; // loops restants (-1 = infinit, 0 = un sol play)
SDL_AudioStream* stream{nullptr};
MusicState state{MusicState::INVALID};
};
struct FadeState {
bool active{false};
Uint64 start_time{0};
int duration_ms{0};
float initial_volume{0.0F};
};
struct OutgoingMusic {
SDL_AudioStream* stream{nullptr};
FadeState fade;
};
// --- Internal Global State (inline, C++17) ---
inline Music* current_music{nullptr};
inline Channel channels[MAX_SIMULTANEOUS_CHANNELS];
inline SDL_AudioSpec audio_spec{DEFAULT_SPEC};
inline float music_volume{1.0F};
inline float sound_volume[MAX_GROUPS];
inline bool music_enabled{true};
inline bool sound_enabled{true};
inline SDL_AudioDeviceID sdl_audio_device{0};
inline OutgoingMusic outgoing_music;
inline FadeState incoming_fade;
// --- Forward Declarations ---
inline void stopMusic();
inline void stopChannel(int channel);
inline auto playSoundOnChannel(Sound* sound, int channel, int loop = 0, int group = 0) -> int;
inline void crossfadeMusic(Music* music, int crossfade_ms, int loop = -1);
// --- Music streaming internals ---
// Bytes-per-sample per canal (sempre s16)
inline constexpr int MUSIC_BYTES_PER_SAMPLE = 2;
// Quants shorts decodifiquem per crida a get_samples_short_interleaved.
// 8192 shorts = 4096 samples/channel en estèreo ≈ 85ms de so a 48kHz.
inline constexpr int MUSIC_CHUNK_SHORTS = 8192;
// Umbral d'audio per davant del cursor de reproducció. Mantenim ≥ 0.5 s a
// l'SDL_AudioStream per absorbir jitter de frame i evitar underruns.
inline constexpr float MUSIC_LOW_WATER_SECONDS = 0.5F;
// Decodifica un chunk del vorbis i el volca a l'stream. Retorna samples
// decodificats per canal (0 = EOF de l'stream vorbis).
inline auto feedMusicChunk(Music* music) -> int {
if ((music == nullptr) || (music->vorbis == nullptr) || (music->stream == nullptr)) { return 0; }
short chunk[MUSIC_CHUNK_SHORTS];
const int NUM_CHANNELS = music->spec.channels;
const int SAMPLES_PER_CHANNEL = stb_vorbis_get_samples_short_interleaved(
music->vorbis,
NUM_CHANNELS,
chunk,
MUSIC_CHUNK_SHORTS);
if (SAMPLES_PER_CHANNEL <= 0) { return 0; }
const int BYTES = SAMPLES_PER_CHANNEL * NUM_CHANNELS * MUSIC_BYTES_PER_SAMPLE;
SDL_PutAudioStreamData(music->stream, chunk, BYTES);
return SAMPLES_PER_CHANNEL;
}
// Reompli l'stream fins que tinga ≥ MUSIC_LOW_WATER_SECONDS bufferats.
// En arribar a EOF del vorbis, aplica el loop (times) o deixa drenar.
inline void pumpMusic(Music* music) {
if ((music == nullptr) || (music->vorbis == nullptr) || (music->stream == nullptr)) { return; }
const int BYTES_PER_SECOND = music->spec.freq * music->spec.channels * MUSIC_BYTES_PER_SAMPLE;
const int LOW_WATER_BYTES = static_cast<int>(MUSIC_LOW_WATER_SECONDS * static_cast<float>(BYTES_PER_SECOND));
while (SDL_GetAudioStreamAvailable(music->stream) < LOW_WATER_BYTES) {
const int DECODED = feedMusicChunk(music);
if (DECODED > 0) { continue; }
// EOF: si queden loops, rebobinar; si no, tallar i deixar drenar.
if (music->times != 0) {
stb_vorbis_seek_start(music->vorbis);
if (music->times > 0) { music->times--; }
} else {
break;
}
}
}
// Pre-carrega `duration_ms` de so dins l'stream actual abans que l'stream
// siga robat per outgoing_music (crossfade o fade-out). Imprescindible amb
// streaming: l'stream robat no es pot re-alimentar perquè perd la referència
// al seu vorbis decoder. No aplica loop — si el vorbis s'esgota abans, parem.
inline void preFillOutgoing(Music* music, int duration_ms) {
if ((music == nullptr) || (music->vorbis == nullptr) || (music->stream == nullptr)) { return; }
const int BYTES_PER_SECOND = music->spec.freq * music->spec.channels * MUSIC_BYTES_PER_SAMPLE;
const int NEEDED_BYTES = static_cast<int>((static_cast<std::int64_t>(duration_ms) * BYTES_PER_SECOND) / 1000);
while (SDL_GetAudioStreamAvailable(music->stream) < NEEDED_BYTES) {
const int DECODED = feedMusicChunk(music);
if (DECODED <= 0) { break; } // EOF: deixem drenar el que hi haja
}
}
// --- update() helpers ---
inline void updateOutgoingFade() {
if ((outgoing_music.stream == nullptr) || !outgoing_music.fade.active) { return; }
const Uint64 NOW = SDL_GetTicks();
const Uint64 ELAPSED = NOW - outgoing_music.fade.start_time;
if (ELAPSED >= static_cast<Uint64>(outgoing_music.fade.duration_ms)) {
SDL_DestroyAudioStream(outgoing_music.stream);
outgoing_music.stream = nullptr;
outgoing_music.fade.active = false;
} else {
const float PERCENT = static_cast<float>(ELAPSED) / static_cast<float>(outgoing_music.fade.duration_ms);
SDL_SetAudioStreamGain(outgoing_music.stream, outgoing_music.fade.initial_volume * (1.0F - PERCENT));
}
}
inline void updateIncomingFade() {
if (!incoming_fade.active) { return; }
const Uint64 NOW = SDL_GetTicks();
const Uint64 ELAPSED = NOW - incoming_fade.start_time;
if (ELAPSED >= static_cast<Uint64>(incoming_fade.duration_ms)) {
incoming_fade.active = false;
SDL_SetAudioStreamGain(current_music->stream, music_volume);
} else {
const float PERCENT = static_cast<float>(ELAPSED) / static_cast<float>(incoming_fade.duration_ms);
SDL_SetAudioStreamGain(current_music->stream, music_volume * PERCENT);
}
}
inline void updateCurrentMusic() {
if (!music_enabled || (current_music == nullptr) || current_music->state != MusicState::PLAYING) { return; }
updateIncomingFade();
// Streaming: rellenem l'stream fins al low-water-mark i parem si el
// vorbis s'ha esgotat i no queden loops.
pumpMusic(current_music);
if (current_music->times == 0 && SDL_GetAudioStreamAvailable(current_music->stream) == 0) {
stopMusic();
}
}
inline void updateSoundChannels() {
if (!sound_enabled) { return; }
for (int i = 0; i < MAX_SIMULTANEOUS_CHANNELS; ++i) {
auto& ch = channels[i];
if (ch.state != ChannelState::PLAYING) { continue; }
if (ch.times != 0) {
if (static_cast<Uint32>(SDL_GetAudioStreamAvailable(ch.stream)) < (ch.sound->length / 2)) {
SDL_PutAudioStreamData(ch.stream, ch.sound->buffer.get(), ch.sound->length);
if (ch.times > 0) { ch.times--; }
}
} else {
if (SDL_GetAudioStreamAvailable(ch.stream) == 0) { stopChannel(i); }
}
}
}
inline void update() {
updateOutgoingFade();
updateCurrentMusic();
updateSoundChannels();
}
inline void init(int freq, SDL_AudioFormat format, int num_channels) {
audio_spec = {.format = format, .channels = num_channels, .freq = freq};
if (sdl_audio_device != 0) { SDL_CloseAudioDevice(sdl_audio_device); }
sdl_audio_device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &audio_spec);
if (sdl_audio_device == 0) { std::cout << "Failed to initialize SDL audio!" << '\n'; }
for (auto& ch : channels) { ch.state = ChannelState::FREE; }
std::ranges::fill(sound_volume, 0.5F);
}
inline void quit() {
if (outgoing_music.stream != nullptr) {
SDL_DestroyAudioStream(outgoing_music.stream);
outgoing_music.stream = nullptr;
}
if (sdl_audio_device != 0) { SDL_CloseAudioDevice(sdl_audio_device); }
sdl_audio_device = 0;
}
// --- Music Functions ---
inline auto loadMusic(const Uint8* buffer, Uint32 length) -> Music* {
if ((buffer == nullptr) || length == 0) { return nullptr; }
// Allocem el Music primer per aprofitar el seu `std::vector<Uint8>`
// com a propietari del OGG comprimit. stb_vorbis guarda un punter
// persistent al buffer; com que ací no el resize'jem, el .data() és
// estable durant tot el cicle de vida del music.
auto* music = new Music();
music->ogg_data.assign(buffer, buffer + length);
int vorbis_error = 0;
music->vorbis = stb_vorbis_open_memory(music->ogg_data.data(),
static_cast<int>(length),
&vorbis_error,
nullptr);
if (music->vorbis == nullptr) {
std::cout << "loadMusic: stb_vorbis_open_memory failed (error " << vorbis_error << ")" << '\n';
delete music;
return nullptr;
}
const stb_vorbis_info INFO = stb_vorbis_get_info(music->vorbis);
music->spec.channels = INFO.channels;
music->spec.freq = static_cast<int>(INFO.sample_rate);
music->spec.format = SDL_AUDIO_S16;
music->state = MusicState::STOPPED;
return music;
}
// Overload amb filename — els callers l'usen per poder comparar la música
// en curs amb getMusicFilename() i no rearrancar-la si ja és la mateixa.
inline auto loadMusic(Uint8* buffer, Uint32 length, const char* filename) -> Music* {
Music* music = loadMusic(static_cast<const Uint8*>(buffer), length);
if ((music != nullptr) && (filename != nullptr)) { music->filename = filename; }
return music;
}
inline auto loadMusic(const char* filename) -> Music* {
// Carreguem primer el arxiu en memòria i després el descomprimim.
FILE* f = std::fopen(filename, "rb");
if (f == nullptr) { return nullptr; }
std::fseek(f, 0, SEEK_END);
const long FSIZE = std::ftell(f);
std::fseek(f, 0, SEEK_SET);
if (FSIZE <= 0) {
std::fclose(f);
return nullptr;
}
auto* buffer = static_cast<Uint8*>(std::malloc(static_cast<size_t>(FSIZE) + 1));
if (buffer == nullptr) {
std::fclose(f);
return nullptr;
}
if (std::fread(buffer, FSIZE, 1, f) != 1) {
std::fclose(f);
std::free(buffer);
return nullptr;
}
std::fclose(f);
Music* music = loadMusic(static_cast<const Uint8*>(buffer), static_cast<Uint32>(FSIZE));
if (music != nullptr) { music->filename = filename; }
std::free(buffer);
return music;
}
inline void playMusic(Music* music, int loop = -1) {
if (!music_enabled || (music == nullptr) || (music->vorbis == nullptr)) { return; }
stopMusic();
current_music = music;
current_music->state = MusicState::PLAYING;
current_music->times = loop;
// Rebobinem l'stream de vorbis al principi. Cobreix tant play-per-primera-
// vegada com replays/canvis de track que tornen a la mateixa pista.
stb_vorbis_seek_start(current_music->vorbis);
current_music->stream = SDL_CreateAudioStream(&current_music->spec, &audio_spec);
if (current_music->stream == nullptr) {
std::cout << "Failed to create audio stream!" << '\n';
current_music->state = MusicState::STOPPED;
return;
}
SDL_SetAudioStreamGain(current_music->stream, music_volume);
// Pre-cargem el buffer abans de bindejar per evitar un underrun inicial.
pumpMusic(current_music);
if (!SDL_BindAudioStream(sdl_audio_device, current_music->stream)) {
std::cout << "[ERROR] SDL_BindAudioStream failed!" << '\n';
}
}
inline auto getMusicFilename(const Music* music = nullptr) -> const char* {
if (music == nullptr) { music = current_music; }
if ((music == nullptr) || music->filename.empty()) { return nullptr; }
return music->filename.c_str();
}
inline void pauseMusic() {
if (!music_enabled) { return; }
if ((current_music == nullptr) || current_music->state != MusicState::PLAYING) { return; }
current_music->state = MusicState::PAUSED;
SDL_UnbindAudioStream(current_music->stream);
}
inline void resumeMusic() {
if (!music_enabled) { return; }
if ((current_music == nullptr) || current_music->state != MusicState::PAUSED) { return; }
current_music->state = MusicState::PLAYING;
SDL_BindAudioStream(sdl_audio_device, current_music->stream);
}
inline void stopMusic() {
// Limpiar outgoing crossfade si existe
if (outgoing_music.stream != nullptr) {
SDL_DestroyAudioStream(outgoing_music.stream);
outgoing_music.stream = nullptr;
outgoing_music.fade.active = false;
}
incoming_fade.active = false;
if ((current_music == nullptr) || current_music->state == MusicState::INVALID || current_music->state == MusicState::STOPPED) { return; }
current_music->state = MusicState::STOPPED;
if (current_music->stream != nullptr) {
SDL_DestroyAudioStream(current_music->stream);
current_music->stream = nullptr;
}
// Deixem el handle de vorbis viu — es tanca en deleteMusic.
// Rebobinem perquè un futur playMusic comence des del principi.
if (current_music->vorbis != nullptr) {
stb_vorbis_seek_start(current_music->vorbis);
}
}
inline void fadeOutMusic(int milliseconds) {
if (!music_enabled) { return; }
if ((current_music == nullptr) || current_music->state != MusicState::PLAYING) { return; }
// Destruir outgoing anterior si existe
if (outgoing_music.stream != nullptr) {
SDL_DestroyAudioStream(outgoing_music.stream);
outgoing_music.stream = nullptr;
}
// Pre-omplim l'stream amb `milliseconds` de so: un cop robat, ja no
// tindrà accés al vorbis decoder i només podrà drenar el que tinga.
preFillOutgoing(current_music, milliseconds);
// Robar el stream del current_music al outgoing
outgoing_music.stream = current_music->stream;
outgoing_music.fade = {.active = true, .start_time = SDL_GetTicks(), .duration_ms = milliseconds, .initial_volume = music_volume};
// Dejar current_music sin stream (ya lo tiene outgoing)
current_music->stream = nullptr;
current_music->state = MusicState::STOPPED;
if (current_music->vorbis != nullptr) { stb_vorbis_seek_start(current_music->vorbis); }
incoming_fade.active = false;
}
inline void crossfadeMusic(Music* music, int crossfade_ms, int loop) {
if (!music_enabled || (music == nullptr) || (music->vorbis == nullptr)) { return; }
// Destruir outgoing anterior si existe (crossfade durante crossfade)
if (outgoing_music.stream != nullptr) {
SDL_DestroyAudioStream(outgoing_music.stream);
outgoing_music.stream = nullptr;
outgoing_music.fade.active = false;
}
// Robar el stream de la musica actual al outgoing para el fade-out.
// Pre-omplim amb `crossfade_ms` de so perquè no es quede en silenci
// abans d'acabar el fade (l'stream robat ja no pot alimentar-se).
if ((current_music != nullptr) && current_music->state == MusicState::PLAYING && (current_music->stream != nullptr)) {
preFillOutgoing(current_music, crossfade_ms);
outgoing_music.stream = current_music->stream;
outgoing_music.fade = {.active = true, .start_time = SDL_GetTicks(), .duration_ms = crossfade_ms, .initial_volume = music_volume};
current_music->stream = nullptr;
current_music->state = MusicState::STOPPED;
if (current_music->vorbis != nullptr) { stb_vorbis_seek_start(current_music->vorbis); }
}
// Iniciar la nueva pista con gain=0 (el fade-in la sube gradualmente)
current_music = music;
current_music->state = MusicState::PLAYING;
current_music->times = loop;
stb_vorbis_seek_start(current_music->vorbis);
current_music->stream = SDL_CreateAudioStream(&current_music->spec, &audio_spec);
if (current_music->stream == nullptr) {
std::cout << "Failed to create audio stream for crossfade!" << '\n';
current_music->state = MusicState::STOPPED;
return;
}
SDL_SetAudioStreamGain(current_music->stream, 0.0F);
pumpMusic(current_music); // pre-carrega abans de bindejar
SDL_BindAudioStream(sdl_audio_device, current_music->stream);
// Configurar fade-in
incoming_fade = {.active = true, .start_time = SDL_GetTicks(), .duration_ms = crossfade_ms, .initial_volume = 0.0F};
}
inline auto getMusicState() -> MusicState {
if (!music_enabled) { return MusicState::DISABLED; }
if (current_music == nullptr) { return MusicState::INVALID; }
return current_music->state;
}
inline void deleteMusic(Music* music) {
if (music == nullptr) { return; }
if (current_music == music) {
stopMusic();
current_music = nullptr;
}
if (music->stream != nullptr) { SDL_DestroyAudioStream(music->stream); }
if (music->vorbis != nullptr) { stb_vorbis_close(music->vorbis); }
// ogg_data (std::vector) i filename (std::string) s'alliberen sols
// al destructor de Music.
delete music;
}
inline auto setMusicVolume(float volume) -> float {
music_volume = SDL_clamp(volume, 0.0F, 1.0F);
if ((current_music != nullptr) && (current_music->stream != nullptr)) {
SDL_SetAudioStreamGain(current_music->stream, music_volume);
}
return music_volume;
}
inline void setMusicPosition(float /*value*/) {
// No implementat amb el backend de streaming.
}
inline auto getMusicPosition() -> float {
return 0.0F;
}
inline void enableMusic(bool value) {
if (!value && (current_music != nullptr) && (current_music->state == MusicState::PLAYING)) { stopMusic(); }
music_enabled = value;
}
// --- Sound Functions ---
inline auto loadSound(std::uint8_t* buffer, std::uint32_t size) -> Sound* {
auto sound = std::make_unique<Sound>();
Uint8* raw = nullptr;
if (!SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), true, &sound->spec, &raw, &sound->length)) {
std::cout << "Failed to load WAV from memory: " << SDL_GetError() << '\n';
return nullptr;
}
sound->buffer.reset(raw); // adopta el SDL_malloc'd buffer
return sound.release();
}
inline auto loadSound(const char* filename) -> Sound* {
auto sound = std::make_unique<Sound>();
Uint8* raw = nullptr;
if (!SDL_LoadWAV(filename, &sound->spec, &raw, &sound->length)) {
std::cout << "Failed to load WAV file: " << SDL_GetError() << '\n';
return nullptr;
}
sound->buffer.reset(raw); // adopta el SDL_malloc'd buffer
return sound.release();
}
inline auto playSound(Sound* sound, int loop = 0, int group = 0) -> int {
if (!sound_enabled || (sound == nullptr)) { return -1; }
int channel = 0;
while (channel < MAX_SIMULTANEOUS_CHANNELS && channels[channel].state != ChannelState::FREE) { channel++; }
if (channel == MAX_SIMULTANEOUS_CHANNELS) {
// No hi ha canal lliure, reemplacem el primer
channel = 0;
}
return playSoundOnChannel(sound, channel, loop, group);
}
inline auto playSoundOnChannel(Sound* sound, int channel, int loop, int group) -> int {
if (!sound_enabled || (sound == nullptr)) { return -1; }
if (channel < 0 || channel >= MAX_SIMULTANEOUS_CHANNELS) { return -1; }
stopChannel(channel);
channels[channel].sound = sound;
channels[channel].times = loop;
channels[channel].pos = 0;
channels[channel].group = group;
channels[channel].state = ChannelState::PLAYING;
channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &audio_spec);
if (channels[channel].stream == nullptr) {
std::cout << "Failed to create audio stream for sound!" << '\n';
channels[channel].state = ChannelState::FREE;
return -1;
}
SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer.get(), channels[channel].sound->length);
SDL_SetAudioStreamGain(channels[channel].stream, sound_volume[group]);
SDL_BindAudioStream(sdl_audio_device, channels[channel].stream);
return channel;
}
inline void deleteSound(Sound* sound) {
if (sound == nullptr) { return; }
for (int i = 0; i < MAX_SIMULTANEOUS_CHANNELS; i++) {
if (channels[i].sound == sound) { stopChannel(i); }
}
// buffer es destrueix automàticament via RAII (SdlFreeDeleter).
delete sound;
}
inline void pauseChannel(int channel) {
if (!sound_enabled) { return; }
if (channel == -1) {
for (auto& ch : channels) {
if (ch.state == ChannelState::PLAYING) {
ch.state = ChannelState::PAUSED;
SDL_UnbindAudioStream(ch.stream);
}
}
} else if (channel >= 0 && channel < MAX_SIMULTANEOUS_CHANNELS) {
if (channels[channel].state == ChannelState::PLAYING) {
channels[channel].state = ChannelState::PAUSED;
SDL_UnbindAudioStream(channels[channel].stream);
}
}
}
inline void resumeChannel(int channel) {
if (!sound_enabled) { return; }
if (channel == -1) {
for (auto& ch : channels) {
if (ch.state == ChannelState::PAUSED) {
ch.state = ChannelState::PLAYING;
SDL_BindAudioStream(sdl_audio_device, ch.stream);
}
}
} else if (channel >= 0 && channel < MAX_SIMULTANEOUS_CHANNELS) {
if (channels[channel].state == ChannelState::PAUSED) {
channels[channel].state = ChannelState::PLAYING;
SDL_BindAudioStream(sdl_audio_device, channels[channel].stream);
}
}
}
inline void stopChannel(int channel) {
if (channel == -1) {
for (auto& ch : channels) {
if (ch.state != ChannelState::FREE) {
if (ch.stream != nullptr) { SDL_DestroyAudioStream(ch.stream); }
ch.stream = nullptr;
ch.state = ChannelState::FREE;
ch.pos = 0;
ch.sound = nullptr;
}
}
} else if (channel >= 0 && channel < MAX_SIMULTANEOUS_CHANNELS) {
if (channels[channel].state != ChannelState::FREE) {
if (channels[channel].stream != nullptr) { SDL_DestroyAudioStream(channels[channel].stream); }
channels[channel].stream = nullptr;
channels[channel].state = ChannelState::FREE;
channels[channel].pos = 0;
channels[channel].sound = nullptr;
}
}
}
inline auto getChannelState(int channel) -> ChannelState {
if (!sound_enabled) { return ChannelState::DISABLED; }
if (channel < 0 || channel >= MAX_SIMULTANEOUS_CHANNELS) { return ChannelState::INVALID; }
return channels[channel].state;
}
inline auto setSoundVolume(float volume, int group = -1) -> float {
const float V = SDL_clamp(volume, 0.0F, 1.0F);
if (group == -1) {
std::ranges::fill(sound_volume, V);
} else if (group >= 0 && group < MAX_GROUPS) {
sound_volume[group] = V;
} else {
return V;
}
// Aplicar volum als canals actius.
for (auto& ch : channels) {
if ((ch.state == ChannelState::PLAYING) || (ch.state == ChannelState::PAUSED)) {
if (group == -1 || ch.group == group) {
if (ch.stream != nullptr) {
SDL_SetAudioStreamGain(ch.stream, sound_volume[ch.group]);
}
}
}
}
return V;
}
inline void enableSound(bool value) {
if (!value) {
stopChannel(-1);
}
sound_enabled = value;
}
inline auto setVolume(float volume) -> float {
const float V = setMusicVolume(volume);
setSoundVolume(V, -1);
return V;
}
} // namespace Ja
@@ -1,25 +1,24 @@
#include "define_buttons.hpp"
#include "core/input/define_buttons.hpp"
#include <algorithm> // Para __all_of_fn, all_of
#include <algorithm> // Para __all_of_fn, all_of, ranges::transform
#include <iterator> // Para back_inserter
#include <memory> // Para unique_ptr, allocator, shared_ptr, operator==, make_unique
#include "input.hpp" // Para Input
#include "input_types.hpp" // Para InputAction
#include "lang.hpp" // Para getText
#include "options.hpp" // Para Gamepad
#include "param.hpp" // Para Param, param, ParamGame, ParamServiceMenu
#include "resource.hpp" // Para Resource
#include "ui/window_message.hpp" // Para WindowMessage
#include "utils.hpp" // Para Zone
#include "core/input/input.hpp" // Para Input
#include "core/locale/lang.hpp" // Para getText
#include "core/resources/resource.hpp" // Para Resource
#include "game/options.hpp" // Para Gamepad
#include "game/ui/window_message.hpp" // Para WindowMessage
#include "utils/param.hpp" // Para Param, param, ParamGame, ParamServiceMenu
#include "utils/utils.hpp" // Para Zone
DefineButtons::DefineButtons()
: input_(Input::get()) {
clearButtons();
auto gamepads = input_->getGamepads();
for (const auto& gamepad : gamepads) {
controller_names_.emplace_back(Input::getControllerName(gamepad));
}
const auto GAMEPADS = input_->getGamepads();
controller_names_.reserve(GAMEPADS.size());
std::ranges::transform(GAMEPADS, std::back_inserter(controller_names_), Input::getControllerName);
// Crear la ventana de mensaje
WindowMessage::Config config(param.service_menu.window_message);
@@ -8,8 +8,8 @@
#include <utility>
#include <vector>
#include "input.hpp"
#include "ui/window_message.hpp"
#include "core/input/input.hpp"
#include "game/ui/window_message.hpp"
namespace Options {
struct Gamepad;
@@ -5,8 +5,8 @@
#include <utility>
#include <vector>
#include "core/input/input_types.hpp" // Solo incluimos los tipos compartidos
#include "external/json.hpp"
#include "input_types.hpp" // Solo incluimos los tipos compartidos
// --- Estructuras ---
struct GamepadConfig {
@@ -1,26 +1,28 @@
#include "global_inputs.hpp"
#include "core/input/global_inputs.hpp"
#include <algorithm> // Para __any_of_fn, any_of
#include <functional> // Para function
#include <iterator> // Para pair
#include <string> // Para basic_string, operator+, allocator, char_traits, string, to_string
#include <utility> // Para pair
#include <vector> // Para vector
#include "audio.hpp" // Para Audio
#include "input.hpp" // Para Input
#include "input_types.hpp" // Para InputAction
#include "lang.hpp" // Para getText, getLangFile, getLangName, getNextLangCode, loadFromFile
#include "options.hpp" // Para Video, video, Settings, settings, Audio, audio, Window, window
#include "screen.hpp" // Para Screen
#include "section.hpp" // Para Name, name, Options, options, AttractMode, attract_mode
#include "ui/notifier.hpp" // Para Notifier
#include "ui/service_menu.hpp" // Para ServiceMenu
#include "utils.hpp" // Para boolToOnOff
#include "core/audio/audio.hpp" // Para Audio
#include "core/input/input.hpp" // Para Input
#include "core/locale/lang.hpp" // Para getText, getLangFile, getLangName, getNextLangCode, loadFromFile
#include "core/rendering/screen.hpp" // Para Screen
#include "core/system/section.hpp" // Para Name, name, Options, options, AttractMode, attract_mode
#include "game/options.hpp" // Para Video, video, Settings, settings, Audio, audio, Window, window
#include "game/ui/notifier.hpp" // Para Notifier
#include "game/ui/service_menu.hpp" // Para ServiceMenu
#include "utils/utils.hpp" // Para boolToOnOff
namespace GlobalInputs {
// Termina
void quit() {
#ifdef __EMSCRIPTEN__
// En la versión web no se permite salir: el navegador gestiona el cierre.
return;
#else
const std::string CODE = "QUIT";
if (Notifier::get()->checkCode(CODE)) {
// Si la notificación de salir está activa, cambia de sección
@@ -30,6 +32,7 @@ namespace GlobalInputs {
// Si la notificación de salir no está activa, muestra la notificación
Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 01"), std::string()}, -1, CODE);
}
#endif
}
// Reinicia
@@ -79,21 +82,15 @@ namespace GlobalInputs {
void nextPreset() {
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
Screen::nextCrtPiPreset();
const std::string name = Options::crtpi_presets.empty() ? "" : Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset)).name;
Notifier::get()->show({"CrtPi: " + name});
const std::string NAME = Options::crtpi_presets.empty() ? "" : Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset)).name;
Notifier::get()->show({"CrtPi: " + NAME});
} else {
Screen::nextPostFXPreset();
const std::string name = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset)).name;
Notifier::get()->show({"PostFX: " + name});
const std::string NAME = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset)).name;
Notifier::get()->show({"PostFX: " + NAME});
}
}
// Activa o desactiva el supersampling
void toggleSupersampling() {
Screen::toggleSupersampling();
Notifier::get()->show({"SS: " + std::string(Options::video.supersampling.enabled ? "ON" : "OFF")});
}
// Cambia al siguiente idioma
void setNextLang() {
const std::string CODE = "LANG";
@@ -224,10 +221,6 @@ namespace GlobalInputs {
nextPreset();
return true;
}
if (Input::get()->checkAction(Input::Action::TOGGLE_SUPERSAMPLING, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
toggleSupersampling();
return true;
}
return false;
}
@@ -1,4 +1,4 @@
#include "input.hpp"
#include "core/input/input.hpp"
#include <SDL3/SDL.h> // Para SDL_GetGamepadAxis, SDL_GamepadAxis, SDL_GamepadButton, SDL_GetError, SDL_JoystickID, SDL_AddGamepadMappingsFromFile, SDL_Event, SDL_EventType, SDL_GetGamepadButton, SDL_GetKeyboardState, SDL_INIT_GAMEPAD, SDL_InitSubSystem, SDL_LogError, SDL_OpenGamepad, SDL_PollEvent, SDL_WasInit, Sint16, SDL_Gamepad, SDL_LogCategory, SDL_Scancode
@@ -8,7 +8,40 @@
#include <unordered_map> // Para unordered_map, _Node_iterator, operator==, _Node_iterator_base, _Node_const_iterator
#include <utility> // Para pair, move
#include "ui/logger.hpp" // Para info
// Emscripten-only: SDL 3.4+ ja no casa el GUID dels mandos de Chrome Android
// amb gamecontrollerdb (el gamepad.id d'Android no porta Vendor/Product, el
// parser extreu valors escombraries, el GUID resultant no està a la db i el
// gamepad queda obert amb un mapping incorrecte). Com el W3C Gamepad API
// garanteix el layout estàndard quan el navegador reporta mapping=="standard",
// injectem un mapping SDL amb eixe layout per al GUID del joystick abans
// d'obrir-lo com gamepad. Fora d'Emscripten és un no-op.
static void installWebStandardMapping(SDL_JoystickID jid) {
#ifdef __EMSCRIPTEN__
SDL_GUID guid = SDL_GetJoystickGUIDForID(jid);
char guidStr[33];
SDL_GUIDToString(guid, guidStr, sizeof(guidStr));
const char* name = SDL_GetJoystickNameForID(jid);
if (!name || !*name) name = "Standard Gamepad";
char mapping[512];
SDL_snprintf(mapping, sizeof(mapping),
"%s,%s,"
"a:b0,b:b1,x:b2,y:b3,"
"leftshoulder:b4,rightshoulder:b5,"
"lefttrigger:b6,righttrigger:b7,"
"back:b8,start:b9,"
"leftstick:b10,rightstick:b11,"
"dpup:b12,dpdown:b13,dpleft:b14,dpright:b15,"
"guide:b16,"
"leftx:a0,lefty:a1,rightx:a2,righty:a3,"
"platform:Emscripten",
guidStr,
name);
SDL_AddGamepadMapping(mapping);
#else
(void)jid;
#endif
}
// Singleton
Input* Input::instance = nullptr;
@@ -85,53 +118,22 @@ auto Input::checkAction(Action action, bool repeat, bool check_keyboard, const s
// Comprueba si hay almenos una acción activa
auto Input::checkAnyInput(bool check_keyboard, const std::shared_ptr<Gamepad>& gamepad) -> bool {
// Obtenemos el número total de acciones posibles para iterar sobre ellas.
const auto JUST_PRESSED = [](const auto& pair) { return pair.second.just_pressed; };
// --- Comprobación del Teclado ---
if (check_keyboard) {
for (const auto& pair : keyboard_.bindings) {
// Simplemente leemos el estado pre-calculado por Input::update().
// Ya no se llama a SDL_GetKeyboardState ni se modifica el estado '.active'.
if (pair.second.just_pressed) {
return true; // Se encontró una acción recién pulsada.
}
}
if (check_keyboard && std::ranges::any_of(keyboard_.bindings, JUST_PRESSED)) {
return true;
}
// --- Comprobación del Mando ---
// Comprobamos si hay mandos y si el índice solicitado es válido.
if (gamepad != nullptr) {
// Iteramos sobre todas las acciones, no sobre el número de mandos.
for (const auto& pair : gamepad->bindings) {
// Leemos el estado pre-calculado para el mando y la acción específicos.
if (pair.second.just_pressed) {
return true; // Se encontró una acción recién pulsada en el mando.
}
}
}
// Si llegamos hasta aquí, no se detectó ninguna nueva pulsación.
return false;
return gamepad != nullptr && std::ranges::any_of(gamepad->bindings, JUST_PRESSED);
}
// Comprueba si hay algún botón pulsado
auto Input::checkAnyButton(bool repeat) -> bool {
// Solo comprueba los botones definidos previamente
for (auto bi : BUTTON_INPUTS) {
// Comprueba el teclado
if (checkAction(bi, repeat, CHECK_KEYBOARD)) {
return true;
}
// Comprueba los mandos
for (const auto& gamepad : gamepads_) {
if (checkAction(bi, repeat, DO_NOT_CHECK_KEYBOARD, gamepad)) {
return true;
}
}
}
return false;
return std::ranges::any_of(BUTTON_INPUTS, [this, repeat](auto bi) {
if (checkAction(bi, repeat, CHECK_KEYBOARD)) { return true; }
return std::ranges::any_of(gamepads_, [this, bi, repeat](const auto& gamepad) {
return checkAction(bi, repeat, DO_NOT_CHECK_KEYBOARD, gamepad);
});
});
}
// Comprueba si hay algun mando conectado
@@ -145,9 +147,8 @@ auto Input::getControllerName(const std::shared_ptr<Gamepad>& gamepad) -> std::s
// Obtiene la lista de nombres de mandos
auto Input::getControllerNames() const -> std::vector<std::string> {
std::vector<std::string> names;
for (const auto& gamepad : gamepads_) {
names.push_back(gamepad->name);
}
names.reserve(gamepads_.size());
std::ranges::transform(gamepads_, std::back_inserter(names), [](const auto& gamepad) { return gamepad->name; });
return names;
}
@@ -156,21 +157,15 @@ auto Input::getNumGamepads() const -> int { return gamepads_.size(); }
// Obtiene el gamepad a partir de un event.id
auto Input::getGamepad(SDL_JoystickID id) const -> std::shared_ptr<Input::Gamepad> {
for (const auto& gamepad : gamepads_) {
if (gamepad->instance_id == id) {
return gamepad;
}
}
return nullptr;
const auto IT = std::ranges::find_if(gamepads_,
[id](const auto& gamepad) { return gamepad->instance_id == id; });
return IT != gamepads_.end() ? *IT : nullptr;
}
auto Input::getGamepadByName(const std::string& name) const -> std::shared_ptr<Input::Gamepad> {
for (const auto& gamepad : gamepads_) {
if (gamepad && gamepad->name == name) {
return gamepad;
}
}
return nullptr;
const auto IT = std::ranges::find_if(gamepads_,
[&name](const auto& gamepad) { return gamepad && gamepad->name == name; });
return IT != gamepads_.end() ? *IT : nullptr;
}
// Obtiene el SDL_GamepadButton asignado a un action
@@ -305,29 +300,16 @@ void Input::addGamepadMappingsFromFile() {
}
}
void Input::discoverGamepads() {
// Enumera los gamepads ya conectados sin drenar la cola de eventos de SDL
// (necesario con SDL_MAIN_USE_CALLBACKS, que entrega los eventos por SDL_AppEvent).
int count = 0;
SDL_JoystickID* joysticks = SDL_GetGamepads(&count);
if (joysticks == nullptr) {
return;
}
for (int i = 0; i < count; ++i) {
addGamepad(joysticks[i]);
}
SDL_free(joysticks);
}
void Input::initSDLGamePad() {
if (SDL_WasInit(SDL_INIT_GAMEPAD) != 1) {
if (!SDL_InitSubSystem(SDL_INIT_GAMEPAD)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_GAMEPAD could not initialize! SDL Error: %s", SDL_GetError());
std::cout << "SDL_GAMEPAD could not initialize! SDL Error: " << SDL_GetError() << '\n';
} else {
addGamepadMappingsFromFile();
loadGamepadConfigs();
discoverGamepads();
Logger::info("Input System initialized successfully");
// Los mandos ya conectados llegan como SDL_EVENT_GAMEPAD_ADDED en el
// primer pase del pump de eventos (antes del primer SDL_AppIterate),
// por lo que no hace falta enumerarlos aquí a mano.
}
}
}
@@ -339,7 +321,7 @@ void Input::resetInputStates() {
key.second.just_pressed = false;
}
// Resetear todos los ControllerBindings.active a false
for (auto& gamepad : gamepads_) {
for (const auto& gamepad : gamepads_) {
for (auto& binding : gamepad->bindings) {
binding.second.is_held = false;
binding.second.just_pressed = false;
@@ -378,11 +360,13 @@ auto Input::handleEvent(const SDL_Event& event) -> std::string {
return addGamepad(event.gdevice.which);
case SDL_EVENT_GAMEPAD_REMOVED:
return removeGamepad(event.gdevice.which);
default:
return {};
}
return {};
}
auto Input::addGamepad(int device_index) -> std::string {
installWebStandardMapping(device_index);
SDL_Gamepad* pad = SDL_OpenGamepad(device_index);
if (pad == nullptr) {
std::cerr << "Error al abrir el gamepad: " << SDL_GetError() << '\n';
@@ -447,6 +431,27 @@ void Input::applyGamepadConfig(std::shared_ptr<Gamepad> gamepad) {
return config.path == gamepad->path;
});
// Fallback por nombre: si el mismo dispositivo se enchufa a otro puerto, su
// path cambia pero el nombre suele mantenerse. Recuperamos su configuración
// y actualizamos el path guardado al actual. Solo se acepta un match cuyo
// path guardado NO corresponda a otro mando ya conectado, para no arruinar
// setups con varios mandos idénticos en puertos distintos (cabinet arcade).
if (config_it == gamepad_configs_.end() && !gamepad->name.empty()) {
auto is_path_active = [this](const std::string& query_path) -> bool {
return std::ranges::any_of(gamepads_, [&query_path](const std::shared_ptr<Gamepad>& g) -> bool {
return g && g->path == query_path;
});
};
config_it = std::ranges::find_if(gamepad_configs_, [&gamepad, &is_path_active](const GamepadConfig& config) -> bool {
return config.name == gamepad->name && !is_path_active(config.path);
});
if (config_it != gamepad_configs_.end()) {
std::cout << "Gamepad '" << gamepad->name << "' found by name, refreshing path to: " << gamepad->path << '\n';
config_it->path = gamepad->path;
saveGamepadConfigs();
}
}
if (config_it != gamepad_configs_.end()) {
// Se encontró una configuración específica para este puerto/dispositivo. La aplicamos.
std::cout << "Applying custom config for gamepad at path: " << gamepad->path << '\n';
@@ -456,7 +461,6 @@ void Input::applyGamepadConfig(std::shared_ptr<Gamepad> gamepad) {
}
}
}
// Opcional: Podrías añadir un fallback para buscar por nombre si no se encuentra por ruta.
}
void Input::saveGamepadConfigFromGamepad(std::shared_ptr<Gamepad> gamepad) {
@@ -527,19 +531,11 @@ auto Input::findAvailableGamepadByName(const std::string& gamepad_name) -> std::
}
// Buscar por nombre
for (const auto& gamepad : gamepads_) {
if (gamepad && gamepad->name == gamepad_name) {
return gamepad;
}
}
auto by_name = std::ranges::find_if(gamepads_,
[&gamepad_name](const auto& gamepad) { return gamepad && gamepad->name == gamepad_name; });
if (by_name != gamepads_.end()) { return *by_name; }
// Si no se encuentra por nombre, devolver el primer gamepad válido
for (const auto& gamepad : gamepads_) {
if (gamepad) {
return gamepad;
}
}
// Si llegamos aquí, no hay gamepads válidos
return nullptr;
auto first_valid = std::ranges::find_if(gamepads_, [](const auto& gamepad) { return gamepad != nullptr; });
return first_valid != gamepads_.end() ? *first_valid : nullptr;
}
@@ -6,11 +6,10 @@
#include <memory> // Para shared_ptr
#include <string> // Para string, basic_string
#include <unordered_map> // Para unordered_map
#include <utility> // Para pair
#include <vector> // Para vector
#include "gamepad_config_manager.hpp" // for GamepadConfig (ptr only), GamepadConfigs
#include "input_types.hpp" // for InputAction
#include "core/input/gamepad_config_manager.hpp" // for GamepadConfig (ptr only), GamepadConfigs
#include "core/input/input_types.hpp" // for InputAction
// --- Clase Input: gestiona la entrada de teclado y mandos (singleton) ---
class Input {
@@ -32,7 +31,7 @@ class Input {
bool is_held; // Está pulsada ahora mismo
bool just_pressed; // Se acaba de pulsar en este fotograma
KeyState(Uint8 scancode = 0, bool is_held = false, bool just_pressed = false)
explicit KeyState(Uint8 scancode = 0, bool is_held = false, bool just_pressed = false)
: scancode(scancode),
is_held(is_held),
just_pressed(just_pressed) {}
@@ -45,7 +44,7 @@ class Input {
bool axis_active; // Estado del eje
bool trigger_active{false}; // Estado del trigger como botón digital
ButtonState(int btn = static_cast<int>(SDL_GAMEPAD_BUTTON_INVALID), bool is_held = false, bool just_pressed = false, bool axis_act = false)
explicit ButtonState(int btn = static_cast<int>(SDL_GAMEPAD_BUTTON_INVALID), bool is_held = false, bool just_pressed = false, bool axis_act = false)
: button(btn),
is_held(is_held),
just_pressed(just_pressed),
@@ -84,7 +83,6 @@ class Input {
{Action::TOGGLE_VIDEO_POSTFX, KeyState(SDL_SCANCODE_F4)},
{Action::NEXT_SHADER, KeyState(SDL_SCANCODE_8)},
{Action::NEXT_POSTFX_PRESET, KeyState(SDL_SCANCODE_9)},
{Action::TOGGLE_SUPERSAMPLING, KeyState(SDL_SCANCODE_0)},
{Action::TOGGLE_VIDEO_INTEGER_SCALE, KeyState(SDL_SCANCODE_F5)},
{Action::TOGGLE_VIDEO_VSYNC, KeyState(SDL_SCANCODE_F6)},
@@ -105,10 +103,20 @@ class Input {
std::string path;
std::unordered_map<Action, ButtonState> bindings;
Gamepad(SDL_Gamepad* gamepad)
// Recorta el nombre del mando hasta el primer '(' o '[' y elimina espacios finales.
// Evita nombres como "Retroid Controller (vendor: 1001) ..." en las notificaciones.
static auto trimName(const char* raw) -> std::string {
std::string s(raw != nullptr ? raw : "");
const auto POS = s.find_first_of("([");
if (POS != std::string::npos) { s.erase(POS); }
while (!s.empty() && s.back() == ' ') { s.pop_back(); }
return s;
}
explicit Gamepad(SDL_Gamepad* gamepad)
: pad(gamepad),
instance_id(SDL_GetJoystickID(SDL_GetGamepadJoystick(gamepad))),
name(std::string(SDL_GetGamepadName(gamepad))),
name(trimName(SDL_GetGamepadName(gamepad))),
path(std::string(SDL_GetGamepadPath(pad))),
bindings{
// Movimiento del jugador
@@ -138,7 +146,7 @@ class Input {
// Reasigna un botón a una acción
void rebindAction(Action action, SDL_GamepadButton new_button) {
bindings[action] = static_cast<int>(new_button);
bindings[action] = ButtonState{static_cast<int>(new_button)};
}
};
@@ -206,7 +214,6 @@ class Input {
auto addGamepad(int device_index) -> std::string;
auto removeGamepad(SDL_JoystickID id) -> std::string;
void addGamepadMappingsFromFile();
void discoverGamepads();
// --- Métodos para integración con GamepadConfigManager ---
void loadGamepadConfigs();
@@ -1,4 +1,4 @@
#include "input_types.hpp"
#include "core/input/input_types.hpp"
#include <utility> // Para pair
@@ -24,7 +24,6 @@ const std::unordered_map<InputAction, std::string> ACTION_TO_STRING = {
{InputAction::TOGGLE_VIDEO_POSTFX, "TOGGLE_VIDEO_POSTFX"},
{InputAction::NEXT_SHADER, "NEXT_SHADER"},
{InputAction::NEXT_POSTFX_PRESET, "NEXT_POSTFX_PRESET"},
{InputAction::TOGGLE_SUPERSAMPLING, "TOGGLE_SUPERSAMPLING"},
{InputAction::TOGGLE_VIDEO_INTEGER_SCALE, "TOGGLE_VIDEO_INTEGER_SCALE"},
{InputAction::TOGGLE_VIDEO_VSYNC, "TOGGLE_VIDEO_VSYNC"},
{InputAction::RESET, "RESET"},
@@ -57,7 +56,6 @@ const std::unordered_map<std::string, InputAction> STRING_TO_ACTION = {
{"TOGGLE_VIDEO_POSTFX", InputAction::TOGGLE_VIDEO_POSTFX},
{"NEXT_SHADER", InputAction::NEXT_SHADER},
{"NEXT_POSTFX_PRESET", InputAction::NEXT_POSTFX_PRESET},
{"TOGGLE_SUPERSAMPLING", InputAction::TOGGLE_SUPERSAMPLING},
{"TOGGLE_VIDEO_INTEGER_SCALE", InputAction::TOGGLE_VIDEO_INTEGER_SCALE},
{"TOGGLE_VIDEO_VSYNC", InputAction::TOGGLE_VIDEO_VSYNC},
{"RESET", InputAction::RESET},
@@ -2,11 +2,12 @@
#include <SDL3/SDL.h>
#include <cstdint>
#include <string>
#include <unordered_map>
// --- Enums ---
enum class InputAction : int { // Acciones de entrada posibles en el juego
enum class InputAction : std::uint8_t { // Acciones de entrada posibles en el juego
// Inputs de movimiento
UP,
DOWN,
@@ -34,7 +35,6 @@ enum class InputAction : int { // Acciones de entrada posibles en el juego
TOGGLE_VIDEO_POSTFX,
NEXT_SHADER,
NEXT_POSTFX_PRESET,
TOGGLE_SUPERSAMPLING,
TOGGLE_VIDEO_INTEGER_SCALE,
TOGGLE_VIDEO_VSYNC,
RESET,
@@ -1,4 +1,4 @@
#include "mouse.hpp"
#include "core/input/mouse.hpp"
#include <SDL3/SDL.h> // Para SDL_GetTicks, Uint32, SDL_HideCursor, SDL_Show...
@@ -1,9 +1,11 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <functional>
#include <string>
#include <utility>
#include <vector>
// --- Clase PauseManager: maneja el sistema de pausa del juego ---
class PauseManager {
@@ -48,7 +50,7 @@ class PauseManager {
// --- Métodos principales ---
void setFlag(Source source, bool enable) { // Establece/quita una fuente de pausa específica
bool was_paused = isPaused();
const bool WAS_PAUSED = isPaused();
if (enable) {
flags_ |= source;
@@ -56,7 +58,8 @@ class PauseManager {
flags_ &= ~source; // Ahora funciona: Source &= uint8_t
}
if (was_paused != isPaused()) {
// cppcheck-suppress knownConditionTrueFalse // false-positive: flags_ ha estat modificat entre les dues crides a isPaused()
if (WAS_PAUSED != isPaused()) {
notifyPauseChanged();
}
}
@@ -88,30 +91,16 @@ class PauseManager {
return "Active";
}
std::vector<std::string> parts;
if (hasFlag(Source::PLAYER)) { parts.emplace_back("Player"); }
if (hasFlag(Source::SERVICE_MENU)) { parts.emplace_back("ServiceMenu"); }
if (hasFlag(Source::FOCUS_LOST)) { parts.emplace_back("FocusLoss"); }
std::string result = "Paused by: ";
bool first = true;
if (hasFlag(Source::PLAYER)) {
if (!first) {
result += ", ";
}
result += "Player";
first = false;
for (size_t i = 0; i < parts.size(); ++i) {
if (i > 0) { result += ", "; }
result += parts[i];
}
if (hasFlag(Source::SERVICE_MENU)) {
if (!first) {
result += ", ";
}
result += "ServiceMenu";
first = false;
}
if (hasFlag(Source::FOCUS_LOST)) {
if (!first) {
result += ", ";
}
result += "FocusLoss";
}
return result;
}
void setCallback(std::function<void(bool)> callback) { // Permite cambiar el callback en runtime
+23 -37
View File
@@ -1,5 +1,6 @@
#include "lang.hpp"
#include "core/locale/lang.hpp"
#include <algorithm> // Para ranges::find_if
#include <cstddef> // Para size_t
#include <exception> // Para exception
#include <fstream> // Para basic_ifstream, basic_istream, ifstream
@@ -7,13 +8,13 @@
#include <utility> // Para pair
#include <vector> // Para vector
#include "asset.hpp" // Para Asset
#include "difficulty.hpp" // Para Difficulty
#include "external/json.hpp" // Para basic_json, iteration_proxy_value, oper...
#include "options.hpp" // Para SettingsOpt...
#include "resource_helper.hpp" // Para ResourceHelper
#include "core/resources/asset.hpp" // Para Asset
#include "core/resources/resource_helper.hpp" // Para ResourceHelper
#include "external/json.hpp" // Para basic_json, iteration_proxy_value, oper...
#include "game/gameplay/difficulty.hpp" // Para Difficulty
#include "game/options.hpp" // Para SettingsOpt...
using json = nlohmann::json;
using Json = nlohmann::json;
namespace Lang {
std::unordered_map<std::string, std::string> texts;
@@ -32,12 +33,12 @@ namespace Lang {
auto resource_data = ResourceHelper::loadFile(file_path);
try {
json j;
Json j;
if (!resource_data.empty()) {
// Cargar desde datos del pack
std::string content(resource_data.begin(), resource_data.end());
j = json::parse(content);
j = Json::parse(content);
} else {
// Fallback a filesystem directo
std::ifstream rfile(file_path);
@@ -80,35 +81,23 @@ namespace Lang {
// Obtiene un idioma del vector de idiomas a partir de un código
auto getLanguage(Code code) -> Language {
for (const auto& lang : languages) {
if (lang.code == code) {
return lang;
}
}
// Si no se encuentra, devuelve el primero por defecto
return languages[0];
const auto IT = std::ranges::find_if(languages,
[code](const auto& lang) { return lang.code == code; });
return IT != languages.end() ? *IT : languages[0];
}
// Devuelve el código de un idioma a partir de un nombre
auto getCodeFromName(const std::string& name) -> Code {
for (const auto& lang : languages) {
if (lang.name == name) {
return lang.code;
}
}
// Si no se encuentra, devuelve el primero por defecto
return languages[0].code;
const auto IT = std::ranges::find_if(languages,
[&name](const auto& lang) { return lang.name == name; });
return IT != languages.end() ? IT->code : languages[0].code;
}
// Devuelve el nombre de un idioma a partir de un código
auto getNameFromCode(Code code) -> std::string {
for (const auto& lang : languages) {
if (lang.code == code) {
return lang.name;
}
}
// Si no se encuentra, devuelve el nombre del primer idioma por defecto
return languages[0].name;
const auto IT = std::ranges::find_if(languages,
[code](const auto& lang) { return lang.code == code; });
return IT != languages.end() ? IT->name : languages[0].name;
}
// Actualiza los nombres de los idiomas
@@ -155,13 +144,10 @@ namespace Lang {
// Obtiene una fichero a partir de un lang::Code
auto getLanguageFileName(Lang::Code code) -> std::string {
for (const auto& lang : languages) {
if (lang.code == code) {
return Asset::get()->getPath(lang.file_name);
}
}
// Si no se encuentra, devuelve el fichero del primer idioma por defecto
return Asset::get()->getPath(languages[0].file_name);
const auto IT = std::ranges::find_if(languages,
[code](const auto& lang) { return lang.code == code; });
const auto& file = (IT != languages.end()) ? IT->file_name : languages[0].file_name;
return Asset::get()->getPath(file);
}
// Establece el idioma
@@ -1,12 +1,13 @@
#pragma once
#include <cstdint> // Para std::uint8_t
#include <string> // Para string, basic_string
#include <utility> // Para move
// --- Namespace Lang: gestión de idiomas y textos ---
namespace Lang {
// --- Enums ---
enum class Code : int {
enum class Code : std::uint8_t {
SPANISH = 0, // Español
VALENCIAN = 1, // Valenciano
ENGLISH = 2 // Inglés
@@ -1,5 +1,6 @@
// NOLINTNEXTLINE(bugprone-reserved-identifier) -- requerido por <cmath> para exponer M_PI en MSVC
#define _USE_MATH_DEFINES
#include "background.hpp"
#include "core/rendering/background.hpp"
#include <SDL3/SDL.h> // Para SDL_FRect, SDL_SetRenderTarget, SDL_CreateTexture, SDL_DestroyTexture, SDL_GetRenderTarget, SDL_RenderTexture, SDL_SetTextureAlphaMod, SDL_SetTextureBlendMode, SDL_BLENDMODE_BLEND, SDL_PixelFormat, SDL_RenderClear, SDL_SetRenderDrawColor, SDL_TextureAccess, SDL_FPoint
@@ -8,14 +9,14 @@
#include <string> // Para basic_string
#include <utility> // Para move
#include "animated_sprite.hpp" // Para AnimatedSprite
#include "moving_sprite.hpp" // Para MovingSprite
#include "param.hpp" // Para Param, ParamBackground, param
#include "resource.hpp" // Para Resource
#include "screen.hpp" // Para Screen
#include "sprite.hpp" // Para Sprite
#include "texture.hpp" // Para Texture
#include "utils.hpp" // Para easeOutCubic
#include "core/rendering/screen.hpp" // Para Screen
#include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite
#include "core/rendering/sprite/moving_sprite.hpp" // Para MovingSprite
#include "core/rendering/sprite/sprite.hpp" // Para Sprite
#include "core/rendering/texture.hpp" // Para Texture
#include "core/resources/resource.hpp" // Para Resource
#include "utils/param.hpp" // Para Param, ParamBackground, param
#include "utils/utils.hpp" // Para easeOutCubic
// Constructor
Background::Background(float total_progress_to_complete)
@@ -29,10 +30,10 @@ Background::Background(float total_progress_to_complete)
moon_texture_(Resource::get()->getTexture("game_moon.png")),
grass_sprite_(std::make_unique<AnimatedSprite>(Resource::get()->getTexture("game_grass.png"), Resource::get()->getAnimation("game_grass.ani"))),
total_progress_to_complete_(total_progress_to_complete),
progress_per_stage_(total_progress_to_complete_ / STAGES),
sun_completion_progress_(total_progress_to_complete_ * SUN_COMPLETION_FACTOR),
minimum_completed_progress_(total_progress_to_complete_ * MINIMUM_COMPLETED_PROGRESS_PERCENTAGE),
TOTAL_PROGRESS_TO_COMPLETE(total_progress_to_complete),
PROGRESS_PER_STAGE(TOTAL_PROGRESS_TO_COMPLETE / STAGES),
SUM_COMPLETION_PROGRESS(TOTAL_PROGRESS_TO_COMPLETE * SUN_COMPLETION_FACTOR),
MINIMUM_COMPLETED_PROGRESS(TOTAL_PROGRESS_TO_COMPLETE * MINIMUM_COMPLETED_PROGRESS_PERCENTAGE),
rect_(SDL_FRect{.x = 0, .y = 0, .w = static_cast<float>(gradients_texture_->getWidth() / 2), .h = static_cast<float>(gradients_texture_->getHeight() / 2)}),
src_rect_({.x = 0, .y = 0, .w = 320, .h = 240}),
@@ -167,7 +168,7 @@ void Background::incrementProgress(float amount) {
if (state_ == State::NORMAL) {
float old_progress = progress_;
progress_ += amount;
progress_ = std::min(progress_, total_progress_to_complete_);
progress_ = std::min(progress_, TOTAL_PROGRESS_TO_COMPLETE);
// Notifica el cambio si hay callback y el progreso cambió
if (progress_callback_ && progress_ != old_progress) {
@@ -179,7 +180,7 @@ void Background::incrementProgress(float amount) {
// Establece la progresión absoluta
void Background::setProgress(float absolute_progress) {
float old_progress = progress_;
progress_ = std::clamp(absolute_progress, 0.0F, total_progress_to_complete_);
progress_ = std::clamp(absolute_progress, 0.0F, TOTAL_PROGRESS_TO_COMPLETE);
// Notifica el cambio si hay callback y el progreso cambió
if (progress_callback_ && progress_ != old_progress) {
@@ -282,27 +283,27 @@ void Background::updateProgression(float delta_time) {
float eased_t = easeOutCubic(static_cast<double>(t));
// Interpolación desde progreso inicial hasta mínimo
float progress_range = completion_initial_progress_ - minimum_completed_progress_;
float progress_range = completion_initial_progress_ - MINIMUM_COMPLETED_PROGRESS;
progress_ = completion_initial_progress_ - (progress_range * eased_t);
} else {
// Transición completada, fijar al valor mínimo
progress_ = minimum_completed_progress_;
progress_ = MINIMUM_COMPLETED_PROGRESS;
}
}
// Calcula la transición de los diferentes fondos
const float GRADIENT_NUMBER_FLOAT = std::min(progress_ / progress_per_stage_, 3.0F);
const float GRADIENT_NUMBER_FLOAT = std::min(progress_ / PROGRESS_PER_STAGE, 3.0F);
const float PERCENT = GRADIENT_NUMBER_FLOAT - static_cast<int>(GRADIENT_NUMBER_FLOAT);
gradient_number_ = static_cast<size_t>(GRADIENT_NUMBER_FLOAT);
transition_ = PERCENT;
// Calcula la posición del sol
const float SUN_PROGRESSION = std::min(progress_ / sun_completion_progress_, 1.0F);
const float SUN_PROGRESSION = std::min(progress_ / SUM_COMPLETION_PROGRESS, 1.0F);
sun_index_ = static_cast<size_t>(SUN_PROGRESSION * (sun_path_.size() - 1));
// Calcula la posición de la luna
const float MOON_PROGRESSION = std::min(progress_ / total_progress_to_complete_, 1.0F);
const float MOON_PROGRESSION = std::min(progress_ / TOTAL_PROGRESS_TO_COMPLETE, 1.0F);
moon_index_ = static_cast<size_t>(MOON_PROGRESSION * (moon_path_.size() - 1));
// Actualiza la velocidad de las nubes
@@ -318,12 +319,12 @@ void Background::updateCloudsSpeed() {
// Velocidad base según progreso (de -3.0 a -120.0 píxeles/segundo, igual que la versión original)
float base_clouds_speed = (-CLOUDS_INITIAL_SPEED_PX_PER_S) +
(-CLOUDS_FINAL_SPEED_RANGE_PX_PER_S * (progress_ / total_progress_to_complete_));
(-CLOUDS_FINAL_SPEED_RANGE_PX_PER_S * (progress_ / TOTAL_PROGRESS_TO_COMPLETE));
// En estado completado, las nubes se ralentizan gradualmente
if (state_ == State::COMPLETED) {
float completion_factor = (progress_ - minimum_completed_progress_) /
(total_progress_to_complete_ - minimum_completed_progress_);
float completion_factor = (progress_ - MINIMUM_COMPLETED_PROGRESS) /
(TOTAL_PROGRESS_TO_COMPLETE - MINIMUM_COMPLETED_PROGRESS);
completion_factor = std::max(0.1F, completion_factor);
base_clouds_speed *= completion_factor;
}
@@ -562,14 +563,13 @@ void Background::createMoonPath() {
const int FREEZE_START_INDEX = static_cast<int>(NUM_STEPS * (1.0F - FREEZE_PERCENTAGE));
for (int i = 0; i < NUM_STEPS; ++i) {
double theta = i * STEP;
float x = CENTER_X + (RADIUS * cos(theta));
float y = CENTER_Y - (RADIUS * sin(theta));
if (i >= FREEZE_START_INDEX && !moon_path_.empty()) {
moon_path_.push_back(moon_path_.back()); // Repite el último punto válido
} else {
moon_path_.push_back({.x = x, .y = y});
const double THETA = i * STEP;
const float X = CENTER_X + (RADIUS * cos(THETA));
const float Y = CENTER_Y - (RADIUS * sin(THETA));
moon_path_.push_back({.x = X, .y = Y});
}
}
}
@@ -4,11 +4,12 @@
#include <array> // Para array
#include <cstddef> // Para size_t
#include <cstdint> // Para std::uint8_t
#include <functional> // Para function
#include <memory> // Para unique_ptr, shared_ptr
#include <vector> // Para vector
#include "color.hpp" // Para Color
#include "utils/color.hpp" // Para Color
class MovingSprite;
class Sprite;
@@ -19,7 +20,7 @@ class AnimatedSprite;
class Background {
public:
// --- Enums ---
enum class State {
enum class State : std::uint8_t {
NORMAL, // Progresión normal del día
COMPLETED // Reducción gradual de la actividad
};
@@ -28,8 +29,8 @@ class Background {
using ProgressCallback = std::function<void(float)>; // Callback para sincronización
// --- Constructor y destructor ---
Background(float total_progress_to_complete = 6100.0F); // Constructor principal
~Background(); // Destructor
explicit Background(float total_progress_to_complete = 6100.0F); // Constructor principal
~Background(); // Destructor
// --- Métodos principales ---
void update(float delta_time); // Actualiza la lógica del objeto
@@ -87,10 +88,10 @@ class Background {
std::unique_ptr<AnimatedSprite> grass_sprite_; // Sprite con la hierba
// --- Variables de configuración ---
const float total_progress_to_complete_; // Progreso total para completar
const float progress_per_stage_; // Progreso por etapa
const float sun_completion_progress_; // Progreso de completado del sol
const float minimum_completed_progress_; // Progreso mínimo calculado dinámicamente
const float TOTAL_PROGRESS_TO_COMPLETE; // Progreso total para completar
const float PROGRESS_PER_STAGE; // Progreso por etapa
const float SUM_COMPLETION_PROGRESS; // Progreso de completado del sol
const float MINIMUM_COMPLETED_PROGRESS; // Progreso mínimo calculado dinámicamente
ProgressCallback progress_callback_; // Callback para notificar cambios de progreso
// --- Variables de estado ---
@@ -1,4 +1,4 @@
#include "fade.hpp"
#include "core/rendering/fade.hpp"
#include <SDL3/SDL.h>
@@ -6,9 +6,9 @@
#include <cstdlib>
#include <utility>
#include "color.hpp"
#include "param.hpp"
#include "screen.hpp"
#include "core/rendering/screen.hpp"
#include "utils/color.hpp"
#include "utils/param.hpp"
// Constructor
Fade::Fade()
@@ -481,7 +481,7 @@ void Fade::activate() {
case Type::DIAGONAL: {
rect1_ = {.x = 0, .y = 0, .w = static_cast<float>(param.game.width / num_squares_width_), .h = static_cast<float>(param.game.height / num_squares_height_)};
square_.clear();
square_age_.assign(num_squares_width_ * num_squares_height_, -1);
square_age_.assign(static_cast<size_t>(num_squares_width_) * num_squares_height_, -1);
for (int i = 0; i < num_squares_width_ * num_squares_height_; ++i) {
rect1_.x = (i % num_squares_width_) * rect1_.w;
rect1_.y = (i / num_squares_width_) * rect1_.h;
+265
View File
@@ -0,0 +1,265 @@
#include "core/rendering/gif.hpp"
#include <SDL3/SDL.h> // Para SDL_LogError, SDL_LogCategory, SDL_LogInfo
#include <cstring> // Para memcpy, size_t
#include <iostream> // Para std::cout
#include <stdexcept> // Para runtime_error
#include <string> // Para char_traits, operator==, basic_string, string
namespace GIF {
namespace {
inline void readBytes(const uint8_t *&buffer, void *dst, size_t size) {
std::memcpy(dst, buffer, size);
buffer += size;
}
// Llavor del diccionari LZW: 0..N-1 com a entrades base, i salta 2 (clear_code + stop_code).
void resetDictionary(std::vector<DictionaryEntry> &dict, int code_length, int &dictionary_ind) {
dict.resize(1 << (code_length + 1));
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++) {
dict[dictionary_ind].byte = static_cast<uint8_t>(dictionary_ind);
dict[dictionary_ind].prev = -1;
dict[dictionary_ind].len = 1;
}
dictionary_ind += 2;
}
// Llig `code_length + 1` bits LSB-first del flux d'entrada. Llança si s'acaba el buffer.
auto readNextCode(const uint8_t *&input, int &input_length, int code_length, unsigned int &mask) -> int {
int code = 0;
for (int i = 0; i < code_length + 1; i++) {
if (input_length <= 0) {
std::cout << "Unexpected end of input in decompress" << '\n';
throw std::runtime_error("Unexpected end of input in decompress");
}
const int BIT = ((*input & mask) != 0) ? 1 : 0;
mask <<= 1;
if (mask == 0x100) {
mask = 0x01;
input++;
input_length--;
}
code |= (BIT << i);
}
return code;
}
// Afig una nova entrada al diccionari. Resol el cas especial KwKwK (code == dictionary_ind)
// començant la cadena des de `prev` en lloc de des de `code`.
void addDictionaryEntry(std::vector<DictionaryEntry> &dict, int dictionary_ind, int code, int prev) {
int ptr = (code == dictionary_ind) ? prev : code;
while (dict[ptr].prev != -1) {
ptr = dict[ptr].prev;
}
dict[dictionary_ind].byte = dict[ptr].byte;
dict[dictionary_ind].prev = prev;
dict[dictionary_ind].len = dict[prev].len + 1;
}
// Escriu la cadena de bytes associada a `code` en `out` (en ordre invers seguint .prev).
// Retorna la longitud del match per avançar el cursor de l'eixida.
auto emitMatch(const std::vector<DictionaryEntry> &dict, int code, uint8_t *out) -> int {
const int MATCH_LEN = dict[code].len;
int cur_code = code;
while (cur_code != -1) {
out[dict[cur_code].len - 1] = dict[cur_code].byte;
if (dict[cur_code].prev == cur_code) {
std::cout << "Internal error; self-reference detected." << '\n';
throw std::runtime_error("Internal error in decompress: self-reference");
}
cur_code = dict[cur_code].prev;
}
return MATCH_LEN;
}
// Descompone (uncompress) el bloque comprimido usando LZW.
void decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out) {
if (code_length < 2 || code_length > 12) {
std::cout << "Invalid LZW code length: " << code_length << '\n';
throw std::runtime_error("Invalid LZW code length");
}
int prev = -1;
std::vector<DictionaryEntry> dictionary;
int dictionary_ind = 0;
unsigned int mask = 0x01;
const int RESET_CODE_LENGTH = code_length;
const int CLEAR_CODE = 1 << code_length;
const int STOP_CODE = CLEAR_CODE + 1;
resetDictionary(dictionary, code_length, dictionary_ind);
while (input_length > 0) {
const int CODE = readNextCode(input, input_length, code_length, mask);
if (CODE == CLEAR_CODE) {
code_length = RESET_CODE_LENGTH;
resetDictionary(dictionary, code_length, dictionary_ind);
prev = -1;
continue;
}
if (CODE == STOP_CODE) { break; }
if (prev > -1 && code_length < 12) {
if (CODE > dictionary_ind) {
std::cout << "LZW error: code (" << CODE << ") exceeds dictionary_ind (" << dictionary_ind << ")" << '\n';
throw std::runtime_error("LZW error: code exceeds dictionary_ind.");
}
addDictionaryEntry(dictionary, dictionary_ind, CODE, prev);
dictionary_ind++;
if ((dictionary_ind == (1 << (code_length + 1))) && (code_length < 11)) {
code_length++;
dictionary.resize(1 << (code_length + 1));
}
}
prev = CODE;
if (CODE < 0 || static_cast<size_t>(CODE) >= dictionary.size()) {
std::cout << "Invalid LZW code " << CODE << ", dictionary size " << static_cast<unsigned long>(dictionary.size()) << '\n';
throw std::runtime_error("LZW error: invalid code encountered");
}
out += emitMatch(dictionary, CODE, out);
}
}
// Lee los sub-bloques de datos y los acumula en un std::vector<uint8_t>.
auto readSubBlocks(const uint8_t *&buffer) -> std::vector<uint8_t> {
std::vector<uint8_t> data;
uint8_t block_size = *buffer;
buffer++;
while (block_size != 0) {
data.insert(data.end(), buffer, buffer + block_size);
buffer += block_size;
block_size = *buffer;
buffer++;
}
return data;
}
// Procesa el Image Descriptor y retorna el vector de datos sin comprimir.
auto processImageDescriptor(const uint8_t *&buffer, const std::vector<RGB> &gct, int resolution_bits) -> std::vector<uint8_t> {
ImageDescriptor image_descriptor;
readBytes(buffer, &image_descriptor, sizeof(ImageDescriptor));
uint8_t lzw_code_size;
readBytes(buffer, &lzw_code_size, sizeof(uint8_t));
std::vector<uint8_t> compressed_data = readSubBlocks(buffer);
int uncompressed_data_length = image_descriptor.image_width * image_descriptor.image_height;
std::vector<uint8_t> uncompressed_data(uncompressed_data_length);
decompress(lzw_code_size, compressed_data.data(), static_cast<int>(compressed_data.size()), uncompressed_data.data());
return uncompressed_data;
}
// Procesa el stream completo del GIF y devuelve los datos sin comprimir.
auto processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h) -> std::vector<uint8_t> {
uint8_t header[6];
std::memcpy(header, buffer, 6);
buffer += 6;
std::string header_str(reinterpret_cast<char *>(header), 6);
if (header_str != "GIF87a" && header_str != "GIF89a") {
std::cout << "Formato de archivo GIF inválido: " << header_str << '\n';
throw std::runtime_error("Formato de archivo GIF inválido.");
}
ScreenDescriptor screen_descriptor;
readBytes(buffer, &screen_descriptor, sizeof(ScreenDescriptor));
w = screen_descriptor.width;
h = screen_descriptor.height;
int color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1;
std::vector<RGB> global_color_table;
if ((screen_descriptor.fields & 0x80) != 0) {
const size_t GLOBAL_COLOR_TABLE_SIZE = 1U << (((screen_descriptor.fields & 0x07) + 1));
global_color_table.resize(GLOBAL_COLOR_TABLE_SIZE);
std::memcpy(global_color_table.data(), buffer, 3 * GLOBAL_COLOR_TABLE_SIZE);
buffer += 3 * GLOBAL_COLOR_TABLE_SIZE;
}
uint8_t block_type = *buffer++;
while (block_type != TRAILER) {
if (block_type == EXTENSION_INTRODUCER) {
uint8_t extension_label = *buffer++;
switch (extension_label) {
case GRAPHIC_CONTROL: {
uint8_t block_size = *buffer++;
buffer += block_size;
uint8_t sub_block_size = *buffer++;
while (sub_block_size != 0) {
buffer += sub_block_size;
sub_block_size = *buffer++;
}
break;
}
case APPLICATION_EXTENSION:
case COMMENT_EXTENSION:
case PLAINTEXT_EXTENSION: {
uint8_t block_size = *buffer++;
buffer += block_size;
uint8_t sub_block_size = *buffer++;
while (sub_block_size != 0) {
buffer += sub_block_size;
sub_block_size = *buffer++;
}
break;
}
default: {
uint8_t block_size = *buffer++;
buffer += block_size;
uint8_t sub_block_size = *buffer++;
while (sub_block_size != 0) {
buffer += sub_block_size;
sub_block_size = *buffer++;
}
break;
}
}
} else if (block_type == IMAGE_DESCRIPTOR) {
return processImageDescriptor(buffer, global_color_table, color_resolution_bits);
} else {
std::cout << "Unrecognized block type: 0x" << std::hex << static_cast<int>(block_type) << std::dec << '\n';
return std::vector<uint8_t>{};
}
block_type = *buffer++;
}
return std::vector<uint8_t>{};
}
} // namespace
auto loadPalette(const uint8_t *buffer) -> std::vector<uint32_t> {
uint8_t header[6];
std::memcpy(header, buffer, 6);
buffer += 6;
ScreenDescriptor screen_descriptor;
std::memcpy(&screen_descriptor, buffer, sizeof(ScreenDescriptor));
buffer += sizeof(ScreenDescriptor);
std::vector<uint32_t> global_color_table;
if ((screen_descriptor.fields & 0x80) != 0) {
int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
global_color_table.resize(global_color_table_size);
for (int i = 0; i < global_color_table_size; ++i) {
uint8_t r = buffer[0];
uint8_t g = buffer[1];
uint8_t b = buffer[2];
global_color_table[i] = (r << 16) | (g << 8) | b;
buffer += 3;
}
}
return global_color_table;
}
auto loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h) -> std::vector<uint8_t> {
return processGifStream(buffer, w, h);
}
} // namespace GIF
+75
View File
@@ -0,0 +1,75 @@
#pragma once
#include <cstdint> // Para uint8_t, uint16_t, uint32_t
#include <vector> // Para vector
namespace GIF {
// Constantes definidas con constexpr, en lugar de macros
constexpr uint8_t EXTENSION_INTRODUCER = 0x21;
constexpr uint8_t IMAGE_DESCRIPTOR = 0x2C;
constexpr uint8_t TRAILER = 0x3B;
constexpr uint8_t GRAPHIC_CONTROL = 0xF9;
constexpr uint8_t APPLICATION_EXTENSION = 0xFF;
constexpr uint8_t COMMENT_EXTENSION = 0xFE;
constexpr uint8_t PLAINTEXT_EXTENSION = 0x01;
#pragma pack(push, 1)
struct ScreenDescriptor {
uint16_t width;
uint16_t height;
uint8_t fields;
uint8_t background_color_index;
uint8_t pixel_aspect_ratio;
};
struct RGB {
uint8_t r, g, b;
};
struct ImageDescriptor {
uint16_t image_left_position;
uint16_t image_top_position;
uint16_t image_width;
uint16_t image_height;
uint8_t fields;
};
#pragma pack(pop)
struct DictionaryEntry {
uint8_t byte;
int prev;
int len;
};
struct Extension {
uint8_t extension_code;
uint8_t block_size;
};
struct GraphicControlExtension {
uint8_t fields;
uint16_t delay_time;
uint8_t transparent_color_index;
};
struct ApplicationExtension {
uint8_t application_id[8];
uint8_t version[3];
};
struct PlaintextExtension {
uint16_t left, top, width, height;
uint8_t cell_width, cell_height;
uint8_t foreground_color, background_color;
};
// Carga la paleta (global color table) a partir de un buffer,
// retornándola en un vector de uint32_t (cada color se compone de R, G, B).
auto loadPalette(const uint8_t *buffer) -> std::vector<uint32_t>;
// Carga el stream GIF; devuelve un vector con los datos de imagen sin comprimir y
// asigna el ancho y alto mediante referencias.
auto loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h) -> std::vector<uint8_t>;
} // namespace GIF
@@ -1,30 +1,74 @@
#include "screen.hpp"
#include "core/rendering/screen.hpp"
#include <SDL3/SDL.h> // Para SDL_SetRenderTarget, SDL_RenderTexture, SDL_SetRenderDrawColor, SDL_SetRenderVSync, SDL_LogCategory, SDL_GetError, SDL_LogError, SDL_LogInfo, SDL_RendererLogicalPresentation, SDL_SetRenderLogicalPresentation, SDL_CreateTexture, SDL_DestroyTexture, SDL_DestroyWindow, SDL_GetDisplayName, SDL_GetTicks, SDL_Quit, SDL_RENDERER_VSYNC_DISABLED, SDL_RenderClear, SDL_CreateRenderer, SDL_CreateWindow, SDL_DestroyRenderer, SDL_DisplayID, SDL_FRect, SDL_GetCurrentDisplayMode, SDL_GetDisplays, SDL_GetRenderTarget, SDL_GetWindowPosition, SDL_GetWindowSize, SDL_Init, SDL_LogWarn, SDL_PixelFormat, SDL_RenderFillRect, SDL_RenderPresent, SDL_SetHint, SDL_SetRenderDrawBlendMode, SDL_SetTextureScaleMode, SDL_SetWindowFullscreen, SDL_SetWindowPosition, SDL_SetWindowSize, SDL_TextureAccess, SDL_free, SDL_BLENDMODE_BLEND, SDL_HINT_RENDER_DRIVER, SDL_INIT_VIDEO, SDL_ScaleMode, SDL_WINDOW_FULLSCREEN, SDL_WindowFlags
#include <SDL3/SDL.h> // Para SDL_SetRenderTarget, SDL_RenderTexture, SDL_SetRenderDrawColor, SDL_SetRenderVSync, SDL_LogCategory, SDL_GetError, SDL_LogError, SDL_LogInfo, SDL_RendererLogicalPresentation, SDL_SetRenderLogicalPresentation, SDL_CreateTexture, SDL_DestroyTexture, SDL_DestroyWindow, SDL_GetDisplayName, SDL_GetTicks, SDL_Quit, SDL_RENDERER_VSYNC_DISABLED, SDL_RenderClear, SDL_CreateRenderer, SDL_CreateWindow, SDL_DestroyRenderer, SDL_DisplayID, SDL_FRect, SDL_GetCurrentDisplayMode, SDL_GetDisplays, SDL_GetRenderTarget, SDL_GetWindowPosition, SDL_GetWindowSize, SDL_Init, SDL_LogWarn, SDL_PixelFormat, SDL_RenderFillRect, SDL_RenderPresent, SDL_SetHint, SDL_SetRenderDrawBlendMode, SDL_SetTextureScaleMode, SDL_SetWindowFullscreen, SDL_SetWindowPosition, SDL_SetWindowSize, SDL_SyncWindow, SDL_TextureAccess, SDL_free, SDL_BLENDMODE_BLEND, SDL_HINT_RENDER_DRIVER, SDL_INIT_VIDEO, SDL_ScaleMode, SDL_WINDOW_FULLSCREEN, SDL_WindowFlags
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#include <emscripten/html5.h>
#endif
#include <algorithm> // Para min, max
#include <cstring> // Para memcpy
#include <iostream> // Para std::cout
#include <memory> // Para allocator, shared_ptr, unique_ptr, __shared_ptr_access, make_shared, make_unique
#include <string> // Para basic_string, operator+, char_traits, to_string, string
#include <vector> // Para vector
#include "asset.hpp" // Para Asset
#include "director.hpp" // Para Director::debug_config
#include "mouse.hpp" // Para updateCursorVisibility
#include "options.hpp" // Para Video, video, Window, window
#include "param.hpp" // Para Param, param, ParamGame, ParamDebug
#include "rendering/sdl3gpu/sdl3gpu_shader.hpp" // Para SDL3GPUShader
#include "resource.hpp" // Para Resource
#include "text.hpp" // Para Text
#include "texture.hpp" // Para Texture
#include "ui/logger.hpp" // Para info
#include "ui/notifier.hpp" // Para Notifier
#include "ui/service_menu.hpp" // Para ServiceMenu
#include "utils.hpp" // Para toLower
#include "core/input/mouse.hpp" // Para updateCursorVisibility
#ifndef NO_SHADERS
#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp" // Para SDL3GPUShader
#endif
#include "core/rendering/text.hpp" // Para Text
#include "core/rendering/texture.hpp" // Para Texture
#include "core/resources/asset.hpp" // Para Asset
#ifdef _DEBUG
#include "core/resources/resource.hpp" // Para Resource (només a debug, font 8bithud)
#include "core/system/director.hpp" // Para Director::debug_config (només a debug)
#endif
#include "game/options.hpp" // Para Video, video, Window, window
#include "game/ui/notifier.hpp" // Para Notifier
#include "game/ui/service_menu.hpp" // Para ServiceMenu
#include "utils/param.hpp" // Para Param, param, ParamGame, ParamDebug
// Singleton
Screen* Screen::instance = nullptr;
#ifdef __EMSCRIPTEN__
// ============================================================================
// Restauración del canvas en wasm/Emscripten
// ============================================================================
// SDL3 + Emscripten no notifica de forma fiable los cambios de estado del
// canvas HTML (fullscreen exit vía Esc, rotación del dispositivo, etc.).
// Registramos callbacks nativos de Emscripten que delegan en
// Screen::handleCanvasResized(), el cual re-aplica el modo de fullscreen y
// reajusta la ventana para que SDL salga de su estado interno de fullscreen.
//
// Los callbacks difieren el trabajo con emscripten_async_call(0ms) porque el
// navegador todavía no ha acabado de redimensionar el canvas cuando el evento
// se dispara; posponer al siguiente tick garantiza valores estables.
//
// Referencias: libsdl-org/SDL#13300, libsdl-org/SDL#11389.
// ============================================================================
namespace {
void deferredCanvasResize(void* /*user_data*/) {
if (Screen::get() != nullptr) { Screen::get()->handleCanvasResized(); }
}
auto onEmFullscreenChange(int /*event_type*/, const EmscriptenFullscreenChangeEvent* event, void* /*user_data*/) -> EM_BOOL {
// Sincronizamos Options::video.fullscreen con el estado real del navegador
// antes de diferir la restauración: cuando el usuario sale con Esc no pasa
// por toggleFullscreen() y el estado interno quedaría desincronizado.
Options::video.fullscreen = (event != nullptr && event->isFullscreen != 0);
emscripten_async_call(deferredCanvasResize, nullptr, 0);
return EM_FALSE;
}
auto onEmOrientationChange(int /*event_type*/, const EmscriptenOrientationChangeEvent* /*event*/, void* /*user_data*/) -> EM_BOOL {
emscripten_async_call(deferredCanvasResize, nullptr, 0);
return EM_FALSE;
}
} // namespace
#endif
// Inicializa la instancia única del singleton
void Screen::init() {
Screen::instance = new Screen();
@@ -39,19 +83,14 @@ auto Screen::get() -> Screen* { return Screen::instance; }
// Constructor
Screen::Screen()
: window_(nullptr),
renderer_(nullptr),
game_canvas_(nullptr),
service_menu_(nullptr),
notifier_(nullptr),
src_rect_(SDL_FRect{.x = 0, .y = 0, .w = param.game.width, .h = param.game.height}),
: src_rect_(SDL_FRect{.x = 0, .y = 0, .w = param.game.width, .h = param.game.height}),
dst_rect_(SDL_FRect{.x = 0, .y = 0, .w = param.game.width, .h = param.game.height}) {
// Arranca SDL VIDEO, crea la ventana y el renderizador
initSDLVideo();
// Crea la textura de destino
game_canvas_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height);
SDL_SetTextureScaleMode(game_canvas_, SDL_SCALEMODE_NEAREST);
SDL_SetTextureScaleMode(game_canvas_, Options::video.scale_mode);
// Inicializar buffer de píxeles para SDL3GPU
pixel_buffer_.resize(static_cast<size_t>(param.game.width) * static_cast<size_t>(param.game.height));
@@ -67,6 +106,14 @@ Screen::Screen()
// Renderizar una vez la textura vacía para que tenga contenido válido antes de inicializar los shaders (evita pantalla negra)
SDL_RenderTexture(renderer_, game_canvas_, nullptr, nullptr);
// Aplicar la configuración inicial completa (vsync + logical presentation +
// fullscreen + tamaño de ventana). En Emscripten es necesario porque el
// canvas HTML tiene un tamaño propio y SDL_CreateWindow solo no basta para
// que SDL sincronice su viewport interno con el canvas real: sin este
// applySettings el canvas inicial sale descolocado con barras negras a los
// lados y el juego pequeño en el centro hasta el primer toggle de fullscreen.
applySettings();
// Limpiar renderer
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255);
SDL_RenderClear(renderer_);
@@ -143,10 +190,39 @@ void Screen::setFullscreenMode() {
SDL_SetWindowFullscreen(window_, Options::video.fullscreen);
}
// Camibia entre pantalla completa y ventana
// Cambia entre pantalla completa y ventana. Usamos applySettings en vez de
// setFullscreenMode porque applySettings también re-aplica la logical
// presentation — sin eso, al entrar en fullscreen SDL no recalcula el viewport
// y el juego se ve pequeño (especialmente en Android).
void Screen::toggleFullscreen() {
Options::video.fullscreen = !Options::video.fullscreen;
setFullscreenMode();
applySettings();
}
// Re-sincroniza SDL con el estado real del canvas del navegador. Lo invocan los
// callbacks nativos de Emscripten (vegeu el bloc al principi del fitxer) cuando
// se detecta un fullscreenchange o un orientationchange. Re-aplica fullscreen y
// reajusta la ventana porque SDL no emite SDL_EVENT_WINDOW_LEAVE_FULLSCREEN en
// Emscripten y su estado interno queda desincronizado al salir con Esc.
// Fuera de Emscripten es un no-op (desktop sí emite los events correctamente).
void Screen::handleCanvasResized() {
#ifdef __EMSCRIPTEN__
// SDL_SetWindowFullscreen es imprescindible para sacar a SDL de su estado
// interno de fullscreen cuando el usuario ha salido sin pasar por
// toggleFullscreen (onEmFullscreenChange ya ha actualizado Options).
SDL_SetWindowFullscreen(window_, Options::video.fullscreen);
SDL_SyncWindow(window_);
adjustWindowSize();
#endif
}
// Registra los callbacks nativos de Emscripten que restauran el canvas cuando
// SDL3 no emite los events equivalentes. Fuera de Emscripten es un no-op.
void Screen::registerEmscriptenEventCallbacks() { // NOLINT(readability-convert-member-functions-to-static)
#ifdef __EMSCRIPTEN__
emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, EM_TRUE, onEmFullscreenChange);
emscripten_set_orientationchange_callback(nullptr, EM_TRUE, onEmOrientationChange);
#endif
}
// Cambia el tamaño de la ventana
@@ -240,49 +316,49 @@ void Screen::renderShake() {
}
}
#ifdef _DEBUG
// Compone la línia d'informació de debug: "fps - driver - shader preset"
auto Screen::buildDebugInfoText() const -> std::string {
std::string info_text = std::to_string(fps_.last_value) + " fps";
// Driver GPU
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
const std::string DRIVER = shader_backend_->getDriverName();
info_text += DRIVER.empty() ? "" : " - " + toLower(DRIVER);
} else {
info_text += " - sdl";
}
// Shader + preset (només si està activat)
if (!Options::video.shader.enabled) { return info_text; }
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
const std::string PRESET_NAME = Options::crtpi_presets.empty() ? "" : Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset)).name;
info_text += " - crtpi " + toLower(PRESET_NAME);
} else {
const std::string PRESET_NAME = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset)).name;
info_text += " - postfx " + toLower(PRESET_NAME);
}
return info_text;
}
// Muestra información por pantalla
void Screen::renderInfo() const {
if (debug_info_.show) {
const Color GOLD(0xFF, 0xD7, 0x00);
const Color GOLD_SHADOW = GOLD.DARKEN(150);
if (!debug_info_.show) { return; }
// Construir texto: fps - driver - preset
std::string info_text = std::to_string(fps_.last_value) + " fps";
const Color GOLD(0xFF, 0xD7, 0x00);
const Color GOLD_SHADOW = GOLD.darken(150);
// Driver GPU
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
const std::string DRIVER = shader_backend_->getDriverName();
if (!DRIVER.empty()) {
info_text += " - " + toLower(DRIVER);
}
} else {
info_text += " - sdl";
}
// Shader + preset
if (Options::video.shader.enabled) {
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
const std::string PRESET_NAME = Options::crtpi_presets.empty() ? "" : Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset)).name;
info_text += " - crtpi " + toLower(PRESET_NAME);
} else {
const std::string PRESET_NAME = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset)).name;
info_text += " - postfx " + toLower(PRESET_NAME);
if (Options::video.supersampling.enabled) { info_text += " (ss)"; }
}
}
// Centrado arriba
const int TEXT_WIDTH = debug_info_.text->length(info_text);
const int X_POS = (static_cast<int>(param.game.width) - TEXT_WIDTH) / 2;
debug_info_.text->writeDX(Text::COLOR | Text::STROKE, X_POS, 1, info_text, 1, GOLD, 1, GOLD_SHADOW);
const std::string INFO_TEXT = buildDebugInfoText();
const int TEXT_WIDTH = debug_info_.text->length(INFO_TEXT);
const int X_POS = (static_cast<int>(param.game.width) - TEXT_WIDTH) / 2;
debug_info_.text->writeDX(Text::COLOR | Text::STROKE, X_POS, 1, INFO_TEXT, 1, GOLD, 1, GOLD_SHADOW);
#ifdef RECORDING
const std::string REC_TEXT = "recording";
const int REC_WIDTH = debug_info_.text->length(REC_TEXT);
const int REC_X = (static_cast<int>(param.game.width) - REC_WIDTH) / 2;
debug_info_.text->writeDX(Text::COLOR | Text::STROKE, REC_X, 1 + debug_info_.text->getCharacterSize(), REC_TEXT, 1, GOLD, 1, GOLD_SHADOW);
const std::string REC_TEXT = "recording";
const int REC_WIDTH = debug_info_.text->length(REC_TEXT);
const int REC_X = (static_cast<int>(param.game.width) - REC_WIDTH) / 2;
debug_info_.text->writeDX(Text::COLOR | Text::STROKE, REC_X, 1 + debug_info_.text->getCharacterSize(), REC_TEXT, 1, GOLD, 1, GOLD_SHADOW);
#endif
}
}
#endif
// Inicializa shaders (SDL3GPU)
@@ -290,7 +366,7 @@ void Screen::initShaders() {
#ifndef NO_SHADERS
auto* self = Screen::get();
if (self == nullptr) {
SDL_Log("Screen::initShaders: instance is null, skipping");
std::cout << "Screen::initShaders: instance is null, skipping" << '\n';
return;
}
if (!self->shader_backend_) {
@@ -300,14 +376,10 @@ void Screen::initShaders() {
Options::video.gpu.acceleration ? Options::video.gpu.preferred_driver : FALLBACK_DRIVER);
}
if (!self->shader_backend_->isHardwareAccelerated()) {
const bool ok = self->shader_backend_->init(self->window_, self->game_canvas_, "", "");
SDL_Log("Screen::initShaders: SDL3GPUShader::init() = %s", ok ? "OK" : "FAILED");
const bool OK = self->shader_backend_->init(self->window_, self->game_canvas_, "", "");
std::cout << "Screen::initShaders: SDL3GPUShader::init() = " << (OK ? "OK" : "FAILED") << '\n';
}
if (self->shader_backend_ && self->shader_backend_->isHardwareAccelerated()) {
self->shader_backend_->setLinearUpscale(Options::video.supersampling.linear_upscale);
self->shader_backend_->setDownscaleAlgo(Options::video.supersampling.downscale_algo);
self->shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
if (!Options::video.shader.enabled) {
// Passthrough: POSTFX con parámetros a cero
self->shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX);
@@ -371,9 +443,7 @@ void Screen::renderAttenuate() {
auto Screen::initSDLVideo() -> bool {
// Inicializar SDL
if (!SDL_Init(SDL_INIT_VIDEO)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"FATAL: Failed to initialize SDL_VIDEO! SDL Error: %s",
SDL_GetError());
std::cout << "FATAL: Failed to initialize SDL_VIDEO! SDL Error: " << SDL_GetError() << '\n';
return false;
}
@@ -383,8 +453,7 @@ auto Screen::initSDLVideo() -> bool {
// Configurar hint para renderizado
#ifdef __APPLE__
if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal")) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Warning: Failed to set Metal hint!");
std::cout << "Warning: Failed to set Metal hint!" << '\n';
}
#endif
@@ -405,9 +474,7 @@ auto Screen::initSDLVideo() -> bool {
window_flags);
if (window_ == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"FATAL: Failed to create window! SDL Error: %s",
SDL_GetError());
std::cout << "FATAL: Failed to create window! SDL Error: " << SDL_GetError() << '\n';
SDL_Quit();
return false;
}
@@ -415,9 +482,7 @@ auto Screen::initSDLVideo() -> bool {
// Crear renderer
renderer_ = SDL_CreateRenderer(window_, nullptr);
if (renderer_ == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"FATAL: Failed to create renderer! SDL Error: %s",
SDL_GetError());
std::cout << "FATAL: Failed to create renderer! SDL Error: " << SDL_GetError() << '\n';
SDL_DestroyWindow(window_);
window_ = nullptr;
SDL_Quit();
@@ -429,24 +494,16 @@ auto Screen::initSDLVideo() -> bool {
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
SDL_SetRenderVSync(renderer_, Options::video.vsync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
Logger::info("Video system initialized successfully");
registerEmscriptenEventCallbacks();
return true;
}
// Obtiene información sobre la pantalla
void Screen::getDisplayInfo() {
int i;
int num_displays = 0;
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
if (displays != nullptr) {
for (i = 0; i < num_displays; ++i) {
SDL_DisplayID instance_id = displays[i];
const char* name = SDL_GetDisplayName(instance_id);
// SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Display %" SDL_PRIu32 ": %s", instance_id, (name != nullptr) ? name : "Unknown");
Logger::info(std::string("Display ") + std::to_string(instance_id) + ": " + (name != nullptr ? name : "Unknown"));
}
const auto* dm = SDL_GetCurrentDisplayMode(displays[0]);
// Guarda información del monitor en display_monitor_
@@ -465,13 +522,6 @@ void Screen::getDisplayInfo() {
std::to_string(dm->h) + " @ " +
std::to_string(static_cast<int>(dm->refresh_rate)) + " Hz";
// Muestra información sobre el tamaño de la pantalla y de la ventana de juego
// SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Current display mode: %dx%d @ %dHz", static_cast<int>(dm->w), static_cast<int>(dm->h), static_cast<int>(dm->refresh_rate));
Logger::info("Current display mode: " + Options::video.info);
// SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Window resolution: %dx%d x%d", static_cast<int>(param.game.width), static_cast<int>(param.game.height), Options::window.zoom);
Logger::info("Window resolution: " + std::to_string(static_cast<int>(param.game.width)) + "x" + std::to_string(static_cast<int>(param.game.height)) + "x" + std::to_string(Options::window.zoom));
// Calcula el máximo factor de zoom que se puede aplicar a la pantalla
const int MAX_ZOOM = std::min(dm->w / param.game.width, (dm->h - WINDOWS_DECORATIONS) / param.game.height);
@@ -534,40 +584,25 @@ void Screen::nextCrtPiPreset() {
}
}
// Alterna supersampling
void Screen::toggleSupersampling() {
Options::video.supersampling.enabled = !Options::video.supersampling.enabled;
auto* self = Screen::get();
if (self != nullptr && self->shader_backend_ && self->shader_backend_->isHardwareAccelerated()) {
self->shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
self->applyCurrentCrtPiPreset();
} else {
self->applyCurrentPostFXPreset();
}
}
}
// Aplica el preset PostFX activo al backend
void Screen::applyCurrentPostFXPreset() {
if (!shader_backend_) { return; }
shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
Rendering::PostFXParams p{};
if (Options::video.shader.enabled && !Options::postfx_presets.empty()) {
const auto& preset = Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset));
p.vignette = preset.vignette;
p.scanlines = preset.scanlines;
p.chroma = preset.chroma;
p.chroma_min = preset.chroma_min;
p.chroma_max = preset.chroma_max;
p.mask = preset.mask;
p.gamma = preset.gamma;
p.curvature = preset.curvature;
p.bleeding = preset.bleeding;
p.flicker = preset.flicker;
SDL_Log("Screen::applyCurrentPostFXPreset: preset='%s' scan=%.2f vign=%.2f chroma=%.2f",
preset.name.c_str(),
p.scanlines,
p.vignette,
p.chroma);
p.scan_dark_ratio = preset.scan_dark_ratio;
p.scan_dark_floor = preset.scan_dark_floor;
p.scan_edge_soft = preset.scan_edge_soft;
std::cout << "Screen::applyCurrentPostFXPreset: preset='" << preset.name << "' scan=" << p.scanlines << " vign=" << p.vignette << " chroma=[" << p.chroma_min << ".." << p.chroma_max << "]\n";
}
shader_backend_->setPostFXParams(p);
}
@@ -594,7 +629,7 @@ void Screen::applyCurrentCrtPiPreset() {
.enable_sharper = preset.enable_sharper,
};
shader_backend_->setCrtPiParams(p);
SDL_Log("Screen::applyCurrentCrtPiPreset: preset='%s'", preset.name.c_str());
std::cout << "Screen::applyCurrentCrtPiPreset: preset='" << preset.name << "'" << '\n';
}
}
@@ -607,19 +642,43 @@ void Screen::toggleIntegerScale() {
}
}
// Alterna entre activar y desactivar el V-Sync
// Alterna entre activar y desactivar el V-Sync. ÚNICA via "user-facing" que
// modifica la preferencia persistent (Options::video.vsync); la resta de
// crides a setVSync apliquen l'estat al hardware sense escriure preferència.
void Screen::toggleVSync() {
Options::video.vsync = !Options::video.vsync;
SDL_SetRenderVSync(renderer_, Options::video.vsync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
if (shader_backend_) {
shader_backend_->setVSync(Options::video.vsync);
}
setVSync(Options::video.vsync);
}
// Establece el estado del V-Sync
// Aplica Options::video.scale_mode a la textura del canvas de juego
void Screen::applyFilter() {
SDL_SetTextureScaleMode(game_canvas_, Options::video.scale_mode);
}
// Alterna el modo de filtrado entre nearest y linear
void Screen::toggleFilter() {
Options::video.scale_mode = (Options::video.scale_mode == SDL_SCALEMODE_NEAREST)
? SDL_SCALEMODE_LINEAR
: SDL_SCALEMODE_NEAREST;
applyFilter();
}
// Devuelve true si el backend SDL3GPU está activo y con aceleración hardware
auto Screen::isHardwareAccelerated() -> bool {
auto* self = Screen::get();
return self != nullptr && self->shader_backend_ && self->shader_backend_->isHardwareAccelerated();
}
// Aplica V-Sync al renderer SDL i al backend GPU. NO toca la preferència
// persistent (Options::video.vsync) — és responsabilitat del caller (menú
// servei, toggleVSync). Així Resource::beginLoad/finishBoot poden canviar
// el vsync efectiu durant el preload sense clobberar la preferència de
// l'usuari (bug històric: el seu vsync=true es perdia entre llançaments).
void Screen::setVSync(bool enabled) {
Options::video.vsync = enabled;
SDL_SetRenderVSync(renderer_, enabled ? 1 : SDL_RENDERER_VSYNC_DISABLED);
if (shader_backend_) {
shader_backend_->setVSync(enabled);
}
}
// Obtiene los punteros a los singletones
@@ -637,12 +696,17 @@ void Screen::getSingletons() {
#endif
}
// Aplica los valores de las opciones
// Aplica los valores de las opciones.
// IMPORTANTE: el orden importa. SDL_SetRenderLogicalPresentation calcula el
// viewport en función del tamaño actual de la ventana SDL, así que DEBE llamarse
// DESPUÉS de setFullscreenMode/adjustWindowSize — si no, al entrar en fullscreen
// el viewport queda cacheado al tamaño de la ventana pequeña previa y el juego
// se ve pequeño y centrado con barras negras alrededor.
void Screen::applySettings() {
SDL_SetRenderVSync(renderer_, Options::video.vsync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
SDL_SetRenderLogicalPresentation(Screen::get()->getRenderer(), param.game.width, param.game.height, Options::video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
setVSync(Options::video.vsync);
setFullscreenMode();
adjustWindowSize();
SDL_SetRenderLogicalPresentation(renderer_, param.game.width, param.game.height, Options::video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
}
// Crea el objeto de texto
@@ -6,9 +6,9 @@
#include <string> // Para string
#include <vector> // Para vector
#include "color.hpp" // Para Color
#include "options.hpp" // Para VideoOptions, video
#include "rendering/shader_backend.hpp" // Para Rendering::ShaderType
#include "core/rendering/shader_backend.hpp" // Para Rendering::ShaderType
#include "game/options.hpp" // Para VideoOptions, video
#include "utils/color.hpp" // Para Color
// Forward declarations
class Notifier;
@@ -34,6 +34,7 @@ class Screen {
// --- Configuración de ventana y render ---
void setFullscreenMode(); // Establece el modo de pantalla completa
void toggleFullscreen(); // Cambia entre pantalla completa y ventana
void handleCanvasResized(); // Restaura el canvas cuando SDL3 no reporta el cambio (emscripten only: salida de fullscreen con Esc, rotación); no-op fuera de emscripten
void setWindowZoom(int zoom); // Cambia el tamaño de la ventana
auto decWindowSize() -> bool; // Reduce el tamaño de la ventana
auto incWindowSize() -> bool; // Aumenta el tamaño de la ventana
@@ -43,11 +44,12 @@ class Screen {
// --- Efectos visuales ---
void shake(int desp = 2, float delay_s = 0.05F, float duration_s = 0.133F) { shake_effect_.enable(src_rect_, dst_rect_, desp, delay_s, duration_s); }
void flash(Color color, float duration_s = 0.167F, float delay_s = 0.0F) { flash_effect_ = FlashEffect(true, duration_s, delay_s, color); }
static void toggleShaders(); // Alterna activar/desactivar shaders
static void nextShader(); // Cambia entre PostFX y CrtPi
static void nextPostFXPreset(); // Avanza al siguiente preset PostFX
static void nextCrtPiPreset(); // Avanza al siguiente preset CrtPi
static void toggleSupersampling(); // Alterna supersampling
static void toggleShaders(); // Alterna activar/desactivar shaders
static void nextShader(); // Cambia entre PostFX y CrtPi
static void nextPostFXPreset(); // Avanza al siguiente preset PostFX
static void nextCrtPiPreset(); // Avanza al siguiente preset CrtPi
void toggleFilter(); // Alterna SDL_SCALEMODE_NEAREST ↔ SDL_SCALEMODE_LINEAR
void applyFilter(); // Aplica Options::video.scale_mode a game_canvas_
void toggleIntegerScale();
void toggleVSync(); // Alterna entre activar y desactivar el V-Sync
void setVSync(bool enabled); // Establece el estado del V-Sync
@@ -59,10 +61,11 @@ class Screen {
void hide() { SDL_HideWindow(window_); } // Oculta la ventana
void getSingletons(); // Obtiene los punteros a los singletones
[[nodiscard]] static auto getVSync() -> bool { return Options::video.vsync; } // Obtiene el valor de V-Sync
[[nodiscard]] static auto isHardwareAccelerated() -> bool; // True si SDL3GPU está activo
[[nodiscard]] auto getText() const -> std::shared_ptr<Text> { return text_; } // Obtiene el puntero al texto de Screen
// --- Display Monitor getters ---
[[nodiscard]] auto getDisplayMonitorName() const -> std::string { return display_monitor_.name; }
[[nodiscard]] auto getDisplayMonitorName() const -> const std::string& { return display_monitor_.name; }
[[nodiscard]] auto getDisplayMonitorWidth() const -> int { return display_monitor_.width; }
[[nodiscard]] auto getDisplayMonitorHeight() const -> int { return display_monitor_.height; }
[[nodiscard]] auto getDisplayMonitorRefreshRate() const -> int { return display_monitor_.refresh_rate; }
@@ -210,11 +213,11 @@ class Screen {
#endif
// --- Objetos y punteros ---
SDL_Window* window_; // Ventana de la aplicación
SDL_Renderer* renderer_; // El renderizador de la ventana
SDL_Texture* game_canvas_; // Textura donde se dibuja todo antes de volcarse al renderizador
ServiceMenu* service_menu_; // Objeto para mostrar el menú de servicio
Notifier* notifier_; // Objeto para mostrar las notificaciones por pantalla
SDL_Window* window_ = nullptr; // Ventana de la aplicación
SDL_Renderer* renderer_ = nullptr; // El renderizador de la ventana
SDL_Texture* game_canvas_ = nullptr; // Textura donde se dibuja todo antes de volcarse al renderizador
ServiceMenu* service_menu_ = nullptr; // Objeto para mostrar el menú de servicio
Notifier* notifier_ = nullptr; // Objeto para mostrar las notificaciones por pantalla
std::shared_ptr<Text> text_; // Objeto para escribir texto en pantalla
std::unique_ptr<Rendering::ShaderBackend> shader_backend_; // Backend de shaders (SDL3GPU)
@@ -232,18 +235,20 @@ class Screen {
#endif
// --- Métodos internos ---
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
void renderFlash(); // Dibuja el efecto de flash en la pantalla
void renderShake(); // Aplica el efecto de agitar la pantalla
void renderInfo() const; // Muestra información por pantalla
void renderPresent(); // Selecciona y ejecuta el método de renderizado adecuado
void applyCurrentPostFXPreset(); // Aplica el preset PostFX activo al backend
void applyCurrentCrtPiPreset(); // Aplica el preset CrtPi activo al backend
void adjustWindowSize(); // Calcula el tamaño de la ventana
void getDisplayInfo(); // Obtiene información sobre la pantalla
void renderOverlays(); // Renderiza todos los overlays y efectos
void renderAttenuate(); // Atenúa la pantalla
void createText(); // Crea el objeto de texto
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
void registerEmscriptenEventCallbacks(); // Registra callbacks nativos para restaurar el canvas en wasm (no-op fuera de emscripten)
void renderFlash(); // Dibuja el efecto de flash en la pantalla
void renderShake(); // Aplica el efecto de agitar la pantalla
void renderInfo() const; // Muestra información por pantalla
[[nodiscard]] auto buildDebugInfoText() const -> std::string; // Compone fps + driver + shader/preset para renderInfo
void renderPresent(); // Selecciona y ejecuta el método de renderizado adecuado
void applyCurrentPostFXPreset(); // Aplica el preset PostFX activo al backend
void applyCurrentCrtPiPreset(); // Aplica el preset CrtPi activo al backend
void adjustWindowSize(); // Calcula el tamaño de la ventana
void getDisplayInfo(); // Obtiene información sobre la pantalla
void renderOverlays(); // Renderiza todos los overlays y efectos
void renderAttenuate(); // Atenúa la pantalla
void createText(); // Crea el objeto de texto
// --- Constructores y destructor privados (singleton) ---
Screen(); // Constructor privado
@@ -0,0 +1,144 @@
#pragma once
#ifdef __APPLE__
// Fragment shader del shader "crtpi" (algoritme CRT-Pi): scanlines amb
// pesos gaussians, multisample opcional, gamma i màscara de subpíxels.
namespace Rendering::Msl {
inline constexpr const char* kCrtpiFrag = R"(
#include <metal_stdlib>
using namespace metal;
struct PostVOut {
float4 pos [[position]];
float2 uv;
};
struct CrtPiUniforms {
float scanline_weight;
float scanline_gap_brightness;
float bloom_factor;
float input_gamma;
float output_gamma;
float mask_brightness;
float curvature_x;
float curvature_y;
int mask_type;
int enable_scanlines;
int enable_multisample;
int enable_gamma;
int enable_curvature;
int enable_sharper;
float texture_width;
float texture_height;
};
static float2 crtpi_distort(float2 coord, float2 screen_scale, float cx, float cy) {
float2 curvature = float2(cx, cy);
float2 barrel_scale = 1.0f - (0.23f * curvature);
coord *= screen_scale;
coord -= 0.5f;
float rsq = coord.x * coord.x + coord.y * coord.y;
coord += coord * (curvature * rsq);
coord *= barrel_scale;
if (abs(coord.x) >= 0.5f || abs(coord.y) >= 0.5f) { return float2(-1.0f); }
coord += 0.5f;
coord /= screen_scale;
return coord;
}
static float crtpi_scan_weight(float dist, float sw, float gap) {
return max(1.0f - dist * dist * sw, gap);
}
static float crtpi_scan_line(float dy, float filter_w, float sw, float gap, bool ms) {
float w = crtpi_scan_weight(dy, sw, gap);
if (ms) {
w += crtpi_scan_weight(dy - filter_w, sw, gap);
w += crtpi_scan_weight(dy + filter_w, sw, gap);
w *= 0.3333333f;
}
return w;
}
fragment float4 crtpi_fs(PostVOut in [[stage_in]],
texture2d<float> tex [[texture(0)]],
sampler samp [[sampler(0)]],
constant CrtPiUniforms& u [[buffer(0)]]) {
float2 tex_size = float2(u.texture_width, u.texture_height);
// Amplada del filtre de scanline analític. 768 = alçada de referència
// CRT a la qual es va tarar l'algoritme original; 3 = divisió per
// subpíxel (R/G/B) del multisample. El resultat escala amb la textura
// d'entrada, de manera que més alçada → filtre més fi.
const float CRT_REFERENCE_HEIGHT = 768.0f;
const float SUBPIXEL_DIV = 3.0f;
float filter_width = (CRT_REFERENCE_HEIGHT / u.texture_height) / SUBPIXEL_DIV;
float2 texcoord = in.uv;
if (u.enable_curvature != 0) {
texcoord = crtpi_distort(texcoord, float2(1.0f, 1.0f), u.curvature_x, u.curvature_y);
if (texcoord.x < 0.0f) { return float4(0.0f, 0.0f, 0.0f, 1.0f); }
}
float2 coord_in_pixels = texcoord * tex_size;
float2 tc;
float scan_weight;
if (u.enable_sharper != 0) {
float2 temp = floor(coord_in_pixels) + 0.5f;
tc = temp / tex_size;
float2 deltas = coord_in_pixels - temp;
scan_weight = crtpi_scan_line(deltas.y, filter_width, u.scanline_weight, u.scanline_gap_brightness, u.enable_multisample != 0);
float2 signs = sign(deltas);
deltas.x *= 2.0f;
deltas = deltas * deltas;
deltas.y = deltas.y * deltas.y;
deltas.x *= 0.5f;
deltas.y *= 8.0f;
deltas /= tex_size;
deltas *= signs;
tc = tc + deltas;
} else {
float temp_y = floor(coord_in_pixels.y) + 0.5f;
float y_coord = temp_y / tex_size.y;
float dy = coord_in_pixels.y - temp_y;
scan_weight = crtpi_scan_line(dy, filter_width, u.scanline_weight, u.scanline_gap_brightness, u.enable_multisample != 0);
float sign_y = sign(dy);
dy = dy * dy;
dy = dy * dy;
dy *= 8.0f;
dy /= tex_size.y;
dy *= sign_y;
tc = float2(texcoord.x, y_coord + dy);
}
float3 colour = tex.sample(samp, tc).rgb;
if (u.enable_scanlines != 0) {
if (u.enable_gamma != 0) { colour = pow(colour, float3(u.input_gamma)); }
colour *= scan_weight * u.bloom_factor;
if (u.enable_gamma != 0) { colour = pow(colour, float3(1.0f / u.output_gamma)); }
}
if (u.mask_type == 1) {
float wm = fract(in.pos.x * 0.5f);
float3 mask = (wm < 0.5f) ? float3(u.mask_brightness, 1.0f, u.mask_brightness)
: float3(1.0f, u.mask_brightness, 1.0f);
colour *= mask;
} else if (u.mask_type == 2) {
float wm = fract(in.pos.x * 0.3333333f);
float3 mask = float3(u.mask_brightness);
if (wm < 0.3333333f) mask.x = 1.0f;
else if (wm < 0.6666666f) mask.y = 1.0f;
else mask.z = 1.0f;
colour *= mask;
}
return float4(colour, 1.0f);
}
)";
} // namespace Rendering::Msl
#endif // __APPLE__
@@ -0,0 +1,168 @@
#pragma once
#ifdef __APPLE__
// Fragment shader del shader "postfx": vignette, chroma, scanlines, mask,
// gamma, curvature, bleeding i flicker. Els paràmetres venen via uniforms.
//
// IMPORTANT: mantenir sincronitzat a mà amb data/shaders/postfx.frag. SDL3 GPU
// compila aquest string MSL en runtime; no hi ha generador automàtic. Qualsevol
// canvi a la struct d'uniforms o a la lògica del GLSL cal replicar-lo ací al
// mateix commit. Mida total = 64 bytes (4 × vec4).
namespace Rendering::Msl {
inline constexpr const char* kPostfxFrag = R"(
#include <metal_stdlib>
using namespace metal;
struct PostVOut {
float4 pos [[position]];
float2 uv;
};
struct PostFXUniforms {
float vignette_strength;
float chroma_min;
float scanline_strength;
float screen_height;
float mask_strength;
float gamma_strength;
float curvature;
float bleeding;
float pixel_scale;
float time;
float flicker;
float chroma_max;
// vec4 #3 — paràmetres de scanlines (exposats per preset YAML)
float scan_dark_ratio;
float scan_dark_floor;
float scan_edge_soft;
float pad3;
};
// Mostreig bilinear horitzontal d'un canal RGB. Evita el "tic-tac" del sampler
// NEAREST quan l'offset de chroma és subpíxel.
static float sampleBilinearX(float2 uv_target, int channel, texture2d<float> scene, sampler samp) {
float2 tex_size = float2(scene.get_width(), scene.get_height());
float px = uv_target.x * tex_size.x - 0.5f;
float p_floor = floor(px);
float f = px - p_floor;
float4 c0 = scene.sample(samp, float2((p_floor + 0.5f) / tex_size.x, uv_target.y));
float4 c1 = scene.sample(samp, float2((p_floor + 1.5f) / tex_size.x, uv_target.y));
return mix(c0[channel], c1[channel], f);
}
static float3 rgb_to_ycc(float3 rgb) {
return float3(
0.299f*rgb.r + 0.587f*rgb.g + 0.114f*rgb.b,
-0.169f*rgb.r - 0.331f*rgb.g + 0.500f*rgb.b + 0.5f,
0.500f*rgb.r - 0.419f*rgb.g - 0.081f*rgb.b + 0.5f
);
}
static float3 ycc_to_rgb(float3 ycc) {
float y = ycc.x;
float cb = ycc.y - 0.5f;
float cr = ycc.z - 0.5f;
return clamp(float3(
y + 1.402f*cr,
y - 0.344f*cb - 0.714f*cr,
y + 1.772f*cb
), 0.0f, 1.0f);
}
fragment float4 postfx_fs(PostVOut in [[stage_in]],
texture2d<float> scene [[texture(0)]],
sampler samp [[sampler(0)]],
constant PostFXUniforms& u [[buffer(0)]]) {
float2 uv = in.uv;
if (u.curvature > 0.0f) {
float2 c = uv - 0.5f;
float rsq = dot(c, c);
float2 dist = float2(0.05f, 0.1f) * u.curvature;
float2 barrelScale = 1.0f - 0.23f * dist;
c += c * (dist * rsq);
c *= barrelScale;
if (abs(c.x) >= 0.5f || abs(c.y) >= 0.5f) {
return float4(0.0f, 0.0f, 0.0f, 1.0f);
}
uv = c + 0.5f;
}
float3 base = scene.sample(samp, uv).rgb;
float3 colour;
if (u.bleeding > 0.0f) {
float tw = float(scene.get_width());
float step = 1.0f / tw;
float3 ycc = rgb_to_ycc(base);
float3 ycc_l2 = rgb_to_ycc(scene.sample(samp, uv - float2(2.0f*step, 0.0f)).rgb);
float3 ycc_l1 = rgb_to_ycc(scene.sample(samp, uv - float2(1.0f*step, 0.0f)).rgb);
float3 ycc_r1 = rgb_to_ycc(scene.sample(samp, uv + float2(1.0f*step, 0.0f)).rgb);
float3 ycc_r2 = rgb_to_ycc(scene.sample(samp, uv + float2(2.0f*step, 0.0f)).rgb);
ycc.yz = (ycc_l2.yz + ycc_l1.yz*2.0f + ycc.yz*2.0f + ycc_r1.yz*2.0f + ycc_r2.yz) / 8.0f;
colour = mix(base, ycc_to_rgb(ycc), u.bleeding);
} else {
colour = base;
}
// Chroma — varia entre chroma_min i chroma_max via sinusoidal; si min == max
// queda estàtic. Mostreig bilinear horitzontal per evitar el "tic-tac" del
// NEAREST sampler amb offsets subpíxel.
if (u.chroma_min > 0.0f || u.chroma_max > 0.0f) {
float ca = mix(u.chroma_min, u.chroma_max, 0.5f + 0.5f * sin(u.time * 7.3f)) * 0.005f;
colour.r = sampleBilinearX(uv + float2(ca, 0.0f), 0, scene, samp);
colour.b = sampleBilinearX(uv - float2(ca, 0.0f), 2, scene, samp);
}
if (u.gamma_strength > 0.0f) {
float3 lin = pow(colour, float3(2.4f));
colour = mix(colour, lin, u.gamma_strength);
}
// Scanlines — 3 subpíxels per fila lògica (2 brillants + 1 fosca). Transició
// suavitzada amb smoothstep d'ample ≈ 1 píxel físic (estil crtpi: filtratge
// analític continu). scan_edge_soft = 0 recupera el step dur de l'original.
if (u.scanline_strength > 0.0f) {
float ps = max(u.pixel_scale, 1.0f);
float sub = fract(uv.y * u.screen_height);
float dark_center = 1.0f - u.scan_dark_ratio * 0.5f;
float d = abs(sub - dark_center);
d = min(d, 1.0f - d);
float half_width = u.scan_dark_ratio * 0.5f;
float softness = u.scan_edge_soft * 0.5f / ps;
float band = 1.0f - smoothstep(half_width - softness, half_width + softness, d);
float scan = mix(1.0f, u.scan_dark_floor, band);
colour *= mix(1.0f, scan, u.scanline_strength);
}
if (u.gamma_strength > 0.0f) {
float3 enc = pow(colour, float3(1.0f/2.2f));
colour = mix(colour, enc, u.gamma_strength);
}
float2 d = uv - 0.5f;
float vignette = 1.0f - dot(d, d) * u.vignette_strength;
colour *= clamp(vignette, 0.0f, 1.0f);
if (u.mask_strength > 0.0f) {
float whichMask = fract(in.pos.x * 0.3333333f);
float3 mask = float3(0.80f);
if (whichMask < 0.3333333f) mask.x = 1.0f;
else if (whichMask < 0.6666667f) mask.y = 1.0f;
else mask.z = 1.0f;
colour = mix(colour, colour * mask, u.mask_strength);
}
if (u.flicker > 0.0f) {
float flicker_wave = sin(u.time * 100.0f) * 0.5f + 0.5f;
colour *= 1.0f - u.flicker * 0.04f * flicker_wave;
}
return float4(colour, 1.0f);
}
)";
} // namespace Rendering::Msl
#endif // __APPLE__
@@ -0,0 +1,30 @@
#pragma once
#ifdef __APPLE__
// Vertex shader compartit per tots els pipelines de post-procés:
// fullscreen-triangle que cobreix tota l'àrea del swapchain amb UVs a [0,1].
namespace Rendering::Msl {
inline constexpr const char* kPostfxVert = R"(
#include <metal_stdlib>
using namespace metal;
struct PostVOut {
float4 pos [[position]];
float2 uv;
};
vertex PostVOut postfx_vs(uint vid [[vertex_id]]) {
const float2 positions[3] = { {-1.0, -1.0}, {3.0, -1.0}, {-1.0, 3.0} };
const float2 uvs[3] = { { 0.0, 1.0}, {2.0, 1.0}, { 0.0,-1.0} };
PostVOut out;
out.pos = float4(positions[vid], 0.0, 1.0);
out.uv = uvs[vid];
return out;
}
)";
} // namespace Rendering::Msl
#endif // __APPLE__
@@ -0,0 +1,559 @@
#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp"
#include <SDL3/SDL_log.h>
#include <algorithm> // std::min, std::max
#include <cmath> // std::floor
#include <cstring> // memcpy, strlen
#include <iostream> // Para std::cout
#ifndef __APPLE__
#include "core/rendering/sdl3gpu/spv/crtpi_frag_spv.h"
#include "core/rendering/sdl3gpu/spv/postfx_frag_spv.h"
#include "core/rendering/sdl3gpu/spv/postfx_vert_spv.h"
#endif
#ifdef __APPLE__
#include "core/rendering/sdl3gpu/msl/crtpi_frag.msl.h"
#include "core/rendering/sdl3gpu/msl/postfx_frag.msl.h"
#include "core/rendering/sdl3gpu/msl/postfx_vert.msl.h"
#endif
namespace Rendering {
// ---------------------------------------------------------------------------
// Destructor
// ---------------------------------------------------------------------------
SDL3GPUShader::~SDL3GPUShader() {
destroy();
}
// ---------------------------------------------------------------------------
// init
// ---------------------------------------------------------------------------
auto SDL3GPUShader::init(SDL_Window* window,
SDL_Texture* texture,
const std::string& /*vertex_source*/,
const std::string& /*fragment_source*/) -> bool {
if (is_initialized_) {
cleanup();
}
window_ = window;
float fw = 0.0F;
float fh = 0.0F;
SDL_GetTextureSize(texture, &fw, &fh);
game_width_ = static_cast<int>(fw);
game_height_ = static_cast<int>(fh);
uniforms_.screen_height = static_cast<float>(game_height_);
// 1. GPU disabled by config
if (preferred_driver_ == "none") {
std::cout << "SDL3GPUShader: GPU disabled by config, using SDL_Renderer fallback" << '\n';
driver_name_ = "";
return false;
}
// 2. Create GPU device (solo si no existe ya)
if (device_ == nullptr) {
#ifdef __APPLE__
const SDL_GPUShaderFormat PREFERRED = SDL_GPU_SHADERFORMAT_MSL | SDL_GPU_SHADERFORMAT_METALLIB;
#else
const SDL_GPUShaderFormat PREFERRED = SDL_GPU_SHADERFORMAT_SPIRV;
#endif
const char* preferred = preferred_driver_.empty() ? nullptr : preferred_driver_.c_str();
device_ = SDL_CreateGPUDevice(PREFERRED, false, preferred);
if (device_ == nullptr && preferred != nullptr) {
std::cout << "SDL3GPUShader: driver '" << preferred << "' not available, falling back to auto" << '\n';
device_ = SDL_CreateGPUDevice(PREFERRED, false, nullptr);
}
if (device_ == nullptr) {
std::cout << "SDL3GPUShader: SDL_CreateGPUDevice failed: " << SDL_GetError() << '\n';
return false;
}
driver_name_ = SDL_GetGPUDeviceDriver(device_);
std::cout << "SDL3GPUShader: driver = " << driver_name_ << '\n';
if (!SDL_ClaimWindowForGPUDevice(device_, window_)) {
std::cout << "SDL3GPUShader: SDL_ClaimWindowForGPUDevice failed: " << SDL_GetError() << '\n';
SDL_DestroyGPUDevice(device_);
device_ = nullptr;
return false;
}
SDL_SetGPUSwapchainParameters(device_, window_, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, bestPresentMode(vsync_));
}
// 3. Create scene texture (game resolution)
SDL_GPUTextureCreateInfo tex_info = {};
tex_info.type = SDL_GPU_TEXTURETYPE_2D;
tex_info.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
tex_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
tex_info.width = static_cast<Uint32>(game_width_);
tex_info.height = static_cast<Uint32>(game_height_);
tex_info.layer_count_or_depth = 1;
tex_info.num_levels = 1;
scene_texture_ = SDL_CreateGPUTexture(device_, &tex_info);
if (scene_texture_ == nullptr) {
std::cout << "SDL3GPUShader: failed to create scene texture: " << SDL_GetError() << '\n';
cleanup();
return false;
}
// 4. Create upload transfer buffer (game resolution)
SDL_GPUTransferBufferCreateInfo tb_info = {};
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
tb_info.size = static_cast<Uint32>(game_width_ * game_height_ * 4);
upload_buffer_ = SDL_CreateGPUTransferBuffer(device_, &tb_info);
if (upload_buffer_ == nullptr) {
std::cout << "SDL3GPUShader: failed to create upload buffer: " << SDL_GetError() << '\n';
cleanup();
return false;
}
// 5. Create nearest sampler
SDL_GPUSamplerCreateInfo samp_info = {};
samp_info.min_filter = SDL_GPU_FILTER_NEAREST;
samp_info.mag_filter = SDL_GPU_FILTER_NEAREST;
samp_info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;
samp_info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
samp_info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
samp_info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
sampler_ = SDL_CreateGPUSampler(device_, &samp_info);
if (sampler_ == nullptr) {
std::cout << "SDL3GPUShader: failed to create sampler: " << SDL_GetError() << '\n';
cleanup();
return false;
}
// 6. Create pipelines
if (!createPipeline()) {
cleanup();
return false;
}
if (!createCrtPiPipeline()) {
cleanup();
return false;
}
is_initialized_ = true;
std::cout << "SDL3GPUShader: initialized OK (" << game_width_ << "x" << game_height_ << ")" << '\n';
return true;
}
// ---------------------------------------------------------------------------
// createPipeline — PostFX → swapchain
// ---------------------------------------------------------------------------
auto SDL3GPUShader::createPipeline() -> bool {
const SDL_GPUTextureFormat SWAPCHAIN_FMT = SDL_GetGPUSwapchainTextureFormat(device_, window_);
#ifdef __APPLE__
SDL_GPUShader* vert = createShaderMSL(device_, Msl::kPostfxVert, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
SDL_GPUShader* frag = createShaderMSL(device_, Msl::kPostfxFrag, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
#else
SDL_GPUShader* vert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
SDL_GPUShader* frag = createShaderSPIRV(device_, kpostfx_frag_spv, kpostfx_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
#endif
if ((vert == nullptr) || (frag == nullptr)) {
std::cout << "SDL3GPUShader: failed to compile PostFX shaders" << '\n';
if (vert != nullptr) { SDL_ReleaseGPUShader(device_, vert); }
if (frag != nullptr) { SDL_ReleaseGPUShader(device_, frag); }
return false;
}
SDL_GPUColorTargetBlendState no_blend = {};
no_blend.enable_blend = false;
no_blend.enable_color_write_mask = false;
SDL_GPUColorTargetDescription color_target = {};
color_target.format = SWAPCHAIN_FMT;
color_target.blend_state = no_blend;
SDL_GPUVertexInputState no_input = {};
SDL_GPUGraphicsPipelineCreateInfo pipe_info = {};
pipe_info.vertex_shader = vert;
pipe_info.fragment_shader = frag;
pipe_info.vertex_input_state = no_input;
pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
pipe_info.target_info.num_color_targets = 1;
pipe_info.target_info.color_target_descriptions = &color_target;
pipeline_ = SDL_CreateGPUGraphicsPipeline(device_, &pipe_info);
SDL_ReleaseGPUShader(device_, vert);
SDL_ReleaseGPUShader(device_, frag);
if (pipeline_ == nullptr) {
std::cout << "SDL3GPUShader: PostFX pipeline creation failed: " << SDL_GetError() << '\n';
return false;
}
return true;
}
// ---------------------------------------------------------------------------
// createCrtPiPipeline
// ---------------------------------------------------------------------------
auto SDL3GPUShader::createCrtPiPipeline() -> bool {
const SDL_GPUTextureFormat SWAPCHAIN_FMT = SDL_GetGPUSwapchainTextureFormat(device_, window_);
#ifdef __APPLE__
SDL_GPUShader* vert = createShaderMSL(device_, Msl::kPostfxVert, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
SDL_GPUShader* frag = createShaderMSL(device_, Msl::kCrtpiFrag, "crtpi_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
#else
SDL_GPUShader* vert = createShaderSPIRV(device_, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
SDL_GPUShader* frag = createShaderSPIRV(device_, kcrtpi_frag_spv, kcrtpi_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
#endif
if ((vert == nullptr) || (frag == nullptr)) {
if (vert != nullptr) { SDL_ReleaseGPUShader(device_, vert); }
if (frag != nullptr) { SDL_ReleaseGPUShader(device_, frag); }
return false;
}
SDL_GPUColorTargetBlendState no_blend = {};
SDL_GPUColorTargetDescription ct = {};
ct.format = SWAPCHAIN_FMT;
ct.blend_state = no_blend;
SDL_GPUVertexInputState no_input = {};
SDL_GPUGraphicsPipelineCreateInfo pi = {};
pi.vertex_shader = vert;
pi.fragment_shader = frag;
pi.vertex_input_state = no_input;
pi.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
pi.target_info.num_color_targets = 1;
pi.target_info.color_target_descriptions = &ct;
crtpi_pipeline_ = SDL_CreateGPUGraphicsPipeline(device_, &pi);
SDL_ReleaseGPUShader(device_, vert);
SDL_ReleaseGPUShader(device_, frag);
if (crtpi_pipeline_ == nullptr) {
std::cout << "SDL3GPUShader: CrtPi pipeline creation failed: " << SDL_GetError() << '\n';
return false;
}
return true;
}
// ---------------------------------------------------------------------------
// uploadPixels — direct memcpy
// ---------------------------------------------------------------------------
void SDL3GPUShader::uploadPixels(const Uint32* pixels, int width, int height) {
if (!is_initialized_ || (upload_buffer_ == nullptr)) { return; }
void* mapped = SDL_MapGPUTransferBuffer(device_, upload_buffer_, false);
if (mapped == nullptr) {
std::cout << "SDL3GPUShader: SDL_MapGPUTransferBuffer failed: " << SDL_GetError() << '\n';
return;
}
std::memcpy(mapped, pixels, static_cast<size_t>(width) * height * 4);
SDL_UnmapGPUTransferBuffer(device_, upload_buffer_);
}
// ---------------------------------------------------------------------------
// render — CrtPi direct OR PostFX direct → swapchain
// ---------------------------------------------------------------------------
void SDL3GPUShader::render() {
if (!is_initialized_) { return; }
SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(device_);
if (cmd == nullptr) {
std::cout << "SDL3GPUShader: SDL_AcquireGPUCommandBuffer failed: " << SDL_GetError() << '\n';
return;
}
// ---- Copy pass: transfer buffer → scene texture ----
SDL_GPUCopyPass* copy = SDL_BeginGPUCopyPass(cmd);
if (copy != nullptr) {
SDL_GPUTextureTransferInfo src = {};
src.transfer_buffer = upload_buffer_;
src.offset = 0;
src.pixels_per_row = static_cast<Uint32>(game_width_);
src.rows_per_layer = static_cast<Uint32>(game_height_);
SDL_GPUTextureRegion dst = {};
dst.texture = scene_texture_;
dst.w = static_cast<Uint32>(game_width_);
dst.h = static_cast<Uint32>(game_height_);
dst.d = 1;
SDL_UploadToGPUTexture(copy, &src, &dst, false);
SDL_EndGPUCopyPass(copy);
}
// ---- Acquire swapchain texture ----
SDL_GPUTexture* swapchain = nullptr;
Uint32 sw = 0;
Uint32 sh = 0;
if (!SDL_AcquireGPUSwapchainTexture(cmd, window_, &swapchain, &sw, &sh)) {
std::cout << "SDL3GPUShader: SDL_AcquireGPUSwapchainTexture failed: " << SDL_GetError() << '\n';
SDL_SubmitGPUCommandBuffer(cmd);
return;
}
if (swapchain == nullptr) {
SDL_SubmitGPUCommandBuffer(cmd);
return;
}
// ---- Viewport calculation ----
float vx = 0.0F;
float vy = 0.0F;
float vw = 0.0F;
float vh = 0.0F;
if (integer_scale_) {
const int SCALE = std::max(1, std::min(static_cast<int>(sw) / game_width_, static_cast<int>(sh) / game_height_));
vw = static_cast<float>(game_width_ * SCALE);
vh = static_cast<float>(game_height_ * SCALE);
} else {
const float SCALE = std::min(
static_cast<float>(sw) / static_cast<float>(game_width_),
static_cast<float>(sh) / static_cast<float>(game_height_));
vw = static_cast<float>(game_width_) * SCALE;
vh = static_cast<float>(game_height_) * SCALE;
}
vx = std::floor((static_cast<float>(sw) - vw) * 0.5F);
vy = std::floor((static_cast<float>(sh) - vh) * 0.5F);
uniforms_.pixel_scale = (game_height_ > 0) ? (vh / static_cast<float>(game_height_)) : 1.0F;
uniforms_.time = static_cast<float>(SDL_GetTicks()) / 1000.0F;
SDL_GPUColorTargetInfo ct = {};
ct.texture = swapchain;
ct.load_op = SDL_GPU_LOADOP_CLEAR;
ct.store_op = SDL_GPU_STOREOP_STORE;
ct.clear_color = {.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &ct, 1, nullptr);
if (pass != nullptr) {
SDL_GPUViewport vp = {.x = vx, .y = vy, .w = vw, .h = vh, .min_depth = 0.0F, .max_depth = 1.0F};
SDL_SetGPUViewport(pass, &vp);
SDL_GPUTextureSamplerBinding binding = {};
binding.texture = scene_texture_;
binding.sampler = sampler_;
SDL_BindGPUFragmentSamplers(pass, 0, &binding, 1);
if (active_shader_ == ShaderType::CRTPI && crtpi_pipeline_ != nullptr) {
SDL_BindGPUGraphicsPipeline(pass, crtpi_pipeline_);
crtpi_uniforms_.texture_width = static_cast<float>(game_width_);
crtpi_uniforms_.texture_height = static_cast<float>(game_height_);
SDL_PushGPUFragmentUniformData(cmd, 0, &crtpi_uniforms_, sizeof(CrtPiUniforms));
} else {
SDL_BindGPUGraphicsPipeline(pass, pipeline_);
SDL_PushGPUFragmentUniformData(cmd, 0, &uniforms_, sizeof(PostFXUniforms));
}
SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0);
SDL_EndGPURenderPass(pass);
}
SDL_SubmitGPUCommandBuffer(cmd);
}
// ---------------------------------------------------------------------------
// cleanup
// ---------------------------------------------------------------------------
void SDL3GPUShader::cleanup() {
is_initialized_ = false;
if (device_ != nullptr) {
SDL_WaitForGPUIdle(device_);
auto release_pipeline = [this](SDL_GPUGraphicsPipeline*& p) {
if (p != nullptr) {
SDL_ReleaseGPUGraphicsPipeline(device_, p);
p = nullptr;
}
};
auto release_texture = [this](SDL_GPUTexture*& t) {
if (t != nullptr) {
SDL_ReleaseGPUTexture(device_, t);
t = nullptr;
}
};
release_pipeline(pipeline_);
release_pipeline(crtpi_pipeline_);
release_texture(scene_texture_);
if (upload_buffer_ != nullptr) {
SDL_ReleaseGPUTransferBuffer(device_, upload_buffer_);
upload_buffer_ = nullptr;
}
if (sampler_ != nullptr) {
SDL_ReleaseGPUSampler(device_, sampler_);
sampler_ = nullptr;
}
}
}
// ---------------------------------------------------------------------------
// destroy
// ---------------------------------------------------------------------------
void SDL3GPUShader::destroy() {
cleanup();
if (device_ != nullptr) {
if (window_ != nullptr) {
SDL_ReleaseWindowFromGPUDevice(device_, window_);
}
SDL_DestroyGPUDevice(device_);
device_ = nullptr;
}
window_ = nullptr;
}
// ---------------------------------------------------------------------------
// Shader creation helpers
// ---------------------------------------------------------------------------
auto SDL3GPUShader::createShaderMSL(SDL_GPUDevice* device,
const char* msl_source,
const char* entrypoint,
SDL_GPUShaderStage stage,
Uint32 num_samplers,
Uint32 num_uniform_buffers) -> SDL_GPUShader* {
SDL_GPUShaderCreateInfo info = {};
info.code = reinterpret_cast<const Uint8*>(msl_source);
info.code_size = std::strlen(msl_source) + 1;
info.entrypoint = entrypoint;
info.format = SDL_GPU_SHADERFORMAT_MSL;
info.stage = stage;
info.num_samplers = num_samplers;
info.num_uniform_buffers = num_uniform_buffers;
SDL_GPUShader* shader = SDL_CreateGPUShader(device, &info);
if (shader == nullptr) {
std::cout << "SDL3GPUShader: MSL shader '" << entrypoint << "' failed: " << SDL_GetError() << '\n';
}
return shader;
}
auto SDL3GPUShader::createShaderSPIRV(SDL_GPUDevice* device, // NOLINT(readability-convert-member-functions-to-static)
const uint8_t* spv_code,
size_t spv_size,
const char* entrypoint,
SDL_GPUShaderStage stage,
Uint32 num_samplers,
Uint32 num_uniform_buffers) -> SDL_GPUShader* {
SDL_GPUShaderCreateInfo info = {};
info.code = spv_code;
info.code_size = spv_size;
info.entrypoint = entrypoint;
info.format = SDL_GPU_SHADERFORMAT_SPIRV;
info.stage = stage;
info.num_samplers = num_samplers;
info.num_uniform_buffers = num_uniform_buffers;
SDL_GPUShader* shader = SDL_CreateGPUShader(device, &info);
if (shader == nullptr) {
std::cout << "SDL3GPUShader: SPIRV shader '" << entrypoint << "' failed: " << SDL_GetError() << '\n';
}
return shader;
}
void SDL3GPUShader::setPostFXParams(const PostFXParams& p) {
uniforms_.vignette_strength = p.vignette;
uniforms_.chroma_min = p.chroma_min;
uniforms_.chroma_max = p.chroma_max;
uniforms_.scanline_strength = p.scanlines;
uniforms_.mask_strength = p.mask;
uniforms_.gamma_strength = p.gamma;
uniforms_.curvature = p.curvature;
uniforms_.bleeding = p.bleeding;
uniforms_.flicker = p.flicker;
uniforms_.scan_dark_ratio = p.scan_dark_ratio;
uniforms_.scan_dark_floor = p.scan_dark_floor;
uniforms_.scan_edge_soft = p.scan_edge_soft;
}
void SDL3GPUShader::setCrtPiParams(const CrtPiParams& p) {
crtpi_uniforms_.scanline_weight = p.scanline_weight;
crtpi_uniforms_.scanline_gap_brightness = p.scanline_gap_brightness;
crtpi_uniforms_.bloom_factor = p.bloom_factor;
crtpi_uniforms_.input_gamma = p.input_gamma;
crtpi_uniforms_.output_gamma = p.output_gamma;
crtpi_uniforms_.mask_brightness = p.mask_brightness;
crtpi_uniforms_.curvature_x = p.curvature_x;
crtpi_uniforms_.curvature_y = p.curvature_y;
crtpi_uniforms_.mask_type = p.mask_type;
crtpi_uniforms_.enable_scanlines = p.enable_scanlines ? 1 : 0;
crtpi_uniforms_.enable_multisample = p.enable_multisample ? 1 : 0;
crtpi_uniforms_.enable_gamma = p.enable_gamma ? 1 : 0;
crtpi_uniforms_.enable_curvature = p.enable_curvature ? 1 : 0;
crtpi_uniforms_.enable_sharper = p.enable_sharper ? 1 : 0;
}
void SDL3GPUShader::setActiveShader(ShaderType type) {
active_shader_ = type;
}
auto SDL3GPUShader::bestPresentMode(bool vsync) const -> SDL_GPUPresentMode {
if (vsync) { return SDL_GPU_PRESENTMODE_VSYNC; }
if (SDL_WindowSupportsGPUPresentMode(device_, window_, SDL_GPU_PRESENTMODE_IMMEDIATE)) {
return SDL_GPU_PRESENTMODE_IMMEDIATE;
}
if (SDL_WindowSupportsGPUPresentMode(device_, window_, SDL_GPU_PRESENTMODE_MAILBOX)) {
return SDL_GPU_PRESENTMODE_MAILBOX;
}
return SDL_GPU_PRESENTMODE_VSYNC;
}
void SDL3GPUShader::setVSync(bool vsync) {
vsync_ = vsync;
if (device_ != nullptr && window_ != nullptr) {
SDL_SetGPUSwapchainParameters(device_, window_, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, bestPresentMode(vsync_));
}
}
void SDL3GPUShader::setScaleMode(bool integer_scale) {
integer_scale_ = integer_scale;
}
// ---------------------------------------------------------------------------
// reinitTexturesAndBuffer
// ---------------------------------------------------------------------------
auto SDL3GPUShader::reinitTexturesAndBuffer() -> bool {
if (device_ == nullptr) { return false; }
SDL_WaitForGPUIdle(device_);
if (scene_texture_ != nullptr) {
SDL_ReleaseGPUTexture(device_, scene_texture_);
scene_texture_ = nullptr;
}
if (upload_buffer_ != nullptr) {
SDL_ReleaseGPUTransferBuffer(device_, upload_buffer_);
upload_buffer_ = nullptr;
}
uniforms_.screen_height = static_cast<float>(game_height_);
SDL_GPUTextureCreateInfo tex_info = {};
tex_info.type = SDL_GPU_TEXTURETYPE_2D;
tex_info.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
tex_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
tex_info.width = static_cast<Uint32>(game_width_);
tex_info.height = static_cast<Uint32>(game_height_);
tex_info.layer_count_or_depth = 1;
tex_info.num_levels = 1;
scene_texture_ = SDL_CreateGPUTexture(device_, &tex_info);
if (scene_texture_ == nullptr) {
std::cout << "SDL3GPUShader: reinit — failed to create scene texture: " << SDL_GetError() << '\n';
return false;
}
SDL_GPUTransferBufferCreateInfo tb_info = {};
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
tb_info.size = static_cast<Uint32>(game_width_ * game_height_ * 4);
upload_buffer_ = SDL_CreateGPUTransferBuffer(device_, &tb_info);
if (upload_buffer_ == nullptr) {
std::cout << "SDL3GPUShader: reinit — failed to create upload buffer: " << SDL_GetError() << '\n';
SDL_ReleaseGPUTexture(device_, scene_texture_);
scene_texture_ = nullptr;
return false;
}
std::cout << "SDL3GPUShader: reinit — scene " << game_width_ << "x" << game_height_ << '\n';
return true;
}
} // namespace Rendering
@@ -4,25 +4,32 @@
#include <SDL3/SDL_gpu.h>
#include <string>
#include <utility>
#include "rendering/shader_backend.hpp"
#include "core/rendering/shader_backend.hpp"
// PostFX uniforms pushed to fragment stage each frame.
// 12 floats = 48 bytes — meets Metal/Vulkan 16-byte alignment requirement.
// 16 floats = 64 bytes (4 × vec4) — std140/scalar layout for Vulkan + Metal.
struct PostFXUniforms {
// vec4 #0
float vignette_strength;
float chroma_strength;
float chroma_min;
float scanline_strength;
float screen_height;
// vec4 #1
float mask_strength;
float gamma_strength;
float curvature;
float bleeding;
// vec4 #2
float pixel_scale;
float time;
float oversample;
float flicker;
float chroma_max;
// vec4 #3 — paràmetres de scanlines exposats al preset YAML
float scan_dark_ratio;
float scan_dark_floor;
float scan_edge_soft;
float pad3;
};
// CrtPi uniforms pushed to fragment stage each frame.
@@ -46,22 +53,13 @@ struct CrtPiUniforms {
float texture_height;
};
// Downscale uniforms for Lanczos downscale fragment stage.
// 1 int + 3 floats = 16 bytes.
struct DownscaleUniforms {
int algorithm;
float pad0;
float pad1;
float pad2;
};
namespace Rendering {
/**
* @brief Backend de shaders usando SDL3 GPU API (Metal en macOS, Vulkan/SPIR-V en Win/Linux)
*
* Pipeline: Surface pixels (CPU) SDL_GPUTransferBuffer SDL_GPUTexture (scene)
* [Upscale ] PostFX/CrtPi render pass [Lanczos downscale ] swapchain present
* PostFX/CrtPi render pass swapchain present
*/
class SDL3GPUShader : public ShaderBackend {
public:
@@ -85,13 +83,6 @@ namespace Rendering {
void setPostFXParams(const PostFXParams& p) override;
void setVSync(bool vsync) override;
void setScaleMode(bool integer_scale) override;
void setOversample(int factor) override;
void setLinearUpscale(bool linear) override;
[[nodiscard]] auto isLinearUpscale() const -> bool override { return linear_upscale_; }
void setDownscaleAlgo(int algo) override;
[[nodiscard]] auto getDownscaleAlgo() const -> int override { return downscale_algo_; }
[[nodiscard]] auto getSsTextureSize() const -> std::pair<int, int> override;
void setActiveShader(ShaderType type) override;
void setCrtPiParams(const CrtPiParams& p) override;
@@ -116,39 +107,27 @@ namespace Rendering {
auto createPipeline() -> bool;
auto createCrtPiPipeline() -> bool;
auto reinitTexturesAndBuffer() -> bool;
auto recreateScaledTexture(int factor) -> bool;
static auto calcSsFactor(float zoom) -> int;
[[nodiscard]] auto bestPresentMode(bool vsync) const -> SDL_GPUPresentMode;
SDL_Window* window_ = nullptr;
SDL_GPUDevice* device_ = nullptr;
SDL_GPUGraphicsPipeline* pipeline_ = nullptr;
SDL_GPUGraphicsPipeline* crtpi_pipeline_ = nullptr;
SDL_GPUGraphicsPipeline* postfx_offscreen_pipeline_ = nullptr;
SDL_GPUGraphicsPipeline* upscale_pipeline_ = nullptr;
SDL_GPUGraphicsPipeline* downscale_pipeline_ = nullptr;
SDL_GPUTexture* scene_texture_ = nullptr;
SDL_GPUTexture* scaled_texture_ = nullptr;
SDL_GPUTexture* postfx_texture_ = nullptr;
SDL_GPUTransferBuffer* upload_buffer_ = nullptr;
SDL_GPUSampler* sampler_ = nullptr;
SDL_GPUSampler* linear_sampler_ = nullptr;
PostFXUniforms uniforms_{.vignette_strength = 0.6F, .chroma_strength = 0.15F, .scanline_strength = 0.7F, .screen_height = 192.0F, .pixel_scale = 1.0F, .oversample = 1.0F};
PostFXUniforms uniforms_{.vignette_strength = 0.6F, .chroma_min = 0.15F, .scanline_strength = 0.7F, .screen_height = 192.0F, .pixel_scale = 1.0F, .chroma_max = 0.15F, .scan_dark_ratio = 0.333F, .scan_dark_floor = 0.42F, .scan_edge_soft = 1.0F};
CrtPiUniforms crtpi_uniforms_{.scanline_weight = 6.0F, .scanline_gap_brightness = 0.12F, .bloom_factor = 3.5F, .input_gamma = 2.4F, .output_gamma = 2.2F, .mask_brightness = 0.80F, .curvature_x = 0.05F, .curvature_y = 0.10F, .mask_type = 2, .enable_scanlines = 1, .enable_multisample = 1, .enable_gamma = 1};
ShaderType active_shader_ = ShaderType::POSTFX;
int game_width_ = 0;
int game_height_ = 0;
int ss_factor_ = 0;
int oversample_ = 1;
int downscale_algo_ = 1;
std::string driver_name_;
std::string preferred_driver_;
bool is_initialized_ = false;
bool vsync_ = true;
bool integer_scale_ = false;
bool linear_upscale_ = false;
};
} // namespace Rendering
@@ -0,0 +1,2 @@
DisableFormat: true
SortIncludes: Never
@@ -0,0 +1,4 @@
# source/core/rendering/sdl3gpu/spv/.clang-tidy
Checks: '-*'
WarningsAsErrors: ''
HeaderFilterRegex: ''
@@ -10357,5 +10357,6 @@ static const uint8_t kcrtpi_frag_spv[] = {
0x38,
0x00,
0x01,
0x00};
0x00,
};
static const size_t kcrtpi_frag_spv_size = 10356;
@@ -1445,5 +1445,6 @@ static const uint8_t kpostfx_vert_spv[] = {
0x38,
0x00,
0x01,
0x00};
0x00,
};
static const size_t kpostfx_vert_spv_size = 1444;
@@ -2,13 +2,13 @@
#include <SDL3/SDL.h>
#include <cstdint>
#include <string>
#include <utility>
namespace Rendering {
/** @brief Identificador del shader de post-procesado activo */
enum class ShaderType { POSTFX,
enum class ShaderType : std::uint8_t { POSTFX,
CRTPI };
/**
@@ -17,12 +17,19 @@ namespace Rendering {
struct PostFXParams {
float vignette = 0.0F;
float scanlines = 0.0F;
float chroma = 0.0F;
// Aberració cromàtica — varia entre min i max via sinusoidal; si coincideixen
// queda estàtica. Permet un "floor" perquè la imatge mai sigui lliure de chroma.
float chroma_min = 0.0F;
float chroma_max = 0.0F;
float mask = 0.0F;
float gamma = 0.0F;
float curvature = 0.0F;
float bleeding = 0.0F;
float flicker = 0.0F;
// Forma de les scanlines — 3 subpíxels per fila lògica per defecte.
float scan_dark_ratio{0.333F};
float scan_dark_floor{0.42F};
float scan_edge_soft{1.0F};
};
/**
@@ -65,15 +72,6 @@ namespace Rendering {
virtual void setPostFXParams(const PostFXParams& /*p*/) {}
virtual void setVSync(bool /*vsync*/) {}
virtual void setScaleMode(bool /*integer_scale*/) {}
virtual void setOversample(int /*factor*/) {}
virtual void setLinearUpscale(bool /*linear*/) {}
[[nodiscard]] virtual auto isLinearUpscale() const -> bool { return false; }
virtual void setDownscaleAlgo(int /*algo*/) {}
[[nodiscard]] virtual auto getDownscaleAlgo() const -> int { return 0; }
[[nodiscard]] virtual auto getSsTextureSize() const -> std::pair<int, int> { return {0, 0}; }
[[nodiscard]] virtual auto isHardwareAccelerated() const -> bool = 0;
@@ -1,17 +1,17 @@
#include "animated_sprite.hpp"
#include "core/rendering/sprite/animated_sprite.hpp"
#include <SDL3/SDL.h> // Para SDL_LogWarn, SDL_LogCategory, SDL_LogError, SDL_FRect
#include <algorithm> // Para min
#include <cstddef> // Para size_t
#include <fstream> // Para basic_istream, basic_ifstream, istream, basic_ios, ifstream, istringstream, stringstream
#include <iostream> // Para std::cout
#include <sstream> // Para basic_istringstream, basic_stringstream
#include <stdexcept> // Para runtime_error
#include <utility> // Para move, pair
#include "resource_helper.hpp" // Para loadFile
#include "texture.hpp" // Para Texture
#include "ui/logger.hpp" // Para dots
#include "core/rendering/texture.hpp" // Para Texture
#include "core/resources/resource_helper.hpp" // Para loadFile
// Carga las animaciones en un vector(Animations) desde un fichero
auto loadAnimationsFromFile(const std::string& file_path) -> AnimationsFileBuffer {
@@ -31,15 +31,13 @@ auto loadAnimationsFromFile(const std::string& file_path) -> AnimationsFileBuffe
if (!using_resource_data) {
file.open(file_path);
if (!file) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Fichero no encontrado %s", file_path.c_str());
std::cout << "Error: Fichero no encontrado " << file_path << '\n';
throw std::runtime_error("Fichero no encontrado: " + file_path);
}
}
std::istream& input_stream = using_resource_data ? stream : static_cast<std::istream&>(file);
Logger::dots("Animation : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]");
std::vector<std::string> buffer;
std::string line;
while (std::getline(input_stream, line)) {
@@ -82,7 +80,7 @@ auto AnimatedSprite::getAnimationIndex(const std::string& name) -> int {
}
// Si no se encuentra, muestra una advertencia y devuelve -1
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "** Warning: could not find \"%s\" animation", name.c_str());
std::cout << "** Warning: could not find \"" << name << "\" animation" << '\n';
return -1;
}
@@ -219,7 +217,7 @@ void AnimatedSprite::processConfigLine(const std::string& line, AnimationConfig&
config.frame_height = value;
updateFrameCalculations(config);
} else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Warning: unknown parameter %s", key.c_str());
std::cout << "Warning: unknown parameter " << key << '\n';
}
}
@@ -275,7 +273,7 @@ void AnimatedSprite::processAnimationParameter(const std::string& line, Animatio
} else if (key == "frames") {
parseFramesParameter(value, animation, config);
} else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Warning: unknown parameter %s", key.c_str());
std::cout << "Warning: unknown parameter " << key << '\n';
}
}
@@ -9,7 +9,7 @@
#include <utility> // Para move
#include <vector> // Para vector
#include "moving_sprite.hpp" // for MovingSprite
#include "core/rendering/sprite/moving_sprite.hpp" // for MovingSprite
// Declaración adelantada
class Texture;
@@ -1,11 +1,11 @@
#include "card_sprite.hpp"
#include "core/rendering/sprite/card_sprite.hpp"
#include <algorithm> // Para std::clamp
#include <functional> // Para function
#include <utility> // Para move
#include "texture.hpp" // Para Texture
#include "utils.hpp" // Para easeOutBounce, easeOutCubic
#include "core/rendering/texture.hpp" // Para Texture
#include "utils/utils.hpp" // Para easeOutBounce, easeOutCubic
// Constructor
CardSprite::CardSprite(std::shared_ptr<Texture> texture)
@@ -31,7 +31,7 @@ auto CardSprite::enable() -> bool {
// Ángulo inicial
rotate_.angle = start_angle_;
rotate_.center = {pos_.w / 2.0F, pos_.h / 2.0F};
rotate_.center = {.x = pos_.w / 2.0F, .y = pos_.h / 2.0F};
shadow_visible_ = true;
return true;
@@ -55,7 +55,7 @@ void CardSprite::startExit() {
// Rotación continua
rotate_.enabled = true;
rotate_.amount = exit_rotate_amount_;
rotate_.center = {pos_.w / 2.0F, pos_.h / 2.0F};
rotate_.center = {.x = pos_.w / 2.0F, .y = pos_.h / 2.0F};
}
// Actualiza según el estado
@@ -80,7 +80,7 @@ void CardSprite::updateEntering(float delta_time) {
double eased = entry_easing_(static_cast<double>(progress));
// Zoom: de start_zoom_ a 1.0 con rebote
auto current_zoom = static_cast<float>(start_zoom_ + (1.0 - start_zoom_) * eased);
auto current_zoom = static_cast<float>(start_zoom_ + ((1.0 - start_zoom_) * eased));
horizontal_zoom_ = current_zoom;
vertical_zoom_ = current_zoom;
@@ -90,8 +90,8 @@ void CardSprite::updateEntering(float delta_time) {
// Posición: de entry_start a landing con easing suave (sin rebote)
// Usamos easeOutCubic para que el desplazamiento sea fluido
double pos_eased = easeOutCubic(static_cast<double>(progress));
auto current_x = static_cast<float>(entry_start_x_ + (landing_x_ - entry_start_x_) * pos_eased);
auto current_y = static_cast<float>(entry_start_y_ + (landing_y_ - entry_start_y_) * pos_eased);
auto current_x = static_cast<float>(entry_start_x_ + ((landing_x_ - entry_start_x_) * pos_eased));
auto current_y = static_cast<float>(entry_start_y_ + ((landing_y_ - entry_start_y_) * pos_eased));
setPos(current_x, current_y);
// Detecta el primer toque (cuando el easing alcanza ~1.0 por primera vez)
@@ -117,12 +117,9 @@ void CardSprite::updateExiting(float delta_time) {
// Ganar altura gradualmente (zoom hacia el objetivo)
if (exit_zoom_speed_ > 0.0F && horizontal_zoom_ < exit_target_zoom_) {
float new_zoom = horizontal_zoom_ + exit_zoom_speed_ * delta_time;
if (new_zoom > exit_target_zoom_) {
new_zoom = exit_target_zoom_;
}
horizontal_zoom_ = new_zoom;
vertical_zoom_ = new_zoom;
const float NEW_ZOOM = std::min(horizontal_zoom_ + (exit_zoom_speed_ * delta_time), exit_target_zoom_);
horizontal_zoom_ = NEW_ZOOM;
vertical_zoom_ = NEW_ZOOM;
}
if (isOffScreen()) {
@@ -164,8 +161,8 @@ void CardSprite::renderShadow() {
// Offset respecto a la tarjeta: base + extra proporcional a la altura
// La sombra se aleja en diagonal abajo-derecha (opuesta a la luz en 0,0)
float offset_x = shadow_offset_x_ + height * SHADOW_HEIGHT_MULTIPLIER;
float offset_y = shadow_offset_y_ + height * SHADOW_HEIGHT_MULTIPLIER;
float offset_x = shadow_offset_x_ + (height * SHADOW_HEIGHT_MULTIPLIER);
float offset_y = shadow_offset_y_ + (height * SHADOW_HEIGHT_MULTIPLIER);
shadow_texture_->render(
pos_.x + offset_x,
@@ -2,15 +2,16 @@
#include <SDL3/SDL.h> // Para SDL_FPoint
#include <cstdint> // Para std::uint8_t
#include <functional> // Para function
#include <memory> // Para shared_ptr
#include "moving_sprite.hpp" // Para MovingSprite
#include "core/rendering/sprite/moving_sprite.hpp" // Para MovingSprite
class Texture;
// --- Estados de la tarjeta ---
enum class CardState {
enum class CardState : std::uint8_t {
IDLE, // No activada todavía
ENTERING, // Animación de entrada (zoom + rotación + desplazamiento con rebote)
LANDED, // En reposo sobre la mesa
@@ -1,9 +1,9 @@
#include "moving_sprite.hpp"
#include "core/rendering/sprite/moving_sprite.hpp"
#include <cmath> // Para std::abs
#include <utility>
#include "texture.hpp" // Para Texture
#include "core/rendering/texture.hpp" // Para Texture
// Constructor
MovingSprite::MovingSprite(std::shared_ptr<Texture> texture, SDL_FRect pos, Rotate rotate, float horizontal_zoom, float vertical_zoom, SDL_FlipMode flip)
@@ -4,7 +4,7 @@
#include <memory> // Para shared_ptr
#include "sprite.hpp" // for Sprite
#include "core/rendering/sprite/sprite.hpp" // for Sprite
class Texture;
@@ -1,5 +1,5 @@
// IWYU pragma: no_include <bits/std_abs.h>
#include "path_sprite.hpp"
#include "core/rendering/sprite/path_sprite.hpp"
#include <cmath> // Para abs
#include <functional> // Para function
@@ -2,22 +2,23 @@
#include <SDL3/SDL.h> // Para SDL_FPoint
#include <cstdint> // Para std::uint8_t
#include <functional> // Para std::function
#include <memory> // Para shared_ptr
#include <utility>
#include <vector> // Para vector
#include "sprite.hpp" // Para Sprite
#include "core/rendering/sprite/sprite.hpp" // Para Sprite
class Texture;
// --- Enums ---
enum class PathType { // Tipos de recorrido
enum class PathType : std::uint8_t { // Tipos de recorrido
VERTICAL,
HORIZONTAL,
};
enum class PathCentered { // Centrado del recorrido
enum class PathCentered : std::uint8_t { // Centrado del recorrido
ON_X,
ON_Y,
NONE,
@@ -25,12 +26,12 @@ enum class PathCentered { // Centrado del recorrido
// --- Estructuras ---
struct Path { // Define un recorrido para el sprite
float start_pos; // Posición inicial
float end_pos; // Posición final
PathType type; // Tipo de movimiento (horizontal/vertical)
float fixed_pos; // Posición fija en el eje contrario
float duration_s; // Duración de la animación en segundos
float waiting_time_s; // Tiempo de espera una vez en el destino
float start_pos = 0.0F; // Posición inicial
float end_pos = 0.0F; // Posición final
PathType type = PathType::HORIZONTAL; // Tipo de movimiento (horizontal/vertical)
float fixed_pos = 0.0F; // Posición fija en el eje contrario
float duration_s = 0.0F; // Duración de la animación en segundos
float waiting_time_s = 0.0F; // Tiempo de espera una vez en el destino
std::function<double(double)> easing_function; // Función de easing
float elapsed_time = 0.0F; // Tiempo transcurrido
float waiting_elapsed = 0.0F; // Tiempo de espera transcurrido
@@ -1,6 +1,4 @@
#include "smart_sprite.hpp"
#include "moving_sprite.hpp" // Para MovingSprite
#include "core/rendering/sprite/smart_sprite.hpp"
// Actualiza la posición y comprueba si ha llegado a su destino (time-based)
void SmartSprite::update(float delta_time) {
@@ -3,16 +3,16 @@
#include <memory> // Para shared_ptr
#include <utility>
#include "animated_sprite.hpp" // Para AnimatedSprite
#include "core/rendering/sprite/moving_sprite.hpp" // Para MovingSprite
class Texture;
// --- Clase SmartSprite: sprite animado que se mueve hacia un destino y puede deshabilitarse automáticamente ---
class SmartSprite : public AnimatedSprite {
// --- Clase SmartSprite: sprite que se mueve hacia un destino y puede deshabilitarse automáticamente ---
class SmartSprite : public MovingSprite {
public:
// --- Constructor y destructor ---
explicit SmartSprite(std::shared_ptr<Texture> texture)
: AnimatedSprite(std::move(texture)) {}
: MovingSprite(std::move(texture)) {}
~SmartSprite() override = default;
// --- Métodos principales ---
@@ -1,9 +1,9 @@
#include "sprite.hpp"
#include "core/rendering/sprite/sprite.hpp"
#include <utility>
#include <vector> // Para vector
#include "texture.hpp" // Para Texture
#include "core/rendering/texture.hpp" // Para Texture
// Constructor
Sprite::Sprite(std::shared_ptr<Texture> texture, float pos_x, float pos_y, float width, float height)
@@ -1,4 +1,4 @@
#include "text.hpp"
#include "core/rendering/text.hpp"
#include <SDL3/SDL.h> // Para SDL_FRect, Uint8, SDL_GetRenderTarget, SDL_RenderClear, SDL_SetRenderDrawColor, SDL_SetRenderTarget, SDL_BLENDMODE_BLEND, SDL_PixelFormat, SDL_TextureAccess, SDL_GetTextureAlphaMod
@@ -10,13 +10,12 @@
#include <utility> // Para std::cmp_less_equal
#include <vector> // Para vector
#include "color.hpp" // Para Color
#include "resource_helper.hpp" // Para loadFile
#include "screen.hpp" // Para Screen
#include "sprite.hpp" // Para Sprite
#include "texture.hpp" // Para Texture
#include "ui/logger.hpp" // Para dots
#include "utils.hpp" // Para getFileName
#include "core/rendering/screen.hpp" // Para Screen
#include "core/rendering/sprite/sprite.hpp" // Para Sprite
#include "core/rendering/texture.hpp" // Para Texture
#include "core/resources/resource_helper.hpp" // Para loadFile
#include "utils/color.hpp" // Para Color
#include "utils/utils.hpp" // Para getFileName
// Constructor
Text::Text(const std::shared_ptr<Texture>& texture, const std::string& text_file) {
@@ -404,9 +403,8 @@ auto Text::loadFile(const std::string& file_path) -> std::shared_ptr<Text::File>
file.open(file_path);
}
std::istream& input_stream = using_resource_data ? stream : static_cast<std::istream&>(file);
if ((using_resource_data && stream.good()) || (!using_resource_data && file.is_open() && file.good())) {
std::istream& input_stream = using_resource_data ? stream : static_cast<std::istream&>(file);
std::string buffer;
// Lee los dos primeros valores del fichero
@@ -432,7 +430,6 @@ auto Text::loadFile(const std::string& file_path) -> std::shared_ptr<Text::File>
line_read++;
};
Logger::dots("Text File : ", getFileName(file_path), "[ LOADED ]");
// Cierra el fichero si se usó
if (!using_resource_data && file.is_open()) {
file.close();
@@ -6,8 +6,8 @@
#include <memory> // Para shared_ptr, unique_ptr
#include <string> // Para string
#include "color.hpp" // Para Color
#include "sprite.hpp" // Para Sprite
#include "core/rendering/sprite/sprite.hpp" // Para Sprite
#include "utils/color.hpp" // Para Color
class Texture;
@@ -39,7 +39,7 @@ class Text {
int kerning;
// Constructor con argumentos por defecto
Style(Uint8 flags = 0,
explicit Style(Uint8 flags = 0,
Color text = Color(),
Color shadow = Color(),
Uint8 distance = 1,
@@ -1,23 +1,23 @@
#define STB_IMAGE_IMPLEMENTATION
#include "texture.hpp"
#include "core/rendering/texture.hpp"
#include <SDL3/SDL.h> // Para SDL_LogError, SDL_LogCategory, Uint8, SDL_...
#include <cstdint> // Para uint32_t
#include <cstring> // Para memcpy
#include <fstream> // Para basic_ifstream, basic_istream, basic_ios
#include <iostream> // Para std::cout
#include <sstream> // Para basic_istringstream
#include <stdexcept> // Para runtime_error
#include <string> // Para basic_string, char_traits, operator+, string
#include <utility>
#include <vector> // Para vector
#include "color.hpp" // Para getFileName, Color, printWithDots
#include "external/gif.hpp" // Para Gif
#include "resource_helper.hpp" // Para ResourceHelper
#include "stb_image.h" // Para stbi_image_free, stbi_load, STBI_rgb_alpha
#include "ui/logger.hpp"
#include "utils.hpp"
#include "core/rendering/gif.hpp" // Para Gif
#include "core/resources/resource_helper.hpp" // Para ResourceHelper
#include "external/stb_image.h" // Para stbi_image_free, stbi_load, STBI_rgb_alpha
#include "utils/color.hpp" // Para getFileName, Color
#include "utils/utils.hpp"
// Constructor
Texture::Texture(SDL_Renderer* renderer, std::string path)
@@ -39,7 +39,7 @@ Texture::Texture(SDL_Renderer* renderer, std::string path)
surface_ = loadSurface(path_);
// Añade la propia paleta del fichero a la lista
addPaletteFromGifFile(path_, true); // Usar modo silencioso
addPaletteFromGifFile(path_);
// Crea la textura, establece el BlendMode y copia la surface a la textura
createBlank(width_, height_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING);
@@ -80,10 +80,9 @@ auto Texture::loadFromFile(const std::string& file_path) -> bool {
}
if (data == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Fichero no encontrado %s", getFileName(file_path).c_str());
std::cout << "Error: Fichero no encontrado " << getFileName(file_path) << '\n';
throw std::runtime_error("Fichero no encontrado: " + getFileName(file_path));
}
Logger::dots("Texture : ", getFileName(file_path), "[ LOADED ]");
int pitch;
SDL_PixelFormat pixel_format;
@@ -99,12 +98,12 @@ auto Texture::loadFromFile(const std::string& file_path) -> bool {
// Carga la imagen desde una ruta específica
auto* loaded_surface = SDL_CreateSurfaceFrom(width, height, pixel_format, static_cast<void*>(data), pitch);
if (loaded_surface == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to load image %s", file_path.c_str());
std::cout << "Unable to load image " << file_path << '\n';
} else {
// Crea la textura desde los pixels de la surface
new_texture = SDL_CreateTextureFromSurface(renderer_, loaded_surface);
if (new_texture == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to create texture from %s! SDL Error: %s", file_path.c_str(), SDL_GetError());
std::cout << "Unable to create texture from " << file_path << "! SDL Error: " << SDL_GetError() << '\n';
} else {
// Obtiene las dimensiones de la imagen
width_ = loaded_surface->w;
@@ -126,7 +125,7 @@ auto Texture::createBlank(int width, int height, SDL_PixelFormat format, SDL_Tex
// Crea una textura sin inicializar
texture_ = SDL_CreateTexture(renderer_, format, access, width, height);
if (texture_ == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to create blank texture! SDL Error: %s", SDL_GetError());
std::cout << "Unable to create blank texture! SDL Error: " << SDL_GetError() << '\n';
} else {
width_ = width;
height_ = height;
@@ -236,7 +235,7 @@ auto Texture::loadSurface(const std::string& file_path) -> std::shared_ptr<Surfa
// Fallback a filesystem directo
std::ifstream file(file_path, std::ios::binary | std::ios::ate);
if (!file) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Fichero no encontrado %s", file_path.c_str());
std::cout << "Error: Fichero no encontrado " << file_path << '\n';
throw std::runtime_error("Fichero no encontrado: " + file_path);
}
@@ -247,18 +246,16 @@ auto Texture::loadSurface(const std::string& file_path) -> std::shared_ptr<Surfa
// Leer el contenido del archivo en un buffer
buffer.resize(size);
if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error al leer el fichero %s", file_path.c_str());
std::cout << "Error al leer el fichero " << file_path << '\n';
throw std::runtime_error("Error al leer el fichero: " + file_path);
}
}
// Crear un objeto Gif y llamar a la función loadGif
GIF::Gif gif;
Uint16 w = 0;
Uint16 h = 0;
std::vector<Uint8> raw_pixels = gif.loadGif(buffer.data(), w, h);
std::vector<Uint8> raw_pixels = GIF::loadGif(buffer.data(), w, h);
if (raw_pixels.empty()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: No se pudo cargar el GIF %s", file_path.c_str());
std::cout << "Error: No se pudo cargar el GIF " << file_path << '\n';
return nullptr;
}
@@ -273,7 +270,6 @@ auto Texture::loadSurface(const std::string& file_path) -> std::shared_ptr<Surfa
width_ = w;
height_ = h;
SDL_LogInfo(SDL_LOG_CATEGORY_TEST, "GIF %s cargado correctamente.", file_path.c_str());
return surface;
}
@@ -302,7 +298,7 @@ void Texture::setPaletteColor(int palette, int index, Uint32 color) {
}
// Carga una paleta desde un fichero
auto Texture::loadPaletteFromFile(const std::string& file_path, bool quiet) -> Palette {
auto Texture::loadPaletteFromFile(const std::string& file_path) -> Palette {
Palette palette;
std::vector<Uint8> buffer;
@@ -315,7 +311,7 @@ auto Texture::loadPaletteFromFile(const std::string& file_path, bool quiet) -> P
// Fallback a filesystem directo
std::ifstream file(file_path, std::ios::binary | std::ios::ate);
if (!file) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Fichero no encontrado %s", file_path.c_str());
std::cout << "Error: Fichero no encontrado " << file_path << '\n';
throw std::runtime_error("Fichero no encontrado: " + file_path);
}
@@ -325,20 +321,15 @@ auto Texture::loadPaletteFromFile(const std::string& file_path, bool quiet) -> P
buffer.resize(size);
if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: No se pudo leer completamente el fichero %s", file_path.c_str());
std::cout << "Error: No se pudo leer completamente el fichero " << file_path << '\n';
throw std::runtime_error("Error al leer el fichero: " + file_path);
}
}
if (!quiet) {
Logger::dots("Palette : ", getFileName(file_path), "[ LOADED ]");
}
// Usar la nueva función loadPalette, que devuelve un vector<uint32_t>
GIF::Gif gif;
std::vector<uint32_t> pal = gif.loadPalette(buffer.data());
std::vector<uint32_t> pal = GIF::loadPalette(buffer.data());
if (pal.empty()) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Advertencia: No se encontró paleta en el archivo %s", file_path.c_str());
std::cout << "Advertencia: No se encontró paleta en el archivo " << file_path << '\n';
return palette; // Devuelve un vector vacío si no hay paleta
}
@@ -347,19 +338,18 @@ auto Texture::loadPaletteFromFile(const std::string& file_path, bool quiet) -> P
palette[i] = (pal[i] << 8) | 0xFF; // Resultado: 0xRRGGBBAA
}
SDL_LogInfo(SDL_LOG_CATEGORY_TEST, "Paleta cargada correctamente desde %s", file_path.c_str());
return palette;
}
// Añade una paleta a la lista
void Texture::addPaletteFromGifFile(const std::string& path, bool quiet) {
palettes_.emplace_back(loadPaletteFromFile(path, quiet));
void Texture::addPaletteFromGifFile(const std::string& path) {
palettes_.emplace_back(loadPaletteFromFile(path));
setPaletteColor(palettes_.size() - 1, 0, 0x00000000);
}
// Añade una paleta a la lista
void Texture::addPaletteFromPalFile(const std::string& path) {
palettes_.emplace_back(readPalFile(path, true)); // Usar modo silencioso
palettes_.emplace_back(readPalFile(path));
setPaletteColor(palettes_.size() - 1, 0, 0x00000000);
}
@@ -375,7 +365,7 @@ void Texture::setPalette(size_t palette) {
auto Texture::getRenderer() -> SDL_Renderer* { return renderer_; }
// Carga una paleta desde un archivo .pal
auto Texture::readPalFile(const std::string& file_path, bool quiet) -> Palette {
auto Texture::readPalFile(const std::string& file_path) -> Palette {
Palette palette{};
palette.fill(0); // Inicializar todo con 0 (transparente por defecto)
@@ -49,10 +49,10 @@ class Texture {
void setAlpha(Uint8 alpha); // Establece el alpha para la modulación
// --- Paletas ---
void addPaletteFromGifFile(const std::string& path, bool quiet = false); // Añade una paleta a la lista
void addPaletteFromPalFile(const std::string& path); // Añade una paleta a la lista
void setPaletteColor(int palette, int index, Uint32 color); // Establece un color de la paleta
void setPalette(size_t palette); // Cambia la paleta de la textura
void addPaletteFromGifFile(const std::string& path); // Añade una paleta a la lista
void addPaletteFromPalFile(const std::string& path); // Añade una paleta a la lista
void setPaletteColor(int palette, int index, Uint32 color); // Establece un color de la paleta
void setPalette(size_t palette); // Cambia la paleta de la textura
// --- Getters ---
[[nodiscard]] auto getWidth() const -> int; // Obtiene el ancho de la imagen
@@ -74,10 +74,10 @@ class Texture {
int current_palette_ = 0; // Índice de la paleta en uso
// --- Métodos internos ---
auto loadSurface(const std::string& file_path) -> std::shared_ptr<Surface>; // Crea una surface desde un fichero .gif
void flipSurface(); // Vuelca la surface en la textura
static auto loadPaletteFromFile(const std::string& file_path, bool quiet = false) -> Palette; // Carga una paleta desde un fichero
void unloadTexture(); // Libera la memoria de la textura
void unloadSurface(); // Libera la surface actual
static auto readPalFile(const std::string& file_path, bool quiet = false) -> Palette; // Carga una paleta desde un archivo .pal
auto loadSurface(const std::string& file_path) -> std::shared_ptr<Surface>; // Crea una surface desde un fichero .gif
void flipSurface(); // Vuelca la surface en la textura
static auto loadPaletteFromFile(const std::string& file_path) -> Palette; // Carga una paleta desde un fichero
void unloadTexture(); // Libera la memoria de la textura
void unloadSurface(); // Libera la surface actual
static auto readPalFile(const std::string& file_path) -> Palette; // Carga una paleta desde un archivo .pal
};
@@ -1,4 +1,4 @@
#include "tiled_bg.hpp"
#include "core/rendering/tiled_bg.hpp"
#include <SDL3/SDL.h> // Para SDL_SetRenderTarget, SDL_CreateTexture, SDL_DestroyTexture, SDL_FRect, SDL_GetRenderTarget, SDL_RenderTexture, SDL_PixelFormat, SDL_TextureAccess
@@ -9,9 +9,9 @@
#include <numbers> // Para pi
#include <string> // Para basic_string
#include "resource.hpp" // Para Resource
#include "screen.hpp" // Para Screen
#include "sprite.hpp" // Para Sprite
#include "core/rendering/screen.hpp" // Para Screen
#include "core/rendering/sprite/sprite.hpp" // Para Sprite
#include "core/resources/resource.hpp" // Para Resource
// Constructor
TiledBG::TiledBG(SDL_FRect pos, TiledBGMode mode)
@@ -2,10 +2,12 @@
#include <SDL3/SDL.h> // Para SDL_FRect, SDL_SetTextureColorMod, SDL_Renderer, SDL_Texture
#include "color.hpp" // Para Color
#include <cstdint> // Para std::uint8_t
#include "utils/color.hpp" // Para Color
// --- Enums ---
enum class TiledBGMode : int { // Modos de funcionamiento para el tileado de fondo
enum class TiledBGMode : std::uint8_t { // Modos de funcionamiento para el tileado de fondo
CIRCLE = 0,
DIAGONAL = 1,
RANDOM = 2,
@@ -1,6 +1,7 @@
#include "writer.hpp"
#include "core/rendering/writer.hpp"
#include "text.hpp" // Para Text
// Text es completat ací per `text_->write/length` via shared_ptr; include-cleaner no detecta l'ús indirecte.
#include "core/rendering/text.hpp" // IWYU pragma: keep
// Actualiza el objeto (delta_time en ms)
void Writer::update(float delta_time) {
@@ -1,4 +1,4 @@
#include "asset.hpp"
#include "core/resources/asset.hpp"
#include <SDL3/SDL.h> // Para SDL_LogWarn, SDL_LogCategory, SDL_LogError
@@ -11,9 +11,8 @@
#include <sstream> // Para basic_istringstream
#include <stdexcept> // Para runtime_error
#include "resource_helper.hpp" // Para loadFile
#include "ui/logger.hpp" // Para info, section, CYAN
#include "utils.hpp" // Para getFileName
#include "core/resources/resource_helper.hpp" // Para loadFile
#include "utils/utils.hpp" // Para getFileName
// Singleton
Asset* Asset::instance = nullptr;
@@ -37,9 +36,7 @@ void Asset::addToMap(const std::string& file_path, Type type, bool required, boo
// Verificar si ya existe el archivo
if (file_list_.contains(filename)) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Warning: Asset '%s' already exists, overwriting",
filename.c_str());
std::cout << "Warning: Asset '" << filename << "' already exists, overwriting" << '\n';
}
file_list_.emplace(filename, Item{std::move(full_path), type, required});
@@ -50,29 +47,39 @@ void Asset::add(const std::string& file_path, Type type, bool required, bool abs
addToMap(file_path, type, required, absolute);
}
// Carga recursos desde un archivo de configuración con soporte para variables
// Carga recursos desde un archivo de configuración en el filesystem
void Asset::loadFromFile(const std::string& config_file_path, const std::string& prefix, const std::string& system_folder) {
std::ifstream file(config_file_path);
if (!file.is_open()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error: Cannot open config file: %s",
config_file_path.c_str());
std::cout << "Error: Cannot open config file: " << config_file_path << '\n';
return;
}
parseConfigStream(file, prefix, system_folder);
}
// Carga recursos desde un buffer en memoria (p.ex. obtenido del pack)
void Asset::loadFromBuffer(const std::vector<uint8_t>& buffer, const std::string& prefix, const std::string& system_folder) {
if (buffer.empty()) {
std::cout << "Error: Asset index buffer is empty" << '\n';
return;
}
std::string content(buffer.begin(), buffer.end());
std::istringstream stream(content);
parseConfigStream(stream, prefix, system_folder);
}
// Parsea el contenido del índice (formato común a loadFromFile y loadFromBuffer)
void Asset::parseConfigStream(std::istream& stream, const std::string& prefix, const std::string& system_folder) {
std::string line;
int line_number = 0;
while (std::getline(file, line)) {
while (std::getline(stream, line)) {
++line_number;
// Limpiar espacios en blanco al principio y final
line.erase(0, line.find_first_not_of(" \t\r"));
line.erase(line.find_last_not_of(" \t\r") + 1);
// DEBUG: mostrar línea leída (opcional, puedes comentar esta línea)
// SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Line %d: '%s'", line_number, line.c_str());
// Ignorar líneas vacías y comentarios
if (line.empty() || line[0] == '#' || line[0] == ';') {
continue;
@@ -89,9 +96,7 @@ void Asset::loadFromFile(const std::string& config_file_path, const std::string&
// Verificar que tenemos al menos tipo y ruta
if (parts.size() < 2) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Warning: Malformed line %d in config file (insufficient fields)",
line_number);
std::cout << "Warning: Malformed line " << line_number << " in config file (insufficient fields)" << '\n';
continue;
}
@@ -118,15 +123,9 @@ void Asset::loadFromFile(const std::string& config_file_path, const std::string&
addToMap(path, type, required, absolute);
} catch (const std::exception& e) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error parsing line %d in config file: %s",
line_number,
e.what());
std::cout << "Error parsing line " << line_number << " in config file: " << e.what() << '\n';
}
}
std::cout << "Loaded " << file_list_.size() << " assets from config file" << '\n';
file.close();
}
// Devuelve la ruta completa a un fichero (búsqueda O(1))
@@ -136,7 +135,7 @@ auto Asset::getPath(const std::string& filename) const -> std::string {
return it->second.file;
}
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Warning: file %s not found", filename.c_str());
std::cout << "Warning: file " << filename << " not found" << '\n';
return "";
}
@@ -147,7 +146,7 @@ auto Asset::loadData(const std::string& filename) const -> std::vector<uint8_t>
return ResourceHelper::loadFile(it->second.file);
}
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Warning: file %s not found for data loading", filename.c_str());
std::cout << "Warning: file " << filename << " not found for data loading" << '\n';
return {};
}
@@ -160,9 +159,6 @@ auto Asset::exists(const std::string& filename) const -> bool {
auto Asset::check() const -> bool {
bool success = true;
// SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n** CHECKING FILES");
Logger::section("CHECKING FILES", Logger::CYAN);
// Agrupar por tipo para mostrar organizado
std::unordered_map<Type, std::vector<const Item*>> by_type;
@@ -177,19 +173,11 @@ auto Asset::check() const -> bool {
Type asset_type = static_cast<Type>(type);
if (by_type.contains(asset_type)) {
Logger::info(getTypeName(asset_type) + " FILES");
bool type_success = true;
for (const auto* item : by_type[asset_type]) {
if (!checkFile(item->file)) {
success = false;
type_success = false;
}
}
if (type_success) {
Logger::info("All files are OK.\n");
}
}
}
return success;
@@ -215,9 +203,7 @@ auto Asset::checkFile(const std::string& path) const -> bool {
file.close();
if (!success) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error: Could not open file: %s",
path.c_str());
std::cout << "Error: Could not open file: " << path << '\n';
}
return success;
@@ -1,6 +1,7 @@
#pragma once
#include <cstdint> // Para uint8_t
#include <iosfwd> // Para istream (forward decl)
#include <string> // Para string
#include <unordered_map> // Para unordered_map
#include <utility> // Para move
@@ -10,7 +11,7 @@
class Asset {
public:
// --- Enums ---
enum class Type : int {
enum class Type : std::uint8_t {
BITMAP, // Imágenes
MUSIC, // Música
SOUND, // Sonidos
@@ -32,7 +33,8 @@ class Asset {
// --- Métodos para la gestión de recursos ---
void add(const std::string& file_path, Type type, bool required = true, bool absolute = false);
void loadFromFile(const std::string& config_file_path, const std::string& prefix = "", const std::string& system_folder = ""); // Con soporte para variables
void loadFromFile(const std::string& config_file_path, const std::string& prefix = "", const std::string& system_folder = ""); // Carga el índice desde el filesystem
void loadFromBuffer(const std::vector<uint8_t>& buffer, const std::string& prefix = "", const std::string& system_folder = ""); // Carga el índice desde memoria (p.ex. desde el pack)
[[nodiscard]] auto getPath(const std::string& filename) const -> std::string;
[[nodiscard]] auto loadData(const std::string& filename) const -> std::vector<uint8_t>; // Carga datos del archivo
[[nodiscard]] auto check() const -> bool;
@@ -63,6 +65,7 @@ class Asset {
void addToMap(const std::string& file_path, Type type, bool required, bool absolute); // Añade archivo al mapa
[[nodiscard]] static auto replaceVariables(const std::string& path, const std::string& prefix, const std::string& system_folder) -> std::string; // Reemplaza variables en la ruta
static auto parseOptions(const std::string& options, bool& required, bool& absolute) -> void; // Parsea opciones
void parseConfigStream(std::istream& stream, const std::string& prefix, const std::string& system_folder); // Lógica común de parseo del índice
// --- Constructores y destructor privados (singleton) ---
explicit Asset(std::string executable_path) // Constructor privado
@@ -1,9 +1,11 @@
#include "asset_integrated.hpp"
#include "core/resources/asset_integrated.hpp"
#include <filesystem>
#include <fstream>
#include <iostream>
#include "core/resources/resource_loader.hpp"
bool AssetIntegrated::resource_pack_enabled = false;
void AssetIntegrated::initWithResourcePack(const std::string& executable_path,
@@ -86,20 +88,11 @@ auto AssetIntegrated::getSystemPath(const std::string& filename) -> std::string
}
auto AssetIntegrated::shouldUseResourcePack(const std::string& filepath) -> bool {
// Los archivos que NO van al pack:
// - Archivos de config/ (ahora están fuera de data/)
// - Archivos con absolute=true en assets.txt
// - Archivos de sistema (${SYSTEM_FOLDER})
// Els fitxers que NO van al pack:
// - Fitxers amb absolute=true a assets.txt
// - Fitxers de sistema (${SYSTEM_FOLDER})
// - Fitxers al costat del binari (com gamecontrollerdb.txt)
if (filepath.find("/config/") != std::string::npos ||
filepath.starts_with("config/")) {
return false;
}
if (filepath.find("/data/") != std::string::npos ||
filepath.starts_with("data/")) {
return true;
}
return false;
return filepath.find("/data/") != std::string::npos ||
filepath.starts_with("data/");
}
@@ -1,9 +1,6 @@
#pragma once
#include <memory>
#include "asset.hpp"
#include "resource_loader.hpp"
#include "core/resources/asset.hpp"
// Extensión de Asset que integra ResourceLoader
class AssetIntegrated : public Asset {
@@ -1,30 +1,27 @@
#include "resource.hpp"
#include "core/resources/resource.hpp"
#include <SDL3/SDL.h> // Para SDL_LogInfo, SDL_LogCategory, SDL_LogError, SDL_SetRenderDrawColor, SDL_EventType, SDL_PollEvent, SDL_RenderFillRect, SDL_RenderRect, SDLK_ESCAPE, SDL_Event
#include <algorithm> // Para ranges::transform, ranges::find_if
#include <array> // Para array
#include <cstdlib> // Para exit
#include <exception> // Para exception
#include <filesystem> // Para exists, path, remove
#include <fstream> // Para basic_ofstream, basic_ios, basic_ostream::write, ios, ofstream
#include <ranges> // Para __find_if_fn, find_if, __find_fn, find
#include <iostream> // Para std::cout
#include <iterator> // Para back_inserter
#include <stdexcept> // Para runtime_error
#include <utility> // Para move
#include "asset.hpp" // Para Asset
#include "color.hpp" // Para Color, NO_COLOR_MOD
#include "external/jail_audio.hpp" // Para JA_LoadMusic, JA_LoadSound, JA_DeleteMusic, JA_DeleteSound
#include "lang.hpp" // Para getText
#include "param.hpp" // Para Param, param, ParamPlayer, ParamResource, ParamGame
#include "resource_helper.hpp" // Para loadFile
#include "screen.hpp" // Para Screen
#include "text.hpp" // Para Text
#include "ui/logger.hpp" // Para info, CR, dots
#include "utils.hpp" // Para getFileName
#include "version.h" // Para APP_NAME, GIT_HASH
struct JA_Music_t; // lines 11-11
struct JA_Sound_t; // lines 12-12
#include "core/audio/jail_audio.hpp" // Para Ja::loadMusic, Ja::loadSound, Ja::deleteMusic, Ja::deleteSound
#include "core/locale/lang.hpp" // Para getText
#include "core/rendering/screen.hpp" // Para Screen
#include "core/rendering/text.hpp" // Para Text
#include "core/resources/asset.hpp" // Para Asset
#include "core/resources/resource_helper.hpp" // Para loadFile
#include "game/options.hpp" // Para Options::loading
#include "utils/color.hpp" // Para Color, NO_COLOR_MOD
#include "utils/defines.hpp" // Para Texts::VERSION
#include "utils/param.hpp" // Para Param, param, ParamPlayer, ParamResource, ParamGame
#include "utils/utils.hpp" // Para getFileName
#include "version.h" // Para APP_NAME, GIT_HASH
// Helper para cargar archivos de audio desde pack o filesystem en memoria
namespace {
@@ -65,7 +62,8 @@ Resource::Resource(LoadingMode mode)
Screen::get()->show();
if (loading_mode_ == LoadingMode::PRELOAD) {
loading_text_ = Screen::get()->getText();
load();
// Ya NO llamamos load() aquí: Director bombea beginLoad() + loadStep()
// desde iterate() para mantener vivo el bucle SDL3 durante la carga.
} else {
// En modo lazy, cargamos lo mínimo indispensable
initResourceLists();
@@ -80,21 +78,16 @@ Resource::~Resource() {
// Carga los recursos esenciales que siempre se necesitan (modo lazy)
void Resource::loadEssentialResources() {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Loading essential resources for lazy mode");
// Cargar recursos de texto básicos que se usan para crear texturas
loadTextFilesQuiet(); // <- VERSIÓN SILENCIOSA
loadEssentialTextures(); // Ya es silenciosa
createText(); // Crear objetos de texto
createTextTextures(); // Crear texturas generadas (game_text_xxx)
createPlayerTextures(); // Crea las texturas de jugadores con todas sus variantes de paleta
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Essential resources loaded");
}
// Carga los ficheros de texto del juego (versión silenciosa)
void Resource::loadTextFilesQuiet() {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n>> TEXT FILES (quiet load)");
auto list = Asset::get()->getListByType(Asset::Type::FONT);
for (const auto& l : list) {
@@ -103,7 +96,6 @@ void Resource::loadTextFilesQuiet() {
auto it = std::ranges::find_if(text_files_, [&name](const auto& t) -> auto { return t.name == name; });
if (it != text_files_.end()) {
it->text_file = Text::loadFile(l);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Text file loaded: %s", name.c_str());
}
}
}
@@ -141,42 +133,37 @@ void Resource::loadEssentialTextures() {
// Inicializa las listas de recursos sin cargar el contenido (modo lazy)
void Resource::initResourceLists() {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Initializing resource lists for lazy loading");
const auto FILE_TO_NAME = [](const auto& file) { return getFileName(file); };
// Inicializa lista de sonidos
auto sound_list = Asset::get()->getListByType(Asset::Type::SOUND);
const auto SOUND_LIST = Asset::get()->getListByType(Asset::Type::SOUND);
sounds_.clear();
for (const auto& file : sound_list) {
sounds_.emplace_back(getFileName(file));
}
sounds_.reserve(SOUND_LIST.size());
std::ranges::transform(SOUND_LIST, std::back_inserter(sounds_), [&](const auto& file) { return ResourceSound(FILE_TO_NAME(file)); });
// Inicializa lista de músicas
auto music_list = Asset::get()->getListByType(Asset::Type::MUSIC);
const auto MUSIC_LIST = Asset::get()->getListByType(Asset::Type::MUSIC);
musics_.clear();
for (const auto& file : music_list) {
musics_.emplace_back(getFileName(file));
}
musics_.reserve(MUSIC_LIST.size());
std::ranges::transform(MUSIC_LIST, std::back_inserter(musics_), [&](const auto& file) { return ResourceMusic(FILE_TO_NAME(file)); });
// Inicializa lista de texturas
auto texture_list = Asset::get()->getListByType(Asset::Type::BITMAP);
const auto TEXTURE_LIST = Asset::get()->getListByType(Asset::Type::BITMAP);
textures_.clear();
for (const auto& file : texture_list) {
textures_.emplace_back(getFileName(file));
}
textures_.reserve(TEXTURE_LIST.size());
std::ranges::transform(TEXTURE_LIST, std::back_inserter(textures_), [&](const auto& file) { return ResourceTexture(FILE_TO_NAME(file)); });
// Inicializa lista de ficheros de texto
auto text_file_list = Asset::get()->getListByType(Asset::Type::FONT);
const auto TEXT_FILE_LIST = Asset::get()->getListByType(Asset::Type::FONT);
text_files_.clear();
for (const auto& file : text_file_list) {
text_files_.emplace_back(getFileName(file));
}
text_files_.reserve(TEXT_FILE_LIST.size());
std::ranges::transform(TEXT_FILE_LIST, std::back_inserter(text_files_), [&](const auto& file) { return ResourceTextFile(FILE_TO_NAME(file)); });
// Inicializa lista de animaciones
auto animation_list = Asset::get()->getListByType(Asset::Type::ANIMATION);
const auto ANIMATION_LIST = Asset::get()->getListByType(Asset::Type::ANIMATION);
animations_.clear();
for (const auto& file : animation_list) {
animations_.emplace_back(getFileName(file));
}
animations_.reserve(ANIMATION_LIST.size());
std::ranges::transform(ANIMATION_LIST, std::back_inserter(animations_), [&](const auto& file) { return ResourceAnimation(FILE_TO_NAME(file)); });
// Los demos se cargan directamente sin mostrar progreso (son pocos y pequeños)
loadDemoDataQuiet();
@@ -197,15 +184,12 @@ void Resource::initResourceLists() {
"smb2_grad"};
texts_.clear();
for (const auto& text_name : TEXT_OBJECTS) {
texts_.emplace_back(text_name); // Constructor con nullptr por defecto
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Resource lists initialized for lazy loading");
texts_.reserve(TEXT_OBJECTS.size());
std::ranges::transform(TEXT_OBJECTS, std::back_inserter(texts_), [](const auto& text_name) { return ResourceText(text_name); });
}
// Obtiene el sonido a partir de un nombre (con carga perezosa)
auto Resource::getSound(const std::string& name) -> JA_Sound_t* {
auto Resource::getSound(const std::string& name) -> Ja::Sound* {
auto it = std::ranges::find_if(sounds_, [&name](const auto& s) -> auto { return s.name == name; });
if (it != sounds_.end()) {
@@ -216,12 +200,12 @@ auto Resource::getSound(const std::string& name) -> JA_Sound_t* {
return it->sound;
}
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Sonido no encontrado %s", name.c_str());
std::cout << "Error: Sonido no encontrado " << name << '\n';
throw std::runtime_error("Sonido no encontrado: " + name);
}
// Obtiene la música a partir de un nombre (con carga perezosa)
auto Resource::getMusic(const std::string& name) -> JA_Music_t* {
auto Resource::getMusic(const std::string& name) -> Ja::Music* {
auto it = std::ranges::find_if(musics_, [&name](const auto& m) -> auto { return m.name == name; });
if (it != musics_.end()) {
@@ -232,7 +216,7 @@ auto Resource::getMusic(const std::string& name) -> JA_Music_t* {
return it->music;
}
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Música no encontrada %s", name.c_str());
std::cout << "Error: Música no encontrada " << name << '\n';
throw std::runtime_error("Música no encontrada: " + name);
}
@@ -248,7 +232,7 @@ auto Resource::getTexture(const std::string& name) -> std::shared_ptr<Texture> {
return it->texture;
}
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Imagen no encontrada %s", name.c_str());
std::cout << "Error: Imagen no encontrada " << name << '\n';
throw std::runtime_error("Imagen no encontrada: " + name);
}
@@ -264,7 +248,7 @@ auto Resource::getTextFile(const std::string& name) -> std::shared_ptr<Text::Fil
return it->text_file;
}
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: TextFile no encontrado %s", name.c_str());
std::cout << "Error: TextFile no encontrado " << name << '\n';
throw std::runtime_error("TextFile no encontrado: " + name);
}
@@ -280,7 +264,7 @@ auto Resource::getText(const std::string& name) -> std::shared_ptr<Text> {
return it->text;
}
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Text no encontrado %s", name.c_str());
std::cout << "Error: Text no encontrado " << name << '\n';
throw std::runtime_error("Text no encontrado: " + name);
}
@@ -296,14 +280,14 @@ auto Resource::getAnimation(const std::string& name) -> AnimationsFileBuffer& {
return it->animation;
}
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Animación no encontrada %s", name.c_str());
std::cout << "Error: Animación no encontrada " << name << '\n';
throw std::runtime_error("Animación no encontrada: " + name);
}
// Obtiene el fichero con los datos para el modo demostración a partir de un índice
auto Resource::getDemoData(int index) -> DemoData& {
if (index < 0 || std::cmp_greater_equal(index, demos_.size())) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Index %d out of range for demo data (size: %d)", index, static_cast<int>(demos_.size()));
std::cout << "Index " << index << " out of range for demo data (size: " << static_cast<int>(demos_.size()) << ")" << '\n';
static DemoData empty_demo_;
return empty_demo_;
}
@@ -312,63 +296,51 @@ auto Resource::getDemoData(int index) -> DemoData& {
// --- Métodos de carga perezosa ---
auto Resource::loadSoundLazy(const std::string& name) -> JA_Sound_t* {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Loading sound lazily: %s", name.c_str());
auto Resource::loadSoundLazy(const std::string& name) -> Ja::Sound* {
auto sound_list = Asset::get()->getListByType(Asset::Type::SOUND);
for (const auto& file : sound_list) {
if (getFileName(file) == name) {
auto audio_data = loadAudioData(file);
if (!audio_data.data.empty()) {
return JA_LoadSound(audio_data.data.data(), audio_data.data.size());
return Ja::loadSound(audio_data.data.data(), audio_data.data.size());
}
// Fallback a cargar desde disco si no está en pack
return JA_LoadSound(file.c_str());
return Ja::loadSound(file.c_str());
}
}
return nullptr;
}
auto Resource::loadMusicLazy(const std::string& name) -> JA_Music_t* {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Loading music lazily: %s", name.c_str());
auto Resource::loadMusicLazy(const std::string& name) -> Ja::Music* {
auto music_list = Asset::get()->getListByType(Asset::Type::MUSIC);
for (const auto& file : music_list) {
if (getFileName(file) == name) {
auto audio_data = loadAudioData(file);
if (!audio_data.data.empty()) {
return JA_LoadMusic(audio_data.data.data(), audio_data.data.size());
return Ja::loadMusic(audio_data.data.data(), audio_data.data.size());
}
// Fallback a cargar desde disco si no está en pack
return JA_LoadMusic(file.c_str());
return Ja::loadMusic(file.c_str());
}
}
return nullptr;
}
auto Resource::loadTextureLazy(const std::string& name) -> std::shared_ptr<Texture> {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Loading texture lazily: %s", name.c_str());
auto texture_list = Asset::get()->getListByType(Asset::Type::BITMAP);
for (const auto& file : texture_list) {
if (getFileName(file) == name) {
return std::make_shared<Texture>(Screen::get()->getRenderer(), file);
}
}
return nullptr;
const auto TEXTURE_LIST = Asset::get()->getListByType(Asset::Type::BITMAP);
const auto IT = std::ranges::find_if(TEXTURE_LIST,
[&name](const auto& file) { return getFileName(file) == name; });
return IT != TEXTURE_LIST.end() ? std::make_shared<Texture>(Screen::get()->getRenderer(), *IT) : nullptr;
}
auto Resource::loadTextFileLazy(const std::string& name) -> std::shared_ptr<Text::File> {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Loading text file lazily: %s", name.c_str());
auto text_file_list = Asset::get()->getListByType(Asset::Type::FONT);
for (const auto& file : text_file_list) {
if (getFileName(file) == name) {
return Text::loadFile(file);
}
}
return nullptr;
const auto TEXT_FILE_LIST = Asset::get()->getListByType(Asset::Type::FONT);
const auto IT = std::ranges::find_if(TEXT_FILE_LIST,
[&name](const auto& file) { return getFileName(file) == name; });
return IT != TEXT_FILE_LIST.end() ? Text::loadFile(*IT) : nullptr;
}
auto Resource::loadTextLazy(const std::string& name) -> std::shared_ptr<Text> {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Loading text object lazily: %s", name.c_str());
// Mapeo de objetos de texto a sus recursos
struct TextMapping {
std::string key;
@@ -390,28 +362,22 @@ auto Resource::loadTextLazy(const std::string& name) -> std::shared_ptr<Text> {
{.key = "smb2", .texture_file = "smb2.png", .text_file = "smb2.txt"},
{.key = "smb2_grad", .texture_file = "smb2_grad.png", .text_file = "smb2.txt"}};
for (const auto& mapping : TEXT_MAPPINGS) {
if (mapping.key == name) {
// Cargar las dependencias automáticamente
auto texture = getTexture(mapping.texture_file); // Esto cargará la textura si no está cargada
auto text_file = getTextFile(mapping.text_file); // Esto cargará el archivo de texto si no está cargado
if (texture && text_file) {
return std::make_shared<Text>(texture, text_file);
}
}
const auto IT = std::ranges::find_if(TEXT_MAPPINGS,
[&name](const auto& mapping) { return mapping.key == name; });
if (IT == TEXT_MAPPINGS.end()) {
return nullptr;
}
return nullptr;
auto texture = getTexture(IT->texture_file); // Esto cargará la textura si no está cargada
auto text_file = getTextFile(IT->text_file); // Esto cargará el archivo de texto si no está cargado
return (texture && text_file) ? std::make_shared<Text>(texture, text_file) : nullptr;
}
auto Resource::loadAnimationLazy(const std::string& name) -> AnimationsFileBuffer {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Loading animation lazily: %s", name.c_str());
auto animation_list = Asset::get()->getListByType(Asset::Type::ANIMATION);
for (const auto& file : animation_list) {
if (getFileName(file) == name) {
return loadAnimationsFromFile(file);
}
const auto ANIMATION_LIST = Asset::get()->getListByType(Asset::Type::ANIMATION);
const auto IT = std::ranges::find_if(ANIMATION_LIST,
[&name](const auto& file) { return getFileName(file) == name; });
if (IT != ANIMATION_LIST.end()) {
return loadAnimationsFromFile(*IT);
}
// Si no se encuentra, retorna vector vacío
return AnimationsFileBuffer{};
@@ -428,31 +394,102 @@ void Resource::clear() {
demos_.clear();
}
// Carga todos los recursos del juego y muestra el progreso de carga
// Carga síncrona completa: usado por Resource::reload() (hot-reload en debug).
// En arranque normal la carga la bombea Director::iterate() vía loadStep().
void Resource::load() {
// Prepara la gestión del progreso de carga
beginLoad();
while (!loadStep(INT_MAX)) {
// Presupuesto infinito: una sola pasada carga todo
}
Screen::get()->setVSync(saved_vsync_);
}
// Prepara el estado del cargador incremental. No carga nada todavía.
void Resource::beginLoad() {
calculateTotalResources();
initProgressBar();
// Muerstra la ventana y desactiva el sincronismo vertical
auto* screen = Screen::get();
auto vsync = Screen::getVSync();
screen->setVSync(false);
saved_vsync_ = Screen::getVSync();
Screen::get()->setVSync(false); // Maximiza FPS durante el preload
// SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n** LOADING RESOURCES");
loadSounds(); // Carga sonidos
loadMusics(); // Carga músicas
loadTextures(); // Carga texturas
loadTextFiles(); // Carga ficheros de texto
loadAnimations(); // Carga animaciones
loadDemoData(); // Carga datos de demo
createText(); // Crea objetos de texto
createTextTextures(); // Crea texturas a partir de texto
createPlayerTextures(); // Crea las texturas de jugadores con todas sus variantes de paleta
// SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n** RESOURCES LOADED");
stage_ = LoadStage::SOUNDS;
stage_index_ = 0;
}
// Restablece el sincronismo vertical a su valor original
screen->setVSync(vsync);
auto Resource::isLoadDone() const -> bool {
return stage_ == LoadStage::DONE;
}
// Avança una etapa que descarrega una llista d'assets.
void Resource::advanceListLoadStage(const std::vector<std::string>& list, void (Resource::*load_one)(size_t), LoadStage next_stage) {
if (stage_index_ >= list.size()) {
stage_ = next_stage;
stage_index_ = 0;
return;
}
(this->*load_one)(stage_index_++);
}
// Bombea la máquina de etapas hasta agotar el presupuesto de tiempo o completar la carga.
// Devuelve true cuando ya no queda nada por cargar.
auto Resource::loadStep(int budget_ms) -> bool {
if (stage_ == LoadStage::DONE) { return true; }
const Uint64 START_NS = SDL_GetTicksNS();
const Uint64 BUDGET_NS = static_cast<Uint64>(budget_ms) * 1'000'000ULL;
while (stage_ != LoadStage::DONE) {
switch (stage_) {
case LoadStage::SOUNDS: {
if (stage_index_ == 0) { sounds_.clear(); }
advanceListLoadStage(Asset::get()->getListByType(Asset::Type::SOUND), &Resource::loadOneSound, LoadStage::MUSICS);
break;
}
case LoadStage::MUSICS: {
if (stage_index_ == 0) { musics_.clear(); }
advanceListLoadStage(Asset::get()->getListByType(Asset::Type::MUSIC), &Resource::loadOneMusic, LoadStage::TEXTURES);
break;
}
case LoadStage::TEXTURES: {
if (stage_index_ == 0) { textures_.clear(); }
advanceListLoadStage(Asset::get()->getListByType(Asset::Type::BITMAP), &Resource::loadOneTexture, LoadStage::TEXT_FILES);
break;
}
case LoadStage::TEXT_FILES: {
if (stage_index_ == 0) { text_files_.clear(); }
advanceListLoadStage(Asset::get()->getListByType(Asset::Type::FONT), &Resource::loadOneTextFile, LoadStage::ANIMATIONS);
break;
}
case LoadStage::ANIMATIONS: {
if (stage_index_ == 0) { animations_.clear(); }
advanceListLoadStage(Asset::get()->getListByType(Asset::Type::ANIMATION), &Resource::loadOneAnimation, LoadStage::DEMO_DATA);
break;
}
case LoadStage::DEMO_DATA: {
if (stage_index_ == 0) { demos_.clear(); }
advanceListLoadStage(Asset::get()->getListByType(Asset::Type::DEMODATA), &Resource::loadOneDemoData, LoadStage::CREATE_TEXT);
break;
}
case LoadStage::CREATE_TEXT:
createText();
stage_ = LoadStage::CREATE_TEXT_TEXTURES;
break;
case LoadStage::CREATE_TEXT_TEXTURES:
createTextTextures();
stage_ = LoadStage::CREATE_PLAYER_TEXTURES;
break;
case LoadStage::CREATE_PLAYER_TEXTURES:
createPlayerTextures();
stage_ = LoadStage::DONE;
break;
case LoadStage::DONE:
break;
}
if ((SDL_GetTicksNS() - START_NS) >= BUDGET_NS) { break; }
}
return stage_ == LoadStage::DONE;
}
// Recarga todos los recursos (limpia y vuelve a cargar)
@@ -465,106 +502,78 @@ void Resource::reload() {
}
}
// Carga los sonidos del juego
void Resource::loadSounds() {
// Carga un sonido concreto desde la lista de assets
void Resource::loadOneSound(size_t idx) {
auto list = Asset::get()->getListByType(Asset::Type::SOUND);
sounds_.clear();
int failed = 0;
for (const auto& l : list) {
auto name = getFileName(l);
updateLoadingProgress(name);
auto audio_data = loadAudioData(l);
JA_Sound_t* sound = nullptr;
if (!audio_data.data.empty()) {
sound = JA_LoadSound(audio_data.data.data(), audio_data.data.size());
} else {
sound = JA_LoadSound(l.c_str());
}
if (sound == nullptr) {
Logger::error(" Sound load failed: " + name);
++failed;
}
sounds_.emplace_back(name, sound);
const auto& path = list[idx];
auto name = getFileName(path);
updateLoadingProgress(name);
auto audio_data = loadAudioData(path);
Ja::Sound* sound = nullptr;
if (!audio_data.data.empty()) {
sound = Ja::loadSound(audio_data.data.data(), audio_data.data.size());
} else {
sound = Ja::loadSound(path.c_str());
}
Logger::info("Sounds loaded: " + std::to_string(list.size() - failed) + "/" + std::to_string(list.size()));
if (sound == nullptr) {
std::cout << "Sound load failed: " << name << '\n';
}
sounds_.emplace_back(name, sound);
}
// Carga las músicas del juego
void Resource::loadMusics() {
// Carga una música concreta desde la lista de assets
void Resource::loadOneMusic(size_t idx) {
auto list = Asset::get()->getListByType(Asset::Type::MUSIC);
musics_.clear();
int failed = 0;
for (const auto& l : list) {
auto name = getFileName(l);
updateLoadingProgress(name);
auto audio_data = loadAudioData(l);
JA_Music_t* music = nullptr;
if (!audio_data.data.empty()) {
music = JA_LoadMusic(audio_data.data.data(), audio_data.data.size());
} else {
music = JA_LoadMusic(l.c_str());
}
if (music == nullptr) {
Logger::error(" Music load failed: " + name);
++failed;
}
musics_.emplace_back(name, music);
const auto& path = list[idx];
auto name = getFileName(path);
updateLoadingProgress(name);
auto audio_data = loadAudioData(path);
Ja::Music* music = nullptr;
if (!audio_data.data.empty()) {
music = Ja::loadMusic(audio_data.data.data(), audio_data.data.size());
} else {
music = Ja::loadMusic(path.c_str());
}
Logger::info("Musics loaded: " + std::to_string(list.size() - failed) + "/" + std::to_string(list.size()));
if (music == nullptr) {
std::cout << "Music load failed: " << name << '\n';
}
musics_.emplace_back(name, music);
}
// Carga las texturas del juego
void Resource::loadTextures() {
// Carga una textura concreta desde la lista de assets
void Resource::loadOneTexture(size_t idx) {
auto list = Asset::get()->getListByType(Asset::Type::BITMAP);
textures_.clear();
for (const auto& l : list) {
auto name = getFileName(l);
updateLoadingProgress(name);
textures_.emplace_back(name, std::make_shared<Texture>(Screen::get()->getRenderer(), l));
}
Logger::info("Textures loaded: " + std::to_string(list.size()));
const auto& path = list[idx];
auto name = getFileName(path);
updateLoadingProgress(name);
textures_.emplace_back(name, std::make_shared<Texture>(Screen::get()->getRenderer(), path));
}
// Carga los ficheros de texto del juego
void Resource::loadTextFiles() {
// Carga un fichero de texto concreto desde la lista de assets
void Resource::loadOneTextFile(size_t idx) {
auto list = Asset::get()->getListByType(Asset::Type::FONT);
text_files_.clear();
for (const auto& l : list) {
auto name = getFileName(l);
updateLoadingProgress(name);
text_files_.emplace_back(name, Text::loadFile(l));
}
Logger::info("Text files loaded: " + std::to_string(list.size()));
const auto& path = list[idx];
auto name = getFileName(path);
updateLoadingProgress(name);
text_files_.emplace_back(name, Text::loadFile(path));
}
// Carga las animaciones del juego
void Resource::loadAnimations() {
// Carga una animación concreta desde la lista de assets
void Resource::loadOneAnimation(size_t idx) {
auto list = Asset::get()->getListByType(Asset::Type::ANIMATION);
animations_.clear();
for (const auto& l : list) {
auto name = getFileName(l);
updateLoadingProgress(name);
animations_.emplace_back(name, loadAnimationsFromFile(l));
}
Logger::info("Animations loaded: " + std::to_string(list.size()));
const auto& path = list[idx];
auto name = getFileName(path);
updateLoadingProgress(name);
animations_.emplace_back(name, loadAnimationsFromFile(path));
}
// Carga los datos para el modo demostración
void Resource::loadDemoData() {
// Carga un fichero de datos de demo concreto desde la lista de assets
void Resource::loadOneDemoData(size_t idx) {
auto list = Asset::get()->getListByType(Asset::Type::DEMODATA);
demos_.clear();
for (const auto& l : list) {
auto name = getFileName(l);
updateLoadingProgress(name);
demos_.emplace_back(loadDemoDataFromFile(l));
}
Logger::info("Demo files loaded: " + std::to_string(list.size()));
const auto& path = list[idx];
auto name = getFileName(path);
updateLoadingProgress(name);
demos_.emplace_back(loadDemoDataFromFile(path));
}
// Crea las texturas de jugadores con todas sus variantes de paleta
@@ -585,14 +594,10 @@ void Resource::createPlayerTextures() {
const auto& player = players[player_idx]; // Obtenemos el jugador actual
// Encontrar el archivo original de la textura
std::string texture_file_path;
auto texture_list = Asset::get()->getListByType(Asset::Type::BITMAP);
for (const auto& file : texture_list) {
if (getFileName(file) == player.base_texture) {
texture_file_path = file;
break;
}
}
const auto TEXTURE_LIST = Asset::get()->getListByType(Asset::Type::BITMAP);
const auto IT = std::ranges::find_if(TEXTURE_LIST,
[&player](const auto& file) { return getFileName(file) == player.base_texture; });
const std::string TEXTURE_FILE_PATH = (IT != TEXTURE_LIST.end()) ? *IT : std::string{};
// Crear las 4 texturas con sus respectivas paletas
for (int palette_idx = 0; palette_idx < 4; ++palette_idx) {
@@ -601,14 +606,14 @@ void Resource::createPlayerTextures() {
if (palette_idx == 0) {
// Textura 0 - usar la ya cargada y modificar solo paleta 0 (default_shirt)
texture = getTexture(player.base_texture);
texture->setPaletteColor(0, 16, param.player.default_shirt[player_idx].darkest.TO_UINT32());
texture->setPaletteColor(0, 17, param.player.default_shirt[player_idx].dark.TO_UINT32());
texture->setPaletteColor(0, 18, param.player.default_shirt[player_idx].base.TO_UINT32());
texture->setPaletteColor(0, 19, param.player.default_shirt[player_idx].light.TO_UINT32());
texture->setPaletteColor(0, 56, param.player.outline_color[player_idx].TO_UINT32());
texture->setPaletteColor(0, 16, param.player.default_shirt[player_idx].darkest.toUint32());
texture->setPaletteColor(0, 17, param.player.default_shirt[player_idx].dark.toUint32());
texture->setPaletteColor(0, 18, param.player.default_shirt[player_idx].base.toUint32());
texture->setPaletteColor(0, 19, param.player.default_shirt[player_idx].light.toUint32());
texture->setPaletteColor(0, 56, param.player.outline_color[player_idx].toUint32());
} else {
// Crear textura nueva desde archivo usando ResourceHelper
texture = std::make_shared<Texture>(Screen::get()->getRenderer(), texture_file_path);
texture = std::make_shared<Texture>(Screen::get()->getRenderer(), TEXTURE_FILE_PATH);
// Añadir todas las paletas
texture->addPaletteFromPalFile(Asset::get()->getPath(player.palette_files[0]));
@@ -617,18 +622,18 @@ void Resource::createPlayerTextures() {
if (palette_idx == 1) {
// Textura 1 - modificar solo paleta 1 (one_coffee_shirt)
texture->setPaletteColor(1, 16, param.player.one_coffee_shirt[player_idx].darkest.TO_UINT32());
texture->setPaletteColor(1, 17, param.player.one_coffee_shirt[player_idx].dark.TO_UINT32());
texture->setPaletteColor(1, 18, param.player.one_coffee_shirt[player_idx].base.TO_UINT32());
texture->setPaletteColor(1, 19, param.player.one_coffee_shirt[player_idx].light.TO_UINT32());
texture->setPaletteColor(1, 56, param.player.outline_color[player_idx].TO_UINT32());
texture->setPaletteColor(1, 16, param.player.one_coffee_shirt[player_idx].darkest.toUint32());
texture->setPaletteColor(1, 17, param.player.one_coffee_shirt[player_idx].dark.toUint32());
texture->setPaletteColor(1, 18, param.player.one_coffee_shirt[player_idx].base.toUint32());
texture->setPaletteColor(1, 19, param.player.one_coffee_shirt[player_idx].light.toUint32());
texture->setPaletteColor(1, 56, param.player.outline_color[player_idx].toUint32());
} else if (palette_idx == 2) {
// Textura 2 - modificar solo paleta 2 (two_coffee_shirt)
texture->setPaletteColor(2, 16, param.player.two_coffee_shirt[player_idx].darkest.TO_UINT32());
texture->setPaletteColor(2, 17, param.player.two_coffee_shirt[player_idx].dark.TO_UINT32());
texture->setPaletteColor(2, 18, param.player.two_coffee_shirt[player_idx].base.TO_UINT32());
texture->setPaletteColor(2, 19, param.player.two_coffee_shirt[player_idx].light.TO_UINT32());
texture->setPaletteColor(2, 56, param.player.outline_color[player_idx].TO_UINT32());
texture->setPaletteColor(2, 16, param.player.two_coffee_shirt[player_idx].darkest.toUint32());
texture->setPaletteColor(2, 17, param.player.two_coffee_shirt[player_idx].dark.toUint32());
texture->setPaletteColor(2, 18, param.player.two_coffee_shirt[player_idx].base.toUint32());
texture->setPaletteColor(2, 19, param.player.two_coffee_shirt[player_idx].light.toUint32());
texture->setPaletteColor(2, 56, param.player.outline_color[player_idx].toUint32());
}
// Textura 3 (palette_idx == 3) - no modificar nada, usar colores originales
}
@@ -641,7 +646,6 @@ void Resource::createPlayerTextures() {
textures_.emplace_back(texture_name, texture);
}
}
Logger::info("Player textures created: " + std::to_string(players.size() * 4));
}
// Crea texturas a partir de textos para mostrar puntuaciones y mensajes
@@ -666,9 +670,9 @@ void Resource::createTextTextures() {
{"game_text_1000000_points", Lang::getText("[GAME_TEXT] 8")}};
auto text1 = getText("04b_25_enhanced");
for (const auto& s : strings1) {
textures_.emplace_back(s.name, text1->writeDXToTexture(Text::STROKE, s.text, -2, Colors::NO_COLOR_MOD, 1, param.game.item_text_outline_color));
}
std::ranges::transform(strings1, std::back_inserter(textures_), [&](const auto& s) {
return ResourceTexture(s.name, text1->writeDXToTexture(Text::STROKE, s.text, -2, Colors::NO_COLOR_MOD, 1, param.game.item_text_outline_color));
});
// Texturas de tamaño doble
std::vector<NameAndText> strings2 = {
@@ -680,11 +684,9 @@ void Resource::createTextTextures() {
{"game_text_game_over", "Game Over"}};
auto text2 = getText("04b_25_2x_enhanced");
for (const auto& s : strings2) {
textures_.emplace_back(s.name, text2->writeDXToTexture(Text::STROKE, s.text, -4, Colors::NO_COLOR_MOD, 1, param.game.item_text_outline_color));
}
Logger::info("Text textures created: " + std::to_string(strings1.size() + strings2.size()));
std::ranges::transform(strings2, std::back_inserter(textures_), [&](const auto& s) {
return ResourceTexture(s.name, text2->writeDXToTexture(Text::STROKE, s.text, -4, Colors::NO_COLOR_MOD, 1, param.game.item_text_outline_color));
});
}
// Crea los objetos de texto a partir de los archivos de textura y texto
@@ -728,14 +730,13 @@ void Resource::createText() {
texts_.emplace_back(resource.key, std::make_shared<Text>(getTexture(resource.texture_file), getTextFile(resource.text_file)));
}
}
Logger::info("Text objects created: " + std::to_string(resources.size()));
}
// Vacía el vector de sonidos y libera la memoria asociada
void Resource::clearSounds() {
for (auto& sound : sounds_) {
if (sound.sound != nullptr) {
JA_DeleteSound(sound.sound);
Ja::deleteSound(sound.sound);
sound.sound = nullptr;
}
}
@@ -746,7 +747,7 @@ void Resource::clearSounds() {
void Resource::clearMusics() {
for (auto& music : musics_) {
if (music.music != nullptr) {
JA_DeleteMusic(music.music);
Ja::deleteMusic(music.music);
music.music = nullptr;
}
}
@@ -785,8 +786,18 @@ void Resource::renderProgress() {
screen->start();
screen->clean();
// Si la pantalla de carga está desactivada, dejamos todo en negro.
// wait_for_input solo tiene efecto cuando la pantalla está visible.
if (!Options::loading.show) {
screen->coreRender();
return;
}
// Estamos en la fase de espera explícita al usuario tras terminar la carga
const bool WAITING_FOR_INPUT = isLoadDone() && Options::loading.wait_for_input;
auto text_color = param.resource.color;
auto bar_color = param.resource.color.DARKEN(100);
auto bar_color = param.resource.color.darken(100);
const auto TEXT_HEIGHT = loading_text_->getCharacterSize();
// Dibuja el interior de la barra de progreso
@@ -797,14 +808,20 @@ void Resource::renderProgress() {
SDL_SetRenderDrawColor(renderer, bar_color.r, bar_color.g, bar_color.b, bar_color.a);
SDL_RenderRect(renderer, &loading_wired_rect_);
// Escribe el texto de carga encima de la barra
/*
loading_text_->writeColored(
loading_wired_rect_.x,
loading_wired_rect_.y - 9,
Lang::getText("[RESOURCE] LOADING") + " : " + loading_resource_name_,
text_color);
*/
// Texto centrado sobre la barra: mientras carga, el nombre del recurso;
// al terminar en modo wait_for_input, el prompt traducido.
const std::string OVER_BAR_TEXT = WAITING_FOR_INPUT
? Lang::getText("[RESOURCE] PRESS_TO_CONTINUE")
: loading_resource_name_;
if ((Options::loading.show_resource_name || WAITING_FOR_INPUT) && !OVER_BAR_TEXT.empty()) {
loading_text_->writeDX(
Text::CENTER | Text::COLOR,
loading_wired_rect_.x + (loading_wired_rect_.w / 2),
loading_wired_rect_.y - TEXT_HEIGHT - 2,
OVER_BAR_TEXT,
1,
text_color);
}
// Muestra nombre de la aplicación
loading_text_->writeDX(
@@ -815,64 +832,45 @@ void Resource::renderProgress() {
1,
text_color);
// Muestra la versión
// Muestra la versión y el hash del commit
loading_text_->writeDX(
Text::CENTER | Text::COLOR,
param.game.game_area.center_x,
param.game.game_area.center_y + TEXT_HEIGHT,
"(" + std::string(Version::GIT_HASH) + ")",
"ver. " + std::string(Texts::VERSION) + " (" + std::string(Version::GIT_HASH) + ")",
1,
text_color);
// Muestra información del monitor desplazada hacia abajo
/*loading_text_->writeColored(
X_PADDING,
Y_PADDING + 18,
screen->getDisplayMonitorName(),
text_color);
loading_text_->writeColored(
X_PADDING,
Y_PADDING + 27,
std::to_string(screen->getDisplayMonitorWidth()) + "x" + std::to_string(screen->getDisplayMonitorHeight()),
text_color);
loading_text_->writeColored(
X_PADDING,
Y_PADDING + 36,
std::to_string(screen->getDisplayMonitorRefreshRate()) + "Hz",
text_color);*/
// Renderiza el frame en pantalla
screen->coreRender();
}
// Carga los datos para el modo demostración (sin mostrar progreso)
void Resource::loadDemoDataQuiet() {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n>> DEMO FILES (quiet load)");
auto list = Asset::get()->getListByType(Asset::Type::DEMODATA);
const auto LIST = Asset::get()->getListByType(Asset::Type::DEMODATA);
demos_.clear();
for (const auto& l : list) {
demos_.emplace_back(loadDemoDataFromFile(l));
}
demos_.reserve(LIST.size());
std::ranges::transform(LIST, std::back_inserter(demos_), [](const auto& l) { return loadDemoDataFromFile(l); });
}
// Inicializa los rectangulos que definen la barra de progreso
void Resource::initProgressBar() {
const float BAR_Y_POSITION = param.game.height - BAR_HEIGHT - Y_PADDING;
const float WIRED_BAR_WIDTH = param.game.width * BAR_WIDTH_RATIO;
const float BAR_X_POSITION = (param.game.width - WIRED_BAR_WIDTH) / 2.0F;
const float BAR_Y_POSITION = (param.game.height * BAR_Y_RATIO) - (BAR_HEIGHT / 2.0F);
const float WIRED_BAR_WIDTH = param.game.width - (X_PADDING * 2);
loading_wired_rect_ = {.x = X_PADDING, .y = BAR_Y_POSITION, .w = WIRED_BAR_WIDTH, .h = BAR_HEIGHT};
loading_wired_rect_ = {.x = BAR_X_POSITION, .y = BAR_Y_POSITION, .w = WIRED_BAR_WIDTH, .h = BAR_HEIGHT};
const float FULL_BAR_WIDTH = WIRED_BAR_WIDTH * loading_count_.getPercentage();
loading_full_rect_ = {.x = X_PADDING, .y = BAR_Y_POSITION, .w = FULL_BAR_WIDTH, .h = BAR_HEIGHT};
loading_full_rect_ = {.x = BAR_X_POSITION, .y = BAR_Y_POSITION, .w = FULL_BAR_WIDTH, .h = BAR_HEIGHT};
}
// Actualiza el progreso de carga y muestra la barra
// Actualiza el estado del progreso. No renderiza: el repintado lo hace
// Preload::iterate una vez por frame llamando a renderProgress().
void Resource::updateLoadingProgress(std::string name) {
loading_resource_name_ = std::move(name);
loading_count_.increase();
updateProgressBar();
renderProgress();
}
// Actualiza la barra de estado
@@ -3,24 +3,27 @@
#include <SDL3/SDL.h> // Para SDL_FRect
#include <cstddef> // Para size_t
#include <cstdint> // Para std::uint8_t
#include <memory> // Para shared_ptr
#include <string> // Para string
#include <utility> // Para move
#include <vector> // Para vector
#include "animated_sprite.hpp" // Para AnimationsFileBuffer
#include "demo.hpp" // Para DemoData
#include "text.hpp" // Para Text, TextFile
#include "texture.hpp" // Para Texture
#include "core/rendering/sprite/animated_sprite.hpp" // Para AnimationsFileBuffer
#include "core/rendering/text.hpp" // Para Text, TextFile
#include "core/rendering/texture.hpp" // Para Texture
#include "core/system/demo.hpp" // Para DemoData
struct JA_Music_t;
struct JA_Sound_t;
namespace Ja {
struct Music;
struct Sound;
} // namespace Ja
// --- Clase Resource: gestiona todos los recursos del juego (singleton) ---
class Resource {
public:
// --- Enum para el modo de carga ---
enum class LoadingMode {
enum class LoadingMode : std::uint8_t {
PRELOAD, // Carga todos los recursos al inicio
LAZY_LOAD // Carga los recursos bajo demanda
};
@@ -31,8 +34,8 @@ class Resource {
static auto get() -> Resource*; // Obtiene el puntero al objeto Resource
// --- Métodos de acceso a recursos ---
auto getSound(const std::string& name) -> JA_Sound_t*; // Obtiene el sonido por nombre
auto getMusic(const std::string& name) -> JA_Music_t*; // Obtiene la música por nombre
auto getSound(const std::string& name) -> Ja::Sound*; // Obtiene el sonido por nombre
auto getMusic(const std::string& name) -> Ja::Music*; // Obtiene la música por nombre
auto getTexture(const std::string& name) -> std::shared_ptr<Texture>; // Obtiene la textura por nombre
auto getTextFile(const std::string& name) -> std::shared_ptr<Text::File>; // Obtiene el fichero de texto por nombre
auto getText(const std::string& name) -> std::shared_ptr<Text>; // Obtiene el objeto de texto por nombre
@@ -42,6 +45,15 @@ class Resource {
// --- Métodos de recarga de recursos ---
void reload(); // Recarga todos los recursos
// --- Cargador incremental ---
// beginLoad prepara el estado; loadStep carga recursos hasta agotar el presupuesto;
// devuelve true cuando ya no queda nada. renderProgress se llama una vez por frame
// desde la escena Preload.
void beginLoad();
auto loadStep(int budget_ms) -> bool;
[[nodiscard]] auto isLoadDone() const -> bool;
void renderProgress();
// --- Método para obtener el modo de carga actual ---
[[nodiscard]] auto getLoadingMode() const -> LoadingMode { return loading_mode_; }
@@ -49,18 +61,18 @@ class Resource {
// --- Estructuras para recursos individuales ---
struct ResourceSound {
std::string name; // Nombre del sonido
JA_Sound_t* sound; // Objeto con el sonido
Ja::Sound* sound; // Objeto con el sonido
ResourceSound(std::string name, JA_Sound_t* sound = nullptr)
explicit ResourceSound(std::string name, Ja::Sound* sound = nullptr)
: name(std::move(name)),
sound(sound) {}
};
struct ResourceMusic {
std::string name; // Nombre de la música
JA_Music_t* music; // Objeto con la música
Ja::Music* music; // Objeto con la música
ResourceMusic(std::string name, JA_Music_t* music = nullptr)
explicit ResourceMusic(std::string name, Ja::Music* music = nullptr)
: name(std::move(name)),
music(music) {}
};
@@ -69,7 +81,7 @@ class Resource {
std::string name; // Nombre de la textura
std::shared_ptr<Texture> texture; // Objeto con la textura
ResourceTexture(std::string name, std::shared_ptr<Texture> texture = nullptr)
explicit ResourceTexture(std::string name, std::shared_ptr<Texture> texture = nullptr)
: name(std::move(name)),
texture(std::move(texture)) {}
};
@@ -78,7 +90,7 @@ class Resource {
std::string name; // Nombre del fichero
std::shared_ptr<Text::File> text_file; // Objeto con los descriptores de la fuente de texto
ResourceTextFile(std::string name, std::shared_ptr<Text::File> text_file = nullptr)
explicit ResourceTextFile(std::string name, std::shared_ptr<Text::File> text_file = nullptr)
: name(std::move(name)),
text_file(std::move(text_file)) {}
};
@@ -87,7 +99,7 @@ class Resource {
std::string name; // Nombre del objeto
std::shared_ptr<Text> text; // Objeto de texto
ResourceText(std::string name, std::shared_ptr<Text> text = nullptr)
explicit ResourceText(std::string name, std::shared_ptr<Text> text = nullptr)
: name(std::move(name)),
text(std::move(text)) {}
};
@@ -96,7 +108,7 @@ class Resource {
std::string name; // Nombre de la animación
AnimationsFileBuffer animation; // Objeto con las animaciones
ResourceAnimation(std::string name, AnimationsFileBuffer animation = {})
explicit ResourceAnimation(std::string name, AnimationsFileBuffer animation = {})
: name(std::move(name)),
animation(std::move(animation)) {}
};
@@ -107,7 +119,7 @@ class Resource {
size_t loaded{0}; // Número de recursos cargados
ResourceCount() = default;
ResourceCount(size_t total)
explicit ResourceCount(size_t total)
: total(total) {}
void add(size_t amount) { loaded += amount; }
@@ -117,12 +129,15 @@ class Resource {
}
};
// --- Constantes para la pantalla de carga ---
static constexpr float X_PADDING = 60.0F;
static constexpr float Y_PADDING = 20.0F;
// --- Constantes para la barra de progreso de la pantalla de carga ---
// BAR_WIDTH_RATIO: fracción del ancho de la pantalla ocupada por la barra (centrada)
// 1.0 = ancho completo, 0.5 = la mitad del ancho
// BAR_Y_RATIO: posición vertical del CENTRO de la barra en fracción de la altura
// 0.0 = centro en el borde superior, 1.0 = centro en el borde inferior
// BAR_HEIGHT: grosor de la barra en píxeles
static constexpr float BAR_WIDTH_RATIO = 0.5F;
static constexpr float BAR_Y_RATIO = 0.85F;
static constexpr float BAR_HEIGHT = 5.0F;
static constexpr Color BAR_COLOR = Color(128, 128, 128);
static constexpr Color TEXT_COLOR = Color(255, 255, 255);
// --- Modo de carga ---
LoadingMode loading_mode_;
@@ -143,13 +158,24 @@ class Resource {
SDL_FRect loading_wired_rect_;
SDL_FRect loading_full_rect_;
// --- Estado del cargador incremental ---
enum class LoadStage : std::uint8_t {
SOUNDS,
MUSICS,
TEXTURES,
TEXT_FILES,
ANIMATIONS,
DEMO_DATA,
CREATE_TEXT,
CREATE_TEXT_TEXTURES,
CREATE_PLAYER_TEXTURES,
DONE
};
LoadStage stage_{LoadStage::DONE};
size_t stage_index_{0};
bool saved_vsync_{false}; // Vsync previo a beginLoad, restaurado por finishBoot/load
// --- Métodos internos de carga y gestión ---
void loadSounds(); // Carga los sonidos
void loadMusics(); // Carga las músicas
void loadTextures(); // Carga las texturas
void loadTextFiles(); // Carga los ficheros de texto
void loadAnimations(); // Carga las animaciones
void loadDemoData(); // Carga los datos para el modo demostración
void loadDemoDataQuiet(); // Carga los datos de demo sin mostrar progreso (para modo lazy)
void loadEssentialResources(); // Carga recursos esenciales en modo lazy
void loadEssentialTextures(); // Carga solo las texturas esenciales (fuentes)
@@ -164,8 +190,8 @@ class Resource {
// --- Métodos para carga perezosa ---
void initResourceLists(); // Inicializa las listas de recursos sin cargar el contenido
static auto loadSoundLazy(const std::string& name) -> JA_Sound_t*; // Carga un sonido específico bajo demanda
static auto loadMusicLazy(const std::string& name) -> JA_Music_t*; // Carga una música específica bajo demanda
static auto loadSoundLazy(const std::string& name) -> Ja::Sound*; // Carga un sonido específico bajo demanda
static auto loadMusicLazy(const std::string& name) -> Ja::Music*; // Carga una música específica bajo demanda
static auto loadTextureLazy(const std::string& name) -> std::shared_ptr<Texture>; // Carga una textura específica bajo demanda
static auto loadTextFileLazy(const std::string& name) -> std::shared_ptr<Text::File>; // Carga un fichero de texto específico bajo demanda
auto loadTextLazy(const std::string& name) -> std::shared_ptr<Text>; // Carga un objeto de texto específico bajo demanda
@@ -173,11 +199,22 @@ class Resource {
// --- Métodos internos para gestionar el progreso ---
void calculateTotalResources(); // Calcula el número de recursos para cargar
void renderProgress(); // Muestra el progreso de carga
void updateLoadingProgress(std::string name); // Actualiza el progreso de carga
void initProgressBar(); // Inicializa los rectangulos que definen la barra de progreso
void updateProgressBar(); // Actualiza la barra de estado
// Avança una etapa que descarrega una llista d'assets: si `stage_index_` desborda la mida,
// salta a `next_stage`; si no, crida `load_one` per a l'element actual i incrementa.
void advanceListLoadStage(const std::vector<std::string>& list, void (Resource::*load_one)(size_t), LoadStage next_stage);
// --- Helpers del cargador incremental (cargan un único recurso) ---
void loadOneSound(size_t idx);
void loadOneMusic(size_t idx);
void loadOneTexture(size_t idx);
void loadOneTextFile(size_t idx);
void loadOneAnimation(size_t idx);
void loadOneDemoData(size_t idx);
// --- Constructores y destructor privados (singleton) ---
explicit Resource(LoadingMode mode); // Constructor privado con modo de carga
~Resource(); // Destructor privado
@@ -1,11 +1,11 @@
#include "resource_helper.hpp"
#include "core/resources/resource_helper.hpp"
#include <algorithm> // Para replace
#include <cstddef> // Para size_t
#include <fstream> // Para basic_ifstream, basic_ostream, basic_ios, operator<<, ios, basic_istream, endl, operator|, basic_istream::read, basic_istream::seekg, basic_istream::tellg, ifstream, streamsize
#include <iostream> // Para cout
#include "resource_loader.hpp" // Para ResourceLoader
#include "core/resources/resource_loader.hpp" // Para ResourceLoader
namespace ResourceHelper {
static bool resource_system_initialized = false;
@@ -59,20 +59,10 @@ namespace ResourceHelper {
}
auto shouldUseResourcePack(const std::string& filepath) -> bool {
// Archivos que NO van al pack:
// - config/ (ahora está fuera de data/)
// - archivos absolutos del sistema
if (filepath.find("config/") != std::string::npos) {
return false;
}
// Si contiene "data/" es candidato para el pack
if (filepath.find("data/") != std::string::npos) {
return true;
}
return false;
// Si la ruta conté "data/" és candidat al pack. La resta (paths absoluts del
// SYSTEM_FOLDER o relatives al binari com gamecontrollerdb.txt) van sempre al
// filesystem.
return filepath.find("data/") != std::string::npos;
}
auto getPackPath(const std::string& asset_path) -> std::string {
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <cstdint> // Para uint8_t
#include <string> // Para string
#include <vector> // Para vector
// Helper functions para integrar ResourceLoader con el sistema existente
namespace ResourceHelper {
// Inicializa ResourceLoader (llamar al inicio del programa)
auto initializeResourceSystem(const std::string& pack_file = "resources.pack", bool enable_fallback = true) -> bool;
// Cierra ResourceLoader
void shutdownResourceSystem();
// Carga un archivo usando ResourceLoader o fallback a filesystem
auto loadFile(const std::string& filepath) -> std::vector<uint8_t>;
// Verifica si un archivo debería cargarse del pack vs filesystem
auto shouldUseResourcePack(const std::string& filepath) -> bool;
// Convierte ruta Asset a ruta relativa para ResourceLoader
auto getPackPath(const std::string& asset_path) -> std::string;
} // namespace ResourceHelper
@@ -1,17 +1,15 @@
#include "resource_loader.hpp"
#include "core/resources/resource_loader.hpp"
#include <algorithm> // Para replace
#include <filesystem> // Para exists, path, recursive_directory_iterator, directory_entry, relative
#include <fstream> // Para basic_ostream, basic_ifstream, operator<<, basic_ios, endl, ios, basic_istream, operator|, basic_istream::read, basic_istream::seekg, basic_istream::tellg, ifstream, streamsize
#include <iostream> // Para cerr, cout
#include "resource_pack.hpp" // Para ResourcePack
#include "core/resources/resource_pack.hpp" // Para ResourcePack
std::unique_ptr<ResourceLoader> ResourceLoader::instance = nullptr;
ResourceLoader::ResourceLoader()
: resource_pack_(nullptr),
fallback_to_files_(true) {}
ResourceLoader::ResourceLoader() = default;
auto ResourceLoader::getInstance() -> ResourceLoader& {
if (!instance) {
@@ -33,8 +31,6 @@ auto ResourceLoader::initialize(const std::string& pack_file, bool enable_fallba
if (std::filesystem::exists(pack_file)) {
resource_pack_ = new ResourcePack();
if (resource_pack_->loadPack(pack_file)) {
std::cout << "Resource pack loaded successfully: " << pack_file << '\n';
std::cout << "Resources available: " << resource_pack_->getResourceCount() << '\n';
return true;
}
delete resource_pack_;
@@ -43,7 +39,6 @@ auto ResourceLoader::initialize(const std::string& pack_file, bool enable_fallba
}
if (fallback_to_files_) {
std::cout << "Using fallback mode: loading resources from data/ directory" << '\n';
return true;
}
@@ -11,9 +11,9 @@ class ResourcePack;
class ResourceLoader {
private:
static std::unique_ptr<ResourceLoader> instance;
ResourcePack* resource_pack_;
ResourcePack* resource_pack_ = nullptr;
std::string pack_path_;
bool fallback_to_files_;
bool fallback_to_files_ = true;
ResourceLoader();
@@ -1,27 +1,23 @@
#include "resource_pack.hpp"
#include "core/resources/resource_pack.hpp"
#include <algorithm> // Para replace
#include <algorithm> // Para replace, ranges::all_of
#include <array> // Para array
#include <filesystem> // Para path, recursive_directory_iterator, directory_entry, exists, relative
#include <fstream> // Para basic_ifstream, basic_ostream, basic_ofstream, operator<<, basic_ios, basic_istream::read, basic_ostream::write, endl, ios, basic_istream, ifstream, operator|, basic_istream::seekg, basic_istream::tellg, ofstream, streamsize
#include <iostream> // Para cerr
#include <numeric> // Para accumulate
#include <utility> // Para pair
const std::string ResourcePack::DEFAULT_ENCRYPT_KEY = "CCAE_RESOURCES__2024";
ResourcePack::ResourcePack()
: loaded_(false) {}
ResourcePack::ResourcePack() = default;
ResourcePack::~ResourcePack() {
clear();
}
auto ResourcePack::calculateChecksum(const std::vector<uint8_t>& data) -> uint32_t {
uint32_t checksum = 0x12345678;
for (unsigned char i : data) {
checksum = ((checksum << 5) + checksum) + i;
}
return checksum;
return std::accumulate(data.begin(), data.end(), static_cast<uint32_t>(0x12345678), [](uint32_t checksum, unsigned char i) { return ((checksum << 5) + checksum) + i; });
}
void ResourcePack::encryptData(std::vector<uint8_t>& data, const std::string& key) {
@@ -162,20 +158,16 @@ auto ResourcePack::addDirectory(const std::string& directory) -> bool {
return false; // NOLINT(readability-simplify-boolean-expr)
}
for (const auto& entry : std::filesystem::recursive_directory_iterator(directory)) {
if (entry.is_regular_file()) {
return std::ranges::all_of(std::filesystem::recursive_directory_iterator(directory),
[this, &directory](const auto& entry) {
if (!entry.is_regular_file()) {
return true;
}
std::string filepath = entry.path().string();
std::string filename = std::filesystem::relative(entry.path(), directory).string();
std::ranges::replace(filename, '\\', '/');
if (!addFile(filename, filepath)) {
return false;
}
}
}
return true;
return addFile(filename, filepath);
});
}
auto ResourcePack::getResource(const std::string& filename) -> std::vector<uint8_t> {
@@ -17,7 +17,7 @@ class ResourcePack {
private:
std::unordered_map<std::string, ResourceEntry> resources_;
std::vector<uint8_t> data_;
bool loaded_;
bool loaded_ = false;
static auto calculateChecksum(const std::vector<uint8_t>& data) -> uint32_t;
static void encryptData(std::vector<uint8_t>& data, const std::string& key);
@@ -4,7 +4,7 @@
#include <array>
#include "ui/notifier.hpp" // Para Notifier::Position
#include "game/ui/notifier.hpp" // Para Notifier::Position
namespace Defaults::Game {
constexpr float WIDTH = 320.0F;
@@ -177,8 +177,8 @@ namespace Defaults::Player::OutlineColor {
namespace Defaults::Window {
constexpr const char* CAPTION = "© 2025 Coffee Crisis Arcade Edition — JailDesigner";
constexpr int ZOOM = 2;
constexpr int MAX_ZOOM = 2;
constexpr int ZOOM = 3;
constexpr int MAX_ZOOM = 3;
} // namespace Defaults::Window
namespace Defaults::Video {
@@ -188,28 +188,31 @@ namespace Defaults::Video {
constexpr bool INTEGER_SCALE = true;
constexpr bool GPU_ACCELERATION = true;
constexpr bool SHADER_ENABLED = false;
constexpr bool SUPERSAMPLING = false;
constexpr bool LINEAR_UPSCALE = false;
constexpr int DOWNSCALE_ALGO = 1;
} // namespace Defaults::Video
namespace Defaults::Music {
constexpr bool ENABLED = true;
constexpr int VOLUME = 100;
constexpr float VOLUME = 0.8F;
} // namespace Defaults::Music
namespace Defaults::Sound {
constexpr bool ENABLED = true;
constexpr int VOLUME = 100;
constexpr float VOLUME = 1.0F;
} // namespace Defaults::Sound
namespace Defaults::Audio {
constexpr bool ENABLED = true;
constexpr int VOLUME = 100;
constexpr float VOLUME = 1.0F;
} // namespace Defaults::Audio
namespace Defaults::Settings {
constexpr bool AUTOFIRE = true;
constexpr bool SHUTDOWN_ENABLED = false;
constexpr const char* PARAMS_FILE = "param_320x256.txt";
constexpr const char* PARAMS_PRESET = "arcade"; // Identificador intern del preset (correspon a data/config/<preset>.txt)
} // namespace Defaults::Settings
namespace Defaults::Loading {
constexpr bool SHOW = false;
constexpr bool SHOW_RESOURCE_NAME = true;
constexpr bool WAIT_FOR_INPUT = false;
} // namespace Defaults::Loading
@@ -1,12 +1,11 @@
#include "demo.hpp"
#include "core/system/demo.hpp"
#include <SDL3/SDL.h> // Para SDL_IOStream, SDL_IOFromConstMem, SDL_IOFromFile, SDL_ReadIO, SDL_WriteIO, SDL_CloseIO
#include <iostream> // Para std::cout
#include <stdexcept> // Para runtime_error
#include "resource_helper.hpp" // Para ResourceHelper
#include "ui/logger.hpp"
#include "utils.hpp" // Para printWithDots, getFileName
#include "core/resources/resource_helper.hpp" // Para ResourceHelper
// Carga el fichero de datos para la demo
auto loadDemoDataFromFile(const std::string& file_path) -> DemoData {
@@ -24,10 +23,9 @@ auto loadDemoDataFromFile(const std::string& file_path) -> DemoData {
}
if (file == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Fichero no encontrado %s", file_path.c_str());
std::cout << "Error: Fichero no encontrado " << file_path << '\n';
throw std::runtime_error("Fichero no encontrado: " + file_path);
}
Logger::dots("DemoData : ", getFileName(file_path), "[ LOADED ]");
// Lee todos los datos del fichero y los deja en el destino
for (int i = 0; i < TOTAL_DEMO_DATA; ++i) {
@@ -52,18 +50,15 @@ bool saveDemoFile(const std::string& file_path, const DemoData& dd) {
// Guarda los datos
for (const auto& data : dd) {
if (SDL_WriteIO(file, &data, sizeof(DemoKeys)) != sizeof(DemoKeys)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error al escribir el fichero %s", getFileName(file_path).c_str());
std::cout << "Error al escribir el fichero " << getFileName(file_path) << '\n';
success = false;
break;
}
}
if (success) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Writing file %s", getFileName(file_path).c_str());
}
// Cierra el fichero
SDL_CloseIO(file);
} else {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Unable to save %s file! %s", getFileName(file_path).c_str(), SDL_GetError());
std::cout << "Error: Unable to save " << getFileName(file_path) << " file! " << SDL_GetError() << '\n';
success = false;
}
@@ -1,61 +1,106 @@
// IWYU pragma: no_include <bits/chrono.h>
#include "director.hpp"
#include "core/system/director.hpp"
#include <SDL3/SDL.h> // Para SDL_SetLogPriority, SDL_LogCategory, SDL_LogPriority, SDL_Quit
#include <cstdlib> // Para srand, exit, rand, EXIT_FAILURE
#include <ctime> // Para time
#include <filesystem> // Para path, absolute
#include <fstream> // Para ifstream, ofstream
#include <iostream> // Para basic_ostream, operator<<, cerr
#include <memory> // Para make_unique, unique_ptr
#include <span> // Para span
#include <stdexcept> // Para runtime_error
#include <string> // Para allocator, basic_string, char_traits, operator+, string, operator==
#include <cerrno> // Para errno
#include <cstdlib> // Para srand, exit, rand, EXIT_FAILURE
#include <cstring> // Para std::strerror
#include <ctime> // Para time
#include <fstream> // Para ifstream, ofstream
#include <iostream> // Para basic_ostream, operator<<, cerr
#include <memory> // Para make_unique, unique_ptr
#include <stdexcept> // Para runtime_error
#include <string> // Para allocator, basic_string, char_traits, operator+, string, operator==
#include "asset.hpp" // Para Asset
#include "audio.hpp" // Para Audio
#include "external/fkyaml_node.hpp" // Para fkyaml::node
#include "global_events.hpp" // Para GlobalEvents::handle
#include "input.hpp" // Para Input
#include "lang.hpp" // Para setLanguage
#include "manage_hiscore_table.hpp" // Para ManageHiScoreTable
#include "options.hpp" // Para Settings, loadFromFile, saveToFile, settings, setConfigFile, setControllersFile
#include "param.hpp" // Para loadParamsFromFile
#include "player.hpp" // Para Player
#include "resource.hpp" // Para Resource
#include "resource_helper.hpp" // Para initializeResourceSystem
#include "screen.hpp" // Para Screen
#include "section.hpp" // Para Name, Options, name, options, AttractMode, attract_mode
#include "sections/credits.hpp" // Para Credits
#include "sections/game.hpp" // Para Game
#include "sections/hiscore_table.hpp" // Para HiScoreTable
#include "sections/instructions.hpp" // Para Instructions
#include "sections/intro.hpp" // Para Intro
#include "sections/logo.hpp" // Para Logo
#include "sections/title.hpp" // Para Title
#include "shutdown.hpp" // Para resultToString, shutdownSystem, ShutdownResult
#include "system_utils.hpp" // Para createApplicationFolder, resultToString, Result
#include "ui/logger.hpp" // Para section, put
#include "ui/notifier.hpp" // Para Notifier
#include "ui/service_menu.hpp" // Para ServiceMenu
#include "utils.hpp" // Para Overrides, overrides
#ifdef _WIN32
#include <process.h> // Per _execv
#else
#include <unistd.h> // Per execv
#endif
#include "core/audio/audio.hpp" // Para Audio
#include "core/input/input.hpp" // Para Input
#include "core/locale/lang.hpp" // Para setLanguage
#include "core/rendering/screen.hpp" // Para Screen
#include "core/resources/asset.hpp" // Para Asset
#include "core/resources/resource.hpp" // Para Resource
#include "core/resources/resource_helper.hpp" // Para initializeResourceSystem
#include "core/system/global_events.hpp" // Para GlobalEvents::handle
#include "core/system/section.hpp" // Para Name, Options, name, options, AttractMode, attract_mode
#include "core/system/shutdown.hpp" // Para resultToString, shutdownSystem, ShutdownResult
#include "core/system/system_utils.hpp" // Para createApplicationFolder, resultToString, Result
#include "external/fkyaml_node.hpp" // Para fkyaml::node
#include "game/entities/player.hpp" // Para Player
#include "game/gameplay/manage_hiscore_table.hpp" // Para ManageHiScoreTable
#include "game/options.hpp" // Para Settings, loadFromFile, saveToFile, settings, setConfigFile, setControllersFile
#include "game/scenes/credits.hpp" // Para Credits
#include "game/scenes/game.hpp" // Para Game
#include "game/scenes/hiscore_table.hpp" // Para HiScoreTable
#include "game/scenes/instructions.hpp" // Para Instructions
#include "game/scenes/intro.hpp" // Para Intro
#include "game/scenes/logo.hpp" // Para Logo
#include "game/scenes/preload.hpp" // Para Preload
#include "game/scenes/title.hpp" // Para Title
#include "game/ui/notifier.hpp" // Para Notifier
#include "game/ui/service_menu.hpp" // Para ServiceMenu
#include "utils/param.hpp" // Para loadParamsFromFile
namespace {
// Llig un camp opcional d'un YAML cap a `dest`. Si no existeix, no toca `dest`.
// Si existeix però el tipus no encaixa, deixa el valor per defecte i avisa per stderr
// (un debug.yaml mal escrit no ha de tombar l'arrencada, però l'usuari ha de saber-ho).
template <typename T>
void loadYamlField(const fkyaml::node& yaml, const std::string& key, T& dest) {
if (!yaml.contains(key)) { return; }
try {
dest = yaml[key].get_value<T>();
} catch (...) {
std::cerr << "debug.yaml: valor invàlid per a '" << key << "', es manté el valor per defecte\n";
}
}
auto parseInitialSection(const std::string& value) -> Section::Name {
if (value == "logo") { return Section::Name::LOGO; }
if (value == "intro") { return Section::Name::INTRO; }
if (value == "title") { return Section::Name::TITLE; }
if (value == "credits") { return Section::Name::CREDITS; }
if (value == "instructions") { return Section::Name::INSTRUCTIONS; }
if (value == "hiscore") { return Section::Name::HI_SCORE_TABLE; }
return Section::Name::GAME; // "game" i qualsevol valor desconegut
}
auto parseInitialOptions(const std::string& value) -> Section::Options {
if (value == "none") { return Section::Options::NONE; }
if (value == "2p") { return Section::Options::GAME_PLAY_2P; }
if (value == "both") { return Section::Options::GAME_PLAY_BOTH; }
return Section::Options::GAME_PLAY_1P; // "1p" i qualsevol valor desconegut
}
} // namespace
// Constructor
Director::Director(int argc, std::span<char*> argv) {
Director::Director(int /*argc*/, char** argv)
: argv_(argv) {
Section::attract_mode = Section::AttractMode::TITLE_TO_DEMO;
// Establece el nivel de prioridad de la categoría de registro
SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
SDL_SetLogPriority(SDL_LOG_CATEGORY_TEST, SDL_LOG_PRIORITY_ERROR);
Logger::put("Game start\n");
// Inicia la semilla aleatoria usando el tiempo actual en segundos
std::srand(static_cast<unsigned int>(std::time(nullptr)));
// Comprueba los parametros del programa
checkProgramArguments(argc, argv);
std::cout << "Game start\n";
// Obtener la ruta del ejecutable desde SDL
#ifdef __EMSCRIPTEN__
// En Emscripten los assets viven en la raíz del MEMFS virtual (/data, /config),
// preloaded vía --preload-file en el linker. No hay ruta de ejecutable.
executable_path_ = "";
#else
const char* base_path = SDL_GetBasePath();
executable_path_ = (base_path != nullptr) ? base_path : "";
#endif
// Crea la carpeta del sistema donde guardar los datos persistentes
createSystemFolder("jailgames");
@@ -80,7 +125,6 @@ Director::~Director() {
// que close() destruye a continuación.
resetActiveSection();
close();
Logger::put("\nBye!");
}
// Inicializa todo
@@ -96,20 +140,20 @@ void Director::init() {
#endif
// Inicializar sistema de recursos con o sin fallback según el tipo de build
#ifdef RELEASE_BUILD
// Release: Sin fallback - Solo resources.pack (estricto)
#if defined(RELEASE_BUILD) && !defined(__EMSCRIPTEN__)
// Release nativo: Sin fallback - Solo resources.pack (estricto)
ResourceHelper::initializeResourceSystem(pack_path, false);
#else
// Desarrollo: Con fallback - Puede usar data/ si falta el pack (flexible)
// Desarrollo o Emscripten: Con fallback - carga desde filesystem/MEMFS
ResourceHelper::initializeResourceSystem(pack_path, true);
#endif
loadAssets(); // Crea el índice de archivos
Logger::section("INIT INPUT");
Input::init(Asset::get()->getPath("gamecontrollerdb.txt"), Asset::get()->getPath("controllers.json")); // Carga configuración de controles
// gamecontrollerdb.txt no pot anar al pack (SDL_AddGamepadMappingsFromFile només llegeix del FS).
// Sempre viu al costat del binari, fora del índex d'assets.
Input::init(executable_path_ + "gamecontrollerdb.txt", Asset::get()->getPath("controllers.json")); // Carga configuración de controles
Logger::section("INIT CONFIG");
Options::setConfigFile(Asset::get()->getPath("config.yaml")); // Establece el fichero de configuración
Options::setControllersFile(Asset::get()->getPath("controllers.json")); // Establece el fichero de configuración de mandos
Options::setPostFXFile(Asset::get()->getPath("postfx.yaml")); // Establece el fichero de presets PostFX
@@ -117,33 +161,70 @@ void Director::init() {
Options::loadFromFile(); // Carga el archivo de configuración
Options::loadPostFXFromFile(); // Carga los presets PostFX
Options::loadCrtPiFromFile(); // Carga los presets CrtPi
loadParams(); // Carga los parámetros del programa
loadScoreFile(); // Carga el archivo de puntuaciones
#ifdef __EMSCRIPTEN__
// En la versión web el navegador gestiona la ventana: ventana (no
// fullscreen — el canvas ya marca el área), integer scale para píxeles nítidos.
Options::window.zoom = 3;
Options::video.fullscreen = false;
Options::video.integer_scale = true;
// Precarga silenciosa: pantalla negra mientras el .data termina de descargarse.
Options::loading.show = false;
Options::loading.wait_for_input = false;
#endif
loadParams(); // Carga los parámetros del programa
loadScoreFile(); // Carga el archivo de puntuaciones
// Inicialización de subsistemas principales
Lang::setLanguage(Options::settings.language); // Carga el archivo de idioma
Logger::section("INIT VIDEO");
Screen::init(); // Inicializa la pantalla y el sistema de renderizado
Logger::section("INIT AUDIO");
Audio::init(); // Activa el sistema de audio
Logger::section("INIT RESOURCES");
#ifdef _DEBUG
Resource::init(debug_config.resource_loading == "lazy" ? Resource::LoadingMode::LAZY_LOAD : Resource::LoadingMode::PRELOAD);
#else
Resource::init(Resource::LoadingMode::PRELOAD);
#endif
if (Resource::get()->getLoadingMode() == Resource::LoadingMode::PRELOAD) {
// Guarda la sección destino (la que fijó loadDebugConfig o el default)
// y redirige el arranque a la escena PRELOAD hasta que loadStep termine.
Section::post_preload = Section::name;
Section::name = Section::Name::PRELOAD;
Resource::get()->beginLoad();
} else {
// LAZY_LOAD: el constructor de Resource ya cargó lo esencial síncronamente;
// no hay fase de preload, pasamos directamente a post-boot.
finishBoot();
boot_loading_ = false;
}
// ServiceMenu/Notifier/getSingletons se mueven a finishBoot() — dependen
// de Resource y se inicializan al terminar la carga incremental.
}
// Inicializaciones que dependen del Resource cargado. Se llama desde iterate()
// cuando Resource::loadStep() devuelve true, con la ventana y el bucle vivos.
void Director::finishBoot() {
ServiceMenu::init(); // Inicializa el menú de servicio
Notifier::init(std::string(), Resource::get()->getText("8bithud")); // Inicialización del sistema de notificaciones
Screen::get()->getSingletons(); // Obtiene los punteros al resto de singletones
Logger::section("GAME LOG");
// Restaura el vsync a la preferencia del usuario (beginLoad lo había puesto a false)
Screen::get()->setVSync(Options::video.vsync);
// Si NO estamos en modo "wait for input", transiciona ya al destino.
// Si wait_for_input está activo (y la pantalla es visible), nos quedamos
// en PRELOAD hasta que el usuario pulse tecla/botón.
if (!(Options::loading.show && Options::loading.wait_for_input)) {
Section::name = Section::post_preload;
}
}
// Cierra todo y libera recursos del sistema y de los singletons
void Director::close() {
// Allibera tots els singletons i SDL (compartit entre close() i relaunch())
void Director::shutdownSubsystems() {
// Guarda las opciones actuales en el archivo de configuración
Options::saveToFile();
@@ -158,18 +239,45 @@ void Director::close() {
// Libera todos los recursos de SDL
SDL_Quit();
}
// Cierra todo y libera recursos del sistema y de los singletons
void Director::close() {
shutdownSubsystems();
std::cout << "\nBye!\n";
// Apaga el sistema
shutdownSystem(Section::options == Section::Options::SHUTDOWN);
}
// Reemplaça el procés actual per ell mateix (reinici en calent). En cas d'èxit no torna.
// Si no es pot reiniciar (Emscripten, argv invàlid), retorna i el caller fa el reset clàssic.
void Director::relaunch() const {
#ifdef __EMSCRIPTEN__
// Al navegador el reinici real seria location.reload(); aquí caiem al reset intern.
return;
#else
if (argv_ == nullptr || argv_[0] == nullptr) { return; }
std::cout << "Relaunching " << argv_[0] << "...\n";
shutdownSubsystems();
#ifdef _WIN32
_execv(argv_[0], argv_);
#else
execv(argv_[0], argv_);
#endif
// Si arribem aquí, execv ha fallat. Tots els subsistemes ja estan destruïts: no
// podem reprendre el bucle. Sortim amb error.
std::cerr << "Relaunch failed: " << std::strerror(errno) << "\n";
std::exit(EXIT_FAILURE);
#endif
}
// Carga los parametros
void Director::loadParams() {
// Carga los parametros para configurar el juego
#ifdef ANBERNIC
const std::string PARAM_FILE_PATH = Asset::get()->getPath("param_320x240.txt");
const std::string PARAM_FILE_PATH = Asset::get()->getPath("classic.txt");
#else
const std::string PARAM_FILE_PATH = Asset::get()->getPath(Options::settings.params_file);
const std::string PARAM_FILE_PATH = Asset::get()->getPath(Options::settings.params_preset + ".txt");
#endif
loadParamsFromFile(PARAM_FILE_PATH);
}
@@ -180,15 +288,11 @@ void Director::loadScoreFile() {
#ifdef _DEBUG
manager->clear();
#else
if (overrides.clear_hi_score_table) {
manager->clear();
} else {
manager->loadFromFile(Asset::get()->getPath("score.bin"));
}
manager->loadFromFile(Asset::get()->getPath("score.bin"));
#endif
}
// Carga el indice de ficheros desde un fichero
// Carga el indice de ficheros desde el pack (o filesystem como fallback)
void Director::loadAssets() {
#ifdef MACOS_BUNDLE
const std::string PREFIX = "/../Resources";
@@ -196,11 +300,13 @@ void Director::loadAssets() {
const std::string PREFIX;
#endif
// Cargar la configuración de assets (también aplicar el prefijo al archivo de configuración)
std::string config_path = executable_path_ + PREFIX + "/config/assets.txt";
Asset::get()->loadFromFile(config_path, PREFIX, system_folder_);
Logger::put("Assets configuration loaded successfully");
// El índice ahora vive dins el pack a "config/assets.txt" (ResourceHelper li trau el "data/" prefix).
// ResourceHelper::loadFile fa fallback automàtic al filesystem si el pack no està o no conté el fitxer.
auto buffer = ResourceHelper::loadFile("/data/config/assets.txt");
if (buffer.empty()) {
throw std::runtime_error("No s'ha pogut carregar l'índex d'assets (data/config/assets.txt)");
}
Asset::get()->loadFromBuffer(buffer, PREFIX, system_folder_);
// Si falta algun fichero, sale del programa
if (!Asset::get()->check()) {
@@ -208,29 +314,6 @@ void Director::loadAssets() {
}
}
// Comprueba los parametros del programa
void Director::checkProgramArguments(int argc, std::span<char*> argv) {
// Obtener la ruta absoluta del ejecutable
std::filesystem::path exe_path = std::filesystem::absolute(argv[0]);
executable_path_ = exe_path.parent_path().string();
// Asegurar que termine con separador de directorio
if (!executable_path_.empty() && executable_path_.back() != '/' && executable_path_.back() != '\\') {
executable_path_ += "/";
}
// Comprueba el resto de parámetros
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "--320x240") {
overrides.param_file = arg;
} else if (arg == "--clear_score") {
overrides.clear_hi_score_table = true;
}
}
}
// Carga debug.yaml desde la carpeta del sistema (solo en _DEBUG)
void Director::loadDebugConfig() {
const std::string DEBUG_FILE = system_folder_ + "/debug.yaml";
@@ -254,8 +337,9 @@ void Director::loadDebugConfig() {
out << "initial_stage: 0\n";
out << "show_render_info: true\n";
out << "resource_loading: preload\n";
out << "autoplay: false\n";
out << "invincibility: false\n";
out.close();
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Debug config created: %s", DEBUG_FILE.c_str());
}
// Usar defaults de DebugConfig
} else {
@@ -263,69 +347,20 @@ void Director::loadDebugConfig() {
file.close();
try {
auto yaml = fkyaml::node::deserialize(content);
if (yaml.contains("initial_section")) {
try {
debug_config.initial_section = yaml["initial_section"].get_value<std::string>();
} catch (...) {}
}
if (yaml.contains("initial_options")) {
try {
debug_config.initial_options = yaml["initial_options"].get_value<std::string>();
} catch (...) {}
}
if (yaml.contains("initial_stage")) {
try {
debug_config.initial_stage = yaml["initial_stage"].get_value<int>();
} catch (...) {}
}
if (yaml.contains("show_render_info")) {
try {
debug_config.show_render_info = yaml["show_render_info"].get_value<bool>();
} catch (...) {}
}
if (yaml.contains("resource_loading")) {
try {
debug_config.resource_loading = yaml["resource_loading"].get_value<std::string>();
} catch (...) {}
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Debug config loaded: section=%s options=%s stage=%d", debug_config.initial_section.c_str(), debug_config.initial_options.c_str(), debug_config.initial_stage);
loadYamlField(yaml, "initial_section", debug_config.initial_section);
loadYamlField(yaml, "initial_options", debug_config.initial_options);
loadYamlField(yaml, "initial_stage", debug_config.initial_stage);
loadYamlField(yaml, "show_render_info", debug_config.show_render_info);
loadYamlField(yaml, "resource_loading", debug_config.resource_loading);
loadYamlField(yaml, "autoplay", debug_config.autoplay);
loadYamlField(yaml, "invincibility", debug_config.invincibility);
} catch (...) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Error parsing debug.yaml, using defaults");
std::cout << "Error parsing debug.yaml, using defaults" << '\n';
}
}
// Mapear strings a enums
const auto& sec = debug_config.initial_section;
if (sec == "logo") {
Section::name = Section::Name::LOGO;
} else if (sec == "intro") {
Section::name = Section::Name::INTRO;
} else if (sec == "title") {
Section::name = Section::Name::TITLE;
} else if (sec == "game") {
Section::name = Section::Name::GAME;
} else if (sec == "credits") {
Section::name = Section::Name::CREDITS;
} else if (sec == "instructions") {
Section::name = Section::Name::INSTRUCTIONS;
} else if (sec == "hiscore") {
Section::name = Section::Name::HI_SCORE_TABLE;
} else {
Section::name = Section::Name::GAME;
}
const auto& opt = debug_config.initial_options;
if (opt == "none") {
Section::options = Section::Options::NONE;
} else if (opt == "1p") {
Section::options = Section::Options::GAME_PLAY_1P;
} else if (opt == "2p") {
Section::options = Section::Options::GAME_PLAY_2P;
} else if (opt == "both") {
Section::options = Section::Options::GAME_PLAY_BOTH;
} else {
Section::options = Section::Options::GAME_PLAY_1P;
}
Section::name = parseInitialSection(debug_config.initial_section);
Section::options = parseInitialOptions(debug_config.initial_options);
}
// Crea la carpeta del sistema donde guardar datos
@@ -341,6 +376,7 @@ void Director::createSystemFolder(const std::string& folder) {
// Libera todos los unique_ptr de sección (solo uno tiene propiedad a la vez)
void Director::resetActiveSection() {
preload_.reset();
logo_.reset();
intro_.reset();
title_.reset();
@@ -352,9 +388,11 @@ void Director::resetActiveSection() {
// Destruye la sección anterior y construye la nueva cuando Section::name cambia
void Director::handleSectionTransition() {
// RESET: recarga recursos y vuelve a LOGO (el propio reset() cambia Section::name)
// RESET: intenta reinici real via execv; si no es pot (Emscripten o argv invàlid),
// cau al reset intern (recarrega recursos i torna a LOGO, sense recrear Screen/Params).
if (Section::name == Section::Name::RESET) {
resetActiveSection(); // libera recursos actuales antes del reload
relaunch(); // En èxit no torna; el binari es reemplaça
resetActiveSection(); // Fallback: libera recursos actuales antes del reload
reset();
}
@@ -367,6 +405,10 @@ void Director::handleSectionTransition() {
// Construye la nueva
switch (Section::name) {
case Section::Name::PRELOAD:
preload_ = std::make_unique<Preload>();
break;
case Section::Name::LOGO:
logo_ = std::make_unique<Logo>();
break;
@@ -449,11 +491,32 @@ auto Director::iterate() -> SDL_AppResult {
return SDL_APP_SUCCESS;
}
// Fase de boot: carga incremental frame a frame con presupuesto de 50ms.
// Durante esta fase la escena activa es Preload (una barra de progreso).
if (boot_loading_) {
try {
if (Resource::get()->loadStep(50 /*ms*/)) {
finishBoot();
boot_loading_ = false;
// Los SDL_EVENT_GAMEPAD_ADDED iniciales ya los ha drenado la rama
// durante la carga: marcamos startup completo ahora para que los
// ADDED/REMOVED posteriores sí generen notificación.
GlobalEvents::markStartupComplete();
}
} catch (const std::exception& e) {
std::cerr << "Fatal error during resource load: " << e.what() << '\n';
Section::name = Section::Name::QUIT;
return SDL_APP_FAILURE;
}
}
// Gestiona las transiciones entre secciones (destruye la anterior y construye la nueva)
handleSectionTransition();
// Ejecuta un frame de la sección activa
if (logo_) {
if (preload_) {
Preload::iterate();
} else if (logo_) {
logo_->iterate();
} else if (intro_) {
intro_->iterate();
@@ -473,12 +536,14 @@ auto Director::iterate() -> SDL_AppResult {
}
// Procesa un evento SDL (llamado desde SDL_AppEvent)
auto Director::handleEvent(SDL_Event& event) -> SDL_AppResult {
auto Director::handleEvent(const SDL_Event& event) -> SDL_AppResult {
// Eventos globales (SDL_EVENT_QUIT, resize, render target reset, hotplug, service menu, ratón)
GlobalEvents::handle(event);
// Reenvía a la sección activa
if (logo_) {
if (preload_) {
Preload::handleEvent(event);
} else if (logo_) {
logo_->handleEvent(event);
} else if (intro_) {
intro_->handleEvent(event);
@@ -2,17 +2,18 @@
#include <SDL3/SDL.h> // Para SDL_AppResult, SDL_Event
#include <memory> // Para unique_ptr
#include <span> // Para Span
#include <string> // Para string
#include <cstdint> // Para std::uint8_t
#include <memory> // Para unique_ptr
#include <string> // Para string
#include "section.hpp" // Para Section::Name
#include "core/system/section.hpp" // Para Section::Name
namespace Lang {
enum class Code : int;
enum class Code : std::uint8_t;
}
// Declaraciones adelantadas de las secciones
class Preload;
class Logo;
class Intro;
class Title;
@@ -25,12 +26,12 @@ class Credits;
class Director {
public:
// --- Constructor y destructor ---
Director(int argc, std::span<char*> argv);
Director(int argc, char** argv);
~Director();
// --- Callbacks para SDL_MAIN_USE_CALLBACKS ---
auto iterate() -> SDL_AppResult; // Avanza un frame de la sección activa
auto handleEvent(SDL_Event& event) -> SDL_AppResult; // Procesa un evento SDL
auto iterate() -> SDL_AppResult; // Avanza un frame de la sección activa
auto handleEvent(const SDL_Event& event) -> SDL_AppResult; // Procesa un evento SDL
// --- Debug config (accesible desde otras clases) ---
struct DebugConfig {
@@ -39,6 +40,8 @@ class Director {
int initial_stage = 0;
bool show_render_info = true;
std::string resource_loading;
bool autoplay = false;
bool invincibility = false;
DebugConfig()
: initial_section("game"),
@@ -51,8 +54,10 @@ class Director {
// --- Variables internas ---
std::string executable_path_; // Ruta del ejecutable
std::string system_folder_; // Carpeta del sistema para almacenar datos
char** argv_ = nullptr; // argv original; usat per relaunch() (execv)
// --- Sección activa (una y sólo una viva en cada momento) ---
std::unique_ptr<Preload> preload_;
std::unique_ptr<Logo> logo_;
std::unique_ptr<Intro> intro_;
std::unique_ptr<Title> title_;
@@ -62,9 +67,15 @@ class Director {
std::unique_ptr<Credits> credits_;
Section::Name last_built_section_name_ = Section::Name::RESET;
// --- Fase de arranque no bloqueante ---
bool boot_loading_ = true; // True mientras Resource::loadStep está cargando incremental
// --- Inicialización y cierre del sistema ---
void init(); // Inicializa la aplicación
static void close(); // Cierra y libera recursos
void init(); // Inicializa la aplicación (pre-boot)
static void finishBoot(); // Post-boot: inicializa lo que depende de recursos cargados
static void shutdownSubsystems(); // Allibera singletons i SDL (sense apagar el sistema)
static void close(); // Cierra y libera recursos
void relaunch() const; // Reemplaça el procés via execv (fallback silenciós si no es pot)
// --- Configuración inicial ---
static void loadParams(); // Carga los parámetros del programa
@@ -73,8 +84,7 @@ class Director {
void loadDebugConfig(); // Carga debug.yaml (solo en _DEBUG)
// --- Gestión de entrada y archivos ---
void loadAssets(); // Crea el índice de archivos disponibles
void checkProgramArguments(int argc, std::span<char*> argv); // Verifica los parámetros del programa // NOLINT(modernize-avoid-c-arrays)
void loadAssets(); // Crea el índice de archivos disponibles
// --- Gestión de secciones ---
void handleSectionTransition(); // Destruye la sección anterior y construye la nueva si Section::name ha cambiado
+97
View File
@@ -0,0 +1,97 @@
#include "core/system/global_events.hpp"
#include <SDL3/SDL.h> // Para SDL_EventType, SDL_Event, SDL_LogInfo, SDL_LogCategory
#include <cstddef> // Para size_t
#include <iostream> // Para std::cout
#include <string> // Para allocator, operator+, string
#include <vector> // Para vector
#include "core/input/input.hpp" // Para Input
#include "core/input/mouse.hpp" // Para handleEvent
#include "core/locale/lang.hpp" // Para getText
#include "core/rendering/screen.hpp" // Para Screen
#include "core/system/section.hpp" // Para Name, Options, name, options
#include "game/options.hpp" // Para GamepadManager, gamepad_manager
#include "game/ui/notifier.hpp" // Para Notifier
#include "game/ui/service_menu.hpp" // Para ServiceMenu
namespace GlobalEvents {
namespace {
// Durante el arranque se drenan los SDL_EVENT_GAMEPAD_ADDED de los mandos
// ya conectados. Esos eventos sí deben reasignar mandos a jugadores, pero
// no deben mostrar notificación: no son hotplug, son detección inicial.
bool startup_in_progress = true;
} // namespace
// Comprueba los eventos de Input y muestra notificaciones
void handleInputEvents(const SDL_Event& event) {
if (event.type != SDL_EVENT_GAMEPAD_ADDED && event.type != SDL_EVENT_GAMEPAD_REMOVED) {
return;
}
static auto* input_ = Input::get();
auto message = input_->handleEvent(event);
// Reasignar siempre: tanto en arranque como en hotplug en caliente.
Options::gamepad_manager.assignAndLinkGamepads();
Options::gamepad_manager.resyncGamepadsWithPlayers();
// Durante el preload ServiceMenu aún no existe: solo refresca si está vivo.
if (ServiceMenu::get() != nullptr) {
ServiceMenu::get()->refresh();
}
if (startup_in_progress || message.empty()) {
return;
}
// Reemplazo de palabras clave por texto localizado
size_t pos;
while ((pos = message.find(" CONNECTED")) != std::string::npos) {
message.replace(pos, std::string(" CONNECTED").length(), " " + Lang::getText("[NOTIFICATIONS] CONNECTED"));
}
while ((pos = message.find(" DISCONNECTED")) != std::string::npos) {
message.replace(pos, std::string(" DISCONNECTED").length(), " " + Lang::getText("[NOTIFICATIONS] DISCONNECTED"));
}
// Notifier también puede no existir todavía si la notificación se
// disparase antes de finishBoot(). Protegido por si acaso.
if (Notifier::get() != nullptr) {
Notifier::get()->show({message});
}
}
void markStartupComplete() {
startup_in_progress = false;
}
// Comprueba los eventos que se pueden producir en cualquier sección del juego
void handle(const SDL_Event& event) {
switch (event.type) {
case SDL_EVENT_QUIT: // Evento de salida de la aplicación
Section::name = Section::Name::QUIT;
Section::options = Section::Options::NONE;
return;
case SDL_EVENT_RENDER_DEVICE_RESET:
case SDL_EVENT_RENDER_TARGETS_RESET:
std::cout << "SDL_RENDER_TARGETS_RESET" << '\n';
break;
case SDL_EVENT_WINDOW_RESIZED:
Screen::initShaders();
break;
default:
break;
}
// Durante el preload ServiceMenu aún no existe
if (ServiceMenu::get() != nullptr) {
ServiceMenu::get()->handleEvent(event);
}
Mouse::handleEvent(event);
handleInputEvents(event);
}
} // namespace GlobalEvents
+15
View File
@@ -0,0 +1,15 @@
#pragma once
#include <SDL3/SDL.h>
// --- Namespace GlobalEvents: maneja eventos globales del juego ---
namespace GlobalEvents {
// --- Funciones ---
void handle(const SDL_Event& event); // Comprueba los eventos que se pueden producir en cualquier sección del juego
// Marca el fin del arranque: a partir de aquí, los eventos de mando
// generan notificaciones en pantalla. Se debe llamar desde Director::iterate
// en el primer fotograma, después de que SDL haya drenado los
// SDL_EVENT_GAMEPAD_ADDED de los mandos ya conectados al iniciar.
void markStartupComplete();
} // namespace GlobalEvents

Some files were not shown because too many files have changed in this diff Show More