Bläddra i källkod

win-capture: Display compatibility information

derrod 2 år sedan
förälder
incheckning
2e57e09036

+ 18 - 2
plugins/win-capture/CMakeLists.txt

@@ -1,8 +1,19 @@
 project(win-capture)
 
+option(ENABLE_COMPAT_UPDATES "Checks for service updates" OFF)
+
+set(COMPAT_URL
+    "https://obsproject.com/obs2_update/win-capture"
+    CACHE STRING "Default services package URL")
+
+mark_as_advanced(COMPAT_URL)
+
 add_library(win-capture MODULE)
 add_library(OBS::capture ALIAS win-capture)
 
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/compat-config.h.in
+               ${CMAKE_BINARY_DIR}/config/compat-config.h)
+
 target_sources(
   win-capture
   PRIVATE plugin-main.c
@@ -25,10 +36,15 @@ target_sources(
           nt-stuff.c
           nt-stuff.h
           window-capture.c
+          compat-helpers.c
+          compat-helpers.h
+          compat-format-ver.h
           ../../libobs/util/windows/obfuscate.c
-          ../../libobs/util/windows/obfuscate.h)
+          ../../libobs/util/windows/obfuscate.h
+          ${CMAKE_BINARY_DIR}/config/compat-config.h)
 
-target_link_libraries(win-capture PRIVATE OBS::libobs OBS::ipc-util)
+target_link_libraries(win-capture PRIVATE OBS::libobs OBS::ipc-util
+                                          OBS::file-updater Jansson::Jansson)
 
 set_target_properties(win-capture PROPERTIES FOLDER "plugins/win-capture")
 

+ 4 - 0
plugins/win-capture/compat-config.h.in

@@ -0,0 +1,4 @@
+#pragma once
+
+#define COMPAT_URL "@COMPAT_URL@"
+#cmakedefine ENABLE_COMPAT_UPDATES

+ 3 - 0
plugins/win-capture/compat-format-ver.h

@@ -0,0 +1,3 @@
+#pragma once
+
+#define COMPAT_FORMAT_VERSION 1

+ 199 - 0
plugins/win-capture/compat-helpers.c

@@ -0,0 +1,199 @@
+#include <jansson.h>
+
+#include <obs-module.h>
+#include <util/dstr.h>
+#include <util/platform.h>
+#include <util/windows/window-helpers.h>
+
+#include "compat-helpers.h"
+#include "compat-format-ver.h"
+
+enum match_flags {
+	MATCH_EXE = 1 << 0,
+	MATCH_TITLE = 1 << 1,
+	MATCH_CLASS = 1 << 2,
+};
+
+static inline const char *get_string_val(json_t *entry, const char *key)
+{
+	json_t *str_val = json_object_get(entry, key);
+	if (!str_val || !json_is_string(str_val))
+		return NULL;
+
+	return json_string_value(str_val);
+}
+
+static inline int get_int_val(json_t *entry, const char *key)
+{
+	json_t *integer_val = json_object_get(entry, key);
+	if (!integer_val || !json_is_integer(integer_val))
+		return 0;
+
+	return (int)json_integer_value(integer_val);
+}
+
+static inline bool get_bool_val(json_t *entry, const char *key)
+{
+	json_t *bool_val = json_object_get(entry, key);
+	if (!bool_val || !json_is_boolean(bool_val))
+		return false;
+
+	return json_is_true(bool_val);
+}
+
+static json_t *open_json_file(const char *file)
+{
+	char *file_data = os_quick_read_utf8_file(file);
+	json_error_t error;
+	json_t *root;
+	json_t *list;
+	int format_ver;
+
+	if (!file_data)
+		return NULL;
+
+	root = json_loads(file_data, JSON_REJECT_DUPLICATES, &error);
+	bfree(file_data);
+
+	if (!root) {
+		blog(LOG_WARNING,
+		     "compat-helpers.c: [open_json_file] "
+		     "Error reading JSON file (%d): %s",
+		     error.line, error.text);
+		return NULL;
+	}
+
+	format_ver = get_int_val(root, "format_version");
+
+	if (format_ver != COMPAT_FORMAT_VERSION) {
+		blog(LOG_DEBUG,
+		     "compat-helpers.c: [open_json_file] "
+		     "Wrong format version (%d), expected %d",
+		     format_ver, COMPAT_FORMAT_VERSION);
+		json_decref(root);
+		return NULL;
+	}
+
+	list = json_object_get(root, "entries");
+	if (list)
+		json_incref(list);
+	json_decref(root);
+
+	if (!list) {
+		blog(LOG_WARNING, "compat-helpers.c: [open_json_file] "
+				  "No compatibility list");
+		return NULL;
+	}
+
+	return list;
+}
+
+static json_t *open_compat_file(void)
+{
+	char *file;
+	json_t *root = NULL;
+
+	file = obs_module_config_path("compatibility.json");
+	if (file) {
+		root = open_json_file(file);
+		bfree(file);
+	}
+
+	if (!root) {
+		file = obs_module_file("compatibility.json");
+		if (file) {
+			root = open_json_file(file);
+			bfree(file);
+		}
+	}
+
+	return root;
+}
+
+static json_t *compat_entries;
+struct compat_result *check_compatibility(const char *win_title,
+					  const char *win_class,
+					  const char *exe,
+					  enum source_type type)
+{
+	if (!compat_entries) {
+		json_t *root = open_compat_file();
+		if (!root)
+			return NULL;
+
+		compat_entries = root;
+	}
+
+	struct dstr message;
+	struct compat_result *res = NULL;
+	json_t *entry;
+	size_t index;
+
+	json_array_foreach (compat_entries, index, entry) {
+		if (type == GAME_CAPTURE &&
+		    !get_bool_val(entry, "game_capture"))
+			continue;
+		if (type == WINDOW_CAPTURE_WGC &&
+		    !get_bool_val(entry, "window_capture_wgc"))
+			continue;
+		if (type == WINDOW_CAPTURE_BITBLT &&
+		    !get_bool_val(entry, "window_capture"))
+			continue;
+
+		int match_flags = get_int_val(entry, "match_flags");
+		const char *j_exe = get_string_val(entry, "executable");
+		const char *j_title = get_string_val(entry, "window_title");
+		const char *j_class = get_string_val(entry, "window_class");
+
+		if (win_class && (match_flags & MATCH_CLASS) &&
+		    strcmp(win_class, j_class) != 0)
+			continue;
+		if (exe && (match_flags & MATCH_EXE) &&
+		    astrcmpi(exe, j_exe) != 0)
+			continue;
+		/* Title supports partial matches as some games append additional
+		 * information after the title, e.g., "Minecraft 1.18". */
+		if (win_title && (match_flags & MATCH_TITLE) &&
+		    astrcmpi_n(win_title, j_title, strlen(j_title)) != 0)
+			continue;
+
+		/* Attempt to translate and compile message */
+		const char *key = get_string_val(entry, "translation_key");
+		const char *msg = get_string_val(entry, "message");
+		obs_module_get_string(key, &msg);
+
+		dstr_init_copy(&message, msg);
+
+		const char *name = get_string_val(entry, "name");
+		/* Replace placeholders in generic messages */
+		if (name && dstr_find(&message, "%") != NULL) {
+			dstr_replace(&message, "%name%", name);
+		}
+
+		const char *url = get_string_val(entry, "url");
+		/* Append clickable URL in Qt rich text */
+		if (url && strncmp(url, "https://", 8) == 0) {
+			dstr_catf(&message, "<br>\n<a href=\"%s\">%s</a>", url,
+				  url + 8);
+		}
+
+		res = bzalloc(sizeof(struct compat_result));
+		res->severity = get_int_val(entry, "severity");
+		res->message = message.array;
+
+		break;
+	}
+
+	return res;
+}
+
+void compat_result_free(struct compat_result *res)
+{
+	bfree(res->message);
+	bfree(res);
+}
+
+void compat_json_free()
+{
+	json_decref(compat_entries);
+}

+ 19 - 0
plugins/win-capture/compat-helpers.h

@@ -0,0 +1,19 @@
+#pragma once
+
+enum source_type {
+	GAME_CAPTURE,
+	WINDOW_CAPTURE_BITBLT,
+	WINDOW_CAPTURE_WGC,
+};
+
+struct compat_result {
+	char *message;
+	enum obs_text_info_type severity;
+};
+
+extern struct compat_result *check_compatibility(const char *win_title,
+						 const char *win_class,
+						 const char *exe,
+						 enum source_type type);
+extern void compat_result_free(struct compat_result *res);
+extern void compat_json_free();

+ 216 - 0
plugins/win-capture/data/compatibility.json

@@ -0,0 +1,216 @@
+{
+    "$schema": "schema/compatibility-schema-v1.json",
+    "format_version": 1,
+    "entries": [
+        {
+            "name": "CS:GO",
+            "translation_key": "Compatibility.Application.CSGO",
+            "severity": 1,
+            "executable": "csgo.exe",
+            "window_class": "",
+            "window_title": "",
+            "match_flags": 1,
+            "game_capture": true,
+            "window_capture": false,
+            "window_capture_wgc": false,
+            "message": "CS:GO may require the <code>--allow_third_party_software</code> launch option to use Game Capture.",
+            "url": "https://help.steampowered.com/en/faqs/view/09A0-4879-4353-EF95#whitelist"
+        },
+        {
+            "name": "Electron",
+            "translation_key": "Compatibility.Application.ElectronBitBlt",
+            "severity": 0,
+            "executable": "",
+            "window_class": "Chrome_WidgetWin_0",
+            "window_title": "",
+            "match_flags": 4,
+            "game_capture": false,
+            "window_capture": true,
+            "window_capture_wgc": false,
+            "message": "Electron-based applications may not be capturable using the selected Capture Method (BitBlt).",
+            "url": ""
+        },
+        {
+            "name": "Electron",
+            "translation_key": "Compatibility.Application.ElectronBitBlt",
+            "severity": 0,
+            "executable": "",
+            "window_class": "Chrome_WidgetWin_1",
+            "window_title": "",
+            "match_flags": 4,
+            "game_capture": false,
+            "window_capture": true,
+            "window_capture_wgc": false,
+            "message": "Electron-based applications may not be capturable using the selected Capture Method (BitBlt).",
+            "url": ""
+        },
+        {
+            "name": "Electron",
+            "translation_key": "Compatibility.Application.ElectronGameCapture",
+            "severity": 2,
+            "executable": "",
+            "window_class": "Chrome_WidgetWin_0",
+            "window_title": "",
+            "match_flags": 4,
+            "game_capture": true,
+            "window_capture": false,
+            "window_capture_wgc": false,
+            "message": "Games built on Electron cannot be captured using Game Capture. Use Window Capture or Display Capture instead.",
+            "url": ""
+        },
+        {
+            "name": "Electron",
+            "translation_key": "Compatibility.Application.ElectronGameCapture",
+            "severity": 2,
+            "executable": "",
+            "window_class": "Chrome_WidgetWin_1",
+            "window_title": "",
+            "match_flags": 4,
+            "game_capture": true,
+            "window_capture": false,
+            "window_capture_wgc": false,
+            "message": "Games built on Electron cannot be captured using Game Capture. Use Window Capture or Display Capture instead.",
+            "url": ""
+        },
+        {
+            "name": "Minecraft: Java Edition",
+            "translation_key": "Compatibility.Application.Minecraft",
+            "severity": 0,
+            "executable": "javaw.exe",
+            "window_class": "",
+            "window_title": "Minecraft",
+            "match_flags": 3,
+            "game_capture": true,
+            "window_capture": false,
+            "window_capture_wgc": false,
+            "message": "If you're having issues capturing Minecraft: Java Edition check our troubleshooting guide.",
+            "url": "https://obsproject.com/kb/minecraft-java-edition-troubleshooting"
+        },
+        {
+            "name": "Call of Duty",
+            "translation_key": "Compatibility.GameCapture.Admin",
+            "severity": 1,
+            "executable": "cod.exe",
+            "window_class": "",
+            "window_title": "",
+            "match_flags": 1,
+            "game_capture": true,
+            "window_capture": false,
+            "window_capture_wgc": false,
+            "message": "Call of Duty may require OBS to be run as admin to use Game Capture.",
+            "url": "https://obsproject.com/kb/game-capture-troubleshooting"
+        },
+        {
+            "name": "Genshin Impact",
+            "translation_key": "Compatibility.GameCapture.Admin",
+            "severity": 1,
+            "executable": "GenshinImpact.exe",
+            "window_class": "",
+            "window_title": "",
+            "match_flags": 1,
+            "game_capture": true,
+            "window_capture": false,
+            "window_capture_wgc": false,
+            "message": "Genshin Impact may require OBS to be run as admin to use Game Capture.",
+            "url": "https://obsproject.com/kb/game-capture-troubleshooting"
+        },
+        {
+            "name": "Destiny 2",
+            "translation_key": "Compatibility.GameCapture.Blocked",
+            "severity": 2,
+            "executable": "destiny2.exe",
+            "window_class": "",
+            "window_title": "",
+            "match_flags": 1,
+            "game_capture": true,
+            "window_capture": false,
+            "window_capture_wgc": false,
+            "message": "Destiny 2 cannot be captured via Game Capture. Use Window Capture or Display Capture instead.",
+            "url": "https://www.bungie.net/en/Help/Article/46101"
+        },
+        {
+            "name": "Grand Theft Auto: San Andreas",
+            "translation_key": "Compatibility.GameCapture.Blocked",
+            "severity": 2,
+            "executable": "gta-sa.exe",
+            "window_class": "",
+            "window_title": "",
+            "match_flags": 1,
+            "game_capture": true,
+            "window_capture": false,
+            "window_capture_wgc": false,
+            "message": "Grand Theft Auto: San Andreas cannot be captured via Game Capture. Use Window Capture or Display Capture instead.",
+            "url": ""
+        },
+        {
+            "name": "League of Legends Launcher",
+            "translation_key": "Compatibility.GameCapture.Blocked",
+            "severity": 1,
+            "executable": "LeagueClientUx.exe",
+            "window_class": "",
+            "window_title": "",
+            "match_flags": 1,
+            "game_capture": true,
+            "window_capture": false,
+            "window_capture_wgc": false,
+            "message": "League of Legends Launcher cannot be captured via Game Capture. Use Window Capture or Display Capture instead.",
+            "url": ""
+        },
+        {
+            "name": "San Andreas Multiplayer",
+            "translation_key": "Compatibility.GameCapture.Blocked",
+            "severity": 2,
+            "executable": "samp.exe",
+            "window_class": "",
+            "window_title": "",
+            "match_flags": 1,
+            "game_capture": true,
+            "window_capture": false,
+            "window_capture_wgc": false,
+            "message": "San Andreas Multiplayer cannot be captured via Game Capture. Use Window Capture or Display Capture instead.",
+            "url": ""
+        },
+        {
+            "name": "Terraria",
+            "translation_key": "Compatibility.GameCapture.WrongGPU",
+            "severity": 0,
+            "executable": "terraria.exe",
+            "window_class": "",
+            "window_title": "",
+            "match_flags": 1,
+            "game_capture": true,
+            "window_capture": false,
+            "window_capture_wgc": false,
+            "message": "Terraria must run on the same GPU as OBS. If you are not seeing a preview, follow our troubleshooting guide.",
+            "url": "https://obsproject.com/kb/gpu-selection-guide"
+        },
+        {
+            "name": "Chrome",
+            "translation_key": "Compatibility.WindowCapture.BitBlt",
+            "severity": 0,
+            "executable": "chrome.exe",
+            "window_class": "",
+            "window_title": "",
+            "match_flags": 1,
+            "game_capture": false,
+            "window_capture": true,
+            "window_capture_wgc": false,
+            "message": "Chrome may not be capturable using the selected Capture Method (BitBlt).",
+            "url": ""
+        },
+        {
+            "name": "Edge",
+            "translation_key": "Compatibility.WindowCapture.BitBlt",
+            "severity": 0,
+            "executable": "msedge.exe",
+            "window_class": "",
+            "window_title": "",
+            "match_flags": 1,
+            "game_capture": false,
+            "window_capture": true,
+            "window_capture_wgc": false,
+            "message": "Edge may not be capturable using the selected Capture Method (BitBlt).",
+            "url": ""
+        }
+    ]
+}

+ 12 - 0
plugins/win-capture/data/locale/en-US.ini

@@ -39,3 +39,15 @@ GameCapture.Rgb10a2Space="RGB10A2 Color Space"
 GameCapture.Rgb10a2Space.Srgb="sRGB"
 GameCapture.Rgb10a2Space.2100PQ="Rec. 2100 (PQ)"
 Mode="Mode"
+
+# Generic compatibility messages
+Compatibility.GameCapture.Admin="%name% may require OBS to be run as admin to use Game Capture."
+Compatibility.GameCapture.Blocked="%name% cannot be captured via Game Capture. Use Window Capture or Display Capture instead."
+Compatibility.GameCapture.WrongGPU="If the preview is blank, make sure %name% is running on the same GPU as OBS."
+Compatibility.WindowCapture.BitBlt="%name% may not be capturable using the selected Capture Method (BitBlt)."
+
+# Specific compatibility messages
+Compatibility.Application.CSGO="CS:GO may require the <code>--allow_third_party_software</code> launch option to use Game Capture."
+Compatibility.Application.ElectronGameCapture="Games built on Electron cannot be captured using Game Capture. Use Window Capture or Display Capture instead."
+Compatibility.Application.ElectronBitBlt="Electron-based applications may not be capturable using the selected Capture Method (BitBlt)."
+Compatibility.Application.Minecraft="If you're having issues capturing Minecraft: Java Edition, check our troubleshooting guide."

+ 11 - 0
plugins/win-capture/data/package.json

@@ -0,0 +1,11 @@
+{
+    "$schema": "schema/package-schema.json",
+    "url": "https://obsproject.com/obs2_update/win-capture/v1",
+    "version": 1,
+    "files": [
+        {
+            "name": "compatibility.json",
+            "version": 1
+        }
+    ]
+}

+ 99 - 0
plugins/win-capture/data/schema/compatibility-schema-v1.json

@@ -0,0 +1,99 @@
+{
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "type": "object",
+    "properties": {
+        "format_version": {
+            "type": "integer",
+            "description": "Version identifier for parsing this file."
+        },
+        "entries": {
+            "type": "array",
+            "items": {
+                "type": "object",
+                "properties": {
+                    "name": {
+                        "type": "string",
+                        "description": "Name of application affected by this entry."
+                    },
+                    "translation_key": {
+                        "type": "string",
+                        "description": "Translation key OBS should use to provide a localized message."
+                    },
+                    "executable": {
+                        "type": "string",
+                        "description": "Executable name to match."
+                    },
+                    "window_class": {
+                        "type": "string",
+                        "description": "Window class to match."
+                    },
+                    "window_title": {
+                        "type": "string",
+                        "description": "Window title to match."
+                    },
+                    "message": {
+                        "type": "string",
+                        "description": "Message displayed to the user about the issue (if not translated)."
+                    },
+                    "url": {
+                        "$ref": "#/definitions/URL",
+                        "description": "Link that provides additional info about the compatibility issue."
+                    },
+                    "window_capture": {
+                        "type": "boolean",
+                        "description": "Whether or not the issue affects BitBlt window capture.",
+                        "default": false
+                    },
+                    "window_capture_wgc": {
+                        "type": "boolean",
+                        "description": "Whether or not the issue affects WGC window capture.",
+                        "default": false
+                    },
+                    "game_capture": {
+                        "type": "boolean",
+                        "description": "Whether or not the issue affects game capture.",
+                        "default": false
+                    },
+                    "severity": {
+                        "$ref": "#/definitions/severityLevels",
+                        "description": "Level of notice displayed to the user. 0 = Info, 1 = Warning, 2 = Error.\nError should be used if an application cannot be captured using the affected source type(s).\nWarning should be used for correctable errors (e.g., change a game or source setting)\nInfo should be used to provide tips in cases where there is no strict incompatibility.",
+                        "default": 0
+                    },
+                    "match_flags": {
+                        "$ref": "#/definitions/matchFlags",
+                        "description": "Match Flags: 1 = exe, 2 = title, 4 = class. Can be combined."
+                    }
+                },
+                "additionalProperties": false,
+                "required": [
+                    "name",
+                    "message",
+                    "match_flags"
+                ]
+            },
+            "additionalItems": true
+        }
+    },
+    "additionalProperties": true,
+    "required": [
+        "format_version",
+        "entries"
+    ],
+    "definitions": {
+        "URL": {
+            "type": "string",
+            "format": "uri",
+            "pattern": "^(https?://.+)?"
+        },
+        "matchFlags": {
+            "type": "integer",
+            "minimum": 1,
+            "maximum": 7
+        },
+        "severityLevels": {
+            "type": "integer",
+            "minimum": 0,
+            "maximum": 2
+        }
+    }
+}

+ 47 - 0
plugins/win-capture/data/schema/package-schema.json

@@ -0,0 +1,47 @@
+{
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "type": "object",
+    "properties": {
+        "url": {
+            "$ref": "#/definitions/saneUrl",
+            "description": "Points to the base URL of hosted package.json and services.json files, used to automatically fetch the latest version."
+        },
+        "version": {
+            "type": "integer"
+        },
+        "files": {
+            "type": "array",
+            "items": {
+                "type": "object",
+                "properties": {
+                    "name": {
+                        "type": "string",
+                        "description": "Filename to read,, containing service definitions."
+                    },
+                    "version": {
+                        "type": "integer",
+                        "description": "This value should be bumped any time the file defined by the 'name' field is changed. This value will be used when determining whether a new version is available."
+                    }
+                },
+                "required": [
+                    "name",
+                    "version"
+                ]
+            },
+            "description": "List of files to read, each containing a list of services.",
+            "additionalProperties": false
+        }
+    },
+    "required": [
+        "url",
+        "version",
+        "files"
+    ],
+    "definitions": {
+        "saneUrl": {
+            "type": "string",
+            "format": "uri",
+            "pattern": "^https?://"
+        }
+    }
+}

+ 40 - 2
plugins/win-capture/game-capture.c

@@ -12,6 +12,7 @@
 #include <ipc-util/pipe.h>
 #include <util/windows/obfuscate.h>
 #include "inject-library.h"
+#include "compat-helpers.h"
 #include "graphics-hook-info.h"
 #include "graphics-hook-ver.h"
 #include "cursor-capture.h"
@@ -39,6 +40,7 @@
 #define SETTING_ANTI_CHEAT_HOOK      "anti_cheat_hook"
 #define SETTING_HOOK_RATE            "hook_rate"
 #define SETTING_RGBA10A2_SPACE       "rgb10a2_space"
+#define SETTINGS_COMPAT_INFO         "compat_info"
 
 /* deprecated */
 #define SETTING_ANY_FULLSCREEN   "capture_any_fullscreen"
@@ -2247,8 +2249,40 @@ static bool mode_callback(obs_properties_t *ppts, obs_property_t *p,
 static bool window_changed_callback(obs_properties_t *ppts, obs_property_t *p,
 				    obs_data_t *settings)
 {
-	return ms_check_window_property_setting(ppts, p, settings,
-						SETTING_CAPTURE_WINDOW, 1);
+	bool modified = ms_check_window_property_setting(
+		ppts, p, settings, SETTING_CAPTURE_WINDOW, 1);
+
+	if (obs_data_get_bool(settings, SETTING_ANY_FULLSCREEN))
+		return modified;
+
+	const char *window =
+		obs_data_get_string(settings, SETTING_CAPTURE_WINDOW);
+
+	char *class;
+	char *exe;
+	char *title;
+	ms_build_window_strings(window, &class, &title, &exe);
+	struct compat_result *compat =
+		check_compatibility(title, class, exe, GAME_CAPTURE);
+	bfree(title);
+	bfree(exe);
+	bfree(class);
+
+	obs_property_t *p_warn = obs_properties_get(ppts, SETTINGS_COMPAT_INFO);
+
+	if (!compat) {
+		modified = obs_property_visible(p_warn) || modified;
+		obs_property_set_visible(p_warn, false);
+		return modified;
+	}
+
+	obs_property_set_long_description(p_warn, compat->message);
+	obs_property_text_set_info_type(p_warn, compat->severity);
+	obs_property_set_visible(p_warn, true);
+
+	compat_result_free(compat);
+
+	return true;
 }
 
 static BOOL CALLBACK EnumFirstMonitor(HMONITOR monitor, HDC hdc, LPRECT rc,
@@ -2333,6 +2367,10 @@ static obs_properties_t *game_capture_properties(void *data)
 	obs_property_list_add_int(p, TEXT_MATCH_CLASS, WINDOW_PRIORITY_CLASS);
 	obs_property_list_add_int(p, TEXT_MATCH_EXE, WINDOW_PRIORITY_EXE);
 
+	p = obs_properties_add_text(ppts, SETTINGS_COMPAT_INFO, NULL,
+				    OBS_TEXT_INFO);
+	obs_property_set_enabled(p, false);
+
 	obs_properties_add_bool(ppts, SETTING_COMPATIBILITY,
 				TEXT_SLI_COMPATIBILITY);
 

+ 49 - 1
plugins/win-capture/plugin-main.c

@@ -1,8 +1,18 @@
 #include <windows.h>
 #include <obs-module.h>
+#include <util/dstr.h>
 #include <util/windows/win-version.h>
 #include <util/platform.h>
 
+#include <file-updater/file-updater.h>
+
+#include "compat-helpers.h"
+#include "compat-format-ver.h"
+#include "compat-config.h"
+
+#define WIN_CAPTURE_LOG_STRING "[win-capture plugin] "
+#define WIN_CAPTURE_VER_STRING "win-capture plugin (libobs " OBS_VERSION ")"
+
 OBS_DECLARE_MODULE()
 OBS_MODULE_USE_DEFAULT_LOCALE("win-capture", "en-US")
 MODULE_EXPORT const char *obs_module_description(void)
@@ -16,6 +26,7 @@ extern struct obs_source_info window_capture_info;
 extern struct obs_source_info game_capture_info;
 
 static HANDLE init_hooks_thread = NULL;
+static update_info_t *update_info = NULL;
 
 extern bool cached_versions_match(void);
 extern bool load_cached_graphics_offsets(bool is32bit, const char *config_path);
@@ -66,6 +77,27 @@ void wait_for_hook_initialization(void)
 	}
 }
 
+static bool confirm_compat_file(void *param, struct file_download_data *file)
+{
+	if (astrcmpi(file->name, "compatibility.json") == 0) {
+		obs_data_t *data;
+		int format_version;
+
+		data = obs_data_create_from_json((char *)file->buffer.array);
+		if (!data)
+			return false;
+
+		format_version = (int)obs_data_get_int(data, "format_version");
+		obs_data_release(data);
+
+		if (format_version != COMPAT_FORMAT_VERSION)
+			return false;
+	}
+
+	UNUSED_PARAMETER(param);
+	return true;
+}
+
 void init_hook_files(void);
 
 bool graphics_uses_d3d11 = false;
@@ -75,16 +107,30 @@ bool obs_module_load(void)
 {
 	struct win_version_info ver;
 	bool win8_or_above = false;
+	char *local_dir;
 	char *config_dir;
 
+	char update_url[128];
+	snprintf(update_url, sizeof(update_url), "%s/v%d", COMPAT_URL,
+		 COMPAT_FORMAT_VERSION);
+
 	struct win_version_info win1903 = {
 		.major = 10, .minor = 0, .build = 18362, .revis = 0};
 
+	local_dir = obs_module_file(NULL);
 	config_dir = obs_module_config_path(NULL);
 	if (config_dir) {
 		os_mkdirs(config_dir);
-		bfree(config_dir);
+
+		if (local_dir) {
+			update_info = update_info_create(
+				WIN_CAPTURE_LOG_STRING, WIN_CAPTURE_VER_STRING,
+				update_url, local_dir, config_dir,
+				confirm_compat_file, NULL);
+		}
 	}
+	bfree(config_dir);
+	bfree(local_dir);
 
 	get_win_ver(&ver);
 
@@ -117,4 +163,6 @@ bool obs_module_load(void)
 void obs_module_unload(void)
 {
 	wait_for_hook_initialization();
+	update_info_destroy(update_info);
+	compat_json_free();
 }

+ 29 - 0
plugins/win-capture/window-capture.c

@@ -3,6 +3,7 @@
 #include <util/threading.h>
 #include <util/windows/window-helpers.h>
 #include "dc-capture.h"
+#include "compat-helpers.h"
 #include "../../libobs/util/platform.h"
 #include "../../libobs-winrt/winrt-capture.h"
 
@@ -406,6 +407,26 @@ static void update_settings_visibility(obs_properties_t *props,
 	pthread_mutex_unlock(&wc->update_mutex);
 }
 
+static void wc_check_compatibility(struct window_capture *wc,
+				   obs_properties_t *props)
+{
+	obs_property_t *p_warn = obs_properties_get(props, "compat_info");
+
+	struct compat_result *compat =
+		check_compatibility(wc->title, wc->class, wc->executable,
+				    (enum source_type)wc->method);
+	if (!compat) {
+		obs_property_set_visible(p_warn, false);
+		return;
+	}
+
+	obs_property_set_long_description(p_warn, compat->message);
+	obs_property_text_set_info_type(p_warn, compat->severity);
+	obs_property_set_visible(p_warn, true);
+
+	compat_result_free(compat);
+}
+
 static bool wc_capture_method_changed(obs_properties_t *props,
 				      obs_property_t *p, obs_data_t *settings)
 {
@@ -419,6 +440,8 @@ static bool wc_capture_method_changed(obs_properties_t *props,
 
 	update_settings_visibility(props, wc);
 
+	wc_check_compatibility(wc, props);
+
 	return true;
 }
 
@@ -434,6 +457,9 @@ static bool wc_window_changed(obs_properties_t *props, obs_property_t *p,
 	update_settings_visibility(props, wc);
 
 	ms_check_window_property_setting(props, p, settings, "window", 0);
+
+	wc_check_compatibility(wc, props);
+
 	return true;
 }
 
@@ -466,6 +492,9 @@ static obs_properties_t *wc_properties(void *data)
 	obs_property_list_add_int(p, TEXT_MATCH_CLASS, WINDOW_PRIORITY_CLASS);
 	obs_property_list_add_int(p, TEXT_MATCH_EXE, WINDOW_PRIORITY_EXE);
 
+	p = obs_properties_add_text(ppts, "compat_info", NULL, OBS_TEXT_INFO);
+	obs_property_set_enabled(p, false);
+
 	obs_properties_add_bool(ppts, "cursor", TEXT_CAPTURE_CURSOR);
 
 	obs_properties_add_bool(ppts, "compatibility", TEXT_COMPATIBILITY);