compat-helpers.c 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  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, const char *win_class, const char *exe,
  92. enum source_type type)
  93. {
  94. if (!compat_entries) {
  95. json_t *root = open_compat_file();
  96. if (!root)
  97. return NULL;
  98. compat_entries = root;
  99. }
  100. struct dstr message;
  101. struct compat_result *res = NULL;
  102. json_t *entry;
  103. size_t index;
  104. json_array_foreach (compat_entries, index, entry) {
  105. if (type == GAME_CAPTURE && !get_bool_val(entry, "game_capture"))
  106. continue;
  107. if (type == WINDOW_CAPTURE_WGC && !get_bool_val(entry, "window_capture_wgc"))
  108. continue;
  109. if (type == WINDOW_CAPTURE_BITBLT && !get_bool_val(entry, "window_capture"))
  110. continue;
  111. int match_flags = get_int_val(entry, "match_flags");
  112. const char *j_exe = get_string_val(entry, "executable");
  113. const char *j_title = get_string_val(entry, "window_title");
  114. const char *j_class = get_string_val(entry, "window_class");
  115. if ((match_flags & MATCH_CLASS) && (!win_class || strcmp(win_class, j_class) != 0))
  116. continue;
  117. if ((match_flags & MATCH_EXE) && (!exe || astrcmpi(exe, j_exe) != 0))
  118. continue;
  119. /* Title supports partial matches as some games append additional
  120. * information after the title, e.g., "Minecraft 1.18". */
  121. if ((match_flags & MATCH_TITLE) && (!win_title || astrcmpi_n(win_title, j_title, strlen(j_title)) != 0))
  122. continue;
  123. /* Attempt to translate and compile message */
  124. const char *key = get_string_val(entry, "translation_key");
  125. const char *msg = get_string_val(entry, "message");
  126. obs_module_get_string(key, &msg);
  127. dstr_init_copy(&message, msg);
  128. const char *name = get_string_val(entry, "name");
  129. /* Replace placeholders in generic messages */
  130. if (name && dstr_find(&message, "%") != NULL) {
  131. dstr_replace(&message, "%name%", name);
  132. }
  133. const char *url = get_string_val(entry, "url");
  134. /* Append clickable URL in Qt rich text */
  135. if (url && strncmp(url, "https://", 8) == 0) {
  136. dstr_catf(&message, "<br>\n<a href=\"%s\">%s</a>", url, url + 8);
  137. }
  138. res = bzalloc(sizeof(struct compat_result));
  139. res->severity = get_int_val(entry, "severity");
  140. res->message = message.array;
  141. break;
  142. }
  143. return res;
  144. }
  145. void compat_result_free(struct compat_result *res)
  146. {
  147. bfree(res->message);
  148. bfree(res);
  149. }
  150. void compat_json_free()
  151. {
  152. json_decref(compat_entries);
  153. }