summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/joystick/sort_controllers.py
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
committer3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
commit5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch)
tree8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/joystick/sort_controllers.py
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/joystick/sort_controllers.py')
-rwxr-xr-xcontrib/SDL-3.2.8/src/joystick/sort_controllers.py164
1 files changed, 164 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/joystick/sort_controllers.py b/contrib/SDL-3.2.8/src/joystick/sort_controllers.py
new file mode 100755
index 0000000..19aec89
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/joystick/sort_controllers.py
@@ -0,0 +1,164 @@
1#!/usr/bin/env python3
2#
3# Script to sort the game controller database entries in SDL_gamepad.c
4
5import re
6
7
8filename = "SDL_gamepad_db.h"
9input = open(filename)
10output = open(f"{filename}.new", "w")
11parsing_controllers = False
12controllers = []
13controller_guids = {}
14conditionals = []
15split_pattern = re.compile(r'([^"]*")([^,]*,)([^,]*,)([^"]*)(".*)')
16# BUS (1) CRC (3,2) VID (5,4) (6) PID (8,7) (9) VERSION (11,10) MISC (12)
17standard_guid_pattern = re.compile(r'^([0-9a-fA-F]{4})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})(0000)([0-9a-fA-F]{2})([0-9a-fA-F]{2})(0000)([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{4},)$')
18
19# These chipsets are used in multiple controllers with different mappings,
20# without enough unique information to differentiate them. e.g.
21# https://github.com/gabomdq/SDL_GameControllerDB/issues/202
22invalid_controllers = (
23 ('0079', '0006', '0000'), # DragonRise Inc. Generic USB Joystick
24 ('0079', '0006', '6120'), # DragonRise Inc. Generic USB Joystick
25 ('04b4', '2412', 'c529'), # Flydigi Vader 2, Vader 2 Pro, Apex 2, Apex 3, Apex 4
26 ('16c0', '05e1', '0000'), # Xinmotek Controller
27)
28
29def find_element(prefix, bindings):
30 i=0
31 for element in bindings:
32 if element.startswith(prefix):
33 return i
34 i=(i + 1)
35
36 return -1
37
38def get_crc_from_entry(entry):
39 crc = ""
40 line = "".join(entry)
41 bindings = line.split(",")
42 pos = find_element("crc:", bindings)
43 if pos >= 0:
44 crc = bindings[pos][4:]
45 return crc
46
47def save_controller(line):
48 global controllers
49 match = split_pattern.match(line)
50 entry = [ match.group(1), match.group(2), match.group(3) ]
51 bindings = sorted(match.group(4).split(","))
52 if (bindings[0] == ""):
53 bindings.pop(0)
54
55 name = entry[2].rstrip(',')
56
57 crc = ""
58 pos = find_element("crc:", bindings)
59 if pos >= 0:
60 crc = bindings[pos] + ","
61 bindings.pop(pos)
62
63 guid_match = standard_guid_pattern.match(entry[1])
64 if guid_match:
65 groups = guid_match.groups()
66 crc_value = groups[2] + groups[1]
67 vid_value = groups[4] + groups[3]
68 pid_value = groups[7] + groups[6]
69 version_value = groups[10] + groups[9]
70 #print("CRC: %s, VID: %s, PID: %s, VERSION: %s" % (crc_value, vid_value, pid_value, version_value))
71
72 if crc_value == "0000":
73 if crc != "":
74 crc_value = crc[4:-1]
75 else:
76 print("Extracting CRC from GUID of " + name)
77 entry[1] = groups[0] + "0000" + "".join(groups[3:])
78 crc = "crc:" + crc_value + ","
79
80 if (vid_value, pid_value, crc_value) in invalid_controllers:
81 print("Controller '%s' not unique, skipping" % name)
82 return
83
84 pos = find_element("type", bindings)
85 if pos >= 0:
86 bindings.insert(0, bindings.pop(pos))
87
88 pos = find_element("platform", bindings)
89 if pos >= 0:
90 bindings.insert(0, bindings.pop(pos))
91
92 pos = find_element("sdk", bindings)
93 if pos >= 0:
94 bindings.append(bindings.pop(pos))
95
96 pos = find_element("hint:", bindings)
97 if pos >= 0:
98 bindings.append(bindings.pop(pos))
99
100 entry.extend(crc)
101 entry.extend(",".join(bindings) + ",")
102 entry.append(match.group(5))
103 controllers.append(entry)
104
105 entry_id = entry[1] + get_crc_from_entry(entry)
106 if ',sdk' in line or ',hint:' in line:
107 conditionals.append(entry_id)
108
109def write_controllers():
110 global controllers
111 global controller_guids
112 # Check for duplicates
113 for entry in controllers:
114 entry_id = entry[1] + get_crc_from_entry(entry)
115 if (entry_id in controller_guids and entry_id not in conditionals):
116 current_name = entry[2]
117 existing_name = controller_guids[entry_id][2]
118 print("Warning: entry '%s' is duplicate of entry '%s'" % (current_name, existing_name))
119
120 if (not current_name.startswith("(DUPE)")):
121 entry[2] = f"(DUPE) {current_name}"
122
123 if (not existing_name.startswith("(DUPE)")):
124 controller_guids[entry_id][2] = f"(DUPE) {existing_name}"
125
126 controller_guids[entry_id] = entry
127
128 for entry in sorted(controllers, key=lambda entry: f"{entry[2]}-{entry[1]}"):
129 line = "".join(entry) + "\n"
130 line = line.replace("\t", " ")
131 if not line.endswith(",\n") and not line.endswith("*/\n") and not line.endswith(",\r\n") and not line.endswith("*/\r\n"):
132 print("Warning: '%s' is missing a comma at the end of the line" % (line))
133 output.write(line)
134
135 controllers = []
136 controller_guids = {}
137
138for line in input:
139 if parsing_controllers:
140 if (line.startswith("{")):
141 output.write(line)
142 elif (line.startswith(" NULL")):
143 parsing_controllers = False
144 write_controllers()
145 output.write(line)
146 elif (line.startswith("#if")):
147 print(f"Parsing {line.strip()}")
148 output.write(line)
149 elif ("SDL_PRIVATE_GAMEPAD_DEFINITIONS" in line):
150 write_controllers()
151 output.write(line)
152 elif (line.startswith("#endif")):
153 write_controllers()
154 output.write(line)
155 else:
156 save_controller(line)
157 else:
158 if (line.startswith("static const char *")):
159 parsing_controllers = True
160
161 output.write(line)
162
163output.close()
164print(f"Finished writing {filename}.new")