Skip to content

Commit

Permalink
Re-organize infrastructure for startup checks
Browse files Browse the repository at this point in the history
  • Loading branch information
jellysquid3 committed Dec 8, 2023
1 parent cc12279 commit b74c499
Show file tree
Hide file tree
Showing 29 changed files with 562 additions and 214 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package me.jellysquid.mods.sodium.client;

import me.jellysquid.mods.sodium.client.util.workarounds.PreLaunchChecks;
import me.jellysquid.mods.sodium.client.util.workarounds.Workarounds;
import me.jellysquid.mods.sodium.client.util.workarounds.probe.GraphicsAdapterProbe;
import me.jellysquid.mods.sodium.client.compatibility.checks.EarlyDriverScanner;
import me.jellysquid.mods.sodium.client.compatibility.workarounds.Workarounds;
import me.jellysquid.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterProbe;
import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint;

public class SodiumPreLaunch implements PreLaunchEntrypoint {
@Override
public void onPreLaunch() {
GraphicsAdapterProbe.findAdapters();
PreLaunchChecks.checkDrivers();
EarlyDriverScanner.scanDrivers();
Workarounds.init();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package me.jellysquid.mods.sodium.client.compatibility.checks;

/**
* "Checks" are used to determine whether the environment we are running within is actually reasonable. Most often,
* failing checks will crash the game and prompt the user for intervention.
*/
class Configuration {
public static final boolean WIN32_RTSS_HOOKS = configureCheck("win32.rtss", true);
public static final boolean WIN32_DRIVER_INTEL_GEN7 = configureCheck("win32.intelGen7", true);

private static boolean configureCheck(String name, boolean defaultValue) {
var propertyValue = System.getProperty(getPropertyKey(name), null);

if (propertyValue == null) {
return defaultValue;
}

return Boolean.parseBoolean(propertyValue);
}

private static String getPropertyKey(String name) {
return "sodium.checks." + name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package me.jellysquid.mods.sodium.client.compatibility.checks;

import me.jellysquid.mods.sodium.client.platform.MessageBox;
import me.jellysquid.mods.sodium.client.platform.windows.WindowsDriverStoreVersion;
import me.jellysquid.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterProbe;
import me.jellysquid.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterVendor;
import net.minecraft.util.Util;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Performs OpenGL driver validation before the game creates an OpenGL context. This runs during the earliest possible
* opportunity at game startup, and uses a custom hardware prober to search for problematic drivers.
*/
public class EarlyDriverScanner {
private static final Logger LOGGER = LoggerFactory.getLogger("Sodium-EarlyDriverScanner");

private static final String CONSOLE_MESSAGE_TEMPLATE = """
###ERROR_DESCRIPTION###
For more information, please see: ###HELP_URL###
""";

private static final String INTEL_GEN7_DRIVER_MESSAGE = """
The game failed to start because the currently installed Intel Graphics Driver is not compatible.
Installed version: ###CURRENT_DRIVER###
Required version: 15.33.53.5161 (or newer)
You must update your graphics card driver in order to continue.""";

private static final String INTEL_GEN7_DRIVER_HELP_URL = "https://github.com/CaffeineMC/sodium-fabric/wiki/Driver-Compatibility#windows-intel-gen7";

public static void scanDrivers() {
if (Configuration.WIN32_DRIVER_INTEL_GEN7) {
var installedVersion = findBrokenIntelGen7GraphicsDriver();

if (installedVersion != null) {
showUnsupportedDriverMessageBox(
INTEL_GEN7_DRIVER_MESSAGE
.replace("###CURRENT_DRIVER###", installedVersion.getFriendlyString()),
INTEL_GEN7_DRIVER_HELP_URL);
}
}
}

private static void showUnsupportedDriverMessageBox(String message, String url) {
// Always print the information to the log file first, just in case we can't show the message box.
LOGGER.error(CONSOLE_MESSAGE_TEMPLATE
.replace("###ERROR_DESCRIPTION###", message)
.replace("###HELP_URL###", url));

// Try to show a graphical message box (if the platform supports it) and shut down the game.
MessageBox.showMessageBox(null, MessageBox.IconType.ERROR, "Sodium Renderer - Unsupported Driver", message, url);
System.exit(1 /* failure code */);
}

// https://github.com/CaffeineMC/sodium-fabric/issues/899
private static @Nullable WindowsDriverStoreVersion findBrokenIntelGen7GraphicsDriver() {
if (Util.getOperatingSystem() != Util.OperatingSystem.WINDOWS) {
return null;
}

for (var adapter : GraphicsAdapterProbe.getAdapters()) {
if (adapter.vendor() != GraphicsAdapterVendor.INTEL) {
continue;
}

try {
var version = WindowsDriverStoreVersion.parse(adapter.version());

if (version.driverModel() == 10 && version.featureLevel() == 18 && version.major() == 10) {
return version;
}
} catch (WindowsDriverStoreVersion.ParseException ignored) { }
}

return null;
}
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,34 @@
package me.jellysquid.mods.sodium.client.util.workarounds;
package me.jellysquid.mods.sodium.client.compatibility.checks;

import me.jellysquid.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaDriverVersion;
import me.jellysquid.mods.sodium.client.gui.console.Console;
import me.jellysquid.mods.sodium.client.gui.console.message.MessageLevel;
import me.jellysquid.mods.sodium.client.util.workarounds.driver.nvidia.NvidiaGLContextInfo;
import me.jellysquid.mods.sodium.client.util.workarounds.platform.windows.WindowsModuleChecks;
import net.minecraft.text.MutableText;
import me.jellysquid.mods.sodium.client.compatibility.environment.GLContextInfo;
import net.minecraft.text.Text;
import net.minecraft.util.Util;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL11C;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PostLaunchChecks {
/**
* Performs OpenGL driver validation after the game creates an OpenGL context. This runs immediately after OpenGL
* context creation, and uses the implementation details of the OpenGL context to perform validation.
*/
public class LateDriverScanner {
private static final Logger LOGGER = LoggerFactory.getLogger("Sodium-PostlaunchChecks");

public static void checkContext() {
public static void onContextInitialized() {
checkContextImplementation();

if (isUsingPojavLauncher()) {
showConsoleMessage(Text.translatable("sodium.console.pojav_launcher"));
logMessage("It appears that PojavLauncher is being used with an OpenGL compatibility layer. This will " +
Console.instance().logMessage(MessageLevel.SEVERE, Text.translatable("sodium.console.pojav_launcher"), 30.0);
LOGGER.error("It appears that PojavLauncher is being used with an OpenGL compatibility layer. This will " +
"likely cause severe performance issues, graphical issues, and crashes when used with Sodium. This " +
"configuration is not supported -- you are on your own!");
}

// We should also check that nothing problematic has been injected into the process.
WindowsModuleChecks.checkModules();
}

private static void checkContextImplementation() {
GLContextInfo driver = getGraphicsContextInfo();
GLContextInfo driver = GLContextInfo.create();

if (driver == null) {
LOGGER.warn("Could not retrieve identifying strings for OpenGL implementation");
Expand All @@ -41,56 +39,38 @@ private static void checkContextImplementation() {
LOGGER.info("OpenGL Renderer: {}", driver.renderer());
LOGGER.info("OpenGL Version: {}", driver.version());

if (isBrokenNvidiaDriverInstalled(driver)) {
showConsoleMessage(Text.translatable("sodium.console.broken_nvidia_driver"));
logMessage("The NVIDIA graphics driver appears to be out of date. This will likely cause severe " +
if (!isSupportedNvidiaDriver(driver)) {
Console.instance()
.logMessage(MessageLevel.SEVERE, Text.translatable("sodium.console.broken_nvidia_driver"), 30.0);

LOGGER.error("The NVIDIA graphics driver appears to be out of date. This will likely cause severe " +
"performance issues and crashes when used with Sodium. The graphics driver should be updated to " +
"the latest version (version 536.23 or newer).");
}
}

@Nullable
private static GLContextInfo getGraphicsContextInfo() {
String vendor = GL11C.glGetString(GL11C.GL_VENDOR);
String renderer = GL11C.glGetString(GL11C.GL_RENDERER);
String version = GL11C.glGetString(GL11C.GL_VERSION);

if (vendor == null || renderer == null || version == null) {
return null;
}

return new GLContextInfo(vendor, renderer, version);
}

private static void showConsoleMessage(MutableText message) {
Console.instance().logMessage(MessageLevel.SEVERE, message, 30.0);
}

private static void logMessage(String message, Object... args) {
LOGGER.error(message, args);
}

// https://github.com/CaffeineMC/sodium-fabric/issues/1486
// The way which NVIDIA tries to detect the Minecraft process could not be circumvented until fairly recently
// So we require that an up-to-date graphics driver is installed so that our workarounds can disable the Threaded
// Optimizations driver hack.
private static boolean isBrokenNvidiaDriverInstalled(GLContextInfo driver) {
private static boolean isSupportedNvidiaDriver(GLContextInfo driver) {
// The Linux driver has two separate branches which have overlapping version numbers, despite also having
// different feature sets. As a result, we can't reliably determine which Linux drivers are broken...
if (Util.getOperatingSystem() != Util.OperatingSystem.WINDOWS) {
return false;
return true;
}

var version = NvidiaGLContextInfo.tryParse(driver);
var version = NvidiaDriverVersion.tryParse(driver);

if (version != null) {
return version.isWithinRange(
new NvidiaGLContextInfo(526, 47), // Broken in 526.47
new NvidiaGLContextInfo(536, 23) // Fixed in 536.23
return !version.isWithinRange(
new NvidiaDriverVersion(526, 47), // Broken in 526.47
new NvidiaDriverVersion(536, 23) // Fixed in 536.23
);
}

return false;
// If we couldn't determine the version, then it's supported either way.
return true;
}

// https://github.com/CaffeineMC/sodium-fabric/issues/1916
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package me.jellysquid.mods.sodium.client.compatibility.checks;

import net.minecraft.util.WinNativeModuleUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

/**
* Utility class for determining whether the current process has been injected into or otherwise modified. This should
* generally only be accessed after OpenGL context creation, as most third-party software waits until the OpenGL ICD
* is initialized before injecting.
*/
public class ModuleScanner {
private static final Logger LOGGER = LoggerFactory.getLogger("Sodium-Win32ModuleChecks");

private static final String[] RTSS_HOOKS_MODULE_NAMES = { "RTSSHooks64.dll", "RTSSHooks.dll" };

public static void checkModules() {
List<WinNativeModuleUtil.NativeModule> modules;

try {
modules = WinNativeModuleUtil.collectNativeModules();
} catch (Throwable t) {
LOGGER.warn("Failed to scan the currently loaded modules", t);
return;
}

if (modules.isEmpty()) {
// Platforms other than Windows will not return anything.
return;
}

// RivaTuner hooks the wglCreateContext function, and leaves itself behind as a loaded module
if (Configuration.WIN32_RTSS_HOOKS && isModuleLoaded(modules, RTSS_HOOKS_MODULE_NAMES)) {
throw new RuntimeException("RivaTuner Statistics Server (RTSS) is not compatible with Sodium, " +
"see here for more details: https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible");
}
}

private static boolean isModuleLoaded(List<WinNativeModuleUtil.NativeModule> modules, String[] names) {
for (var name : names) {
for (var module : modules) {
if (module.path.equalsIgnoreCase(name)) {
return true;
}
}
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package me.jellysquid.mods.sodium.client.util.workarounds;
package me.jellysquid.mods.sodium.client.compatibility.checks;

import me.jellysquid.mods.sodium.client.gui.console.Console;
import me.jellysquid.mods.sodium.client.gui.console.message.MessageLevel;
Expand All @@ -14,7 +14,7 @@
import java.util.List;
import java.util.Map;

public class InGameChecks {
public class ResourcePackScanner {

private static final Logger LOGGER = LoggerFactory.getLogger("Sodium-InGameChecks");
private static final List<String> VSH_FSH_BLACKLIST = Arrays.asList(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package me.jellysquid.mods.sodium.client.compatibility.environment;

import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL11C;

public record GLContextInfo(String vendor, String renderer, String version) {
@Nullable
public static GLContextInfo create() {
String vendor = GL11C.glGetString(GL11C.GL_VENDOR);
String renderer = GL11C.glGetString(GL11C.GL_RENDERER);
String version = GL11C.glGetString(GL11C.GL_VERSION);

if (vendor == null || renderer == null || version == null) {
return null;
}

return new GLContextInfo(vendor, renderer, version);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package me.jellysquid.mods.sodium.client.util.workarounds.probe;
package me.jellysquid.mods.sodium.client.compatibility.environment.probe;

public record GraphicsAdapterInfo(GraphicsAdapterVendor vendor, String name, String version) {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package me.jellysquid.mods.sodium.client.util.workarounds.probe;
package me.jellysquid.mods.sodium.client.compatibility.environment.probe;

import net.minecraft.util.Util;
import org.slf4j.Logger;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package me.jellysquid.mods.sodium.client.util.workarounds.probe;
package me.jellysquid.mods.sodium.client.compatibility.environment.probe;

import org.jetbrains.annotations.NotNull;

Expand Down
Loading

0 comments on commit b74c499

Please sign in to comment.