diff --git a/addons/common/functions/fnc_checkFiles.sqf b/addons/common/functions/fnc_checkFiles.sqf
index 185ec7e5d1a..f94717aafee 100644
--- a/addons/common/functions/fnc_checkFiles.sqf
+++ b/addons/common/functions/fnc_checkFiles.sqf
@@ -1,7 +1,7 @@
#include "..\script_component.hpp"
/*
- * Author: commy2
- * Compares version numbers of PBOs and DLLs.
+ * Author: commy2, johnb43, LinkIsGrim
+ * Compares version numbers of PBOs and DLLs. Logs and displays error message on any version/addon errors in multiplayer.
*
* Arguments:
* None
@@ -25,6 +25,7 @@ if (canSuspend) exitWith {
///////////////
private _cfgPatches = configFile >> "CfgPatches";
private _mainVersion = getText (_cfgPatches >> "ace_main" >> "versionStr");
+private _mainVersionAr = getArray (_cfgPatches >> "ace_main" >> "versionAr");
private _mainSource = configSourceMod (_cfgPatches >> "ace_main");
// CBA Versioning check - close main display if using incompatible version
@@ -75,18 +76,32 @@ private _oldCompats = [];
if (_oldAddons isNotEqualTo []) then {
_oldAddons = _oldAddons apply {format ["%1.pbo", _x]};
-
- private _errorMsg = if (count _oldAddons > 3) then {
- format ["The following files are outdated: %1, and %2 more.
ACE Main version is %3 from %4.
Loaded mods with outdated ACE files: %5", (_oldAddons select [0, 3]) joinString ", ", (count _oldAddons) - 3, _mainVersion, _mainSource, _oldSources joinString ", "];
+ private _extraSources = _oldSources select {_x != _mainSource};
+ private _conflictingInstall = _extraSources isNotEqualTo [];
+ private _allSources = [_mainSource] + _extraSources;
+
+ // Build the error message
+ private _title = "[ACE] ERROR: OUTDATED FILES";
+ private _reasonMsg = ["Reason: Mismatched addon versions within the same installation, it is likely corrupted.", "Reason: Conflicting ACE installations (listed below)."] select _conflictingInstall;
+ private _fixMsg = ["Fix: Repair ACE or update your repack.", "Fix: Make sure to only load one version of ACE."] select _conflictingInstall;
+
+ private _infoMsgLog = format ["The following files are outdated: %1.
ACE Main version is %2 from folder ""%3"".
All mod folders with ACE files: %4", _oldAddons joinString ", ", _mainVersion, _mainSource, _allSources joinString ", "];
+ private _infoMsg = if (count _oldAddons > 3) then {
+ format ["The following files are outdated: %1, and %2 more.
ACE Main version is %3 from folder ""%4"".
All mod folders with ACE files: %5", (_oldAddons select [0, 3]) joinString ", ", (count _oldAddons) - 3, _mainVersion, _mainSource, _allSources joinString ", "];
} else {
- format ["The following files are outdated: %1.
ACE Main version is %2 from %3.
Loaded mods with outdated ACE files: %4", _oldAddons joinString ", ", _mainVersion, _mainSource, _oldSources joinString ", "];
+ _infoMsgLog
};
+ // Log it
+ private _logMsg = format ["%1: %2%5%5%3%5%5%4", _title, _reasonMsg, _fixMsg, _infoMsgLog, endl];
+ _logMsg = _logMsg regexReplace ["
", endl];
+ ERROR(_logMsg);
+
+ // Display it
if (hasInterface) then {
- ["[ACE] ERROR", _errorMsg] call FUNC(errorMessage);
+ private _errorMsg = format ["%1
%2
%3", _reasonMsg, _fixMsg, _infoMsg];
+ [_title, _errorMsg] call FUNC(errorMessage);
};
-
- ERROR(_errorMsg);
};
if (_oldCompats isNotEqualTo []) then {
@@ -137,51 +152,129 @@ if (isMultiplayer) then {
if (isServer) then {
// Send server's version of ACE to all clients
GVAR(serverVersion) = _mainVersion;
+ GVAR(serverVersionAr) = _mainVersionAr;
GVAR(serverAddons) = _addons;
GVAR(serverSource) = _mainSource;
publicVariable QGVAR(serverVersion);
+ publicVariable QGVAR(serverVersionAr);
publicVariable QGVAR(serverAddons);
publicVariable QGVAR(serverSource);
} else {
GVAR(clientVersion) = _mainVersion;
+ GVAR(clientVersionAr) = _mainVersionAr;
GVAR(clientAddons) = _addons;
- private _fnc_check = {
- if (GVAR(clientVersion) != GVAR(serverVersion)) then {
- private _errorMsg = format ["Client/Server Version Mismatch. Server: %1, Client: %2. Server modDir: %3", GVAR(serverVersion), GVAR(clientVersion), GVAR(serverSource)];
+ private _fnc_diagnose_versionMismatch = {
+ private _title = "[ACE] ERROR: VERSION MISMATCH";
+ private _fixMsg = format ["Fix: %1", "Make sure versions of server and client match. You may be using ACE from different Steam Workshop items, or just haven't updated properly."];
- // Check ACE install
- call FUNC(checkFiles_diagnoseACE);
+ // If versions don't match, someone's outdated, if it's not the client, then it's the server
+ private _clientIsOutdated = [GVAR(serverVersionAr), GVAR(clientVersionAr)] call CBA_versioning_fnc_version_compare;
+ private _reasonMsg = format ["Reason: %1", ["Reason: Server is outdated (client version is higher than server's).", "Reason: Client is outdated (client version is lower than server's)."] select _clientIsOutdated];
- ERROR(_errorMsg);
+ private _infoMsg = format ["Server version is %1, Client version is %2.
Server mod folder is named ""%3"".", GVAR(serverVersion), GVAR(clientVersion), GVAR(serverSource)];
- if (hasInterface) then {
- ["[ACE] ERROR", _errorMsg] call FUNC(errorMessage);
+ [_title, _reasonMsg, _fixMsg, _infoMsg, _infoMsg] // return
+ };
+
+ private _fnc_diagnose_addonMismatch = {
+ private _title = "[ACE] ERROR: ADDON MISMATCH";
+
+ // Figure out why we have a mismatch and where it's coming from
+ // Integrated compats are the usual culprit
+ private _additionalCompats = _addons select {(_x select [0, 10]) == "ace_compat"};
+ _additionalAddons = _additionalAddons - _additionalCompats;
+
+ // Server most likely just uses a different ACE repack with some components removed
+ // Higher priority than compats, as we'll load compats for components as well
+ // Don't show compats in the error message, only components
+ if (_additionalAddons isNotEqualTo []) exitWith {
+ private _reasonMsg = format ["Reason: %1", "Client has ACE components not present on the server."];
+ private _fixMsg = format ["Fix: %1", "Make sure you're using ACE from the same Steam Workshop item or repository as the server."];
+
+ private _infoMsgLog = format ["Client has additional addons: %1.
Server mod folder is named ""%2"".", _additionalAddons joinString ", ", GVAR(serverSource)]; // Build the whole thing so we can log it to RPT
+
+ private _infoMsg = if (count _additionalAddons > 3) then { // Truncate it for display
+ format ["Client has additional addons: %1, and %2 more.
Server mod folder is named ""%3"".", (_additionalAddons select [0, 3]) joinString ", ", (count _additionalAddons) - 3, GVAR(serverSource)];
+ } else {
+ _infoMsgLog
};
+
+ [_title, _reasonMsg, _fixMsg, _infoMsg, _infoMsgLog] // return
};
- private _addons = GVAR(clientAddons) - GVAR(serverAddons);
+ // CDLC/content mod with integrated compats is loaded when it shouldn't be
+ // No need to show which addons, just show the mod that the compats are for
+ if (_additionalCompats isNotEqualTo []) exitWith {
+ // Fix is easy
+ private _fixMsg = format ["Fix: %1", "Make sure your mod list matches or add those mods to the server. Check your server files and '-mod=' parameter if you're the server administrator."];
+
+ private _additionalMods = [];
+ private _loadedModsInfo = getLoadedModsInfo;
+ private _defaultModDirs = [_mainSource] + (_loadedModsInfo select {_x select 2} apply {_x select 1}); // Skip ACE itself and anything vanilla
+
+ { // Evil O(n^infinityAndBeyond) loop, can't do much about it.
+ { // Get the real mod name for the compats we're loading
+ private _sourceModDir = configSourceMod (_cfgPatches >> _x);
+ if !(_sourceModDir in _defaultModDirs) then {
+ _additionalMods pushBackUnique (_loadedModsInfo select {_x select 1 == _sourceModDir} select 0 select 0);
+ };
+ } forEach (getArray (_cfgPatches >> _x >> "requiredAddons"));
+ } forEach _additionalCompats;
+
+ private _reasonMsg = format ["Reason: %1", "Client has extra mods requiring compats loaded (listed below)"];
+ private _infoMsg = format ["Additional compatibility is being loaded for: %1", _additionalMods joinString ", "];
+
+ [_title, _reasonMsg, _fixMsg, _infoMsg, _infoMsg] // return
+ };
+
+ [
+ _title,
+ "Reason: Exceptional combination of additional addons. Good job, you broke our error handling.",
+ "Fix: Open an issue on GitHub with your logs so we can add handling for this.",
+ "Have a cookie.",
+ "Unimplemented addon mismatch"
+ ] // default return
+ };
- if (_addons isNotEqualTo []) then {
- private _errorMsg = format ["Client/Server Addon Mismatch. Client has additional addons: %1. Server modDir: %2", _addons, GVAR(serverSource)];
+ private _fnc_multiplayerCheck = {
+ // Check if we'll actually throw an error
+ private _versionMismatch = GVAR(clientVersion) != GVAR(serverVersion);
+ private _additionalAddons = GVAR(clientAddons) - GVAR(serverAddons);
+ private _addonMismatch = _additionalAddons isNotEqualTo [];
+ // Should have all possibilities
+ if (_versionMismatch || _addonMismatch) then {
// Check ACE install
call FUNC(checkFiles_diagnoseACE);
- ERROR(_errorMsg);
+ // Build the error message
+ // Could be a simple select but leaving the switch so it's easier to expand later
+ private _errorBuilder = switch (true) do {
+ case _versionMismatch: {_fnc_diagnose_versionMismatch};
+ case _addonMismatch: {_fnc_diagnose_addonMismatch};
+ };
+ (call _errorBuilder) params ["_title", "_reasonMsg", "_fixMsg", "_infoMsg", "_infoMsgLog"];
+
+ // Log it
+ private _logMsg = format ["%1: %2%5%5%3%5%5%4", _title, _reasonMsg, _fixMsg, _infoMsgLog, endl];
+ _logMsg = _logMsg regexReplace ["
", endl];
+ ERROR(_logMsg);
+ // Display it
if (hasInterface) then {
- ["[ACE] ERROR", _errorMsg] call FUNC(errorMessage);
+ private _errorMsg = format ["%1
%2
%3", _reasonMsg, _fixMsg, _infoMsg];
+ [_title, _errorMsg] call FUNC(errorMessage);
};
};
};
// Clients have to wait for the variables
if (isNil QGVAR(serverVersion) || isNil QGVAR(serverAddons)) then {
- GVAR(serverVersion) addPublicVariableEventHandler _fnc_check;
+ QGVAR(serverVersion) addPublicVariableEventHandler _fnc_multiplayerCheck;
} else {
- call _fnc_check;
+ call _fnc_multiplayerCheck;
};
};
};