compat-helpers.c 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. #include <jansson.h>
  2. #include <obs-module.h>
  3. #include <util/dstr.h>
  4. #include <util/platform.h>
  5. #include <util/windows/window-helpers.h>
  6. #include "compat-helpers.h"
  7. #include "compat-format-ver.h"
  8. enum match_flags {
  9. MATCH_EXE = 1 << 0,
  10. MATCH_TITLE = 1 << 1,
  11. MATCH_CLASS = 1 << 2,
  12. };
  13. static inline const char *get_string_val(json_t *entry, const char *key)
  14. {
  15. json_t *str_val = json_object_get(entry, key);
  16. if (!str_val || !json_is_string(str_val))
  17. return NULL;
  18. return json_string_value(str_val);
  19. }
  20. static inline int get_int_val(json_t *entry, const char *key)
  21. {
  22. json_t *integer_val = json_object_get(entry, key);
  23. if (!integer_val || !json_is_integer(integer_val))
  24. return 0;
  25. return (int)json_integer_value(integer_val);
  26. }
  27. static inline bool get_bool_val(json_t *entry, const char *key)
  28. {
  29. json_t *bool_val = json_object_get(entry, key);
  30. if (!bool_val || !json_is_boolean(bool_val))
  31. return false;
  32. return json_is_true(bool_val);
  33. }
  34. static json_t *open_json_file(const char *file)
  35. {
  36. char *file_data = os_quick_read_utf8_file(file);
  37. json_error_t error;
  38. json_t *root;
  39. json_t *list;
  40. int format_ver;
  41. if (!file_data)
  42. return NULL;
  43. root = json_loads(file_data, JSON_REJECT_DUPLICATES, &error);
  44. bfree(file_data);
  45. if (!root) {
  46. blog(LOG_WARNING,
  47. "compat-helpers.c: [open_json_file] "
  48. "Error reading JSON file (%d): %s",
  49. error.line, error.text);
  50. return NULL;
  51. }
  52. format_ver = get_int_val(root, "format_version");
  53. if (format_ver != COMPAT_FORMAT_VERSION) {
  54. blog(LOG_DEBUG,
  55. "compat-helpers.c: [open_json_file] "
  56. "Wrong format version (%d), expected %d",
  57. format_ver, COMPAT_FORMAT_VERSION);
  58. json_decref(root);
  59. return NULL;
  60. }
  61. list = json_object_get(root, "entries");
  62. if (list)
  63. json_incref(list);
  64. json_decref(root);
  65. if (!list) {
  66. blog(LOG_WARNING, "compat-helpers.c: [open_json_file] "
  67. "No compatibility list");
  68. return NULL;
  69. }
  70. return list;
  71. }
  72. static json_t *open_compat_file(void)
  73. {
  74. char *file;
  75. json_t *root = NULL;
  76. file = obs_module_config_path("compatibility.json");
  77. if (file) {
  78. root = open_json_file(file);
  79. bfree(file);
  80. }
  81. if (!root) {
  82. file = obs_module_file("compatibility.json");
  83. if (file) {
  84. root = open_json_file(file);
  85. bfree(file);
  86. }
  87. }
  88. return root;
  89. }
  90. static json_t *compat_entries;
  91. struct compat_result *check_compatibility(const char *win_title,
  92. const char *win_class,
  93. const char *exe,
  94. enum source_type type)
  95. {
  96. if (!compat_entries) {
  97. json_t *root = open_compat_file();
  98. if (!root)
  99. return NULL;
  100. compat_entries = root;
  101. }
  102. struct dstr message;
  103. struct compat_result *res = NULL;
  104. json_t *entry;
  105. size_t index;
  106. json_array_foreach (compat_entries, index, entry) {
  107. if (type == GAME_CAPTURE &&
  108. !get_bool_val(entry, "game_capture"))
  109. continue;
  110. if (type == WINDOW_CAPTURE_WGC &&
  111. !get_bool_val(entry, "window_capture_wgc"))
  112. continue;
  113. if (type == WINDOW_CAPTURE_BITBLT &&
  114. !get_bool_val(entry, "window_capture"))
  115. continue;
  116. int match_flags = get_int_val(entry, "match_flags");
  117. const char *j_exe = get_string_val(entry, "executable");
  118. const char *j_title = get_string_val(entry, "window_title");
  119. const char *j_class = get_string_val(entry, "window_class");
  120. if ((match_flags & MATCH_CLASS) &&
  121. (!win_class || strcmp(win_class, j_class) != 0))
  122. continue;
  123. if ((match_flags & MATCH_EXE) &&
  124. (!exe || astrcmpi(exe, j_exe) != 0))
  125. continue;
  126. /* Title supports partial matches as some games append additional
  127. * information after the title, e.g., "Minecraft 1.18". */
  128. if ((match_flags & MATCH_TITLE) &&
  129. (!win_title ||
  130. astrcmpi_n(win_title, j_title, strlen(j_title)) != 0))
  131. continue;
  132. /* Attempt to translate and compile message */
  133. const char *key = get_string_val(entry, "translation_key");
  134. const char *msg = get_string_val(entry, "message");
  135. obs_module_get_string(key, &msg);
  136. dstr_init_copy(&message, msg);
  137. const char *name = get_string_val(entry, "name");
  138. /* Replace placeholders in generic messages */
  139. if (name && dstr_find(&message, "%") != NULL) {
  140. dstr_replace(&message, "%name%", name);
  141. }
  142. const char *url = get_string_val(entry, "url");
  143. /* Append clickable URL in Qt rich text */
  144. if (url && strncmp(url, "https://", 8) == 0) {
  145. dstr_catf(&message, "<br>\n<a href=\"%s\">%s</a>", url,
  146. url + 8);
  147. }
  148. res = bzalloc(sizeof(struct compat_result));
  149. res->severity = get_int_val(entry, "severity");
  150. res->message = message.array;
  151. break;
  152. }
  153. return res;
  154. }
  155. void compat_result_free(struct compat_result *res)
  156. {
  157. bfree(res->message);
  158. bfree(res);
  159. }
  160. void compat_json_free()
  161. {
  162. json_decref(compat_entries);
  163. }