Skip to content

Commit

Permalink
Implement version detection for RivaTuner Statistics Server
Browse files Browse the repository at this point in the history
  • Loading branch information
jellysquid3 committed Jan 18, 2024
1 parent 27aece3 commit 8878f17
Show file tree
Hide file tree
Showing 7 changed files with 354 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package me.jellysquid.mods.sodium.client.compatibility.checks;

import me.jellysquid.mods.sodium.client.platform.windows.api.Kernel32;
import me.jellysquid.mods.sodium.client.platform.windows.api.version.Version;
import net.minecraft.util.WinNativeModuleUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.regex.Pattern;

/**
* Utility class for determining whether the current process has been injected into or otherwise modified. This should
Expand Down Expand Up @@ -33,11 +38,113 @@ public static void checkModules() {

// RivaTuner hooks the wglCreateContext function, and leaves itself behind as a loaded module
if (Configuration.WIN32_RTSS_HOOKS && isModuleLoaded(modules, RTSS_HOOKS_MODULE_NAMES)) {
checkRTSSModules();
}
}

private static void checkRTSSModules() {
LOGGER.warn("RivaTuner Statistics Server (RTSS) has injected into the process! Attempting to apply workarounds for compatibility...");

String version = null;

try {
version = findRTSSModuleVersion();
} catch (Throwable t) {
LOGGER.warn("Exception thrown while reading file version", t);
}

if (version == null) {
LOGGER.warn("Could not determine version of RivaTuner Statistics Server");
} else {
LOGGER.info("Detected RivaTuner Statistics Server version: {}", version);
}

if (version == null || !isRTSSCompatible(version)) {
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 final Pattern RTSS_VERSION_PATTERN = Pattern.compile("^(?<x>\\d*), (?<y>\\d*), (?<z>\\d*), (?<w>\\d*)$");

private static boolean isRTSSCompatible(String version) {
var matcher = RTSS_VERSION_PATTERN.matcher(version);

if (!matcher.matches()) {
return false;
}

try {
int x = Integer.parseInt(matcher.group("x"));
int y = Integer.parseInt(matcher.group("y"));
int z = Integer.parseInt(matcher.group("z"));

// >=7.3.4
return x > 7 || (x == 7 && y > 3) || (x == 7 && y == 3 && z >= 4);
} catch (NumberFormatException e) {
LOGGER.warn("Invalid version string: {}", version);
}

return false;
}

private static String findRTSSModuleVersion() {
long module;

try {
module = Kernel32.getModuleHandleByNames(RTSS_HOOKS_MODULE_NAMES);
} catch (Throwable t) {
LOGGER.warn("Failed to locate module", t);
return null;
}

String moduleFileName;

try {
moduleFileName = Kernel32.getModuleFileName(module);
} catch (Throwable t) {
LOGGER.warn("Failed to get path of module", t);
return null;
}

var modulePath = Path.of(moduleFileName);
var moduleDirectory = modulePath.getParent();

LOGGER.info("Searching directory: {}", moduleDirectory);

var executablePath = moduleDirectory.resolve("RTSS.exe");

if (!Files.exists(executablePath)) {
LOGGER.warn("Could not find executable: {}", executablePath);
return null;
}

LOGGER.info("Parsing file: {}", executablePath);

var version = Version.getModuleFileVersion(executablePath.toAbsolutePath().toString());

if (version == null) {
LOGGER.warn("Couldn't find version structure");
return null;
}

var translation = version.queryEnglishTranslation();

if (translation == null) {
LOGGER.warn("Couldn't find suitable translation");
return null;
}

var fileVersion = version.queryValue("FileVersion", translation);

if (fileVersion == null) {
LOGGER.warn("Couldn't query file version");
return null;
}

return fileVersion;
}

private static boolean isModuleLoaded(List<WinNativeModuleUtil.NativeModule> modules, String[] names) {
for (var name : names) {
for (var module : modules) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,35 @@
package me.jellysquid.mods.sodium.client.platform.windows.api;

import org.jetbrains.annotations.Nullable;
import org.lwjgl.PointerBuffer;
import org.lwjgl.system.*;

import java.nio.ByteBuffer;
import java.nio.IntBuffer;

public class Kernel32 {
private static final SharedLibrary LIBRARY = Library.loadNative("me.jellyquid.mods.sodium", "kernel32");
private static final SharedLibrary LIBRARY = APIUtil.apiCreateLibrary("kernel32");

private static final int MAX_PATH = 32767;

private static final int GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 1 << 0;
private static final int GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = 1 << 2;

private static final long PFN_GetCommandLineW;
private static final long PFN_SetEnvironmentVariableW;

private static final long PFN_GetModuleHandleExW;
private static final long PFN_GetLastError;

private static final long PFN_GetModuleFileNameW;


static {
PFN_GetCommandLineW = APIUtil.apiGetFunctionAddress(LIBRARY, "GetCommandLineW");
PFN_SetEnvironmentVariableW = APIUtil.apiGetFunctionAddress(LIBRARY, "SetEnvironmentVariableW");
PFN_GetModuleHandleExW = APIUtil.apiGetFunctionAddress(LIBRARY, "GetModuleHandleExW");
PFN_GetLastError = APIUtil.apiGetFunctionAddress(LIBRARY, "GetLastError");
PFN_GetModuleFileNameW = APIUtil.apiGetFunctionAddress(LIBRARY, "GetModuleFileNameW");
}

public static void setEnvironmentVariable(String name, @Nullable String value) {
Expand All @@ -35,4 +51,62 @@ public static void setEnvironmentVariable(String name, @Nullable String value) {
public static long getCommandLine() {
return JNI.callP(PFN_GetCommandLineW);
}

public static long getModuleHandleByNames(String[] names) {
for (String name : names) {
var handle = getModuleHandleByName(name);

if (handle != MemoryUtil.NULL) {
return handle;
}
}

throw new RuntimeException("Could not obtain handle of module");
}

public static long getModuleHandleByName(String name) {
try (MemoryStack stack = MemoryStack.stackPush()) {
ByteBuffer lpFunctionNameBuf = stack.malloc(16, MemoryUtil.memLengthUTF16(name, true));
MemoryUtil.memUTF16(name, true, lpFunctionNameBuf);

PointerBuffer phModule = stack.callocPointer(1);

int result;
result = JNI.callPPI(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
MemoryUtil.memAddress(lpFunctionNameBuf), MemoryUtil.memAddress(phModule), PFN_GetModuleHandleExW);

if (result == 0) {
var error = getLastError();

switch (error) {
case 126 /* ERROR_MOD_NOT_FOUND */:
return MemoryUtil.NULL;
default:
throw new RuntimeException("GetModuleHandleEx failed, error=" + error);
}
}

return phModule.get(0);
}
}

public static String getModuleFileName(long phModule) {
ByteBuffer lpFileName = MemoryUtil.memAlignedAlloc(16, MAX_PATH);

try {
int length = JNI.callPPI(phModule, MemoryUtil.memAddress(lpFileName), lpFileName.capacity(), PFN_GetModuleFileNameW);

if (length == 0) {
throw new RuntimeException("GetModuleFileNameW failed, error=" + getLastError());
}

return MemoryUtil.memUTF16(lpFileName, length);
} finally {
MemoryUtil.memAlignedFree(lpFileName);
}
}

public static int getLastError() {
return JNI.callI(PFN_GetLastError);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import org.lwjgl.system.SharedLibrary;

public class User32 {
private static final SharedLibrary LIBRARY = Library.loadNative("me.jellyquid.mods.sodium", "user32");
private static final SharedLibrary LIBRARY = APIUtil.apiCreateLibrary("user32");

private static final long PFN_MessageBoxIndirectW;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package me.jellysquid.mods.sodium.client.platform.windows.api.version;

import org.lwjgl.system.MemoryUtil;

public record LanguageCodePage(int languageId, int codePage) {
static final int STRIDE = Integer.BYTES;

static LanguageCodePage decode(long address) {
var value = MemoryUtil.memGetInt(address);

int languageId = value & 0xFFFF;
int codePage = (value & 0xFFFF0000) >> 16;

return new LanguageCodePage(languageId, codePage);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package me.jellysquid.mods.sodium.client.platform.windows.api.version;

public record QueryResult(long address, int length) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package me.jellysquid.mods.sodium.client.platform.windows.api.version;

import me.jellysquid.mods.sodium.client.platform.windows.api.Kernel32;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.PointerBuffer;
import org.lwjgl.system.*;

import java.nio.ByteBuffer;
import java.nio.IntBuffer;

public class Version {
private static final SharedLibrary LIBRARY = APIUtil.apiCreateLibrary("version");

private static final long PFN_GetFileVersionInfoSizeW;
private static final long PFN_GetFileVersionInfoW;

private static final long PFN_VerQueryValueW;

static {
PFN_GetFileVersionInfoSizeW = APIUtil.apiGetFunctionAddress(LIBRARY, "GetFileVersionInfoSizeW");
PFN_GetFileVersionInfoW = APIUtil.apiGetFunctionAddress(LIBRARY, "GetFileVersionInfoW");

PFN_VerQueryValueW = APIUtil.apiGetFunctionAddress(LIBRARY, "VerQueryValueW");
}


static @Nullable QueryResult query(ByteBuffer pBlock, String subBlock) {
try (MemoryStack stack = MemoryStack.stackPush()) {
ByteBuffer pSubBlock = stack.malloc(16, MemoryUtil.memLengthUTF16(subBlock, true));
MemoryUtil.memUTF16(subBlock, true, pSubBlock);

PointerBuffer pBuffer = stack.callocPointer(1);
IntBuffer pLen = stack.callocInt(1);

int result = JNI.callPPPPI(MemoryUtil.memAddress(pBlock), MemoryUtil.memAddress(pSubBlock),
MemoryUtil.memAddress(pBuffer), MemoryUtil.memAddress(pLen), Version.PFN_VerQueryValueW);

if (result == 0) {
return null;
}

return new QueryResult(pBuffer.get(), pLen.get());
}
}

public static @Nullable VersionInfo getModuleFileVersion(String filename) {
ByteBuffer lptstrFilename = MemoryUtil.memAlignedAlloc(16, MemoryUtil.memLengthUTF16(filename, true));

try (MemoryStack stack = MemoryStack.stackPush()) {
MemoryUtil.memUTF16(filename, true, lptstrFilename);

IntBuffer lpdwHandle = stack.callocInt(1);
int versionInfoLength = JNI.callPPI(MemoryUtil.memAddress(lptstrFilename), MemoryUtil.memAddress(lpdwHandle), PFN_GetFileVersionInfoSizeW);

if (versionInfoLength == 0) {
int error = Kernel32.getLastError();

switch (error) {
case 0x714 /* ERROR_RESOURCE_DATA_NOT_FOUND */:
case 0x715 /* ERROR_RESOURCE_TYPE_NOT_FOUND */:
return null;
default:
throw new RuntimeException("GetFileVersionInfoSizeW failed, error=" + error);
}
}

VersionInfo versionInfo = VersionInfo.allocate(versionInfoLength);
int result = JNI.callPPI(MemoryUtil.memAddress(lptstrFilename), lpdwHandle.get(), versionInfoLength, versionInfo.address(), PFN_GetFileVersionInfoW);

if (result == 0) {
versionInfo.close();

throw new RuntimeException("GetFileVersionInfoW failed, error=" + Kernel32.getLastError());
}

return versionInfo;
} finally {
MemoryUtil.memAlignedFree(lptstrFilename);
}
}
}
Loading

0 comments on commit 8878f17

Please sign in to comment.