diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index e66497b62..0e7a83431 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -1506,10 +1506,21 @@ opencomputers { # to work. logFullNativeLibLoadErrors: false - # Force loading one specific library, to avoid trying to load any - # others. Use this if you get warnings in the log or are told to do - # so for debugging purposes ;-) - forceNativeLibWithName: "" + # Force the platform name for the native libraries, instead of relying + # on auto-detecting it. Use this if your system is using an otherwise + # unsupported operating system or CPU architecture. If unsure, leave blank. + # + # Examples of platform strings include "solaris-x86_64" for 64-bit Solaris, + # or "windows-aarch64" for Windows on the aarch64 (64-bit arm) architecture. + forceNativeLibPlatform: "" + + # Force the native library loader to check the specified directory for natives first, + # before trying to find libraries packaged with OpenComputers. Use this if you want to + # use custom native libraries, or are on an unsupported platform. If unsure, leave blank. + # + # This can be an absolute or relative path. If relative, the base directory will be the .minecraft + # directory of the running Minecraft instance. + forceNativeLibPathFirst: "" # Used to suppress log spam for OpenGL errors on derpy drivers. I'm # quite certain the code in the font render is valid, display list diff --git a/src/main/scala/li/cil/oc/Settings.scala b/src/main/scala/li/cil/oc/Settings.scala index 168640a46..372341cce 100644 --- a/src/main/scala/li/cil/oc/Settings.scala +++ b/src/main/scala/li/cil/oc/Settings.scala @@ -438,7 +438,8 @@ class Settings(val config: Config) { val limitMemory = !config.getBoolean("debug.disableMemoryLimit") val forceCaseInsensitive = config.getBoolean("debug.forceCaseInsensitiveFS") val logFullLibLoadErrors = config.getBoolean("debug.logFullNativeLibLoadErrors") - val forceNativeLib = config.getString("debug.forceNativeLibWithName") + val forceNativeLibPlatform = config.getString("debug.forceNativeLibPlatform") + val forceNativeLibPathFirst = config.getString("debug.forceNativeLibPathFirst") val logOpenGLErrors = config.getBoolean("debug.logOpenGLErrors") val logHexFontErrors = config.getBoolean("debug.logHexFontErrors") val alwaysTryNative = config.getBoolean("debug.alwaysTryNative") diff --git a/src/main/scala/li/cil/oc/server/machine/luac/LuaStateFactory.scala b/src/main/scala/li/cil/oc/server/machine/luac/LuaStateFactory.scala index e54287474..d9c82772a 100644 --- a/src/main/scala/li/cil/oc/server/machine/luac/LuaStateFactory.scala +++ b/src/main/scala/li/cil/oc/server/machine/luac/LuaStateFactory.scala @@ -4,8 +4,8 @@ import java.io.File import java.io.FileInputStream import java.io.FileOutputStream import java.nio.channels.Channels +import java.nio.file.Paths import java.util.regex.Pattern - import com.google.common.base.Strings import com.google.common.io.PatternFilenameFilter import li.cil.oc.OpenComputers @@ -130,36 +130,39 @@ abstract class LuaStateFactory { private var currentLib = "" private val libraryName = { - if (!Strings.isNullOrEmpty(Settings.get.forceNativeLib)) Settings.get.forceNativeLib - - else { - val libExtension = { - if (SystemUtils.IS_OS_MAC) ".dylib" - else if (SystemUtils.IS_OS_WINDOWS) ".dll" - else ".so" - } - - val systemName = { - if (SystemUtils.IS_OS_FREE_BSD) "freebsd" - else if (SystemUtils.IS_OS_NET_BSD) "netbsd" - else if (SystemUtils.IS_OS_OPEN_BSD) "openbsd" - else if (SystemUtils.IS_OS_SOLARIS) "solaris" - else if (SystemUtils.IS_OS_LINUX) "linux" - else if (SystemUtils.IS_OS_MAC) "darwin" - else if (SystemUtils.IS_OS_WINDOWS) "windows" - else "unknown" - } - - val archName = { - if (Architecture.IS_OS_ARM64) "aarch64" - else if (Architecture.IS_OS_ARM) "arm" - else if (Architecture.IS_OS_X64) "x86_64" - else if (Architecture.IS_OS_X86) "x86" - else "unknown" - } - - "libjnlua" + version + "-" + systemName + "-" + archName + libExtension + val libExtension = { + if (SystemUtils.IS_OS_MAC) ".dylib" + else if (SystemUtils.IS_OS_WINDOWS) ".dll" + else ".so" } + + val platformName = { + if (!Strings.isNullOrEmpty(Settings.get.forceNativeLibPlatform)) Settings.get.forceNativeLibPlatform + else { + val systemName = { + if (SystemUtils.IS_OS_FREE_BSD) "freebsd" + else if (SystemUtils.IS_OS_NET_BSD) "netbsd" + else if (SystemUtils.IS_OS_OPEN_BSD) "openbsd" + else if (SystemUtils.IS_OS_SOLARIS) "solaris" + else if (SystemUtils.IS_OS_LINUX) "linux" + else if (SystemUtils.IS_OS_MAC) "darwin" + else if (SystemUtils.IS_OS_WINDOWS) "windows" + else "unknown" + } + + val archName = { + if (Architecture.IS_OS_ARM64) "aarch64" + else if (Architecture.IS_OS_ARM) "arm" + else if (Architecture.IS_OS_X64) "x86_64" + else if (Architecture.IS_OS_X86) "x86" + else "unknown" + } + + systemName + "-" + archName + } + } + + "libjnlua" + version + "-" + platformName + libExtension } protected def create(maxMemory: Option[Int] = None): jnlua.LuaState @@ -170,13 +173,26 @@ abstract class LuaStateFactory { def isAvailable = haveNativeLibrary - val is64Bit = Architecture.IS_OS_X64 + val is64Bit = { + val dataModel = try System.getProperty("sun.arch.data.model") catch { + case ex: SecurityException => null + } + + if (dataModel != null) + "64".equals(dataModel) + else { + // Best effort, will probably miss some esoteric architectures + // Examples this works for: x86_64, ppc64le, aarch64_be + SystemUtils.OS_ARCH.matches(".*64_?([bl]e)?") + } + } // Since we use native libraries we have to do some work. This includes // figuring out what we're running on, so that we can load the proper shared // libraries compiled for that system. It also means we have to unpack the // shared libraries somewhere so that we can load them, because we cannot - // load them directly from a JAR. + // load them directly from a JAR. Lastly, we need to handle library overrides in + // case the user wants to use custom libraries, or are not on a supported platform. def init() { if (libraryName == null) { return @@ -194,108 +210,123 @@ abstract class LuaStateFactory { } } - val libraryUrl = classOf[Machine].getResource(s"/assets/${Settings.resourceDomain}/lib/$libraryName") - if (libraryUrl == null) { - OpenComputers.log.warn(s"Native library with name '$version/$libraryName' not found.") - return + var tmpLibFile: File = null + if (!Strings.isNullOrEmpty(Settings.get.forceNativeLibPathFirst)) { + val libraryTest = new File(Settings.get.forceNativeLibPathFirst, libraryName); + if (libraryTest.canRead) { + tmpLibFile = libraryTest + currentLib = libraryTest.getAbsolutePath + OpenComputers.log.info(s"Found forced-path filesystem library $currentLib.") + } + else + OpenComputers.log.warn(s"forceNativeLibPathFirst is set, but $currentLib was not found there. Falling back to checking the built-in libraries.") } - val tmpLibName = s"OpenComputersMod-${OpenComputers.Version}-$version-$libraryName" - val tmpBasePath = if (Settings.get.nativeInTmpDir) { - val path = System.getProperty("java.io.tmpdir") - if (path == null) "" - else if (path.endsWith("/") || path.endsWith("\\")) path - else path + "/" - } - else "./" - val tmpLibFile = new File(tmpBasePath + tmpLibName) + if (currentLib.isEmpty) { + val libraryUrl = classOf[Machine].getResource(s"/assets/${Settings.resourceDomain}/lib/$libraryName") + if (libraryUrl == null) { + OpenComputers.log.warn(s"Native library with name '$libraryName' not found.") + return + } - // Clean up old library files when not in tmp dir. - if (!Settings.get.nativeInTmpDir) { - val libDir = new File(tmpBasePath) - if (libDir.isDirectory) { - for (file <- libDir.listFiles(new PatternFilenameFilter("^" + Pattern.quote("OpenComputersMod-") + ".*" + Pattern.quote("-" + libraryName) + "$"))) { - if (file.compareTo(tmpLibFile) != 0) { - file.delete() + val tmpLibName = s"OpenComputersMod-${OpenComputers.Version}-$version-$libraryName" + val tmpBasePath = if (Settings.get.nativeInTmpDir) { + val path = System.getProperty("java.io.tmpdir") + if (path == null) "" + else if (path.endsWith("/") || path.endsWith("\\")) path + else path + "/" + } + else "./" + tmpLibFile = new File(tmpBasePath + tmpLibName) + + // Clean up old library files when not in tmp dir. + if (!Settings.get.nativeInTmpDir) { + val libDir = new File(tmpBasePath) + if (libDir.isDirectory) { + for (file <- libDir.listFiles(new PatternFilenameFilter("^" + Pattern.quote("OpenComputersMod-") + ".*" + Pattern.quote("-" + libraryName) + "$"))) { + if (file.compareTo(tmpLibFile) != 0) { + file.delete() + } } } } - } - // If the file, already exists, make sure it's the same we need, if it's - // not disable use of the natives. - if (tmpLibFile.exists()) { - var matching = true - try { - val inCurrent = libraryUrl.openStream() - val inExisting = new FileInputStream(tmpLibFile) - var inCurrentByte = 0 - var inExistingByte = 0 - do { - inCurrentByte = inCurrent.read() - inExistingByte = inExisting.read() - if (inCurrentByte != inExistingByte) { - matching = false - inCurrentByte = -1 - inExistingByte = -1 - } - } - while (inCurrentByte != -1 && inExistingByte != -1) - inCurrent.close() - inExisting.close() - } - catch { - case _: Throwable => - matching = false - } - if (!matching) { - // Try to delete an old instance of the library, in case we have an update - // and deleteOnExit fails (which it regularly does on Windows it seems). - // Note that this should only ever be necessary for dev-builds, where the - // version number didn't change (since the version number is part of the name). + // If the file, already exists, make sure it's the same we need, if it's + // not disable use of the natives. + if (tmpLibFile.exists()) { + var matching = true try { - tmpLibFile.delete() + val inCurrent = libraryUrl.openStream() + val inExisting = new FileInputStream(tmpLibFile) + var inCurrentByte = 0 + var inExistingByte = 0 + do { + inCurrentByte = inCurrent.read() + inExistingByte = inExisting.read() + if (inCurrentByte != inExistingByte) { + matching = false + inCurrentByte = -1 + inExistingByte = -1 + } + } + while (inCurrentByte != -1 && inExistingByte != -1) + inCurrent.close() + inExisting.close() } catch { - case t: Throwable => // Ignore. + case _: Throwable => + matching = false } - if (tmpLibFile.exists()) { - OpenComputers.log.warn(s"Could not update native library '${tmpLibFile.getName}'!") + if (!matching) { + // Try to delete an old instance of the library, in case we have an update + // and deleteOnExit fails (which it regularly does on Windows it seems). + // Note that this should only ever be necessary for dev-builds, where the + // version number didn't change (since the version number is part of the name). + try { + tmpLibFile.delete() + } + catch { + case t: Throwable => // Ignore. + } + if (tmpLibFile.exists()) { + OpenComputers.log.warn(s"Could not update native library '${tmpLibFile.getName}'!") + } } } - } - // Copy the file contents to the temporary file. - try { - val in = Channels.newChannel(libraryUrl.openStream()) + // Copy the file contents to the temporary file. try { - val out = new FileOutputStream(tmpLibFile).getChannel + val in = Channels.newChannel(libraryUrl.openStream()) try { - out.transferFrom(in, 0, Long.MaxValue) - tmpLibFile.deleteOnExit() - // Set file permissions more liberally for multi-user+instance servers. - tmpLibFile.setReadable(true, false) - tmpLibFile.setWritable(true, false) - tmpLibFile.setExecutable(true, false) + val out = new FileOutputStream(tmpLibFile).getChannel + try { + out.transferFrom(in, 0, Long.MaxValue) + tmpLibFile.deleteOnExit() + // Set file permissions more liberally for multi-user+instance servers. + tmpLibFile.setReadable(true, false) + tmpLibFile.setWritable(true, false) + tmpLibFile.setExecutable(true, false) + } + finally { + out.close() + } } finally { - out.close() + in.close() } } - finally { - in.close() + catch { + // Java (or Windows?) locks the library file when opening it, so any + // further tries to update it while another instance is still running + // will fail. We still want to try each time, since the files may have + // been updated. + // Alternatively, the file could not be opened for reading/writing. + case t: Throwable => // Nothing. } + // Try to load the lib. + currentLib = tmpLibFile.getAbsolutePath } - catch { - // Java (or Windows?) locks the library file when opening it, so any - // further tries to update it while another instance is still running - // will fail. We still want to try each time, since the files may have - // been updated. - // Alternatively, the file could not be opened for reading/writing. - case t: Throwable => // Nothing. - } - // Try to load the lib. - currentLib = tmpLibFile.getAbsolutePath + try { LuaStateFactory.synchronized { System.load(currentLib)