Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 20 additions & 14 deletions Sources/Launcher/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func startSocket() {
defer { close(clientSocket) }

var lastMessages: [String] = []
var javaQuitReceived: Bool = false
var exitCode: Int32?

while true {
var buffer: [UInt8] = .init(repeating: 0, count: 16384)
Expand All @@ -106,17 +106,15 @@ func startSocket() {
log("Failed to decode UTF-8 message (bytesRead=\(bytesRead))", error: true)
}
} else if buffer[0] == 0xFF {
javaQuitReceived = true
exitCode = buffer.dropFirst().withUnsafeBytes { $0.load(as: Int32.self) }
}
} else if bytesRead == 0 {
if lastMessages.contains(where: { $0.contains("#@!@# Game crashed!") }) {
log("Game crashed (Minecraft crash log marker detected)")
exit(1)
} else if !javaQuitReceived {
log("JVM terminated unexpectedly (unexpected socket EOF)")
guard let exitCode else {
log("JVM holder terminated unexpectedly (unexpected socket EOF)", error: true)
exit(1)
}
exit(0)
log("Game exited with exit code \(exitCode)")
exit(exitCode)
} else {
perror("read")
exit(1)
Expand All @@ -142,12 +140,19 @@ while true {
}
}

var runnerArguments: [String] = Array(arguments.dropFirst())
runnerArguments.insert(FileManager.default.currentDirectoryPath, at: 0)
runnerArguments.insert(
"-javaagent:\(appBundleURL.appending(path: "Contents/Resources/log-bridge-agent.jar").path)=\(socketPath)",
at: 2
)
var runnerArguments: [String] = [
"--holder",
"--working-directory", FileManager.default.currentDirectoryPath,
"--socket-path", socketPath,
"--args"
] + arguments.dropFirst()

//let process: Process = .init()
//process.executableURL = appBundleURL.appending(path: "Contents/MacOS/runner")
//process.arguments = runnerArguments
//process.currentDirectoryURL = .init(filePath: FileManager.default.currentDirectoryPath)
//try process.run()


let configuration: NSWorkspace.OpenConfiguration = .init()
configuration.createsNewApplicationInstance = true
Expand All @@ -171,6 +176,7 @@ NSWorkspace.shared.openApplication(at: appBundleURL, configuration: configuratio
signal(SIGINT, SIG_IGN)

let handler: () -> Void = {
if application.isTerminated { return }
kill(pid, SIGTERM)
exit(0)
}
Expand Down
146 changes: 134 additions & 12 deletions Sources/Runner/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,103 @@
import Foundation
import AppKit

if CommandLine.arguments.count == 1 {
let alert: NSAlert = .init()
alert.messageText = "You can't open this app directly."
alert.alertStyle = .warning
alert.runModal()
exit(1)
}

let arguments: [String] = Array(CommandLine.arguments.dropFirst())

var holder: Bool = false
var workingDirectory: String?
var socketPath: String?
var javaArguments: [String]?

for (index, arg) in arguments.enumerated() {
if arg == "-h" || arg == "--holder" {
holder = true
} else if arg == "-w" || arg == "--working-directory" {
workingDirectory = arguments[index + 1]
} else if arg == "--socket-path" {
socketPath = arguments[index + 1]
} else if arg == "--args" {
javaArguments = Array(arguments.dropFirst(index + 1))
break
}
}

@MainActor
func launch() {
let workingDirectory: String = ProcessInfo.processInfo.arguments[1]
if chdir(workingDirectory) != 0 {
perror("chdir")
exit(EXIT_FAILURE)
}
let arguments: [String] = Array(ProcessInfo.processInfo.arguments.dropFirst(2))
let executablePath: String = arguments[0]
let argv: [UnsafeMutablePointer<CChar>?] = arguments.map { strdup($0) } + [nil]
guard let javaArguments else { exit(1) }
let executablePath: String = javaArguments[0]
let argv: [UnsafeMutablePointer<CChar>?] = javaArguments.map { strdup($0) } + [nil]
argv.withUnsafeBufferPointer { buffer in
execv(executablePath, buffer.baseAddress)
perror("execv")
}
exit(EXIT_FAILURE)
exit(1)
}

func connectSocket(at path: String) -> Int32? {
let sockfd: Int32 = socket(AF_UNIX, SOCK_STREAM, 0)
if sockfd < 0 {
perror("socket")
return nil
}

var addr = sockaddr_un()
addr.sun_family = sa_family_t(AF_UNIX)
let bytes = path.utf8CString
withUnsafeMutablePointer(to: &addr.sun_path) { sunptr in
let buffer = UnsafeMutableRawPointer(sunptr).assumingMemoryBound(to: CChar.self)
_ = bytes.withUnsafeBufferPointer { ptr in
memcpy(buffer, ptr.baseAddress, bytes.count)
}
}
let len = socklen_t(MemoryLayout<sa_family_t>.size + bytes.count)
let result = withUnsafePointer(to: &addr) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
connect(sockfd, $0, len)
}
}
if result != 0 {
perror("connect")
close(sockfd)
return nil
}
return sockfd
}

func send(data: Data, to sockfd: Int32, type: UInt8, queue: DispatchQueue, completion: (@Sendable () -> Void)? = nil) {
queue.async {
let payload: Data = [type] + data
queue.async {
payload.withUnsafeBytes {
let base = $0.baseAddress?.assumingMemoryBound(to: UInt8.self)
var written: Int = 0
while written < payload.count {
let rc = write(sockfd, base! + written, payload.count - written)
if rc <= 0 { break }
written += rc
}
completion?()
}
}
}
}

func handlePipe(_ pipe: Pipe, to sockfd: Int32, error: Bool, queue: DispatchQueue) {
let handle: FileHandle = pipe.fileHandleForReading
DispatchQueue.global().async {
while true {
let data: Data = handle.availableData
if data.isEmpty { break }
send(data: data, to: sockfd, type: error ? 1 : 0, queue: queue)
}
}
}

class ApplicationDelegate: NSObject, NSApplicationDelegate {
Expand All @@ -30,6 +113,45 @@ class ApplicationDelegate: NSObject, NSApplicationDelegate {
}
}

let delegate: ApplicationDelegate = .init()
NSApplication.shared.delegate = delegate
NSApplication.shared.run()
guard let javaArguments, javaArguments.count > 2 else {
exit(1)
}

if holder {
guard let workingDirectory, let socketPath else {
exit(1)
}

guard let sockfd: Int32 = connectSocket(at: socketPath) else {
exit(1)
}
let socketQueue: DispatchQueue = .init(label: "socket_write_queue")

let process: Process = .init()
process.executableURL = Bundle.main.executableURL
process.arguments = ["--args"] + javaArguments
process.currentDirectoryURL = .init(filePath: workingDirectory)

let outputPipe: Pipe = .init()
let errorPipe: Pipe = .init()
process.standardOutput = outputPipe
process.standardError = errorPipe

process.terminationHandler = { process in
let data: Data = withUnsafeBytes(of: process.terminationStatus) { Data($0) }
send(data: data, to: sockfd, type: 0xFF, queue: socketQueue) {
exit(process.terminationStatus)
}
}

try process.run()

handlePipe(outputPipe, to: sockfd, error: false, queue: socketQueue)
handlePipe(errorPipe, to: sockfd, error: true, queue: socketQueue)

dispatchMain()
} else {
let delegate: ApplicationDelegate = .init()
NSApplication.shared.delegate = delegate
NSApplication.shared.run()
}