diff --git a/inc/Makefile-conf.mk b/inc/Makefile-conf.mk
index 3141d00..3e1eb7d 100644
--- a/inc/Makefile-conf.mk
+++ b/inc/Makefile-conf.mk
@@ -21,11 +21,7 @@ include Makefile
 OBJECTDIR=build/${CONF}/${CND_PLATFORM}
 
 # Object Files
-OBJECTFILES = ${OBJECTDIR}/argparser.o		\
-	      ${OBJECTDIR}/utilsfuncs.o		\
-	      ${OBJECTDIR}/ng.o			\
-	      ${OBJECTDIR}/strlcpy.o	\
-	      ${OBJECTDIR}/jrubyexe.o
+OBJECTFILES = ${OBJECTDIR}/jrubyexe.o
 
 ifdef JAVA_HOME
 JAVA_INCLUDE = $(subst \,/,${JAVA_HOME})/include
@@ -33,10 +29,16 @@ INCLUDES = "-I${JAVA_INCLUDE}"
 endif
 
 ifdef MINGW
-OBJECTFILES += ${OBJECTDIR}/utilsfuncswin.o	\
-	       ${OBJECTDIR}/platformlauncher.o	\
-	       ${OBJECTDIR}/jvmlauncher.o	\
-	       ${OBJECTDIR}/jruby.o
+OBJECTFILES += 
+# Object Files
+OBJECTFILES = ${OBJECTDIR}/argparser.o		\
+	          ${OBJECTDIR}/utilsfuncs.o		\
+	          ${OBJECTDIR}/ng.o			\
+	          ${OBJECTDIR}/strlcpy.o	\
+	          ${OBJECTDIR}/utilsfuncswin.o	\
+	          ${OBJECTDIR}/platformlauncher.o	\
+	          ${OBJECTDIR}/jvmlauncher.o	\
+	          ${OBJECTDIR}/jruby.o
 INCLUDES += "-I${JAVA_INCLUDE}/win32"
 else
 OBJECTFILES += ${OBJECTDIR}/unixlauncher.o
diff --git a/jrubyexe.cpp b/jrubyexe.cpp
index da31c10..c53aa83 100644
--- a/jrubyexe.cpp
+++ b/jrubyexe.cpp
@@ -50,18 +50,18 @@ const char *CON_ATTACH_MSG =
     "*WARNING*: The non-console JRubyW launcher is forced to attach to console.\n"
     "This may cause unexpected behavior of CMD console. Use:\n"
     "    start /wait jrubyw.exe -Xconsole attach [args]\n";
+
+#include "utilsfuncs.h"
 #endif  // JRUBYW
 #else
 #include "unixlauncher.h"
 #endif  // WIN32
 
-#include "utilsfuncs.h"
-
 
 int main(int argc, char *argv[], char* envp[]) {
+#ifdef WIN32
     checkLoggingArg(argc, argv, true);
 
-#ifdef WIN32
 #ifdef JRUBYW
     if (!isConsoleAttached()) {
         logMsg("Console is not attached, assume WINDOW mode");
@@ -78,7 +78,6 @@ int main(int argc, char *argv[], char* envp[]) {
     return loader.start("jruby.dll", argc - 1, argv + 1, argv[0]);
 
 #else  // !WIN32
-    UnixLauncher launcher;
-    return launcher.run(argc, argv, envp);
+    return unixlauncher_run(argc, argv, envp);
 #endif  // WIN32
 }
diff --git a/unixlauncher.c b/unixlauncher.c
new file mode 100644
index 0000000..2b38683
--- /dev/null
+++ b/unixlauncher.c
@@ -0,0 +1,143 @@
+#include <errno.h>
+#include <libgen.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * Copyright 2009-2025 JRuby Team (www.jruby.org).
+ *
+ * This program finds JRUBY_HOME and executes the JRuby launcher script
+ * contained within it.
+ */
+
+static const char script_name[] = "jruby.sh";
+
+
+static char *which(const char *const executable) {
+    const size_t exe_length = strlen(executable);
+    char *exe_path = NULL;
+    size_t exe_path_size = 0;
+
+    // Iterate through PATH to find executable
+    char *dirs = getenv("PATH");
+    if (dirs == NULL) {
+        return NULL;
+    }
+    size_t dirs_length = strlen(dirs);
+    // Temporarily replace null terminator with colon
+    dirs[dirs_length] = ':';
+
+    size_t dir_head = 0;
+    size_t i = 0;
+    do {
+        if (dirs[i] == ':') {
+            // Declare convenient path variables
+            char *const dir = dirs + dir_head;
+            const size_t dir_length = i - dir_head;
+            const size_t new_path_length = dir_length + exe_length + 1;
+
+            // Allocate enough space for concatenated path
+            if (exe_path_size < new_path_length + 1) {
+                // Leave space for null terminator
+                exe_path = realloc(exe_path, new_path_length + 1);
+                exe_path_size = new_path_length + 1;
+            }
+
+            // Concatenate path and executable
+            memcpy(exe_path, dir, dir_length);
+            exe_path[dir_length] = '/';
+            memcpy(exe_path + dir_length + 1, executable, exe_length);
+            exe_path[new_path_length] = '\0';
+
+            // Check if we can execute
+            if (0 == access(exe_path, R_OK | X_OK)) {
+                goto success;
+            }
+
+            dir_head = i + 1;
+        }
+    } while (dirs[i++]);
+
+    // Lookup has failed, free if necessary and return NULL
+    if (exe_path != NULL) {
+        free(exe_path);
+        exe_path = NULL;
+    }
+success:
+    // Restore null terminator
+    dirs[dirs_length] = '\0';
+
+    return exe_path;
+}
+
+
+int unixlauncher_run(int argc, char *argv[], char *envp[]) {
+    if (argc == 0 || argv[0][0] == '\0') {
+        fputs("Error: No executable provided!", stderr);
+        return 2;
+    }
+
+    // Find ourselves
+    char *original_self = argv[0];
+    char *self_path;
+
+    // Detect whether argv[0] contains forward slashes
+    bool self_is_path = false;
+    for (size_t i = 0; original_self[i]; i++) {
+        if (original_self[i] == '/') {
+            self_is_path = true;
+            break;
+        }
+    }
+
+    if (self_is_path) {  // argv[0] is a path to an executable
+        self_path = realpath(original_self, NULL);
+    } else {  // argv[0] is basename of executable
+        // Iterate through PATH to find script
+        self_path = which(argv[0]);
+
+        if (self_path == NULL) {
+            fprintf(stderr, "Error: Could not find %s executable\n", script_name);
+            return 1;
+        }
+
+        // Juggle malloc'd paths
+        char *real_path = realpath(self_path, NULL);
+        free(self_path);
+        self_path = real_path;
+    }
+
+    // Find our parent directory
+    char *script_dir = dirname(self_path);
+    if (self_path != script_dir) {
+        // Free malloc'd self_path if dirname returned statically allocated string
+        free(self_path);
+    }
+    size_t script_dir_length = strlen(script_dir);
+
+    // Allocate space for complete script path
+    size_t script_path_length = strlen(script_name) + script_dir_length + 1;
+    // Leave space for null terminator
+    char *script_path = malloc(script_path_length + 1);
+
+    // Concatenate script dir and script name
+    memcpy(script_path, script_dir, script_dir_length);
+    script_path[script_dir_length] = '/';
+    memcpy(script_path + script_dir_length + 1, script_name, strlen(script_name));
+    script_path[script_path_length] = '\0';
+
+    // Reuse argv for script command line
+    argv[0] = script_path;
+    int ret = execv(argv[0], argv);
+
+    if (ret < 0) {
+        fprintf(stderr, "%s: %s: %s\n", original_self, strerror(errno), script_path);
+    }
+
+    free(self_path);
+    free(script_path);
+    return EXIT_FAILURE;
+}
diff --git a/unixlauncher.cpp b/unixlauncher.cpp
deleted file mode 100644
index d030449..0000000
--- a/unixlauncher.cpp
+++ /dev/null
@@ -1,131 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <limits.h>
-#include <string.h>
-#include "unixlauncher.h"
-#include "utilsfuncs.h"
-
-using namespace std;
-
-extern "C" int nailgunClientMain(int argc, char *argv[], char *env[]);
-
-UnixLauncher::UnixLauncher()
-    : ArgParser()
-{
-}
-
-UnixLauncher::UnixLauncher(const UnixLauncher& orig)
-    : ArgParser(orig)
-{
-}
-
-UnixLauncher::~UnixLauncher() {
-}
-
-int UnixLauncher::run(int argc, char* argv[], char* envp[]) {
-    platformDir = argv[0];
-    if (!initPlatformDir() || !parseArgs(argc - 1, argv + 1)) {
-        return 255;
-    }
-
-    if (nailgunClient) {
-        progArgs.push_front("org.jruby.util.NailMain");
-        char ** nailArgv = convertToArgvArray(progArgs);
-        int nailArgc = progArgs.size();
-
-        if (printCommandLine) {
-            printListToConsole(progArgs);
-            for (int i = 0; i < nailArgc; i++) {
-                free(nailArgv[i]);
-            }
-            delete[] nailArgv;
-            return 0;
-        }
-        return nailgunClientMain(progArgs.size(), (char**)nailArgv, envp);
-    }
-
-    string java("");
-
-    if (getenv("JAVACMD") != NULL) {
-        java = getenv("JAVACMD");
-        if (java.find_last_of('/') == -1) {
-            java = findOnPath(java.c_str());
-        }
-    } else {
-        if (!jdkhome.empty()) {
-            java = jdkhome + "/bin/java";
-        } else if (getenv("JAVA_HOME") != NULL) {
-            string java_home = string(getenv("JAVA_HOME"));
-            jdkhome = java_home;
-            java_home = trimTrailingBackslashes(java_home);
-            java = java_home + "/bin/java";
-        } else {
-            java = findOnPath("java");
-        }
-    }
-
-    if (java.empty()) {
-        printToConsole("No `java' executable found on PATH.");
-        return 255;
-    }
-
-    // still no jdk home, use other means to resolve it
-    if (jdkhome.empty()) {
-        char javaHomeCommand[] = "/usr/libexec/java_home";
-        if (access(javaHomeCommand, R_OK | X_OK) != -1 && !checkDirectory(javaHomeCommand)) {
-            // try java_home command when not set (on MacOS)
-            FILE *fp;
-            char tmp[PATH_MAX + 1];
-
-            fp = popen(javaHomeCommand, "r");
-            if (fp != NULL) {
-                fgets(tmp, sizeof(tmp), fp);
-                tmp[strcspn(tmp, "\n")] = 0;
-                jdkhome = tmp;
-                pclose(fp);
-            } else {
-                logErr(true, false, "failed to run %s", javaHomeCommand);
-            }
-        } else {
-            java = resolveSymlinks(java);
-            int home_index = java.find_last_of('/', java.find_last_of('/') - 1);
-            jdkhome = java.substr(0, home_index);
-        }
-    }
-
-    prepareOptions();
-
-    list<string> commandLine;
-    commandLine.push_back(java);
-    addOptionsToCommandLine(commandLine);
-
-    logMsg("Command line:");
-    for (list<string>::iterator it = commandLine.begin(); it != commandLine.end(); ++it) {
-        logMsg("\t%s", it->c_str());
-    }
-
-    char** newArgv = convertToArgvArray(commandLine);
-    int newArgc = commandLine.size();
-
-    if (printCommandLine) {
-        printListToConsole(commandLine);
-        for (int i = 0; i < newArgc; i++) {
-            free(newArgv[i]);
-        }
-        delete[] newArgv;
-        return 0;
-    }
-
-    if (!fileExists(java.c_str())) {
-        string msg = "No `java' exists at " + java + ", please double-check JAVA_HOME.\n";
-        printToConsole(msg.c_str());
-        return 255;
-    }
-
-    execv(java.c_str(), newArgv);
-
-    // shouldn't get here unless something bad happened with execv
-    logErr(true, true, "execv failed:");
-    return 255;
-}
diff --git a/unixlauncher.h b/unixlauncher.h
index 89a18e0..176c4f6 100644
--- a/unixlauncher.h
+++ b/unixlauncher.h
@@ -1,22 +1,20 @@
 /*
- * Copyright 2009-2010 JRuby Team (www.jruby.org).
+ * Copyright 2009-2025 JRuby Team (www.jruby.org).
  */
 
 
 #ifndef _UNIXLAUNCHER_H_
 #define _UNIXLAUNCHER_H_
 
-#include "argparser.h"
+#ifdef __cplusplus
+extern "C"
+{
+#endif
 
-class UnixLauncher : public ArgParser {
-public:
-    UnixLauncher();
-    virtual ~UnixLauncher();
+int unixlauncher_run(int argc, char *argv[], char *envp[]);
 
-    int run(int argc, char* argv[], char* envp[]);
-
-private:
-    UnixLauncher(const UnixLauncher& orig);
-};
+#ifdef __cplusplus
+}
+#endif
 
 #endif // ! _UNIXLAUNCHER_H_