sort_controllers.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. #!/usr/bin/env python3
  2. #
  3. # Script to sort the game controller database entries in SDL_gamepad.c
  4. import re
  5. filename = "SDL_gamepad_db.h"
  6. input = open(filename)
  7. output = open(f"{filename}.new", "w")
  8. parsing_controllers = False
  9. controllers = []
  10. controller_guids = {}
  11. conditionals = []
  12. split_pattern = re.compile(r'([^"]*")([^,]*,)([^,]*,)([^"]*)(".*)')
  13. # BUS (1) CRC (3,2) VID (5,4) (6) PID (8,7) (9) VERSION (11,10) MISC (12)
  14. standard_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},)$')
  15. # These chipsets are used in multiple controllers with different mappings,
  16. # without enough unique information to differentiate them. e.g.
  17. # https://github.com/gabomdq/SDL_GameControllerDB/issues/202
  18. invalid_controllers = (
  19. ('0079', '0006', '0000'), # DragonRise Inc. Generic USB Joystick
  20. ('0079', '0006', '6120'), # DragonRise Inc. Generic USB Joystick
  21. ('04b4', '2412', 'c529'), # Flydigi Vader 2, Vader 2 Pro, Apex 2, Apex 3
  22. ('16c0', '05e1', '0000'), # Xinmotek Controller
  23. )
  24. def find_element(prefix, bindings):
  25. i=0
  26. for element in bindings:
  27. if element.startswith(prefix):
  28. return i
  29. i=(i + 1)
  30. return -1
  31. def get_crc_from_entry(entry):
  32. crc = ""
  33. line = "".join(entry)
  34. bindings = line.split(",")
  35. pos = find_element("crc:", bindings)
  36. if pos >= 0:
  37. crc = bindings[pos][4:]
  38. return crc
  39. def save_controller(line):
  40. global controllers
  41. match = split_pattern.match(line)
  42. entry = [ match.group(1), match.group(2), match.group(3) ]
  43. bindings = sorted(match.group(4).split(","))
  44. if (bindings[0] == ""):
  45. bindings.pop(0)
  46. name = entry[2].rstrip(',')
  47. crc = ""
  48. pos = find_element("crc:", bindings)
  49. if pos >= 0:
  50. crc = bindings[pos] + ","
  51. bindings.pop(pos)
  52. guid_match = standard_guid_pattern.match(entry[1])
  53. if guid_match:
  54. groups = guid_match.groups()
  55. crc_value = groups[2] + groups[1]
  56. vid_value = groups[4] + groups[3]
  57. pid_value = groups[7] + groups[6]
  58. version_value = groups[10] + groups[9]
  59. #print("CRC: %s, VID: %s, PID: %s, VERSION: %s" % (crc_value, vid_value, pid_value, version_value))
  60. if crc_value == "0000":
  61. if crc != "":
  62. crc_value = crc[4:-1]
  63. else:
  64. print("Extracting CRC from GUID of " + name)
  65. entry[1] = groups[0] + "0000" + "".join(groups[3:])
  66. crc = "crc:" + crc_value + ","
  67. if (vid_value, pid_value, crc_value) in invalid_controllers:
  68. print("Controller '%s' not unique, skipping" % name)
  69. return
  70. pos = find_element("type", bindings)
  71. if pos >= 0:
  72. bindings.insert(0, bindings.pop(pos))
  73. pos = find_element("platform", bindings)
  74. if pos >= 0:
  75. bindings.insert(0, bindings.pop(pos))
  76. pos = find_element("sdk", bindings)
  77. if pos >= 0:
  78. bindings.append(bindings.pop(pos))
  79. pos = find_element("hint:", bindings)
  80. if pos >= 0:
  81. bindings.append(bindings.pop(pos))
  82. entry.extend(crc)
  83. entry.extend(",".join(bindings) + ",")
  84. entry.append(match.group(5))
  85. controllers.append(entry)
  86. entry_id = entry[1] + get_crc_from_entry(entry)
  87. if ',sdk' in line or ',hint:' in line:
  88. conditionals.append(entry_id)
  89. def write_controllers():
  90. global controllers
  91. global controller_guids
  92. # Check for duplicates
  93. for entry in controllers:
  94. entry_id = entry[1] + get_crc_from_entry(entry)
  95. if (entry_id in controller_guids and entry_id not in conditionals):
  96. current_name = entry[2]
  97. existing_name = controller_guids[entry_id][2]
  98. print("Warning: entry '%s' is duplicate of entry '%s'" % (current_name, existing_name))
  99. if (not current_name.startswith("(DUPE)")):
  100. entry[2] = f"(DUPE) {current_name}"
  101. if (not existing_name.startswith("(DUPE)")):
  102. controller_guids[entry_id][2] = f"(DUPE) {existing_name}"
  103. controller_guids[entry_id] = entry
  104. for entry in sorted(controllers, key=lambda entry: f"{entry[2]}-{entry[1]}"):
  105. line = "".join(entry) + "\n"
  106. line = line.replace("\t", " ")
  107. if not line.endswith(",\n") and not line.endswith("*/\n") and not line.endswith(",\r\n") and not line.endswith("*/\r\n"):
  108. print("Warning: '%s' is missing a comma at the end of the line" % (line))
  109. output.write(line)
  110. controllers = []
  111. controller_guids = {}
  112. for line in input:
  113. if parsing_controllers:
  114. if (line.startswith("{")):
  115. output.write(line)
  116. elif (line.startswith(" NULL")):
  117. parsing_controllers = False
  118. write_controllers()
  119. output.write(line)
  120. elif (line.startswith("#if")):
  121. print(f"Parsing {line.strip()}")
  122. output.write(line)
  123. elif (line.startswith("#endif")):
  124. write_controllers()
  125. output.write(line)
  126. else:
  127. save_controller(line)
  128. else:
  129. if (line.startswith("static const char *")):
  130. parsing_controllers = True
  131. output.write(line)
  132. output.close()
  133. print(f"Finished writing {filename}.new")